本期摘要

Docker容器无缘无故退出了,用 docker logs 一看,空空如也。这不是Bug,是容器的几种“静默死亡”方式:OOM Killer 直接杀死进程、健康检查失败被标记为 unhealthy、宿主机磁盘写满导致无法写入、systemd 的 MemoryLimit 把容器掐死。这些情况下容器进程来不及写日志就被干掉了。本文从一次真实排查出发,教你看 docker inspect 的退出码、查系统日志 dmesg、定位 OOM 记录、配置合理的资源限制。读完你就能在日志空白时照样找到元凶。

一次让人摸不着头脑的故障

某个微服务容器运行一段时间后就会自动退出,没有报错,没有日志。重启后又能跑一会,然后又退出。

执行:

bash

docker logs --tail 100 app_container

一片空白。容器内部没有任何日志输出,因为进程根本没来得及写到磁盘就被干掉了。

查看容器退出信息:

bash

docker inspect app_container | grep -E "ExitCode|Error|OOMKilled"

输出:

"ExitCode": 137,
"OOMKilled": true,

真相大白:容器被 OOM Killer 杀掉了。这不是应用代码问题,是内存限制设置过低。

容器静默退出的常见原因

退出状态 含义 元凶
退出码 0 正常退出 进程自己调用exit(0),检查业务逻辑
退出码 137 (137=128+9) 收到 SIGKILL OOM 或被手动 docker kill
"OOMKilled": true 内存不足被杀死 容器内存限制太低,或应用内存泄漏
健康检查失败 容器状态变为 unhealthy 探针配置不当或依赖服务不可用
宿主机磁盘满 容器无法创建日志/文件 磁盘写满导致容器异常退出
systemd MemoryLimit 宿主机 cgroup 限制 systemd 的 MemoryLimit 小于容器限制
app 自身崩溃 退出码非0无日志 日志未配置 stdout/stderr,或写入磁盘失败

排查步骤

第一步:查看容器退出信息

bash

docker inspect <container_id> | jq '.[0].State'

关注字段:

字段 含义
ExitCode 退出码,137=SIGKILL
OOMKilled 是否被OOM杀死
Error 错误信息
Status exited / running / dead

第二步:查看系统 OOM 记录

bash

dmesg | grep -i "out of memory"
dmesg | grep -i "kill"
journalctl -k | grep -i oom

如果看到类似 Out of memory: Kill process 12345 (java) score 987 的记录,说明宿主机或容器触发了 OOM。

第三步:检查容器资源限制

bash

docker inspect <container_id> | jq '.[0].HostConfig.Memory'

0 表示无限制,否则以字节为单位。确认限制是否合理:

bash

docker stats --no-stream <container_id>

查看实际内存使用量。如果实际使用接近限制,就是内存不足。

第四步:检查宿主机资源

bash

free -h                     # 内存
df -h                       # 磁盘
dmesg | tail -20            # 内核日志

磁盘写满也会导致容器异常退出,因为容器无法创建日志文件或写入存储层。

第五步:排查健康检查

bash

docker inspect <container_id> | jq '.[0].State.Health'

健康检查连续失败,容器会被标记为 unhealthy,如果设置了 --health-retries 且达到阈值,Docker 会 kill 容器并重启(取决于 --restart 策略)。

常见解决方案

OOM Killed → 限制内存或增加配额

bash

# 调大内存限制
docker run -m 2g ...

宿主机内存不足 → 清理或扩容

bash

# 清理未使用的容器、镜像
docker system prune -a

健康检查失败 → 调整探针参数

yaml

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 10s

systemd 限制冲突

在 systemd 启动脚本中:

text

MemoryLimit=2G

确保 systemd 的 MemoryLimit 不小于 Docker 的 -m 参数,否则 systemd 会在宿主机 cgroup 层面先杀死容器。

日志未输出到 stdout

Docker 的 docker logs 只能看到输出到 stdout/stderr 的日志。如果应用把日志写到了文件里,需修改日志配置:

python

# Python
logging.basicConfig(stream=sys.stdout)

java

// Spring Boot
logging.file.name = /dev/stdout  # 或使用 application.properties

真实案例

现象 排查 根因 解决
容器跑一会就退出,日志为空 docker inspect 显示 OOMKilled=true 内存限制 512M,实际需要 800M 调大内存限制
重启后立刻退出 docker logs 无输出,dmesg 看到 disk quota exceeded 宿主机 /var 分区写满 清理磁盘空间
容器状态不断 unhealthy-restart Health 字段看到连续失败 健康检查的 /health 依赖数据库 修改健康检查,或先启动依赖服务
容器无日志,出现在 systemd 日志 journalctl -u docker 看到 MemoryLimit 冲突 systemd 限制比容器小 对齐两者限制

永久防范方案

  • 合理设置内存和 CPU 限制,预留 20% 余量

  • 监控宿主机磁盘和内存,建立告警

  • 确保应用日志输出到 stdout/stderr

  • 健康检查不要依赖外部服务(数据库/Redis)

  • 设置合理的 --restart 策略

下期预告

《100个“反常识”经验17:服务器被挖矿了,我第一反应不是kill》

更多推荐