Z.S.K.'s Records

Kubernetes学习(pod驱逐机制及OOM流程)

最过在深入排查oom问题时有幸看到一个在kubernetes中探讨oom-killer问题的文章,本人觉得写得非常详尽且解答了本人的诸多疑惑,遂决定翻译成中文,方便日后求解。

在翻译的过程中,我会尽可能地使用原文的意思,同时也会补充一些知识,会添加一些的本人的理解

前序

以下是原文信息,感兴趣且英文好的可直接阅读

原文: Out-of-memory (OOM) in Kubernetes; Part 4: Pod evictions, OOM scenarios and flows leading to them

作者: Mihai Albert

系列总共由四部分组成的文章,本人将对第2、4章节进行翻译,每章的大概内容如下:

  • 第一章: 主要列了一下这个系列的主要内容,相当于大纲

  • 第二章: oom-killer机制及应用部署在kubernetes中如何处理oom事件

  • 第三章: 非常详细的介绍了kubernetes中的内存相关的metrics items及常用收集工具的使用

  • 第四章: kubernetes中驱逐机制及oom-killer如何协调响应

作者将对第2章及第4章进行翻译,本文是第4章: pod驱逐机制及oom流程

以下是我【为什么翻译这个系列】前碰到的问题,先交代一下背景

背景

某台node的kubelet中出现如下的错误日志:

1
2
3
4
5
6
7
8
9
- Jan 14 18:01:55 aks-agentpool-20086390-vmss00003C kernel: [ 1432.394489] dotnet invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=1000
- Jan 14 18:01:55 aks-agentpool-20086390-vmss00003C kernel: [ 1432.394506] oom_kill_process+0xe6/0x120
- Jan 14 18:01:55 aks-agentpool-20086390-vmss00003C kernel: [ 1432.394642] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=f90b24151029555d49a49d82159ec90c4fec53ba8515bd51a5633d1ff45d8f53,mems_allowed=0,oom_memcg=/kubepods,task_memcg=/kubepods/besteffort/pod5f3d2447-f535-4b3d-979c-216d4980cc3f/f90b24151029555d49a49d82159ec90c4fec53ba8515bd51a5633d1ff45d8f53,task=dotnet,pid=20341,uid=0
- Jan 14 18:01:55 aks-agentpool-20086390-vmss00003C kernel: [ 1432.394676] Memory cgroup out of memory: Killed process 20341 (dotnet) total-vm:172181696kB, anon-rss:4776416kB, file-rss:25296kB, shmem-rss:0kB, UID:0 pgtables:9620kB oom_score_adj:1000
- Jan 14 18:02:17 aks-agentpool-20086390-vmss00003C kubelet[3044]: I0114 18:02:17.686538 3044 kuberuntime_container.go:661] "Killing container with a grace period override" pod="alloc-tests/alloc-mem-leak" podUID=5f3d2447-f535-4b3d-979c-216d4980cc3f containerName="alloc-mem" containerID="containerd://d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616" gracePeriod=0
- Jan 14 18:02:17 aks-agentpool-20086390-vmss00003C containerd[2758]: time="2022-01-14T18:02:17.687846041Z" level=info msg="Kill container \"d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616\""
- Jan 14 18:02:18 aks-agentpool-20086390-vmss00003C kubelet[3044]: I0114 18:02:18.923106 3044 kubelet_pods.go:1285] "Killing unwanted pod" podName="alloc-mem-leak"
- Jan 14 18:02:18 aks-agentpool-20086390-vmss00003C kubelet[3044]: E0114 18:02:18.924926 3044 kuberuntime_container.go:691] "Kill container failed" err="rpc error: code = NotFound desc = an error occurred when try to find container \"d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616\": not found" pod="alloc-tests/alloc-mem-leak" podUID=5f3d2447-f535-4b3d-979c-216d4980cc3f containerName="alloc-mem" containerID={Type:containerd ID:d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616}
- Jan 14 18:02:19 aks-agentpool-20086390-vmss00003C kubelet[3044]: E0114 18:02:19.001858 3044 kubelet_pods.go:1288] "Failed killing the pod" err="failed to \"KillContainer\" for \"alloc-mem\" with KillContainerError: \"rpc error: code = NotFound desc = an error occurred when try to find container \\\"d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616\\\": not found\"" podName="alloc-mem-leak"

从日志其实很容易发现问题,就是有app的内存超限被oom了, 重点在于日志中的最后几行,为什么kubelet会提示Failed killing the pod, container Not Found

问题: 难道app被oom不是通过kubelet吗?

由于篇幅较长,在翻译开始前,本人将对本章内容给出本人认为比较重要的几条结论

结论

pod驱逐

  • Kubernetes没有直接控制OOM-killer

  • pod驱逐和OOM-killer具有相同的目标:确保节点不会在没有任何剩余内存的情况下结束

  • 如果一个或多个pod分配内存的速度太快,以至于Kubelet没有机会在默认的检查窗口(默认10s)中发现它,并且总的pod内存使用试图超过可分配内存加上硬逐出阈值的总和,那么内核的OOM killer将介入并终止pod容器中的一个或多个进程

  • Kubelet驱逐阈值的来源是从memory root group计算所有容器的container_memory_working_set_bytes的metrics计算而来

  • kubernetes的可分配资源(allocatable)无法控制运行在集群节点上、但在集群之外的应用使用资源

  • 不被kubernetes管理的进程的oom_score_adj为0

  • OOM killer作用在容器级,而驱逐作用在pod级别

信号及退出码

  • 如果容器的exit code大于或等于128,意味容器被它接收到的信号杀死

  • 无论是Kubelet决定驱逐容器的父pod还是OOM-killer终止该容器的主进程,Kubernetes报告的退出码将是137,pod驱逐和OOM-killer两种情况发送的都是SIGKILL

  • Kubelet会监控内核发生OOM killer事件

  • 不应该孤立地看待容器终止的exit code。不要盲目地认为容器的退出码是137就认为是被”OOMkilled”,同时需要检查”reason”字段

Pod驱逐

Kubernetes中pod驱逐是什么?这是Kubernetes对资源不足的节点采取的自动操作,通过终止一个或多个Pod来减轻压力。由于本文涉及内存,我们将专门讨论内存,因为内存是节点面临短缺的资源。

在 OOM killerCgroups以及OOM killer中,我们已经看到OOM killer如何确保节点上的可用内存不低于临界水平。因此,很明显,这两种机制pod驱逐和OOM-killer-具有相同的目标:确保节点不会在没有任何剩余内存的情况下结束。那为什么他们两个同时存在呢?

