Docker 容器退出码 137 与 1 的排查实战:从日志到根因的 4 步诊断法
快速答案
- 核心结论:Docker 容器退出码 137 通常由 OOM Killer(内存不足)触发,退出码 1 通常由应用内部错误或配置问题导致,退出码 127 表示命令不存在,退出码 139 表示段错误。
- 第一排查步骤:立即执行
docker logs <container_id> --tail 50查看最后 50 行日志,同时执行docker inspect <container_id> --system检查State.ExitCode和State.OOMKilled字段。 - 最小修复命令:对于 OOM 导致的 137,执行
docker run --memory=512m my-app增加内存限制;对于配置错误导致的 1,使用docker run -it my-app /bin/sh进入容器手动验证文件路径和权限。 - 适用环境:所有 Docker 容器化部署场景,包括微服务、CI/CD、边缘计算和低配云服务器;与 GPT-4、Claude 等大模型配合使用效果最佳,可基于诊断步骤进一步分析日志。
它解决什么问题 / 适用场景
本文档解决的核心问题是:Docker 容器启动后立即退出,或频繁重启,且日志信息不明确。这类问题在以下场景中尤为常见:
- 微服务架构:某个服务容器在部署后频繁重启,导致服务不可用。
- CI/CD 流水线:构建的容器镜像在测试或生产环境无法启动。
- 资源受限环境:在低配云服务器、边缘计算设备上部署应用时,容器因资源不足被强制终止。
- 配置迁移:将应用从开发环境迁移到生产环境时,配置文件路径或权限发生变化。
本文档提供了一套结构化的 4 步诊断法,覆盖 90% 的容器启动失败场景,并附带真实案例和交互式调试技巧。
核心配置 / 参数说明
关键 Docker 命令与参数
| 命令/参数 | 用途 | 示例 |
|---|---|---|
docker logs <container_id> --tail <N> | 查看容器最后 N 行日志 | docker logs my-app --tail 50 |
docker inspect <container_id> --system | 获取容器完整状态信息,包括退出码和 OOM 标志 | docker inspect my-app --system |
docker run -it <image> /bin/bash | 以交互模式进入容器,手动验证环境 | docker run -it my-app /bin/sh |
docker stats <container_id> | 实时查看容器资源使用情况 | docker stats my-app |
docker exec -it <container_id> <command> | 在运行中的容器内执行命令 | docker exec -it my-app ls -la /app/config |
退出码速查表
| 退出码 | 含义 | 常见原因 |
|---|---|---|
| 1 | 应用内部错误 | 配置文件错误、依赖缺失、权限问题 |
| 127 | 命令不存在 | CMD/ENTRYPOINT 路径错误,或基础镜像缺少运行时 |
| 137 | 被 SIGKILL 终止 | OOM Killer(内存不足)、手动 docker kill、系统强制终止 |
| 139 | 段错误 | 内存访问越界、库版本不兼容 |
关键理解:退出码 1-128 表示应用自身错误,129-255 表示被外部信号终止。137 = 128 + 9(SIGKILL),139 = 128 + 11(SIGSEGV)。
4 步诊断法:从日志到根因
第一步:查看日志(docker logs --tail)
BASH# 查看最后 50 行日志 docker logs <container_id> --tail 50 # 持续跟踪日志输出 docker logs -f <container_id>
预期输出:如果日志中有明确错误信息(如 Error: Cannot find module 'express' 或 Permission denied),直接跳转到第三步。如果日志为空或只有启动信息,进入第二步。
第二步:检查容器状态(docker inspect --system)
BASH# 获取容器完整状态 docker inspect <container_id> --system # 重点关注字段: # "State": { # "ExitCode": 137, # "OOMKilled": true, # "Error": "" # }
解读方法:
ExitCode: 137且OOMKilled: true→ 内存不足,跳转到第四步。ExitCode: 1→ 应用错误,回到第一步仔细分析日志。ExitCode: 127→ 命令不存在,检查 Dockerfile 中的 CMD/ENTRYPOINT。ExitCode: 139→ 段错误,检查库版本和内存访问。
第三步:交互式调试(docker run -it)
BASH# 进入容器内部手动验证 docker run -it <image> /bin/sh # 在容器内执行以下检查: # 1. 确认文件存在 ls -la /path/to/config # 2. 确认命令存在 which myapp # 3. 手动执行启动命令 /path/to/myapp
真实案例:作者曾遇到一个 MySQL 容器退出码为 1,日志显示 [ERROR] /usr/sbin/mysqld: unknown variable 'max_connections=200'。进入容器后发现配置文件中的 max_connections=200 写成了 max_connections:200(冒号 vs 等号),修正后容器正常启动。
第四步:资源与系统排查
BASH# 查看容器内存使用峰值 docker stats <container_id> # 检查宿主机内存 free -h # 查看系统日志中的 kill 事件 journalctl -u docker | grep -i kill
OOM 预防措施:
- 为容器设置合理的内存限制:
docker run --memory=512m --memory-reservation=256m my-app - 在应用层面设置内存上限:
- Node.js:
--max-old-space-size=256 - Java:
-Xmx256m - Python:使用
resource模块
- Node.js:
- 使用监控工具(Prometheus + cAdvisor)实时跟踪内存使用
常见报错与排查
Exit Code 137,OOMKilled: true
错误现象:容器频繁重启,docker inspect 显示 OOMKilled: true。
解决方案:
- 使用
docker stats查看实际内存使用峰值。 - 增加内存限制:
docker run --memory=512m my-app。 - 优化应用内存使用(如设置
--max-old-space-size、-Xmx)。 - 检查宿主机内存是否充足,考虑升级实例规格。
Exit Code 1,日志显示 No such file or directory
错误现象:容器启动后立即退出,日志提示文件不存在。
解决方案:
- 使用
docker run -it my-app /bin/sh进入容器,手动执行ls -la /path/to/config。 - 检查 Dockerfile 中的
COPY或ADD指令,确保文件被正确复制。 - 检查挂载卷路径,使用
docker inspect查看Mounts字段。 - 如果是权限问题,确保容器内运行的用户对文件有读取权限。
Exit Code 127,日志显示 command not found
错误现象:容器启动后立即退出,提示命令未找到。
解决方案:
- 检查 Dockerfile 中的
CMD或ENTRYPOINT指令,使用绝对路径。 - 进入容器手动验证:
docker run -it my-app /bin/sh,然后执行which myapp。 - 确认基础镜像包含所需的运行时或依赖。
容器启动后立即退出,无任何日志输出
错误现象:容器启动后立即退出,docker logs 无任何输出。
解决方案:
- 使用
docker run -it my-app在前台运行,直接观察控制台输出。 - 检查 Dockerfile 中的
CMD是否将日志重定向到文件(如> /var/log/app.log 2>&1),如果是,使用docker exec进入容器查看。 - 在启动脚本中添加
set -x启用调试模式。
生产环境实践与注意事项
并发冲突
当多个容器或进程同时访问同一个 Docker 守护进程时,可能遇到 docker: Error response from daemon: conflict 错误。建议在脚本或自动化工具中实现重试机制和锁(如文件锁或分布式锁)。
文件锁定
挂载卷或绑定挂载时,如果宿主机上的文件被其他进程锁定(如日志文件被 logrotate 占用),容器内进程可能无法读写。使用 docker inspect 检查挂载点状态,确保没有文件锁冲突。
权限控制
容器内进程通常以非 root 用户运行(最佳实践)。如果挂载的宿主机目录权限为 700 或属于 root 用户,容器内进程将无法访问。建议:
- 在 Dockerfile 中使用
USER指令指定运行用户。 - 确保挂载卷的权限与该用户匹配(使用
chown或设置uid:gid)。
资源限制
生产环境中,必须为每个容器设置合理的 --memory 和 --memory-reservation 限制,并使用 docker stats 或 Prometheus + cAdvisor 进行实时监控。Exit Code 137 通常由 OOM Killer 触发,预防比事后排查更重要。
常见问题 FAQ
Q: 我的容器总是以 Exit Code 137 退出,但 docker inspect 显示 OOMKilled: false,这是为什么?
A: 如果 OOMKilled 为 false,但退出码是 137,这通常意味着容器进程被 SIGKILL 信号(信号编号 9)手动或由系统终止。137 的计算方式是 128 + 9 = 137。可能的原因包括:
- 有人或脚本执行了
docker kill <container_id>。 - Docker 守护进程本身被重启或升级,导致所有容器被强制终止。
- 宿主机内核或系统服务(如
systemd)因资源紧张或其他原因主动杀死了容器进程。
排查方法:检查系统日志(journalctl -u docker 或 /var/log/messages)查看是否有相关 kill 事件。
Q: 我按照 4 步诊断法检查了日志和配置,但容器仍然立即退出,没有任何明显错误,怎么办?
A: 这种情况通常属于“静默崩溃”(Silent Crash),即应用在初始化阶段失败但没有输出任何错误信息。建议采取以下高级排查步骤:
- 使用
strace追踪系统调用:在宿主机上找到容器进程的 PID(docker inspect --format '{{.State.Pid}}' <container_id>),然后使用strace -p <PID>追踪其系统调用,观察它在哪个系统调用上失败(如open()返回ENOENT)。 - 检查健康检查配置:如果 Dockerfile 或 Compose 文件中定义了
HEALTHCHECK,容器可能因为健康检查失败而被标记为不健康并退出。检查docker inspect中的State.Health字段。 - 检查 Entrypoint 脚本:如果使用了自定义的
entrypoint.sh,确保脚本本身没有语法错误,并且最后一行正确执行了exec "$@"来启动主进程。
Q: 在生产环境中,如何预防容器因 OOM (Exit Code 137) 而频繁重启?
A: 预防 OOM 需要从应用和基础设施两个层面入手:
应用层面:
- 设置内存限制:为每个容器设置合理的
--memory和--memory-swap限制,避免单个容器耗尽宿主机内存。 - 配置应用内存上限:例如,Java 应用设置
-Xmx,Node.js 应用设置--max-old-space-size,Python 应用使用resource模块限制内存。 - 使用内存分析工具:在开发/测试环境中使用
valgrind、heaptrack等工具分析内存泄漏。
基础设施层面:
- 实施资源监控和告警:使用 Prometheus + Grafana 监控容器和宿主机的内存使用率,设置告警阈值(如内存使用率超过 80% 时告警)。
- 使用编排工具的自动修复机制:在 Kubernetes 中,可以配置
resources.limits.memory和restartPolicy: Always,当容器因 OOM 被 Kill 后,Kubernetes 会自动重启它。 - 考虑使用 Swap:虽然不推荐依赖 Swap,但在某些场景下,为宿主机配置适量的 Swap 空间可以给应用提供缓冲时间,避免立即被 OOM Killer 杀死。
相关深度解决方案
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Node.js 堆内存溢出(OOM)实战排查与修复:从应急到根治。
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Docker 容器退出码 137 (OOMKilled) 排查与修复:内存不足导致 SIGKILL 的完整解决方案。