LabHub
学习 学习路径 课程

Docker 基础

容器死了之后,先看什么

在 LabHub 中继续学习

一句话总结

容器诊断应按日志 → 退出码 → 状态字段的顺序进行。若这三项仍无法解决,再进入容器内部。

概念图: 日志 → 退出码 → 状态字段 · 成本很低的信号加以区分 · 大于 128 表示进程因信号终止,减去 128 就是信号编号。 · 必须区分是否由 OOM 导致

为什么需要了解这一点

“容器启动不了”这句话,实际上至少把四种完全不同的情况混在了一起:镜像不存在、命令不存在、应用因无法读取配置而自行退出,或应用被内核终止。这四种情况的处理方法完全不同,好在可以通过成本很低的信号加以区分

退出码最先缩小调查范围

仅凭一个退出码,就能把调查范围缩小一半。规则很简单:大于 128 表示进程因信号终止,减去 128 就是信号编号。

代码 含义 首先查看哪里
0 正常退出 应用完成工作后退出;如果它是服务器,这也不正常
1 应用返回的一般错误 日志最后一行
125 Docker 自身错误 命令选项错误
126 无法执行命令 没有执行权限
127 找不到命令 ENTRYPOINT 路径错误,或镜像中没有 Shell
137 SIGKILL (128+9) 必须区分是否由 OOM 导致
139 SIGSEGV (128+11) 原生库崩溃
143 SIGTERM (128+15) 收到正常终止请求;未必是事故

137 最容易令人混淆。它可能是内核 OOM Killer 所为,也可能是有人执行了 docker kill。区分依据是 State.OOMKilled 这一行。

docker inspect dk-err --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
137 true

若为 true,应提高内存限制或降低应用内存使用量;若为 false,则应调查是谁、为什么终止了进程。这是两个完全不同的调查方向。

如何阅读日志

容器的标准输出与标准错误会被运行时截获并保存。因此,即使容器已经退出,也能通过 docker logs 查看最后时刻。两个流显示时混在一起,但实际上相互独立,可以用 Shell 重定向分别提取。

docker logs dk-err 2>/dev/null      # 표준 출력만
docker logs dk-err 2>&1 1>/dev/null # 표준 오류만
docker logs --tail 50 -t dk-err     # 마지막 50줄에 시각을 붙여서
docker logs --since 10m dk-err      # 최근 10분만

附加时间(-t)很重要。许多应用自己的日志没有时间;如果没有时间,就无法判断“这个错误发生在进程退出前一刻,还是很久以前”。

docker inspect 会在一个 JSON 中保存全部状态。实际诊断中常用的字段并不多。

字段 能说明什么
State.Status created / running / exited / paused
State.ExitCode 是应用返回,还是被信号终止
State.Error 运行时根本无法启动进程的原因
State.OOMKilled 137 是否由 OOM 导致
State.StartedAt / FinishedAt 运行几秒后退出——立即退出通常是配置问题
RestartCount 是否在悄悄重复重启

如果 StartedAtFinishedAt 相差不到一秒,应按配置文件、环境变量、端口冲突的顺序检查,而不是先怀疑应用业务逻辑。

实际工作中的表现

最常见的陷阱是日志为空。原因有三种。

  1. 应用把日志写进文件。 容器中的原则是将日志写到标准输出而非文件;违反这一原则会使整套诊断工具失效。临时可以用 docker exec 进入后读取文件,但容器一旦退出,连这种方法也无法使用。
  2. 日志困在缓冲区里。 Python 发现标准输出不是终端时,会采用块缓冲。若不足 4KB 就退出,缓冲中的日志会全部消失。可通过 PYTHONUNBUFFERED=1python -u 关闭。Node 默认不缓冲,Java 的 System.out 通常按行处理,所以一般没有问题。
  3. 使用了其他日志驱动。--log-driver 设置为 none 或外部收集器,docker logs 将无法显示任何内容。

第二个陷阱是镜像中没有 Shell。distroless 或 scratch 镜像不包含 sh,因此 docker exec -it ... sh 不会工作。看到 OCI runtime exec failed: exec: "sh": executable file not found 后,不应判断“容器异常”;这是正常现象。

此时可以把含有工具的容器连接到相同命名空间进行检查。

docker run -it --rm   --pid container:myapp --network container:myapp   nicolaka/netshoot

通过 --pid 共享进程,通过 --network 共享网络,因此 psss 会直接显示目标容器中的状态。文件系统并不共享,应通过 /proc/1/root/ 查看。

第三个陷阱是不断重启。如果 RestartCount 持续增加,docker logs 只显示当前实例的日志。真正需要的是上一个、刚刚退出的实例日志,但 Docker 没有像 Kubernetes --previous 那样的选项。应暂时把 --restart 策略改为 no,让它只启动一次后再调查。

下个练习将做什么

练习从日志尾部截取内容、分离两个流,并把已退出容器的退出码与日志放在一起确定原因。还会设置较低的内存限制,亲自制造退出码 137,并确认 OOMKilled 显示为 true