Kubernetes没有直接控制OOM-killer,请记住,OOM-killer是Linux内核的一个特性。Kubernetes所能做的(更确定的说是每个节点上的Kubelet),是OOM-killer的一种调节(knobs):例如,通过为OOM_Score_Adj设置不同的值,它改变后者关于哪个牺牲者首先被选择的行为。

不过这仍然没有回答为什么需要这两种机制的,而只是告诉Kubernetes必须接受OOM-killer。

但是kubernetes是什么时候决定驱逐pod的呢?低内存(Low memory situation)是一个相当模糊的概念: 我们已经看到当系统内存低时OOM-killer的表现,因此得出结论,POD驱逐应该在此之前发生。但具体什么时候?

按照 Kubernetes官方 : Kubernetes节点上的可分配(Allocatable)定义为可用于Pod的计算资源量.默认情况下,此功能通过设置 --enforce-node-allocatable=pods  参数来限定一旦pod的内存使用超过该值,Kubelet就会触发回收机制,即 “只要所有pod的总体使用量超过”可分配”,就通过驱逐pod执行强制执行”, 如文件所述这里

作者注:

--enforce-node-allocatable可取(pods,system-reserved,kube-reserved): 用于在当出现超量的情况下,从哪个对象(pod、system、kube)类型进行资源回收

我们可以通过检查kubectl describe node的输出很容易地看到这此值 

