Docker 容器退出码 137 (OOMKilled) 排查与修复:内存不足导致 SIGKILL 的完整解决方案
快速答案
- 结论:退出码 137 表示容器被 SIGKILL 信号(信号 9)终止,最常见原因是内存不足(OOM),无论是容器自身超过内存限制,还是宿主机整体内存耗尽。
- 第一检查:运行
docker inspect <container>查看State.OOMKilled是否为true;同时执行dmesg | grep -i "killed process"检查内核日志。 - 最小修复:增加容器内存限制(
docker run -m 2g ...)或在 Docker Compose 中添加mem_limit: 512m;对于 Kubernetes 环境,调整resources.limits.memory。 - 适用边界:本指南适用于 Docker 单机环境和 Kubernetes 环境,覆盖从原因分析到预防措施的完整闭环,但不包含自动化日志分析脚本或告警配置。
它解决什么问题 / 适用场景
Docker 容器退出码 137 是一个常见但容易被误判的问题。它直接表明容器收到了 SIGKILL 信号,无法优雅关闭。最常见的触发场景包括:
- 容器内存超限:容器使用的内存超过了
--memory参数设置的限制,被 Docker 的 cgroup OOM killer 杀死。 - 宿主机内存耗尽:宿主机整体内存不足,内核主动杀死进程以释放内存,即使容器本身未超限。
- 手动强制终止:有人或自动化脚本执行了
docker kill或kill -9。 - Kubernetes Pod 被驱逐:Pod 超出节点资源限制,被 kubelet 驱逐。
该指南适用于任何使用 Docker 或 Kubernetes 部署的应用,特别是内存敏感或资源密集型的工作负载,如数据库、数据处理任务、大型 Web 应用等。运维人员、开发者和 SRE 是主要受众。
核心排查步骤与根因分析
第一步:确认 OOM 状态
最直接的证据来自 Docker 的 inspect 输出:
BASHdocker inspect <container_name_or_id> --format '{{.State.OOMKilled}}'
如果返回 true,说明容器确实因内存超限被杀死。
同时检查内核日志:
BASHdmesg | grep -i "killed process"
输出示例:
[12345.678901] Memory cgroup out of memory: Killed process 12345 (java) total-vm:2048576kB, anon-rss:1048576kB, file-rss:0kB, shmem-rss:0kB
如果日志中包含 Memory cgroup out of memory,说明是容器 cgroup 限制导致的 OOM;如果只有 Out of memory: Killed process 而没有 cgroup 信息,则可能是宿主机整体内存不足。
第二步:检查容器资源使用趋势
使用 docker stats 实时观察容器内存使用情况:
BASHdocker stats <container_name>
重点关注 MEM USAGE / LIMIT 列。如果内存使用持续增长直至接近限制,则很可能存在内存泄漏。
第三步:区分不同原因
| 现象 | OOMKilled 状态 | dmesg 输出 | 根因 |
|---|---|---|---|
| 容器内存超限 | true | 包含 Memory cgroup | 容器自身超过 --memory 限制 |
| 宿主机内存耗尽 | false | 无 cgroup 信息 | 宿主机整体内存不足 |
手动 kill -9 | false | 无 OOM 相关 | 人为或自动化操作 |
| 应用内部崩溃 | false | 无 OOM 相关 | 应用 bug 导致进程异常退出 |
常见报错与排查
错误 1:docker inspect 显示 OOMKilled: true
解决方案:这是最直接的 OOM 证据。
-
增加容器内存限制:
BASHdocker run -m 2g --memory-reservation 1g your-image -
检查应用内存泄漏:使用
docker stats或应用性能监控工具(如 JDK 的 jvisualvm、Python 的 memory_profiler)观察内存使用趋势。 -
在 Kubernetes 中调整 Pod 资源:
YAMLresources: limits: memory: "2Gi" requests: memory: "1Gi"
错误 2:容器日志中无明确错误,但 dmesg 显示 Out of memory: Killed process
解决方案:这表明是宿主机内核主动杀死了进程。
-
检查宿主机内存使用:
BASHfree -h htop -
减少宿主机负载:停止不必要的容器或进程,或考虑增加物理内存。
-
调整 Docker 的 OOM 优先级(谨慎使用):
BASHdocker run --oom-score-adj -500 your-image负值降低被 OOM killer 优先杀死的概率,但不应依赖此方法解决根本问题。
错误 3:容器被 docker kill 或 kill -9 终止
解决方案:这是人为或自动化操作导致的。
-
追溯操作来源:检查命令历史(
history)或 CI/CD 流水线日志。 -
改用优雅关闭:避免在生产环境中使用
docker kill或kill -9,改用docker stop(发送 SIGTERM)或kill -15。 -
在 Kubernetes 中调整优雅关闭时间:
YAMLspec: terminationGracePeriodSeconds: 60
错误 4:退出码 137,但 OOMKilled: false
解决方案:可能原因包括:
- 应用内部崩溃:检查应用日志,可能是进程自身异常退出但被 Docker 以 SIGKILL 方式处理。
- 内核内存分配失败:罕见情况,检查
dmesg输出中是否有其他内核错误。 - CPU 限制过严:检查
docker inspect中的 CPU 配额设置(NanoCpus、CpuShares),极低 CPU 限制可能导致 cgroup OOM killer 误判。
生产环境实践与注意事项
1. 为所有容器设置合理的资源限制
BASH# Docker 运行 docker run -m 512m --memory-reservation 256m --memory-swap 1g your-image # Docker Compose services: my-service: image: my-image mem_limit: 512m mem_reservation: 256m
注意:mem_limit 是硬限制,超过此限制容器将被 OOM kill。mem_reservation 是软限制,当宿主机内存紧张时,Docker 会尝试将容器内存压缩到此值以下。
2. Kubernetes 资源管理
YAMLapiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: my-container image: my-image resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m"
关键点:
requests用于调度决策,limits是硬限制。- 建议
requests和limits设置不同的值,允许突发流量。 - 使用
requests保证服务质量(QoS),limits防止资源滥用。
3. 监控与告警
使用 Prometheus + Grafana 监控容器内存使用率,设置告警阈值(如内存使用率超过 80% 持续 5 分钟)。
YAML# Prometheus 告警规则示例 groups: - name: container-alerts rules: - alert: ContainerMemoryHigh expr: container_memory_usage_bytes{container!=""} / container_spec_memory_limit_bytes{container!=""} > 0.8 for: 5m labels: severity: warning annotations: summary: "Container {{ $labels.container }} memory usage > 80%"
4. 重启策略
对于关键服务,配置自动重启:
YAML# Docker Compose services: my-service: restart: unless-stopped # Kubernetes spec: restartPolicy: Always
5. 定期审查应用代码
内存泄漏是导致 OOM 的常见原因。定期使用工具(如 Valgrind、AddressSanitizer、Python 的 tracemalloc)检查应用内存使用。
常见问题 FAQ
Q: 退出码 137 和退出码 139 有什么区别?
A: 退出码 137 表示容器被 SIGKILL (信号 9) 终止,通常是因为内存不足 (OOM) 或手动强制杀死。退出码 139 表示容器被 SIGSEGV (信号 11) 终止,通常是由于程序访问了无效内存地址(段错误),这往往是应用本身的 bug(如空指针解引用、缓冲区溢出)导致的。
Q: 在 Docker Compose 中如何为服务设置内存限制以防止退出码 137?
A: 在 docker-compose.yml 文件中,为服务添加 deploy 配置(Swarm 模式)或使用 mem_limit 和 mem_reservation 字段。例如:
YAMLservices: my-service: image: my-image mem_limit: 512m mem_reservation: 256m
注意:mem_limit 是硬限制,超过此限制容器将被 OOM kill。
Q: 如何区分是容器内应用内存泄漏还是宿主机整体内存不足导致的 OOM?
A: 1. 查看 docker inspect <container> 中的 OOMKilled 字段。如果为 true,说明是容器自身超过了其内存限制。2. 查看宿主机 dmesg 输出。如果看到 Memory cgroup out of memory: Killed process 后面跟着容器进程的 PID,说明是容器 cgroup 限制导致的。如果看到 Out of memory: Killed process 但没有 cgroup 信息,则可能是宿主机整体内存不足。3. 使用 docker stats 观察容器内存使用趋势,如果持续增长,则可能是内存泄漏。
Q: 退出码 137 是否一定意味着 OOM?
A: 不一定。虽然 OOM 是最常见的原因,但手动执行 docker kill、kill -9、Kubernetes Pod 驱逐、甚至某些内核错误也可能导致退出码 137。务必结合 docker inspect 和 dmesg 输出综合判断。
相关深度解决方案
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Node.js 堆内存溢出(OOM)实战排查与修复:从应急到根治。
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Docker 容器 DNS 解析失败排查与修复:从临时调试到生产配置。