技术指南

Docker 启动失败排查:保留退出证据再重建

先冻结重启循环并保存状态、日志、事件与解析配置,再按创建、启动、运行和就绪四层定位 Docker 容器失败。

容器启动失败时,最容易丢失的不是运行时间,而是第一现场:实际应用的 Compose 模型、镜像身份、退出状态、时间窗日志、生命周期事件、挂载与端口。先暂停自动重启和人工重试,保存这些证据,再判断失败发生在创建、启动、运行还是就绪阶段。一次只改一个边界,修复后用同一组探针验收。

先定位 create、start、running 还是 ready

阶段可观察证据常见边界
配置解析docker compose config 的退出状态和解析模型变量缺失、文件合并、路径或字段错误
创建CLI/daemon 错误、create/die 事件镜像、端口、挂载、权限或运行参数
进程启动State.ExitCode、Error、FinishedAt、日志入口命令、配置、依赖或信号
持续运行RestartCount、OOMKilled、事件和资源指标崩溃循环、内存、磁盘或外部依赖
业务就绪Health、真实端口和应用请求进程存活但尚未可服务

`docker ps` 默认只列运行中的容器,故障排查要保留停止实例。容器处于 running 只表示主进程尚未退出,不等于数据库迁移完成、依赖可用或应用能够处理真实请求;健康检查和外部探针必须回答不同层的问题。

用一个 UTC 时间窗保存第一现场

示例目录可能包含路径、标签和日志秘密;保持 0600/0700,并在共享前脱敏
incident=$(date --utc +%Y%m%dT%H%M%SZ)
umask 077
mkdir "incident-$incident"

docker container ls --all --no-trunc >"incident-$incident/containers.txt"
docker container inspect APP >"incident-$incident/inspect.json"
docker container logs --timestamps --since 30m --until "$(date --utc --iso-8601=seconds)" APP \
  >"incident-$incident/stdout.log" 2>"incident-$incident/logs.stderr"
docker system events --since 30m --until "$(date --utc --iso-8601=seconds)" \
  --filter type=container --filter container=APP --format json \
  >"incident-$incident/events.jsonl"

State 字段要联合解释,不靠一个退出码猜根因

保存完整 inspect JSON;这些格式化视图只用于快速定位
docker container inspect --format '{{json .State}}' APP
docker container inspect --format '{{json .HostConfig.RestartPolicy}}' APP
docker container inspect --format '{{json .Config.Healthcheck}}' APP
docker container inspect --format '{{json .NetworkSettings.Ports}}' APP
docker container inspect --format '{{json .Mounts}}' APP
字段能说明什么不能单独证明
ExitCode / Error主进程结果与 daemon 启动错误应用内部根因
OOMKilled该容器状态是否记录 OOM kill宿主所有内存压力来源
FinishedAt与日志、事件对齐的退出时间失败从何时开始
RestartCount当前容器累计自动重启次数每次退出原因都相同
Health.Status / Log健康命令的最近结果真实用户路径必然正常

137 常见于 SIGKILL 的 shell 风格结果,但不能脱离 `OOMKilled`、内核/daemon 日志、cgroup 内存事件和当时资源指标直接写成 OOM。143 通常对应 SIGTERM,同样要确认是谁发出信号、停止超时和应用是否完成优雅退出。125/126/127 等值也要结合 CLI 与镜像入口语义查证,不能维护一张脱离版本和上下文的万能退出码表。

先审阅 Compose 实际应用的模型

config 会合并文件、解析变量并展开短语法;输出可能含秘密,禁止直接贴进工单
umask 077
docker compose --env-file .env.production config --format json \
  > resolved-compose.json
docker compose --env-file .env.production config --images
docker compose --env-file .env.production config --hash '*'

`docker compose config` 展示将提交给 Engine 的解析模型,可发现覆盖顺序、插值、相对路径和短语法展开问题。它不证明镜像内入口命令可用,也不证明目标端口、挂载权限或依赖已就绪,因此配置门禁通过后仍需运行时证据。

重启次数上升时先证明循环条件

  1. 把 RestartCount、die/start 事件和每次日志末尾按时间对齐。
  2. 确认当前 restart policy、最大尝试和停止来源;不要把无限重启当可用性。
  3. 若每次同样退出,继续重启只制造噪声;修正候选配置或镜像后再受控重建。
  4. 若退出原因变化,分别保存每轮证据,避免把后续端口冲突覆盖最初配置错误。
  5. 回滚使用已知镜像摘要和已审阅配置,不依赖会漂移的 `latest`。

日志必须带时间边界并与事件对齐

同时保存命令退出状态;不要只截最后一行
start='2026-08-16T20:00:00Z'
end='2026-08-16T20:10:00Z'
docker container logs --timestamps --since "$start" --until "$end" APP
docker system events --since "$start" --until "$end" \
  --filter type=container --filter container=APP --format json

Docker 官方 CLI 支持用 RFC 3339 或相对时间筛选日志,并可用 JSON 格式保存事件。将 create、start、health_status、die 与应用日志对齐,才能区分应用主动退出、健康检查失败、daemon 操作和外部停止。

入口命令失败要复现参数和文件存在性

检查证据修复边界
镜像身份Image ID、RepoDigests、构建/部署版本固定摘要后再比较
入口和参数Config.Entrypoint、Config.Cmd确认 exec/shell 形式与信号传递
文件与架构候选镜像内路径、mode、CPU 架构不要在失败容器可写层临时补文件
应用配置脱敏配置校验命令及退出状态用同一镜像做只读预检