下面是本文中使用的Kubernetes集群的一个节点(7-GiB Azure DS2_v2节点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Addresses:
Hostname: aks-agentpool-20086390-vmss000043
InternalIP: 10.240.0.4
Capacity:
attachable-volumes-azure-disk: 8
cpu: 2
ephemeral-storage: 129900528Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 7120616Ki
pods: 110
Allocatable:
attachable-volumes-azure-disk: 8
cpu: 1900m
ephemeral-storage: 119716326407
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 4675304Ki
pods: 110
System Info:
Machine ID: a9f54faac15846a6866920a1010ee9d7

让我们看看pod驱逐的实际情况:

视频1- kubernetes的pod驱逐表现

启动Kubernetes pod,运行内存泄漏工具的一个实例。不为运行应用的容器指定任何请求或限制。内存泄漏工具以100 MiB的块为单位分配内存,直到达到其设置的4600 MiB输入参数。左上方窗口是泄漏工具分配时的原始输出,右侧窗口跟踪pod的状态,而底部窗口跟踪Kubelet发出的消息。泄漏工具成功地完成了它的运行,这导致在RAM中使用了大约4700 MiB, 包含它分配的4600 MiB和用于底层的. NET运行时的大约100 MB的总和。但是,此值略大于节点的”allocatable”值,后者略低于4600 MiB,如 kubectl describe node 一样,自从 --enforce-node-allocatable默认标记由节点的Kubelet使用,稍后我们看到Kubelet正在驱逐pod,并显示了驱逐原因和方式的明确消息。

我们甚至可以用泄漏工具少分配几个MiB的内存–这样它的总RAM占用量就会低于”可分配”值,而且我们仍然会看到相同的结果。为什么?因为该节点上已经运行了其他Pod,占用的空间略高于200 MiB。您实际上可以在内核日志的逐出消息中看到它们的列表,其中包括Grafana、kube-state-metrics、Prometheus node exporter、几个调试容器和kube-system名称空间中的一些pod。

Allocatable(可分配)

我们已经看到了节点配置的“可分配”内存的值,并且知道它明显小于总容量。实际上,对于我们的D2s_v2 Azure Kubernetes Service(AKS)节点,“allocatable”值仅代表**此7 GB内存的大约65%**。乍看之下,这确实感觉像是一种严重的浪费,但我们先不要问为什么要使用这种机制,而是要关注“可分配”内存的价值是如何计算的。

Microsoft docs资源预留说明了在留出不供pod使用的内存时需要考虑的2个值:

  • 750 MiB作为可用节点内存阈值(threshold),如果达到该阈值,将导致pod驱逐
  • Kubernetes系统守护程序(包括Kubelet)预留多少内存。对于具有7 GiB节点的测试群集,此值总计为1.6 GiB(0.25 x 4 GiB + 0.2 x 3 GiB),相当于1638 MiB

请记住,在撰写本文时(2021年12月),上面的值和公式对AKS有效,因为本文中使用的测试集群运行在AKS上。其他供应商(AWS、GCP等)使用自己的公式和值。看看Kubernetes节点中的可分配内存和CPU,以比较主要提供商之间的差异

–kube-reserved

官方文件中解释了kube-reserved 作为用于捕获kubernetes系统守护进程(如kubelet、container runtime、node problem detector等)的资源预留,而不是为作为pod运行的系统守护进程预留资源。保留的kube通常是节点上pod密度的函数

但是这个标志的值用在哪里呢?Kubernetes官方文档在此图像中明确指出:

图1 -节点容量分布。

来源:Kubernetes文档

由于AKS当前不使用 --system-reserved标志,内存的可分配值计算为总内存容量减去--kube-reserved--eviction-hard标志的值

可以使用--kube-reserved-cgroup强制限制--kube-reserved以让Kubelet、容器运行时和友元都被禁止检查--kube-reserved值。这是用专门为它们创建的cgroup实现的,cgroup设置了相应的限制,就像为pod的父cgroup设置的限制一样。正如我们所看到的,当内存使用量超过cgroup的限制时(此时无法回收任何东西),OOM-killer将介入,显然会给相应的Kubernetes组件带来灾难性的后果。- -kube-reserved-cgroup flag用于保护pod的“可分配”区域,这样Kubernetes守护进程就不会消耗太多内存

驱逐机制一览

我们在上一节中看到的所有内容如何反映在驱逐机制中?只要所有pod的总体使用量小于可分配内存值,就允许pod使用内存。一旦超过了这个阈值,Kubelet就开始发挥作用

kubelet每隔10秒就会根据定义的阈值检查内存使用情况。如果Kubelet决定需要驱逐,则根据用于Kubelet驱逐的Pod selection for kubelet eviction中描述的内部算法对Pod进行排序, 包括QoS等级和单个内存使用情况作为因素, 将计算后得到的列表中的第一个进行驱逐。只要没有降低阈值,Kubelet就会继续驱逐。如果一个或多个pod分配内存的速度太快,以至于Kubelet没有机会在其10s窗口中发现它,并且总的pod内存使用试图超过可分配内存加上硬逐出阈值的总和,那么内核的OOM killer将介入并终止pod容器中的一个或多个进程,正如我们在 cgroup and OOM-killer部分已经展示过

上面引用的Kubernetes官方文章充满了有趣的细节。例如,我们如何知道Kubelet默认每10s检查一次驱逐?该条明确指出:kubelet根据其配置的处理间隔(默认为10秒)评估驱逐阈值,此外,我们将看到Kubelet每10秒记录一次相关数据,我们将在下面进一步讨论这些场景。

一旦发生pod驱逐,发生此情况的节点将进入低内存状态(low-memory state)。这种状态会持续多久?文章明确指出:eviction-pressure-transition-period标志,其控制在将节点条件转变到不同状态之前,kubelet必须等待多长时间,默认值为5分钟

这个时间很重要,因为一旦pod被驱逐,您可能无法在接下来的5分钟内再次调度它或者其它pod。哪些pod会受到影响?Taints and Toleration里提到,控制面会在那些比BestEffort更高Qos的pod添加node.kubernetes.io/memory-pressure标志,这是因为Kubernetes将尽量有限保证Guaranteed及Burstable这类QoS类别中的pod(即使这些pod没有指定memory request)而新的BestEffort pod不会调度到受影响的节点“

Node Allocatable图解

总体pod内存使用情况的概念视图:

绿色箭头显示pod持续分配时内存填充的方向。请记住,该图显示了总体Pod内存使用量的变化情况。这里只有这个指标是相关的,您可以将其视为向右推(当pod的内存使用增加时)或向左拉(当pod的内存使用减少时)的垂直线。只要简单理解这个图即可不要过份解读,比如假设pod使用内存中的哪些区域,因为这会导致错误的结论。

物理上的Pod不会在“beginning”附近消耗内存,Kubelet只会在“end”时使用内存。

从防止过多的pod内存使用的角度来看,总体来说效果很好。让我们回顾一下DS2_v2 AKS节点的流程:只要POD的合计使用量在“可分配”量内,一切都很好。一旦Pod的总内存使用量超过4565 MiB,Pod将开始被逐出。在上图中,这将是任何时候pod的使用进入红色阴影区域。由于Kubelet仅每10秒检查一次回收阈值,因此总体pod内存使用量很可能在短时间内超过该限制,并达到5315 MiB阈值(图中的垂直红线)。当被命中时,OOM killer将终止一些pod容器中的一个或多个进程。无论pod在很短的时间内试图分配多少内存,这次都无关紧要,因为内核会做以下2件事:

  1. 时刻都在监视cgroup的限制

  2. 内核是第一个向进程分配内存的程序,它也是调用OOM killer的内核,OOM killer保证pod的内存使用永远不会超过5315 MiB的硬限制。因此,在图表中,pod的内存使用永远不会到达黑色交叉线区域。

Pod驱逐时关心的metrics

我们经常讨论内存使用量,到底是什么意思呢?以pod可以使用的“可分配”内存总量为例:对于我们的DS2_v2 AKS节点,该值为4565 MiB。当一个节点的RAM有4565 MiB充满了pod的数据时,是否意味着它就在开始驱逐的阈值附近?

换句话说,开始驱逐的度量标准是什么?

回到 Metrics values部分,我们已经看到了很多跟踪每种对象类型的内存使用情况的指标。以容器对象为例,cAdvisor返回了一系列mertrics,如container_memory_rss, container_memory_usage_bytes, container_memory_working_set_bytes等。

那么,当Kubelet查看回收阈值时,它实际上是在比较什么内存指标呢? Kubernetes文档提供了此问题的答案:是working set,其中甚至包含一个小脚本,显示了在节点级别决定逐出的计算。本质上,它将节点的working_set度量计算为memory root cgroup的 memory.usage_in_bytes 减去memory root cgroup中的memory.stat中的 inactive_file字段。

这正是我们过去在研究如何通过资源度量API计算节点度量时遇到的公式,请参见 cAdvisor metrics table的最后几行,这是一个好消息,因为我们可以在下面几节的图表上绘制Kubelet的驱逐决策中使用的相同指标,方法是选择当前提供几乎所有内存指标的指标源:cAdvisor

顺便说一句,如果您想看到Kubelet的代码反映了上面所说的内容–对于allocatable以及-eviction-hard的阈值,请查看Kubelet在做出驱逐决策时使用的内存指标是什么?

OOM场景#2:Pod的内存使用量超过节点的“allocatable”值

让我们考虑一个pod,它不断地使用我们的内存泄漏工具分配内存,没有为运行应用程序的容器设置请求或限制。但是现在Kubelet的日志级别要增加(从默认值–v =2增加到–v =4),并且显示的系统日志要过滤掉那些同时包含”memory”和”evict”的条目。这样,我们将看到Kubelet每10秒根据定义的内存阈值执行一次驱逐和定期检查。

视频2 -另一个pod的驱逐,但这次列出了阈值检查

内存泄漏工具每6秒分配和接触100 MiB内存块(pod清单在这里)。没有为工具提供目标内存值,因此它将尽可能长时间地运行。左上角的窗口是内存泄漏工具在分配时的原始输出,右侧窗口跟踪Pod的状态,而底部窗口跟踪感兴趣的系统消息。

启动过程很顺利,因为内存泄漏工具还没有启动。因此,每10秒从Kubelet-2发出的消息分别捕获pod可用可分配内存和可用节点内存的稳定值。值得注意的是,可分配内存值不是指kubectl describe node output中所指的“可分配”值(对于7-GiB AKS DS2_v2节点为4565 MiB),而是指为kubepod内存cgroup设置的总体限制(对于7-GiB AKS DS2_v2节点为5315 MiB),如–kube-reserved部分所述。

然后创建启动内存泄漏工具的pod,并在5分钟内以稳定的速度分配内存。在05:07,Kubelet检测到它为pod的所有可分配内存跟踪的值下降到750 MiB的硬驱逐阈值以下,并且因为节点的Kubelet使用--enforce-node-allocatable=pod默认标志–驱逐内存泄漏工具pod,并使用显式消息描述它正在做什么。

最后的日志显示该节点被标记为内存不足。

顺便说一句,与前面的pod驱逐不同,这一次您将不再看到为驱逐排序的pod列表,因为该特定消息不再匹配我们的grep过滤器。

让我们看看grafana:

图3-节点和pod的内存使用情况,沿着当前OOM场景的节点空闲内存

此图表上跟踪了3个指标:

  • 节点可用内存(绿色):节点内存容量减去节点内存使用量。prometheus度量公式:<node_memory_capacity> - container_memory_working_set_bytes{id="/",instance=<node>}
  • 节点已用内存使用情况(黄色):在该节点上运行的所有进程(包括容器内的进程)的工作集大小。prometheus度量公式: container_memory_working_set_bytes{id="/",instance=<node>}.有关详细信息,请参阅资源度量API端点表 Resource Metrics API endpoint table
  • 总pod内存使用情况(蓝色):所有容器的工作集大小之和。不包括暂停容器的使用,但这是低无论如何(约500 KiB每pod),prometheus度量公式:sum(container_memory_working_set_bytes{container!="",instance=<node>})

红线是可分配内存值(4565 MiB),其上方的红色暗区(4565 MiB ->5315 MiB)是在总体pod内存使用量进入该区域后发生逐出的区域。因此,标记的阈值仅与蓝色指标相关。它上面的黑色区域(5315 MiB ->)是总的pod内存使用量永远不会达到的地方,因为OOM killer不允许任何pod的容器进程将其带到kubepods内存cgroup限制(5315 MiB)之上,正如我们在kube-reserved部分所看到的。

由于此节点上已经有一些pod在运行,因此总体pod内存使用量从一个较小的非零值(大约130 MiB)开始。然后节点内存和整体pod内存使用率以相同的速率上升,这是正常的,因为节点上没有其他东西进行大量分配。一旦内存泄漏工具pod被驱逐,并且节点回收了它的内存,指标最终会返回到它们的原始值。

驱逐前蓝色指标的最后一个数据点应该在红色区域内吗?不太可能,因为Prometheus client每30秒(默认值)就擦除一次目标,而Kubelet每10秒运行一次驱逐检查。因此,尽管在上面的视频中的日志中看到总体pod内存使用确实超过了可分配值,但图表并没有显示它。

然而,从工具提示中可以明显看出,蓝色指标的下一个值将比列出的4.13 GiB高0.5 GiB,比可分配值(4565 MiB)高出约200 MiB。

但是在这个图表中有些东西看起来不太对。首先,在pod被逐出后,整个pod内存使用量保持不变约5分钟。这不可能是正确的,因为pod被驱逐得相当快。

原因何在?蓝色指标实际上是所有pod容器的总和。由于pod被逐出,它的容器被终止,为它们发出的内存值突然停止。但是,Prometheus会默认将突然消失的指标与其最后一个值保留5分钟(我们在PrometheusPrometheus部分讨论过这一点.

为什么其他2个指标不受影响?它们都跟踪memory root cgroup统计信息,在被逐出的pod的容器停止后,memory root cgroup的值会立即更新。

第二,存在连续的时间戳,其中所有度量值在驱逐时间(22:03:25)前后保持完全相同。第三,总体pod内存使用的增长率有时显得“起伏不定”,但这可能与前一点有关。我不太确定这最后2点的原因,但我确实怀疑cAdvisor或Prometheus的bug

–eviction-hard

Kubernetes自己的计划是只允许节点内存的有限数量给pod,这在保护任何流氓pod对其他pod或节点本身造成伤害方面效果很好。但计划并不总是如预期的那样。一个问题是,该节点上还有其他参与者,它们也使用节点上的有限内存总量。例如,假设OS开始消耗明显资源。尽管Kubelet设置了总体内存使用量的限制,但它并不能保证内存的使用。默认情况下,没有什么可以阻止操作系统或运行在其上的其他进程开始侵入Kubernetes的“可分配”领域。当这种情况发生时,pod将被驱逐得更早:尽管pod的整体内存使用率远远低于“allocatable”值,但还是会达到另一个阈值:eviction-hard。

在官方文档 中描述了--eviction-hard标志,它触发Kubelet尝试“每当节点上的内存可用性低于保留值时,就驱逐pod”。如果驱逐一个pod没有使可用内存远离--eviction-hard阈值,Kubelet将继续驱逐pod。

OOM场景#3:节点可用内存降至--eviction-hard标志值以下

一种方法是在节点本身上运行的常规进程中分配内存,将它作为独立的应用程序直接在节点上运行

我们将在目标节点上增加Kubelet的日志记录级别(从默认的--v=2增加--v=4),这样我们就可以看到Kubelet每隔10秒针对节点可用内存和pod可分配内存的定义内存阈值执行的定期检查。针对系统日志运行的查询为

tail /var/log/syslog -f | grep -i '(?=.*evict)(?=.*memory).*' -P

视频3 -节点可用内存低于硬逐出阈值时的Pod逐出

我们来详细分析一下。内存泄漏工具-一旦启动-每6秒(参数-e以毫秒为单位)以100 MiB(参数-m)的块为单位分配内存。没有为工具提供目标内存值(用于分配最大内存量的参数-x为0表示无限),因此它将尽可能长时间地运行。左上角的窗口是内存泄漏工具直接在节点上分配内存时的原始输出,而底部的窗口跟踪感兴趣的系统消息。

一旦启动内存泄漏工具,它将以稳定的速度分配内存5分钟以上。

首先,请注意Kubelet每隔10秒报告的可分配内存(allocatable)并没有显著下降,因为内存泄漏工具没有作为pod运行,并且节点上运行的几个pod(Grafana/Prometheus和kube-system中的几个)没有任何显著的内存活动。相反,报告的总可用内存(available)确实在持续下降–正如我们所料。

在05:52,Kubelet检测到它跟踪的节点可用内存值下降到750 MiB的硬驱逐阈值以下。它将节点标记为内存压力过大,并开始采取纠正措施。

prometheus node-exporter被驱逐两次。这个pod中的唯一容器没有设置任何请求或限制,这使得它从QoS的角度来看是一个BestEffort,Kubelet将它作为目标,然后将运行在同一节点上的Prometheus本身和Grafana进行驱逐。幸运的是,直接在节点上运行的内存泄漏工具消耗了太多的内存,以至于它被停止,这阻止了Kubelet驱逐更多的pod。

随着节点内存的消失,Kubelet的情况也不太好。在第一次驱逐之后,30s都没有看到Kubelet在日志中写入的内存统计信息。然后,它将逐出最近启动的node-exporter实例(之前已逐出),此后,超过1分钟kubelet都没有在系统日志中写入任何新内容。用于连接到节点并发回控制台输出(左上角窗口)的调试容器在给出OOM killer执行操作的提示之前停滞50秒。

您可能会认为,是OOM-killer将直接运行在节点上内存泄漏工具杀死,它将内存泄漏工具视为最庞大的任务。毕竟,我们在控制台输出中确实得到了相同的“被杀死”的信号,就像我们在OOM-killer时一样。但实际情况并非如此,因为内核日志显示最初终止的不是节点上运行的内存泄漏工具。相反,OOM killer首先选择我们用来从调试容器连接到节点本身的ssh客户端:

图4 - ssh客户端被OOM killer终止

然后,它选择在其中一个Kubernetes容器中生成的bash会话(很可能是调试会话):

图5 -Bash进程接下来被OOM killer终止

您可能会觉得奇怪,几乎不消耗任何内存的进程会被杀死,而不是内存泄漏工具占用节点上几乎所有的内存。最后2个打印屏幕显示了泄漏工具的不成比例使用,与每次终止的相应过程相反:驻留集大小(RSS)是第5列,以内存页为单位(1页= 4 KiB)。

那么,为什么OOM-killer要避开使用最大的进程呢?请注意,1000表示 oom_score_adj (最后2个打印屏幕中的任务列表中的最后一个数字列),其是具有BestEffort的QoS等级的调试容器的结果(没有请求,也没有为其唯一容器设置任何限制),根据 文档。这就是为会被OOM-killer吸引的原因,尽管它们的内存使用率相对较低。另一方面,由于在节点上运行的内存泄漏工具是一个标准进程,而不是通过Kubernetes启动的,因此其 oom_score_adj为0。

由于我们的内存泄漏工具启动,节点内存使用量开始上升,而整体pod内存使用量仍然保持不变。这是意料之中的,因为该工具作为独立于Kubernetes的进程直接在节点上运行。如果它作为一个pod运行,我们最终会遇到--kube-reserved的限制,实际上是在重复前面分析过的OOM场景

请注意,在任何一个点上,pod的整体内存使用量都不会显著下降-node-exporter会被逐出,但它每次都会重新启动,因为它是由kubelet控制的。这也解释了为什么OOM-killer开始行动得相当快–因为在消耗大量内存的节点上运行的pod列表并不完整,Kubelet实际上无法回收太多内存(它所针对的pod无论如何都会重新启动,而且它似乎无法尝试进一步驱逐更多的pod)

更改–eviction-hard阈值

根据中的示意图 图二 的值 --eviction-hard内存值的标志将对pod的可分配内存产生影响。我们将其从默认的750 MiB提升到1000 MiB。标志的值将变为 -eviction-hard=memory.available<1000Mi

可分配内存值已减少。让我们重做刚才的计算

由于AKS当前不使用 --system-reserved标志,内存的可分配值计算为总内存容量减去--kube-reserved--eviction-hard标志的值。运行DS2_v2 AKS节点的编号,我们得到:

  • 7120616 KiB总可用内存,相当于6953 MiB, kubectl describe node
  • 减去1638 MiB,即--kube-reserved
  • 减去1000 MiB,即 --eviction-hard

为4315 MiB,大致相当于中看到的4419304 KiB值 kubectl describe node

现在让我们看一下kubepods cgroup,它是在节点上运行的所有pod的cgroup的父节点。从相应节点上的调试容器:

  • cd /sys/fs/cgroup/memory/kubepods
  • cat memory.limit_in_bytes
  • 5573943296

Kubelet的pod回收机制和内核的OOM killer之间的交互

值得指出的是,由于OOM killer和Kubelet的pod逐出机制的共存, 驱逐只在特定阈值内的指定间隔(默认为10秒)才开始,但OOM killer总是保持警惕,但会有一个硬限制–不时会出现令人惊讶的情况。因此,Kubelet和OOM-killer有时会表现为在某种竞争条件下为杀死行为不端的容器而“战斗”。

在OOM killer杀掉进程后,等Kubelet来到10s窗口时,就会提示试图杀死一个不再存在的容器。

1
2
3
- Jan 14 18:01:55 aks-agentpool-20086390-vmss00003C kernel: [ 1432.394676] Memory cgroup out of memory: Killed process 20341 (dotnet) total-vm:172181696kB, anon-rss:4776416kB, file-rss:25296kB, shmem-rss:0kB, UID:0 pgtables:9620kB oom_score_adj:1000

- Jan 14 18:02:19 aks-agentpool-20086390-vmss00003C kubelet[3044]: E0114 18:02:19.001858 3044 kubelet_pods.go:1288] "Failed killing the pod" err="failed to \"KillContainer\" for \"alloc-mem\" with KillContainerError: \"rpc error: code = NotFound desc = an error occurred when try to find container \\\"d3f3b2f7f02b832711593044c30a165bd991b4af5b1eadbb0c6d313d57660616\\\": not found\"" podName="alloc-mem-leak"

作者注:

这就是作者在【背景】一栏中提出问题的回答:

当某个pod极速申请内存时超了limit,还没等到kubelet 驱逐周期前,会被OOM-killer杀掉,等kubelet再来kill时,container已经被kill了,因此container已不存在

Kubelet杀container是因为OOM吗?

因此,我们知道当节点处于内存压力下时,Kubelet将开始驱逐pod(因为节点的pods allocatable(已分配)值变得太大,或者节点的整体内存不足)。但是,Kubelet终止container是因为它们超过了规定的限制吗?换句话说:

Kubelet会比OOM-killer更快地介入(至少有时),以阻止超过其内存限制的容器吗?

目前的文件(截至2021年12月)似乎在某种程度上表明:

图7 - Kubernetes文档显示Kubelet因超出其内存限制而终止容器

注意,相应的容器确实被OOM杀死了5次,但是父pod的event也提到了一个因为驱逐而被“杀死”的事件,然而,这个pod显然是运行,而不是被驱逐。我无法复现这种行为,但我仍然想知道Kubelet是否真的会终止违反其限制的容器?

但事实似乎并非如此,否则Kubelet将完全依赖于OS OOM killer来杀死超过其配置内存限制的容器。导致我得出这个结论的原因是:

  • Kubelet正在检查是否只在指定的时间间隔(当前为10秒)执行驱逐,但这个时间间隔无法让kubelet立即发现内存被耗尽
  • 内核记录了OOM日志,有专门的代码记录OOM事件,这篇博客详细解释了这一点,但这听起来不太可信, 文章中,Kubelet在确保它能够捕捉到少数情况,当它错过时,容器反而被内核杀死,
  • 即使OOM killer可以为特定的cgroup停止,这并不意味着它完全忽略了分配,如文档中的第10节,”如果OOM killer被禁用,cgroup下的任务在请求可负责内存时将挂起/休眠在内存cgroup的OOM等待队列中,因此,由于OOM-killer处于休眠状态,这不会为Kubelet提供”捕获”使用超过其限制的容器的机会, 相反,OOM Killer首先不会允许分配,因为这些进程将简单地挂起
  • 这篇文章中进行的所有测试都没有发现这种情况
  • 我没有深入代码,我没有找到kubelet不支持通过监控每个容器的内存限制的论点
  • 上面截图中看到的事件的最后一部分是“需要杀死Pod”。此消息不再出现在Kubernetes最新版本的代码中,但可以在页面测试完成时(~2019年),然而,当底层节点不处于内存压力状态时,Kubelet监视OOM事件的方式似乎没有什么根本性的不同–再一次快速浏览一下
  • Kubelet设置了调整后的 oom_score_adj 用于它创建的容器这似乎表明Kubelet对OOM-killer的依赖程度很高

关于pod驱逐的结论

让我们在这部分中总结一下关于OOM终止的容器和pod驱逐的发现:

  1. Pod在总节点内存中的“可分配”百分比各不相同:到目前为止,我们看到的阈值是针对本文中测试AKS集群中使用的节点类型的,即DS2_v2机器。正如我们所看到的,“allocatable”值仅代表这个7 GB内存节点上内存容量的65%。但该百分比随节点的内存容量而变化,因为用于--kube-reserved值的公式是这么计算的,这意味着节点拥有更多内存时,--kube-reserved将消耗更少的内存。例如,在具有16 GiB RAM的Azure D4s_v3上,“allocatable”值跳至78%。

  2. OOM终止的容器和pod驱逐并不总是公平的:正如我们已经看到的,很可能泄漏或以其他方式恶意消耗内存的进程或容器不会受到“惩罚”,而是其他进程或容器在没有自身错误的情况下被终止。为pod设置一个“有保证的”QoS等级确实有帮助,但它不会阻止一个节点在操作系统组件的内存压力下最终驱逐它们。

  3. OOM killer与pod驱逐的作用范围:OOM killer在容器级别上起作用,终止那些容器中的进程(主进程可以是第一个退出的进程,也可以不是–正如我们在Cgroups和OOM killer中详细看到的那样。另一方面,Pod驱逐将针对整个pod及其所有容器。

  4. OOM killer与pod驱逐的响应时间:OOM killer嵌入在内核中,它可以快速捕获任何试图使用超过限制的容器进程。相比之下,Kubelet(负责处理pod驱逐),在默认情况下检查驱逐间隔10秒。

  5. 容器内存限制并不是银弹:并不是那些没有限制的pod才能成为驱逐的目标。设置限制不会阻止pod驱逐。您可以为内存设置一个相对较低的请求值-意味着pod被安排在特定节点上-但有一个极高的限制(实际上比节点的总容量高得多)。随后分配大量内存将导致pod被驱逐。有保证的pod不受这种情况的影响,因为它们比那些“鲁莽”分配的pod具有更好的QoS级别,所以Kubelet将从后一种类型中选择其驱逐目标。

  6. 驱逐过程中涉及3个标志 : --kube-reserved 可保护Kubelet和其他Kubernetes守护程序不被pod分配过多内存。它通过规定”kubepods”内存cgroup的限制来实现这一点,cgroup是所有pod及其容器的父级。因此,如果pod试图使用超过【容量减去 --kube-reserved】的值时,则OOM killer介入,选择并终止这些容器中的一个进程。这保证了pod永远不会达到--kube-reserved的值。如果pod分配的容量小于【容量-kube-reserved】值,但大于kubectl describe node输出中的“allocatable”,则Kubelet将选择并逐出pod(如果kublet在10 s的检查周期内捕获到该值),请注意,操作系统、其他守护进程或在Kubernetes外的进程不会被此标志停止,这仅仅是因为`–kube-reserved``转换为如上所示的设置,即只有pod会受到影响,而不是“常规”操作系统进程

    --eviction-hard标志确保一旦节点低于指定的内存量,pod就开始被逐出,同时它还间接指示pod的“可分配”内存的大小。–system-reserved标志是可用的,但是AKS目前没有为它设置默认值。。

  7. 驱逐期间的宽限期 :在本节执行的整个测试中,所有pod均被驱逐,没有任何终止宽限期。尽管尚未讨论,但在启用所述软收回阈值时,可以有一个宽限期 这里

  8. OOM killed容器与pod驱逐行为 :默认情况下,OOM killer会导致容器重新启动(除非修改 restartpolicy ),除非它们由deployment或statefulset之类的东西管理-否则将永远对pod进行驱逐

kubectl top pod显示内存使用率为100%是否有问题?

作者注:

在这一小节中,原作者非常详细地解释了当使用kubectl top pod时内存使用率>100%是否有问题?

这里只给出原作者结论,篇幅较长就不进行翻译了,对全文的理解没有影响

因此,为了回答最初的问题,只要您的pod具有“保证的”QoS等级(或较低等级,但您知道它们的长期内存使用),并且操作系统本身没有波动的内存使用,那么您完全可以使kubectl top node 输出百分比大于100%,并处于正常情况。如果您不知道pod的内存使用行为,也不知道其QoS类别或节点的操作系统内存使用情况,则应将百分比超过100%视为严重问题,并开始为pod设置适当的请求和限制,观察操作系统内存使用行为等。

信号及退出码

当一个容器终止时,不管它是运行完成还是发生了什么不好的事情,Kubernetes都会记下它的退出代码。您可以在kubectl describe pod的输出中很容易地看到它的父pod。该退出代码提供了有关该容器内主进程发生了什么。
有一些很棒的文章讨论了退出代码和Kubernetes,比如[Exit Codes in Containers and Kubernetes – The Complete Guide。我们接下来要讨论的内容主要为内存不足情况。
Linux上的信号和退出代码是一个相当复杂的话题,所使用的各种shell之间存在差异,而且还有其他各种微妙之处。

但为了简化,主要思想是如果退出代码大于或等于128,那么它意味着我们的容器被它接收到的信号杀死了从退出代码中减去128就是信号值。根据此编号,我们可以找到相应的信号,例如,通过查阅此处信号手册页上的[列表](Linux manual page](https://man7.org/linux/man-pages/man7/signal.7.html)。
撇开软驱逐阈值不谈,无论是Kubelet决定驱逐容器的父pod还是OOM-killer终止该容器的主进程,Kubernetes报告的退出码将是137,pod驱逐和OOM-killer两种情况发送的都是SIGKILL
如果容器是OOM终止的,则针对父pod运行的kubectl describe pod将在reason字段中列出”OOMKilled”。 当容器确实被OOM-killer终止时,Kubelet如何知道需要用OOMKilled标记容器的呢?因为Kubelet会在OOM killer执行操作时监视内核生成的事件,所以它知道发生了什么以及哪个被oom了(参考is Kubelet killing containers due to OOM?
我们还关心另一个退出代码:在我们的内存泄漏工具中,如果它试图分配的内存超过. NET运行时允许的内存,那么进程将以退出代码139终止。这对应于SIGSEGV信号: 非常内存访问
不过,在查看退出代码时应小心。下面是kubectl describe pod的部分输出。发生了什么?

1
2
3
4
5
6
7
8
9
10
11
12
State:          Terminated
Reason: Error
Exit Code: 143
Started: Fri, 28 Jan 2022 23:12:28 +0200
Finished: Fri, 28 Jan 2022 23:15:33 +0200
Last State: Terminated
Reason: Error
Exit Code: 137
Started: Fri, 28 Jan 2022 23:11:33 +0200
Finished: Fri, 28 Jan 2022 23:12:26 +0200
Ready: False
Restart Count: 1

在这里看到退出代码137, OOM killer是否对上个容器执行了kill呢?没有,因为容器运行得很好且没有stop的迹象。直到我使用htop的kill命令通过向主进程发送SIGKILL信号来终止主进程。这个信号的id在Linux上是9(不管CPU架构如何),因此退出代码的最终值是9 + 128 = 137。

再试一次会怎么样?pod再次运行良好,直到我再次使用htop的kill命令以SIGTERM信号终止同一进程。这个实例的id是15,因此退出代码是15 + 128 = 143。
因此,不应该孤立地看待容器终止的退出代码。不要盲目地认为137是”OOMCilled”,也要检查”reason”字段。
终止进程的信号是否可以通过某种方式进行监控?有很多方法可以做到这一点。

这是一个例子How to obtain data about who sends kill signals in Linux

如何在Linux中获取有关谁发送了终止信号的数据。

The major drawback is that I couldn’t use it to see the actual SIGKILL sent by the OOM killer to a process that’s terminated inside a Kubernetes container

主要的缺点是我无法使用它来查OOM-killer发送给运行在kubernetes中某一个容器中被终止的进程的SIGKILL信号,我所看到的只是从containerd发送到containerd-shim的kill信号,很可能是Kubelet决定终止pod用以响应内存泄漏工具进程消失的结果。dotnet进程或其任何线程的id都与kill的目标进程的id不匹配(然而dotnet进程毕竟是containerd-shim进程的子进程)

Metrics Testing

Grafana

kubectl top node

Resource Metrics API endpoint

Summary API endpoint

cAdvisor

cgroups pseudo-filesystem

htop

Why is there a difference between what htop shows for the container process and kubectl top pod?

作者注:

以上小节,原作者做了相关测试来为上述结论提供相关证据,起辅助作用,篇幅太长,对结论影响不大,作者将不对这些节进行翻译

Flows leading to out-of-memory situations

我们已经在上面中看到了一个成功的运行,其中内存泄漏工具可以分配它所请求的内容。现在让我们分析一下事情是如何出错的,即当内存分配导致内存不足(OOM)错误时,反过来触发容器被杀死和pod被驱逐
我们还讨论了运行容器所涉及的各种组件是如何组合在一起的-应用程序的运行时(如果存在)、Kubernetes本身和节点的底层操作系统。而且很明显,他们每个人都可以有自己的记忆极限,如果突破,可能会导致采取激烈的措施。
下图使用所有这些知识来描述如何处理内存分配请求,包括应用运行时(浅蓝色)、Kubernetes的回收机制(蓝灰色)和OOM killer(浅粉色)的决策,以及此请求如何因OOM错误而失败,从而导致严重后果(红色)。

F21 – 可能的内存分配流程图

上面的流程捕获了当容器尝试分配和使用一定数量的内存时会发生什么。结束状态是内存分配成功(绿色框)或失败(任何红色框)。
每个失败的结束状态(任何红框)都有一个或多个与之关联的内存不足(OOM)方案。OOM场景代表了通过图导致特定结果的多个可能“路径”的结果。这些将在下一节OOM场景中描述。请注意,每个场景名称旁边的id(例如,OOM 3中的3)只是本文中使用的一种编号,以便轻松引用每个场景名称-它们在Kubernetes中没有任何意义。
此图中做了一些假设,以简化问题。例如,节点上的overcommit被认为是始终启用的(正如我们在过度提交部分所讨论的),这为我们节省了一些额外的决策块。操作系统是Linux,因为这是本文讨论的唯一操作系统。swap也被认为是关闭的–截至目前(2021年12月)是标准的,但它可能会在未来改变,特别是因为支持它的功能门已经在Kubernetes 1.22中引入。Kubernetes的软驱逐阈值也没有考虑在内。
如果图表只引用了一个容器在特定时间进行内存分配,那么流程中的一些操作就不必绑定到所述容器。图中有两个方框(粉色,红色轮廓),描述了可能影响的操作,而不仅仅是执行当前内存分配的容器:“已调用OS OOM killer”和“Kubelet驱逐一个或多个pod”。因此,请注意,图中至少有2个流通过这些框,因此分配内存并将底层节点置于内存压力之下的容器不会终止。它不仅“侥幸逃脱”并成功地分配和使用内存–至少可以使用一段时间–而且它会导致其他容器或pod在没有自身错误的情况下被终止。这一点在Cgroups、OOM killer和Pod逐出部分有详细介绍。

OOM场景

让我们看看Kubernetes中涉及内存不足(OOM)的一些场景。它们最初是如何发生的,可以从上面导致内存不足情况的流程部分的图表中一目了然。将显示对每种情景的详细分析。
大多数场景都给出了如何重新创建场景的Pod清单,它们包含字段spec.nodeName,以显式地指示应该调度相应pod的节点。

OOM1:容器在超出其内存限制时被OOMKilled

在OOM场景#1中分析了这一确切场景:OOM Scenario #1: Container is OOMKilled when it exceeds its limit。展示了一旦容器超过极限,OOM-killer就将其kill掉
容器的退出代码是137,Kubernetes将”season”字段设置为”OOMGilled”。如果为父pod设置了默认的重新启动策略,则容器将无休止地重新启动。

OOM2:Pod的内存使用量超过节点的”可分配(allocatable)”值

如果您没有对某些pod使用限制或者过度使用限制,就会遇到这种情况。

在此上下文中,我们将”overcommit”理解为某些pod的限制大于其请求值,并且计划pod的内存限制之和超过了节点可以支持的范围。注意,我们并不是指Linux内存过量使用,
回到OOM场景#2:Pod的内存使用超过了节点的”allocatable”值, 结果是pod被逐出,并且–如果它不受诸如deployment对象之类的任何对象控制–永远不会再次启动。
需要记住的一件事是,这个场景里,需要一部分的内存分配执行得相当慢,需要为运行在底层节点上的Kubelet提供了充足的时间,以便在测试pod开始使用过多内存时将其逐出。如果内存分配发生得更快–以至于Kubelet还没来到对”kubepods” cgroup是否达到其极限的定期检查(默认10s),OOM-killer将启动

OOM3:节点可用内存降至硬逐出阈值以下

以上流程图中没有打印错误:这种情况确实出现了两次-这就是为什么在它旁边放置了一个星号。内存泄漏工具作为一个公共进程直接在操作系统上启动。随着节点上的可用内存开始蒸发,Kubelet被触发以驱逐一些pod及其容器,由于这无法回收内存,随后OOM-killer终止了一些容器。这两种”终止”机制-pod驱逐和OOM-killer, 都没有杀掉实际的占用的进程,最终导致内存最终耗尽。
在此详细介绍此场景如何展开:OOM Scenario #3: Node available memory drops below the –eviction-hard flag value
您可能会争辩说,从技术上讲,在这个场景中进行内存分配的不是容器。因为它只作为常规进程运行。但它确实会以不止一种方式对pods造成损害,这一点在图表上很重要。

OOM4:Pod的内存使用超过节点的”可分配(allocatable)”值(快速分配)

等同于OOM场景2,但假设分配内存过快-如在分配内存时不等待下一个内存太长时间-这会触发OOM killer,而不是上面的OOM2中所示的pod逐出。

可以通过减少分配之间的时间或者显著增加每次分配的内存来实现。

OOM5:容器设置了限制,应用程序内部分配内存,但应用程序运行时最终会在超出限制之前分配失败

启动带有一个容器的pod。在容器内运行的应用程序是以使用运行时的语言(例如. NET)编写的。容器设置了内存限制(memory limit)。应用程序开始分配内存,但从到达limit的值,因为它在相当早的时候就报告内存不足。
一个实际的例子:您启动一个pod,该pod具有一个容器,该容器的内存限制设置为2000 MiB。您知道您的. NET应用程序需要大约1700 MiB,因此您认为应该是安全的。但是随着应用程序内存使用量的增长,你会发现你永远不会超过1700 MiB的内存分配。你的容器没有被OOMkilled,但是它奇怪地被重新启动了。
参考Runtime implications around OOM / .NET,在这种情况下,容器的退出代码为139,对应于SIGSEGV信号(更多详细信息,请参见Signals and exit codes),而”原因”字段由Kubernetes设置为”错误”。
如果为父pod设置了默认的重新启动策略,则容器将无休止地重新启动。

问答

Pod驱逐

Q:为什么在您显示的日志中可以看到”Killing container with a grace period override”消息?源代码显示这是一条–v=3级消息,AKS Kubelet以–v=2开头。怎么回事?
A:v1.21.2中就有一个bug,即使没有指定宽限期,上述消息也会被错误地记录–v=2消息。

Q:被驱逐的Pod的状态是否为”OOMKilled”?
A:不是,kubectl describe pod的示例输出如下(已删除不相关部件),被逐出pod的状态设置为”Failed“,原因为”Evicted

Q: 其中一个容器已被OOM kill的Pod的状态是什么?pod状态是否为”OOMkilled”?
A:在kubectl describe pod时,你不会看到pod的状态为”OOMkilled”。但如果运行kubectl get pod,则pod自身的状态将在”OOMkilled”状态之间循环,即使只有一个容器被OOM杀死,而不管其他容器是否正常

Q: 我的一个pod被驱逐,但是当我查看驱逐消息时,它指出其中的一个容器在一个有几GiB空闲可分配内存的节点上占用了不到100 MiB(如下)。这是怎么回事?

A: 截至2021年12月,使用cAdvisor检索使用指标,检索指标的间隔为10 - 15秒。上面看到的输出所对应的pod确实填满了它所运行的7-GiB节点上的整个可分配内存,这导致它被驱逐(没有触发OOM killer)。显然,从仅仅使用~50 MiB内存开始还可以用很久。但问题是它分配内存的速度相当快:它在分配的每100 MiB内存块之间仅暂停200毫秒,从启动到被美瞳需要大约12秒(从最后的消息中可以看出)。我怀疑打印的内存使用值是在创建容器时获得的,因此可以解释为什么这个数字很低,因为根本没有其他机会获得容器内部的新读数,因为它在pod驱逐后已经终止。

Q:我正在执行kubectl describe pod,并在输出的末尾看到以下事件。”杀死”列为原因是否意味着里面的容器被OOM kill?

A: 不,那是Kubelet在决定驱逐pod后终止了container。参见Is Kubelet killing containers due to OOM?。但是要注意,在Kubelet的pod机制和内核的OOM killer之间可能会出现罕见的交互情况,其中event可能不会告诉实际发生了什么(例如,reason中显示”Killing”,因为Kubelet试图驱逐pod,但OOM killer更快,杀死了里面的容器)。

Q:在pod内存使用时,指定container!=""有什么意义?
答:过滤”container”字段不为空的容器可确保我们避免重复计数,因为 root cgroup包含所有内容,然后/kubepods/burstable包含 burstablepod的聚合统计信息等。实际上,我们只是在计数叶子容器。这就是sum(container_memory_working_set_bytes{container!="",instance=<node>})

Q:在一些视频中,我看到内存泄漏工具报告20 GiB的可见内存,但运行它的节点没有那么多的RAM。这是怎么回事?
A:使用的容器映像具有20 GiB的堆硬限制,以便. NET运行时在达到相应容器配置内存限制的75%时不阻止分配。换句话说,这保证了. NET运行时不会在AKS测试集群中的7-GiB节点上生成OOM情况。

Q:我可以使用什么Prometheus指标来查看OOM killer对节点执行操作的次数?
A:使用node_vmstat_oom_kill

signal and exit code

Q: 可以发送一个SIGSEGV到一个进程使它崩溃吗?
A:在使用. NET的内存泄漏工具的情况下,完全可以做到, 而且在处理这个事件的工具代码中没有什么特别的东西。但与内核发送此信号不同,使用kill将其手动发送到另一个进程不会产生显著效果,参考

Flows and OOM Scenarios

Q: 在分配流程图中,为什么说”突然”分配?
A:我所说的”突然”是指内存分配大到足以触发OOM-killer。”突然”分配意味着两种情况-(1)由于操作系统组件(或直接在操作系统上运行的东西)突然分配了大量内存,节点的内存总体非常低,或(2)整个”kubepod” cgroup内存不足(意味着pod试图使用超过”可分配”内存区域和硬逐出阈值的内存)。在这两种情况下,分配都不会是”缓慢的”分配:在(1)的情况下,慢速分配将使系统触发Kubernetes硬驱逐阈值,这将驱逐一些pod并回收一些内存。在(2)的情况下,在”可分配的”图表上的红色散列区域中徘徊的整体pod内存使用(参见节点可分配,图示)将导致Kubelet驱逐pod, 不给OOM-killer采取行动的机会。

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?