bigywater.tech
Kubernetes · 39

OOMKilled:limit 和容器内进程的真实关系

limit 和容器里的进程到底是什么关系?只杀超出的那个进程,还是整个容器?哪些东西算内存?这篇读 cgroup 的 memory.max、memory.oom.group 与 memory.events,用 python、sh 子进程、内存型 emptyDir 和磁盘文件分别试一遍,最后验证不重启在线调大 limit。

memory.oom.group = 1
容器 cgroup 里的这个参数让 OOM 时整个容器的进程一起被杀
oom_kill 4
sh 作 1 号进程、python 是子进程时,一次 OOM 杀掉的进程数(容器整体 OOMKilled)
150 MiB
在线把 limit 从 64Mi 调到 256Mi 后,没有重启、继续分配到的内存

limit 就是 memory.max

实测 · 本机执行输出
  容器启动 1 秒后读 cgroup:memory.max=67108864(64 MiB = limit 64Mi);memory.oom.group=1(1 表示 OOM 时整个 cgroup 里的进程一起杀);memory.high=max
  python 分配 100 MiB 后 5.5 秒内:lastState.terminated reason=OOMKilled exitCode=137(= 128 + 9,SIGKILL);restartCount=1;Pod 级 memory.events:low 0 high 0 max 38 oom 1 oom_kill 2 oom_group_kill 1 sock_throttled 0
  又过 25 秒:restartCount=2;kubectl get STATUS=Running;Pod 级 memory.events:low 0 high 0 max 142 oom 3 oom_kill 6 oom_group_kill 3 sock_throttled 0

limit 64Mi 的容器,cgroup 里 memory.max=67108864(64 MiB),memory.high 是 max(没有软限制),还有 memory.oom.group=1。python 作 1 号进程一次性分配 100 MiB,容器的 lastState.terminated 是 reason: OOMKilled、exitCode: 137(128 + 9,被 SIGKILL)。内核的计数在 Pod 级 cgroup 的 memory.events 里:oom_kill、oom_group_kill 随着每次重启增加(25 秒后 oom_group_kill 3)。

不是只杀超出的进程

实测 · 本机执行输出
  OOM 之前容器里的进程:1 sh -c sleep 3600 & sleep 6; python -c 'imp | 12 sleep 3600 | 13 sleep 6
  python 子进程分配 100 MiB:5.9 秒内容器 lastState.terminated reason=OOMKilled exitCode=137,restartCount=1——PID 1(sh)并没有自己超出内存,整个容器仍被 OOMKilled(memory.oom.group=1)
  容器日志里没有“python 退出码”那行(sh 也被一起杀了):(空);Pod 级 memory.events:low 0 high 0 max 25 oom 1 oom_kill 4 oom_group_kill 1 sock_throttled 0

容器里 sh 是 1 号进程,后台还有两个 sleep,真正分配内存的是子进程 python。因为 memory.oom.group=1,一次 OOM 把整个容器的进程一起杀掉:memory.events 里 oom_kill 4(4 个进程)而 oom_group_kill 1;容器被当作 OOMKilled 重启,sh 里本该在 python 退出后打印的那一行日志没有出现。也就是说“让 1 号进程别超内存就行”是不够的,容器里所有进程的内存加在一起不能超过 limit。

什么算用了内存

实测 · 本机执行输出
  写 100 MiB 到内存型 emptyDir(tmpfs),limit 64Mi:lastState.terminated 的变化:
    + 7.4s restartCount=1 reason=StartError exitCode=128 message=failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?)
    +21.5s restartCount=2 reason=StartError exitCode=128 message=failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?)
  写 100 MiB 到磁盘型 emptyDir:restartCount=0;日志:dd 结束 0;memory.current=62 MiB(其中 file=60 MiB shmem=0 MiB anon=0 MiB);写了 100 MiB 却只占了 limit 以内,因为页缓存可以被回收

写进内存型 emptyDir(medium: Memory,tmpfs)的文件算作容器的内存,而且重启不清除:往里写 100 MiB 的容器反复失败,StartError,message 是 container init was OOM-killed (memory limit too low?)——tmpfs 里的数据还占着内存,新容器刚 init 就被杀了。往磁盘型 emptyDir 写同样 100 MiB,memory.current 只有 62 MiB(file=60 MiB、shmem=0、anon=0),页缓存虽然也算内存,但可以被回收,没有触发 OOM。

在线调大

实测 · 本机执行输出
  容器每秒多分配 10 MiB,limit 64Mi;分配刚开始就执行 kubectl patch --subresource=resize 把 limit 调到 256Mi:pod/grow patched
  20 秒后:restartCount=0;state=running;memory.max=268435456;日志最后一行:已分配 150 MiB
  spec 里 limit=256Mi;status.containerStatuses[0].resources.limits.memory=256Mi

容器每秒多分配 10 MiB,limit 64Mi。分配刚开始就用 kubectl patch --subresource=resize 把 limit 调到 256Mi:20 秒后 restartCount=0、memory.max=268435456,日志停在 已分配 150 MiB——超过了原来的 64Mi 也没被杀;spec 与 status.containerStatuses[0].resources 里的 limit 都是 256Mi。调大是安全的;调小而用量已经超过新值时会发生什么,这里没有测。

分步演示

memory limit 与 OOM
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0,cgroup v2);python:3.12-alpine 与 busybox:1.36;为了能在被杀之前读 cgroup,第 1 节的容器先 sleep 6 再分配;memory.max、memory.oom.group、memory.stat 用 docker exec 读容器 cgroup,memory.events 读 Pod 级 cgroup(容器重启后容器级 cgroup 会重建);第 3 节第一次 OOMKilled 的 lastState 在 0.5 秒轮询间隔里被后面的 StartError 覆盖了,所以只记录到 StartError;在线调整用 kubectl patch --subresource=resize;每种情形各一次
    • 没有验证:cgroup v1 下的行为(没有 memory.oom.group,会只杀单个进程);把 limit 在线调小到低于当前用量;memory.high 与 Memory QoS 特性;节点级 OOM(没有 cgroup limit 时内核按 oom_score_adj 选进程,见 QoS 一篇);内存型 emptyDir 的 sizeLimit;JVM/Go 运行时对 cgroup 内存限制的自动识别
    京ICP备2026067502号-1京公网安备11010502063487号