Exit Code 137 在容器和 Kubernetes 环境中经常出现,面试时如果被问到,首先要纠正一个常见认知:137 不是专属于 OOM 的退出码。它来自 Unix 信号机制,128 加 9 等于 137,其中 9 是 SIGKILL 的编号。任何进程被 SIGKILL 强制终止,在没有特殊包装的情况下,父进程观察到的退出状态都会是 137。容器运行时记录 ExitCode 时也遵循这个规则,所以 docker、containerd、Pod 事件里看到的 137,只说明进程收到了不可捕获的强制终止信号。
排查方向因此一分为二:要么是 cgroup 内存限制触发了内核 OOM Killer,要么是外部组件直接发送了 SIGKILL。前者在 Docker 和 Kubernetes 中有明确的 OOMKilled 标志,后者则可能来自 kubelet 驱逐、节点重启、人工执行 kill -9,甚至安全策略。仅凭一个 Exit Code 137 无法判断根因,必须结合状态字段、内核日志和时间线。下面会按容器状态、主机日志、决策路径三条线展开。

一、从退出码到容器状态:OOMKilled字段是第一个分岔口
先说底层计算。在 Linux 的 wait 系列系统调用中,如果进程是被信号终止,退出状态需要拆成两部分:高字节表示信号编号,低字节可能包含 core dump 等信息。Shell 为了便于脚本判断,会把信号编号加 128 作为退出码。SIGKILL 的编号是 9,于是 128 加 9 得到 137。这一点用 kill -9 杀掉一个后台进程后,执行 wait $pid; echo $? 就能验证。容器运行时只是把这个值透传出来,并没有重新定义。
进入容器排查后,先看 Docker 的 OOMKilled 字段。它是 cgroup 内存限制被突破后,由内核 OOM Killer 杀掉进程时设置的状态。同一个容器对象中,ExitCode 为 137 且 OOMKilled 为 true,基本可以确定是容器自身内存超限,而不是外部手动 kill。在 Kubernetes 里,kubectl describe pod 的 Last State 区域会直接显示 reason: OOMKilled 和 exitCode: 137。
# 查看 Docker 容器的退出码和 OOMKilled 标志
CID=替换为你的容器ID
docker inspect --format='{{.State.ExitCode}} {{.State.OOMKilled}}' $CID
# 在 Kubernetes 中查看上一次退出原因
kubectl describe pod your-pod-name | grep -A 12 'Last State'
如果 OOMKilled 为 false,但退出码仍然是 137,就不能草率下 OOM 的结论。这时要重点排查 kubelet 驱逐、节点维护、人工删除等外部操作。Kubernetes 的 Eviction 机制在节点内存或磁盘资源紧张时,也会向 Pod 发送 SIGKILL,但通常不会把 OOMKilled 标为 true。因此这个字段是第一个也是最重要的分岔口。
另外要留意 cgroup 版本的差异。cgroup v1 中 OOM 事件记录在 memory.oom_control 文件里,而 cgroup v2 使用 memory.events 文件,字段名是 oom_kill。容器运行时和 kubelet 会读取这些文件来填充 OOMKilled 标志。如果宿主机的 cgroup 配置异常,也可能出现状态字段不准确的情况,所以主机侧日志仍然需要检查。
二、主机层取证:内核日志与cgroup事件能给出更直接证据
容器状态字段可能被容器运行时包装,但内核日志不会说谎。当 cgroup 内存限制被突破时,dmesg 中会出现类似 Memory cgroup out of memory: Killed process 4523 (java) 的记录。这条日志包含被杀的进程名、PID、进程占用内存等关键信息。如果是整机内存不足,内核日志则是 Out of memory: Killed process 1234 (java),缺少 cgroup 前缀。两种日志的差异,可以帮助我们判断内存压力到底是容器级别还是节点级别。
在节点上可以直接执行下面的命令,快速过滤最近一次 OOM 事件。如果有多条记录,需要根据时间戳和进程名对应到具体容器。容器化场景下,进程 PID 是容器内的 PID,可能和宿主机 PID 不同,需要通过 docker inspect 或 kubectl get pod 找到对应的运行时间,再进行时间线匹配。
# 在节点上查看最近的 OOM 记录 dmesg -T | grep -i 'killed process' | tail -n 20 # 查看 cgroup v2 的 OOM 事件计数,路径按实际容器替换 cat /sys/fs/cgroup/system.slice/docker-abcd1234.scope/memory.events
如果节点上没有 OOM 相关日志,但 Pod 确实以 137 退出,那大概率是外部发送 SIGKILL。此时要去看 kubelet 日志和 Kubernetes 事件。kubelet 在驱逐 Pod 时会输出 Evicting pod 字样,执行删除时则可能没有额外内核日志。把这些事件的时间戳与容器退出时间放在一起比较,能很快区分是人工操作还是系统压力触发的驱逐。
托管的 Kubernetes 集群通常不允许直接登录节点执行 dmesg,这时可以通过云厂商的节点监控、事件中心或容器日志采集系统来补全证据链。面试中如果主动提到这种受限环境下的替代方案,比如结合 kubectl describe pod 事件、Prometheus 节点内存指标、云端 OOM 告警,会比只回答一条命令更有层次感。
三、区分三类典型场景与面试回答框架
把 Exit Code 137 放到具体场景里,常见原因可以归纳为三类:容器自身内存超限、节点整体内存不足、外部强制终止。三者的核心区别在于 OOMKilled 标志和内核日志。容器自身超限时,OOMKilled 为 true,cgroup OOM 日志清晰;节点整体不足时,可能同时影响多个容器,内核日志为整机 OOM;外部强制终止时,OOMKilled 为 false,内核日志通常没有对应记录。
下面的表格可以作为面试回答时的记忆框架,按照这个顺序去描述排查路径,逻辑会比较完整。
| 典型场景 | ExitCode | OOMKilled | 内核日志 | 常见原因 |
|---|---|---|---|---|
| 容器内存超限 | 137 | true | Memory cgroup out of memory | limit设置过低、内存泄漏 |
| 节点整体内存不足 | 137 | false或true | Out of memory: Killed process | 节点过载、未设置limits |
| 外部强制终止 | 137 | false | 无对应记录 | kubectl delete、驱逐、人工kill |
面试回答时可以先给出结论:137 是 128 加 9 的结果,代表 SIGKILL,但不等于 OOM。然后分两步走:第一步查看容器状态的 OOMKilled 字段,第二步查看主机内核日志和 Kubernetes 事件。最后再补充 cgroup v1 与 v2 的差异、Pod QoS 策略、JVM 堆内存与容器内存上限的关系。这样既覆盖基础原理,又展示出实战排查能力。
预防层面,如果确认是容器内存超限,可以从几个方向调优:将应用堆内存设置得小于容器 limit,例如容器 limit 为 2Gi 时,JVM 最大堆建议设置在 1.4Gi 左右;避免设置过小的 limit 导致启动即被杀死;利用 kubectl top pods 和 docker stats 观察实际内存使用;对关键服务设置 HPA 或 VPA。对于节点整体不足,则需要调整 Pod 反亲和性、节点预留资源、system-reserved 和 kube-reserved 参数,避免系统组件和应用互相争抢内存。
Exit Code 137容器OOMSIGKILL修改时间:2026-10-04 06:35:58