`pull`、`up --force-recreate` 或在容器内手工修改会更换证据对象。先记录当前容器与镜像,再把修复放进版本化 Dockerfile、Compose 或配置来源;候选验证通过后重建,失败则能回到原摘要。

端口冲突、发布和监听是三个问题

把 HOST_PORT、容器 target port、应用监听地址与探针路径分别记录
docker container inspect --format '{{json .NetworkSettings.Ports}}' APP
ss --listening --tcp --numeric --process

# 只从宿主验证已发布的本地端口
curl --fail --silent --show-error --max-time 3 http://127.0.0.1:HOST_PORT/health
  • 先确认端口由哪个进程或容器占用,不先停止未知监听者。
  • 用 inspect 区分 declared/exposed 与实际 published 端口。
  • 在容器网络、宿主回环和真实入口分别探测,定位故障跨越哪一层。
  • 同时检查 IPv4/IPv6、主机防火墙和反向代理,但不要用关闭防火墙作为诊断结论。

挂载问题先核对对象身份,再核对应用 UID

挂载证据要确认
Type / Source / Destination命名卷还是 daemon 主机上的 bind 路径
RW / propagation容器是否应该写入及传播边界
宿主 inode 与 owner路径是否存在、是不是预期对象
容器应用 UID/GID实际进程身份是否具备最小读写权限
SELinux/AppArmor拒绝是否来自强制访问控制
把 APP_UID、路径和预期权限写进服务运行契约;不要打印秘密文件
docker container inspect --format '{{json .Mounts}}' APP
namei --mountpoints --long /verified/host/path
getfacl --absolute-names /verified/host/path

# 服务能启动时,以应用身份验证指定路径
docker compose exec --user APP_UID:APP_GID SERVICE \
  sh -c 'id; test -r /app/config.yaml; test -w /app/data'

depends_on 只解决顺序时,应用仍可能未就绪

Compose 按依赖顺序创建服务,但短写法只保证依赖已经启动。需要 readiness 时,为依赖定义能验证实际服务的 healthcheck,并在 `depends_on` 使用 `condition: service_healthy`;应用本身仍应对短暂依赖失败做有界重试。健康命令必须能在镜像内运行,并设置 start_period、interval、timeout 与 retries。

示例展示就绪语义;变量、镜像和阈值必须按实际服务验证
services:
  app:
    depends_on:
      db:
        condition: service_healthy
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"]
      start_period: 30s
      interval: 10s
      timeout: 5s
      retries: 5

“进程已监听”也不等于 ready。本批次隔离探针在同一进程监听端口时先返回 503,切换就绪后才返回 200。健康检查应覆盖最小关键依赖,外部探针再验证发布端口、反向代理、TLS 和代表性内容。

资源故障要同时看容器、cgroup 和宿主

信号进一步证据
OOMKilled=true容器限制、memory.events、daemon/内核日志、工作集
ExitCode=137OOMKilled、停止来源、SIGKILL、超时和事件
写入失败df bytes、df inodes、配额、只读挂载和应用 UID
CPU/延迟上升docker stats 样本、宿主 load/steal、应用请求延迟

Docker 默认不会自动给容器设置 CPU/内存限制。先测量应用工作集和峰值,再设置硬限制与告警;不要为了阻止退出而禁用 OOM killer,也不要在生产机故意制造 OOM。磁盘容量、inode、日志增长、镜像层和卷需要分别核对。

候选修复一次只改变一个层

  1. 保存旧容器 inspect、镜像摘要、解析配置、挂载身份和回滚命令。
  2. 在隔离环境对候选运行配置校验、入口命令、应用 UID 路径探针和 healthcheck。
  3. 在维护窗口只重建目标服务;不使用会删除卷或全局资源的清理命令。
  4. 观察至少一个完整启动窗口,确认 RestartCount 不再增长且事件序列稳定。
  5. 从容器内、宿主回环和真实入口逐层验收状态、内容与依赖。
  6. 失败时恢复旧摘要和配置并重建,再验证数据与入口;保留失败候选证据。

隔离演练证明了先分层再修复

VPScope 没有安装或启动 Docker daemon,而是在一次性目录中验证底层证据边界:同一缺失配置连续三次都以 78 退出并留下相同 stderr,证明重启没有增加信息;第二个回环监听得到 EADDRINUSE 98;nobody 对 mode 0500 的 root 目录写入得到 EACCES 13;SIGTERM 对应 Python returncode -15 / shell 风格 143。

同一回环服务已经监听时,健康端点先返回 503,设置 ready 后才返回 200,停止后端口关闭。演练没有运行 Docker/Compose、拉镜像、访问 registry、挂载宿主目录、触碰 daemon socket、诱发 OOM 或使用生产数据,因此只能证明诊断顺序和采集边界,不能证明 Docker 的 OOMKilled、restart policy、bind mount 或发布规则行为。

恢复完成要有一组可反证的验收项

  • 解析后的 Compose 模型、镜像摘要和运行参数与批准候选一致。
  • 目标容器在完整观察窗内不再增加 RestartCount,State.Error 为空。
  • 健康状态稳定,健康日志没有持续超时或假阳性。
  • 应用日志时间窗没有重复启动、迁移失败或未处理异常。
  • 挂载对象正确,应用 UID 的最小读写探针通过,原数据仍可读取。
  • 目标端口只发布到需要的宿主地址,内部/回环/真实入口探针逐层通过。
  • 内存、CPU、容量、inode 和日志增长保有余量,没有新的 OOM/资源事件。
  • 回滚使用固定镜像摘要和旧配置演练过,不依赖删除卷或全局清理。
  • 事故包已脱敏,记录根因、单一修复、证据缺口和复查触发。

返回知识库