> For the complete documentation index, see [llms.txt](https://darren.gitbook.io/project/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://darren.gitbook.io/project/shen-ru-qian-chu-k8s/oom-kill-yuan-li-jiang-jie.md).

# oom kill原理讲解

## **简介:**

&#x20;OOM就是out of memory的缩写，虽然linux kernel有很多的内存管理技巧（从cache中回收、swap out等）来满足各种应用空间的vm内存需求，但是，当你的系统配置不合理，让一匹小马拉大车的时候，linux kernel会运行非常缓慢并且在某个时间点分配page frame的时候遇到内存耗尽、无法分配的状况。

应对这种状况首先应该是系统管理员，他需要首先给系统增加内存，不过对于kernel而言，当面对OOM的时候，要根据OOM参数来进行相应的处理。

oom kiiler会在内存紧张的时候，会依次kill内存占用较高的进程，发送Sig15(SIGTERM)或Sig9(SIGKILL)，取决于内核版本(可见uname -a，>= 2.6.32只会发送[sigkill](https://elixir.free-electrons.com/linux/v2.6.18/source/mm/oom_kill.c)，并在/var/log/message中进行记录。里面会记录一些如pid，process name，cpu mask，trace等信息，通过监控可以发现类似问题

## **1.什么是oom killer**

他是Linux内核设计的一种机制，在内存不足的会后，选择一个占用内存较大的进程并kill掉这个进程，以满足内存申请的需求（内存不足的时候该怎么办，其实是个两难的事情，oom killer算是提供了一种方案吧）

## **2.在什么时候触发？**

前面说了，在内存不足的时候触发，再往细节看，主要牵涉到【linux的物理内存结构】和【overcommit机制】

**2.1 内存结构 node、zone、page、order**

对于物理内存内存，linux会把它分很多区（zone），zone上还有node的概念，zone下面是很多page，这些page是根据buddy分配算法组织的，看下面两张图：

[![](http://7xijc0.com1.z0.glb.clouddn.com/memory_node_zone.png)](http://7xijc0.com1.z0.glb.clouddn.com/memory_node_zone.png)

[![](http://7xijc0.com1.z0.glb.clouddn.com/memory_buddy.png)](http://7xijc0.com1.z0.glb.clouddn.com/memory_buddy.png)

上面的概念做下简单的介绍，对后面分析oom killer日志很有必要：

* **Node**：每个CPU下的本地内存节点就是一个Node，如果是UMA架构下，就只有一个Node0,在NUMA架构下，会有多个Node
* **Zone**：每个Node会划分很多域Zone，大概有下面这些：
  * 1\) ZONE\_DMA：定义适合DMA的内存域，该区域的长度依赖于处理器类型。比如ARM所有地址都可以进行DMA，所以该值可以很大，或者干脆不定义DMA类型的内存域。而在IA-32的处理器上，一般定义为16M。
  * 2\) ZONE\_DMA32：只在64位系统上有效，为一些32位外设DMA时分配内存。如果物理内存大于4G，该值为4G，否则与实际的物理内存大小相同。
  * 3\) ZONE\_NORMAL：定义可直接映射到内核空间的普通内存域。在64位系统上，如果物理内存小于4G，该内存域为空。而在32位系统上，该值最大为896M。
  * 4\) ZONE\_HIGHMEM：只在32位系统上有效，标记超过896M范围的内存。在64位系统上，由于地址空间巨大，超过4G的内存都分布在ZONE\_NORMA内存域。
  * 5\) ZONE\_MOVABLE：伪内存域，为了实现减小内存碎片的机制。
  * 分配价值链
    * 除了只能在某个区域分配的内存（比如ZONE\_DMA），普通的内存分配会有一个“价值”的层次结构，按分配的“廉价度”依次为：ZONE\_HIGHMEM > ZONE\_NORMAL > ZONE\_DMA。
    * 即内核在进行内存分配时，优先从高端内存进行分配，其次是普通内存，最后才是DMA内存
* **Page**：zone下面就是真正的内存页了，每个页基础大小是4K，他们维护在一个叫free\_area的数组结构中
  * order：数组的index，也叫order，实际对应的是page的大小，比如order为0，那么就是一堆1个空闲页（4K）组成的链表，order为1，就是一堆2个空闲页（8K）组成的链表，order为2，就是一堆4个空闲页（16K）组成的链表

**2.2 overcommit**

根据2.1，已经知道物理内存的大概结构以及分配的规则，不过实际上还有虚拟内存的存在，他的overcommit机制和oom killer有很大关系：

> 在实际申请内存的时候，比如申请1G，并不会在物理区域中分配1G的真实物理内存，而是分配1G的虚拟内存，等到需要的时候才去真正申请物理内存，也就是说申请不等于分配

所以说，可以申请比物理内存实际大的内存，也就是overcommit，这样会面临一个问题，就是当真的需要这么多内存的时候怎么办—>oom killer!

vm.overcommit\_memory 接受三种值：

* 0 – Heuristic overcommit handling. 这是缺省值，它允许overcommit，但过于明目张胆的overcommit会被拒绝，比如malloc一次性申请的内存大小就超过了系统总内存
* 1 – Always overcommit. 允许overcommit，对内存申请来者不拒。
* 2 – Don’t overcommit. 禁止overcommit。

**3.oom killer 怎么挑选进程？**

linux会为每个进程算一个分数，最终他会将分数最高的进程kill

* /proc/\<pid>/oom\_score\_adj
  * 在计算最终的 badness score 时，会在计算结果是中加上 oom\_score\_adj，取值范围为-1000到1000
  * 如果将该值设置为-1000，则进程永远不会被杀死，因为此时 badness score 永远返回0。
* /proc/\<pid>/oom\_adj
  * 取值是-17到+15，取值越高，越容易被干掉。如果是-17，则表示不能被kill
  * 该设置参数的存在是为了和旧版本的内核兼容
* /proc/\<pid>/oom\_score
  * 这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom\_adj计算出的，消耗内存越多分越高，存活时间越长分越低
* 子进程内存：Linux在计算进程的内存消耗的时候，会将子进程所耗内存的一半同时算到父进程中。这样，那些子进程比较多的进程就要小心了。
* 其他参数（不是很关键，不解释了）
  * /proc/sys/vm/oom\_dump\_tasks
  * /proc/sys/vm/oom\_kill\_allocating\_task
  * /proc/sys/vm/panic\_on\_oom
* 关闭 OOM killer
  * sysctl -w vm.overcommit\_memory=2
  * echo “vm.overcommit\_memory=2” >> /etc/sysctl.conf

**3.1 找出最优可能被杀掉的进程**

```
vi oomscore.sh

#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
    printf "%2d %5d %s\n" \
        "$(cat $proc/oom_score)" \
        "$(basename $proc)" \
        "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

chmod +x oomscore.sh
./oomscore.sh
18   981 /usr/sbin/mysqld
 4 31359 -bash
 4 31056 -bash
 1 31358 sshd: root@pts/6
 1 31244 sshd: vpsee [priv]
 1 31159 -bash
 1 31158 sudo -i
 1 31055 sshd: root@pts/3
 1 30912 sshd: vpsee [priv]
 1 29547 /usr/sbin/sshd -D
```

**3.2 避免的oom killer的方案**

* 直接修改/proc/PID/oom\_adj文件，将其置位-17
* 修改/proc/sys/vm/lowmem\_reserve\_ratio
* 直接关闭oom-killer

参考：

* [node & zone](https://blog.csdn.net/farmwang/article/details/66975372)
* [理解LINUX的MEMORY OVERCOMMIT](http://linuxperf.com/?p=102)
* [linux OOM-killer机制（杀掉进程，释放内存）](https://blog.csdn.net/hunanchenxingyu/article/details/26271293)
* [Taming the OOM killer](https://lwn.net/Articles/317814/)
* [linux OOM 机制分析](https://github.com/datawolf/learning-kernel/blob/master/source/mem-management.rst)
* [理解和配置 Linux 下的 OOM Killer](https://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/)
* [ubuntu 解决cache逐渐变大导致oom-killer将某些进程杀死的情况](http://blog.51cto.com/jlins/1599259)
* 这个<https://ustack.io/2019-11-21-Linux%E5%86%85%E5%AD%98%E6%8E%A7%E5%88%B6%E4%B9%8Boom%20killer%E6%9C%BA%E5%88%B6.html>

&#x20;如果您`memory`为该容器设置了4GiB 的限制，则kubelet（和 [容器运行时](https://kubernetes.io/docs/setup/production-environment/container-runtimes)）执行限制。运行时可防止容器使用超出配置的资源限制的容器。例如：当容器中的进程尝试消耗的内存超过允许的数量时，系统内核将终止尝试分配的进程，并显示内存不足（OOM）错误

```
每个cgroup维护每个cgroup LRU，其结构与
全局VM。当cgroup超过其限制时，我们首先尝试
从cgroup回收内存，以便为新的磁盘空间
cgroup触摸过的页面。如果回收失败，
调用OOM例程以选择并杀死该任务中最大的任务
cgroup
```

## 补充-图解kubernetes资源QOS机制实现原理

### 总结说明：

k8s中所说的oom分两种：1是node节点oom kill，2是pod内部容器oom kill

前者可以通过脚本查看，可用来打印当前系统上 oom\_score 分数最高（最容易被 OOM Killer 杀掉）的进程

```
# vi oomscore.sh
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
    printf "%2d %5d %s\n" \
        "$(cat $proc/oom_score)" \
        "$(basename $proc)" \
        "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10
```

kubelet根据pod的Qos为每个容器设置一个oom\_score\_adj,如果kubelet无法在系统OOM之前回收足够的内存。则oom\_killer会根据内存使用比例来计算oom\_score,最后结果和oom\_score\_adj相加，得分最高的pod将会首先被驱逐。

### OOM-KILL介绍

&#x20;oom\_killer（out of memory killer）是Linux内核的一种内存管理机制，在系统可用内存较少的情况下，内核为保证系统还能够继续运行下去，会选择杀掉一些进程释放掉一些内存。

#### 通常oom\_killer的触发流程是：`进程A想要分配物理内存（通常是当进程真正去读写一块内核已经“分配”给它的内存）->触发缺页异常->内核去分配物理内存->物理内存不够了，触发OOM`

oom kiiler会在内存紧张的时候，会依次kill内存占用较高的进程，发送Sig15(SIGTERM)或Sig9(SIGKILL)，取决于内核版本(可见uname -a，>= 2.6.32只会发送[sigkill](https://elixir.free-electrons.com/linux/v2.6.18/source/mm/oom_kill.c)，并在/var/log/message中进行记录

### 一句话说明oom\_killer的功能： <a href="#e4-b8-80-e5-8f-a5-e8-af-9d-e8-af-b4-e6-98-8eoom_killer-e7-9a-84-e5-8a-9f-e8-83-bd-ef-bc-9a" id="e4-b8-80-e5-8f-a5-e8-af-9d-e8-af-b4-e6-98-8eoom_killer-e7-9a-84-e5-8a-9f-e8-83-bd-ef-bc-9a"></a>

```
当系统物理内存不足时，oom_killer遍历当前所有进程，根据进程的内存使用情况进行打分，然后从中选择一个分数最高的进程，杀之取内存
```

### 函数解析： <a href="#e5-87-bd-e6-95-b0-e8-a7-a3-e6-9e-90-ef-bc-9a" id="e5-87-bd-e6-95-b0-e8-a7-a3-e6-9e-90-ef-bc-9a"></a>

oom\_killer的处理主要集中在`mm/oom_kill.c`。

核心函数为out\_of\_memory，函数处理流程：

1. 通知系统中注册了oom\_notify\_list的模块释放一些内存，如果从这些模块中释放出了一些内存，那么皆大欢喜，直接结束oom killer流程，回收失败, 那只有进入下一步开始oom\_killer了；
2. 触发oom killer通常是由当前进程进行内存分配所引起，而如果当前进程已经挂起了一个SIG\_KILL信号，直接选中当前进程，否则进入下一步；
3. check\_panic\_on\_oom检查系统管理员的态度，看oom时是进行oom killer还是直接panic掉，如果进行oom killer，则进入下一步；
4. 如果系统管理员规定，谁引起oom，杀掉谁，那就杀掉正在尝试分配内存的进程，oom killer结束，否则进入下一步；
5. 调用select\_bad\_process选中合适进程，然后调用oom\_kill\_process杀死选中进程，如果不幸select\_bad\_process没有选出任何进程，那么内核走投无路，只有panic了。

`详情见：`L[inux OOM机制分析](https://cloud.tencent.com/developer/article/1157275)

### 系统相关配置

* /proc/sys/vm/panic\_on\_oom：配置系统产生oom时的动作
* /proc/sys/vm/oom\_kill\_allocating\_task：为true的时候，直接kill掉当前想要分配内存的进程(此进程能够被kill时)
* /proc/\<pid>/oom\_score\_adj（或/proc/\<pid>/oom\_adj）：配置PID指定进程的oom权重，子进程继承该权重值
* /proc/sys/vm/would\_have\_oomkilled：为true时并不会真正杀死oom killer选中进程，只是打印一条警告信息
* echo f >/proc/sysrq-trigger模拟oom
* cat /proc/\<PID>/oom\_score：查看PID进程的oom分数

### k8s中oom说明

如果节点在 `kubelet` 回收内存之前经历了系统 OOM（内存不足）事件，它将基于 [oom-killer](https://lwn.net/Articles/391222/) 做出响应。

&#x20;Kubernetes会根据QoS设置oom的评分调整参数`oom_score_adj`，oom\_killer 根据 内存使用情况算出oom\_score， 并且和`oom_score_adj`综合评价，进程的评分越高，当发生oom时越优先被kill。

`kubelet` 基于 pod 的 service 质量为每个容器设置一个 `oom_score_adj` 值。

| Service 质量   | oom\_score\_adj                                                                    |
| ------------ | ---------------------------------------------------------------------------------- |
| `Guaranteed` | -998                                                                               |
| `BestEffort` | 1000                                                                               |
| `Burstable`  | min(max(2, 1000 - (1000 \* memoryRequestBytes) / machineMemoryCapacityBytes), 999) |

如果 `kubelet` 在节点经历系统 OOM 之前无法回收内存，`oom_killer`将基于它在节点上使用的内存百分比算出一个 `oom_score`，并加上 `oom_score_adj` 得到容器的有效 `oom_score`，然后结束得分最高的容器。

预期的行为应该是拥有最低 service 质量并消耗和调度请求相关内存量最多的容器第一个被结束，以回收内存。

和 pod 驱逐不同，如果一个 pod 的容器是被 OOM 结束的，基于其 `RestartPolicy`，它可能会被 `kubelet` 重新启动。

例子单个节点可分配内存为8010196Ki， 大约7822.45Mi。\
根据Burstable 的计算方式:

```

request 200Mi: (1000 - 1000*200/7822.45) 约为975

request 400Mi: (1000 - 1000*400/7822.45) 约为950
```

&#x20;设置oom 规则代码： [https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/qos/policy.go](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/qos/policy.go?spm=a2c6h.12873639.0.0.528643e3Qxj5Fd\&file=policy.go)

QOS是k8s中一种资源保护机制，其主要是针对不可压缩资源比如的内存的一种控制技术，比如在内存中其通过为不同的Pod和容器构造OOM评分，并且通过内核的策略的辅助，从而实现当节点内存资源不足的时候，内核可以按照策略的优先级，优先kill掉哪些优先级比较低(分值越高优先级越低)的Pod，今天来分析下背后的实现

### 1.关键基础特性

![](https://pic3.zhimg.com/80/v2-8ccd293190b77080f9a624c07fb31c9c_720w.jpg)

### 一切皆文件

在Linux中一切皆文件，控制CGroup本身也是通过配置文件进行的,这是我创建的一个内存Lmits为200M的Pod的容器的配置

```
# pwd
/sys/fs/cgroup
# cat ./memory/kubepods/pod8e172a5c-57f5-493d-a93d-b0b64bca26df/f2fe67dc90cbfd57d873cd8a81a972213822f3f146ec4458adbe54d868cf410c/memory.limit_in_bytes
209715200
```

### **oom score**

在遇到较高内存使用压力时，Linux 内核会杀掉一些不太重要的进程，腾出空间保障系统正常运行。它会给每个进程（`/proc/$ pid / oom_score`）分配一个得分（`oom_score`），分数越高，被 OOM 的概率就越大。

**这个参数本身只反映该进程的可用资源在系统中所占的百分比，并没有“该进程有多重要”的概念**。

### **oomScoreAdj**

手动调整 `oom_score` 可以通过 `oom_score_adj` 来实现，**它允许开发者在内存不足的情况下杀死指定进程**。

具体做法是把可调参数 `/proc/pid/oom_score_adj` 直接添加到 badness() 分数中，范围从 -1000（`OOM_SCORE_ADJ_MIN`）到 +1000（`OOM_SCORE_ADJ_MAX`），使某些任务总是会被考虑 OOM，某些任务则永远不会被 OOM。

如果我们调整了 PROC1 的 `oom_score_adj` (`echo "-1000" > /proc/$(pidof PROC1)/oom_score_adj`），系统在 OOM 时就会先杀死其他进程。

## 参考文章

* [**图解kubernetes资源QOS机制实现原理**](https://zhuanlan.zhihu.com/p/107913304)
* [Kubernetes 资源预留](https://system51.github.io/2020/01/13/reserve-compute-resources-for-system-daemons/)
* [Kubernetes OOM计算规则](https://www.jianshu.com/p/b330c02a7e62)
* [oom killer理解和日志分析:知识储](http://bhsc881114.github.io/2018/06/24/oom%20killer%E7%90%86%E8%A7%A3%E5%92%8C%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90:%E7%9F%A5%E8%AF%86%E5%82%A8%E5%A4%87/)
* [LINUX内核](https://www.kernel.org/doc/html/latest/filesystems/proc.html?highlight=madv_free#chapter-3-per-process-parameters)--\*
* [官方文档](https://kubernetes.io/zh/docs/tasks/administer-cluster/out-of-resource/)
