为了账号安全,请及时绑定邮箱和手机立即绑定

GKE容器被“内存cgroup内存不足”杀死,但监控,本地测试和pprof显示使用率远低于限制

GKE容器被“内存cgroup内存不足”杀死,但监控,本地测试和pprof显示使用率远低于限制

Go
ibeautiful 2022-08-24 18:53:50
我最近将一个新的容器映像推送到我的一个GKE部署中,并注意到API延迟上升,请求开始返回502。查看日志,我发现容器由于OOM而开始崩溃:Memory cgroup out of memory: Killed process 2774370 (main) total-vm:1801348kB, anon-rss:1043688kB, file-rss:12884kB, shmem-rss:0kB, UID:0 pgtables:2236kB oom_score_adj:980从内存使用情况图来看,Pod 使用的内存加起来并不超过 50MB。我最初的资源请求是:...spec:...  template:...    spec:...      containers:      - name: api-server...        resources:          # You must specify requests for CPU to autoscale          # based on CPU utilization          requests:            cpu: "150m"            memory: "80Mi"          limits:            cpu: "1"            memory: "1024Mi"      - name: cloud-sql-proxy        # It is recommended to use the latest version of the Cloud SQL proxy        # Make sure to update on a regular schedule!        image: gcr.io/cloudsql-docker/gce-proxy:1.17        resources:          # You must specify requests for CPU to autoscale          # based on CPU utilization          requests:            cpu: "100m"...然后我尝试将API服务器的请求提高到1GB,但没有帮助。最后,帮助将容器映像还原到以前的版本:通过查看golang二进制文件中的更改,没有明显的内存泄漏。当我在本地运行它时,它最多使用80MB的内存,即使在来自与生产中相同的请求的负载下也是如此。我从GKE控制台获得的上图也显示了Pod使用远远低于1GB内存限制的Pod。所以我的问题是:当GKE监控和本地运行它仅使用1GB限制中的80MB时,什么可能导致GKE终止我的OOM进程?
查看完整描述

3 回答

?
海绵宝宝撒

TA贡献1809条经验 获得超8个赞

我猜这是由Pod QoS类引起的


当系统过度使用时,QoS 类会确定哪个 pod 首先被杀死,以便将释放的资源提供给优先级更高的 Pod。


在你的情况下,你的 Pod 的 QoS 是可爆发的


每个正在运行的进程都有一个超出内存 (OOM) 分数。系统通过比较所有正在运行的进程的OOM分数来选择要终止的进程。当需要释放内存时,得分最高的进程将被终止。有关如何计算的详细信息,请参阅如何计算内核 oom 分数?。score


如果两者都在可爆破类中,哪个 pod 会先被杀死?


简而言之,系统将使用比另一个请求的内存更多的百分比来杀死一个。


Pod A


used: 90m

requests: 100m

limits: 200m

Pod B


used: 150m

requests: 200m

limits: 400m

Pod A之前会被杀死,因为它使用了 90% 的请求内存,而只使用了请求内存的 75%。Pod BPod B


查看完整回答
反对 回复 2022-08-24
?
有只小跳蛙

TA贡献1824条经验 获得超8个赞

确保 QoS 类为“有保证”在你的方案中无济于事。其中一个进程导致父 cgroup 超过其内存限制(反过来由针对相应容器指定的内存限制值设置),OOM 杀手将其终止。这不是 Pod 逐出,因为您可以在日志中清楚地看到 OOM 杀手的商标信息。如果另一个 Pod 分配了如此多的内存,使节点处于内存压力之下,那么“有保证的”QoS 类将会有所帮助 - 在这种情况下,您的“保证”Pod 将幸免于难。但在你的情况下,Kubelet从来没有在所有这些事情中得到一个字 - 就像决定完全驱逐豆荚一样 - 因为OOM杀手行动更快

Burak Serdar在其评论中有一个很好的观点 - 临时分配大内存块。情况很可能就是这样,因为从您粘贴的消息中收集数据的分辨率为60s。那是很多时间。一个人可以在不到1秒的时间内轻松填充GB的RAM。我的假设是,内存“峰值”永远不会被渲染,因为指标永远不会被及时收集(即使你直接查询cAdvisor,它也会很棘手,因为它的分辨率为10-15秒来收集其指标)。

如何了解更多有关正在发生的事情的信息?几个想法:

  • 有一些工具可以显示应用程序实际分配了多少,一直到框架级别。在.NET中,dotMemory是一种常用的工具,可以在容器内运行并捕获正在发生的事情。Go 可能有一个等价物。这种方法的问题在于,当容器被OOM杀死时,该工具也会随之被删除。

  • 在这里,您将找到一个影片,该影片捕获了一个进程分配内存,直到其父容器被 OOM 杀死。相应的 .NET 应用程序会不时地将它使用的内存量写入控制台,即使在容器不再存在后,Kubernetes 日志也会显示这些内存量,从而允许查看发生了什么

  • 限制应用,使其处理少量数据(例如,如果您每分钟只处理 1 张图片,则从内存角度临时查看会发生什么)

  • 查看详细的 OOM 杀手内核日志,了解 cgroup 中的所有进程。在一个容器内有多个进程是完全有效的(就像在其他进程中一样,除了该容器中带有PID 1的进程),OOM杀手很可能杀死其中任何一个。在这种情况下,您可能会偶然发现意想不到的转折。然而,在你的场景中,它似乎被终止了,否则容器就不会被OOM杀死,所以这种情况不太可能发生。

只是为了完整性:底层框架可以对容器强制实施低于内存限制的限制。例如,在.NET中,当在具有内存限制的容器中运行时,此值为75%。换句话说,在限制为 2,000 MiB 的容器内分配内存的 .NET 应用将在 1,500 MiB 时出错。然而,在这种情况下,你得到的退出代码为139(SIGSEGV)。这似乎不适用于这里,因为OOM杀手终止了该过程,并且从内核日志中可以清楚地看到所有1 GiB实际上都被使用了()。据我所知,Go还没有类似的设置,尽管社区一再要求它。anon-rss:1043688kB


查看完整回答
反对 回复 2022-08-24
?
阿波罗的战车

TA贡献1862条经验 获得超6个赞

此处的资源规范是 OOM 的根本原因。


在 Kubernetes 中,所需内存和有限内存的定义不同。所需的内存是 内存 。有限内存是容器可以突增到的内存。但有限的内存并不能保证容器可以拥有该资源。must-have


在大多数生产系统中,不建议有限和所需的资源差异太大。例如,在您的情况下,


requests:

  cpu: "150m"

  memory: "80Mi"

limits:

  cpu: "1"

  memory: "1024Mi"

容器只能有80Mi保证内存,但它可以以某种方式爆发为1024Mi。节点可能没有足够的内存用于容器,容器本身将进入 OOM。


因此,如果要改善这种情况,则需要将资源配置为类似此类的资源。


requests:

  cpu: "150m"

  memory: "1024Mi"

limits:

  cpu: "1"

  memory: "1024Mi"

请注意,CPU很好,因为您不会在低CPU时间下杀死进程。但是OOM将导致该过程被杀死。


正如上面提到的答案,这与 pod 中的服务质量有关。通常,对于大多数最终用户,应始终将容器配置为保证类,即请求 == 受限。在将其配置为突发类之前,您可能需要有一些理由。


查看完整回答
反对 回复 2022-08-24
  • 3 回答
  • 0 关注
  • 131 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信