Docker 故障诊断
排障不是随机尝试命令。稳定的方法是:先保留现场,再从状态和日志收集证据,缩小到容器、应用、网络、存储或宿主机,最后修改并验证。
完成后,你将能够:
- 使用一套固定顺序采集 Docker 故障信息;
- 根据容器状态和退出码判断方向;
- 排查端口、DNS、挂载、内存和磁盘问题;
- 使用临时工具容器诊断精简镜像;
- 在清理前保留必要现场。
1. 通用排障流程
遇到问题时按以下顺序执行:
- 记录现象:何时开始、影响哪个容器、最近改了什么;
- 查看状态:容器是否存在、运行、重启或退出;
- 查看日志和退出码:应用自己通常已经给出原因;
- 检查配置:启动命令、环境变量、挂载、网络、端口、健康检查;
- 检查宿主机:Engine、磁盘、inode、内存、DNS、防火墙;
- 最小化复现:使用前台运行或临时工具容器验证假设;
- 修复并复测:确认原症状消失,同时避免引入新问题。
docker rm、compose down 和 prune 可能删除退出码、日志、可写层和网络关系。先采集证据和备份数据,再决定是否重建或清理。
2. 六条基础采集命令
将 <container> 替换为实际容器名或 ID:
# 1. 查看所有容器,包括已退出和不断重启的容器
docker ps -a --no-trunc
# 2. 查看目标容器最近 200 行日志和时间戳
docker logs --timestamps --tail 200 <container>
# 3. 提取状态、退出码、错误、OOM 和重启次数
docker inspect <container> \
--format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{json .State.Error}} restarts={{.RestartCount}}'
# 4. 查看端口、网络和挂载的完整配置
docker inspect <container>
# 5. 获取当前资源快照,寻找 CPU 或内存异常
docker stats --no-stream <container>
# 6. 查看镜像、容器、卷和构建缓存占用
docker system df --verbose
如果是 Compose 项目:
# 查看服务状态,包括退出码和健康状态
docker compose ps --all
# 按时间顺序查看项目所有服务最近日志
docker compose logs --timestamps --tail 200
# 验证 Compose 解析后的配置和变量值
docker compose config
3. 看懂状态和退出码
| 状态或退出码 | 常见含义 | 下一步 |
|---|---|---|
Created | 已创建但未启动,或启动前失败 | 查看 .State.Error 和挂载配置 |
Restarting | 进程不断退出,重启策略又拉起 | 查看日志、退出码和启动命令 |
Exited (0) | 主进程正常结束 | 判断应用是否本来就应长期运行 |
Exited (1) | 应用通用错误 | 查看应用日志和配置 |
Exited (126) | 命令存在但不能执行 | 检查权限、架构和挂载选项 |
Exited (127) | 找不到命令 | 检查镜像内容、路径和入口命令 |
Exited (137) | 收到 SIGKILL,常见于 OOM 或强制停止 | 检查 OOMKilled、内核日志和内存限制 |
Exited (143) | 收到 SIGTERM 后退出 | 常见于正常停止或部署替换 |
unhealthy | 主进程在运行,但健康检查失败 | 查看健康检查日志和应用端点 |
退出码只是线索,不是最终结论。例如 137 也可能来自 docker kill,必须结合 OOMKilled 和宿主机日志判断。
4. 实验一:容器立即退出
主动创建一个失败容器:
# shell 输出错误信息并以退出码 42 结束
docker run --name exit-demo \
alpine:3.20 \
sh -c 'echo "configuration missing" >&2; exit 42'
# 容器不在 docker ps 默认结果中,因为它已经退出
docker ps -a --filter name=exit-demo
# 日志给出应用层错误
docker logs exit-demo
# 精确读取退出码,预期为 42
docker inspect exit-demo \
--format 'status={{.State.Status}} exit={{.State.ExitCode}}'
# 保留证据检查完毕后删除实验容器
docker rm exit-demo
真实场景中的常见原因:入口命令写错、配置缺失、文件权限不足、依赖服务不可用、程序本应为一次性任务。可以临时覆盖入口命令进入 shell,但不要无依据地修改镜像:
# 忽略镜像原入口并启动 shell,仅用于镜像确实包含 sh 的情况
docker run --rm --interactive --tty \
--entrypoint sh \
<image>:<tag>
5. 实验二:端口无法访问
先复现“容器运行但宿主机无法访问”:
# Nginx 正常运行,但没有使用 --publish
docker run --detach \
--name port-demo \
nginx:1.27-alpine
# 输出为空,说明没有宿主机端口映射
docker port port-demo
# 容器内部访问成功,证明应用本身正在监听 80 端口
docker exec port-demo \
wget -q -S -O /dev/null http://127.0.0.1:80
运行中的容器不能通过简单命令追加端口映射,需要按正确配置重建:
# 删除配置错误的实验容器
docker rm --force port-demo
# 使用正确映射重新创建
docker run --detach \
--name port-demo \
--publish 127.0.0.1:8080:80 \
nginx:1.27-alpine
# 从宿主机验证映射后的端口
curl --head http://127.0.0.1:8080
# 清理实验容器
docker rm --force port-demo
如果仍不通,依次检查:
# 容器是否运行,PORTS 列是否符合预期
docker ps -a --filter name=<container>
# 应用日志是否显示绑定失败或启动错误
docker logs --tail 100 <container>
# 查询 Docker 实际发布的地址和端口
docker port <container>
# macOS/Linux 查看宿主机对应端口的监听者
lsof -nP -iTCP:8080 -sTCP:LISTEN
还要确认应用在容器内监听 0.0.0.0,而不是只监听容器自身的 127.0.0.1;远程访问则继续检查宿主机防火墙、云安全组和上游路由。
6. 实验三:服务名无法解析
两个容器必须位于同一自定义网络,才能稳定地按名称通信:
# 创建网络并启动目标服务
docker network create troubleshoot-net
docker run --detach \
--name dns-web \
--network troubleshoot-net \
nginx:1.27-alpine
# 未加入该网络的临时容器无法解析 dns-web;失败是预期现象
docker run --rm alpine:3.20 \
sh -c 'getent hosts dns-web || echo "name cannot be resolved"'
# 加入同一网络后,Docker DNS 可以解析容器名
docker run --rm \
--network troubleshoot-net \
alpine:3.20 \
getent hosts dns-web
# 查看网络实际连接的容器
docker network inspect troubleshoot-net
# 清理实验环境
docker rm --force dns-web
docker network rm troubleshoot-net
应用连接串应写 dns-web:80 或 Compose 服务名,不要写另一个容器的 localhost,也不要保存容器 IP。
7. OOM 和资源问题
先查看 Docker 记录:
# 检查是否被 OOM Kill、退出码和配置的内存上限
docker inspect <container> \
--format 'oom={{.State.OOMKilled}} exit={{.State.ExitCode}} memory_limit={{.HostConfig.Memory}}'
# 查看存活容器当前内存使用与上限
docker stats --no-stream <container>
Linux 宿主机继续检查内核和服务日志:
# 搜索内核日志中的 OOM Killer 记录
sudo dmesg -T | grep -i -E 'out of memory|killed process|oom'
# 查看最近 30 分钟 Docker Engine 日志
sudo journalctl -u docker --since '30 minutes ago' --no-pager
# 查看宿主机总体内存和 swap 使用
free -h
处理方向包括:修复内存泄漏、降低并发或缓存、为应用设置合理堆上限、根据压测调整容器限额。不要只看一次 docker stats 就无限提高上限,这可能把单容器问题扩大到整台宿主机。
8. 健康检查失败
# 输出健康状态和每次探测的退出码、时间、错误信息
docker inspect <container> \
--format '{{json .State.Health}}'
# 查看镜像或运行配置中的健康检查定义
docker inspect <container> \
--format '{{json .Config.Healthcheck}}'
# 在容器内手动运行同等探测,观察完整错误
docker exec <container> \
wget -S -O /dev/null http://127.0.0.1:8080/health
常见原因是路径、端口或协议错误,镜像内缺少检查命令,启动宽限期太短,或应用依赖尚未就绪。健康检查命令应快速、稳定,并检查真正能代表服务就绪的端点。
9. 挂载与权限问题
# 查看挂载类型、宿主来源、容器目标和读写模式
docker inspect <container> \
--format '{{json .Mounts}}'
# 查看容器实际运行 UID/GID
docker exec <container> id
# 查看目标目录权限;镜像必须包含 ls
docker exec <container> \
ls -ld /path/to/mount
# 在 Linux 宿主机查看 bind mount 源目录权限
ls -ld /host/source/path
逐项确认:源路径是否正确、文件还是目录、是否只读、UID/GID 是否匹配、SELinux 标签是否允许。不要把 chmod -R 777 当作通用修复,它会扩大安全风险并掩盖真正的部署契约问题。
10. 磁盘或 inode 耗尽
# 查看 Docker 各类对象的可回收空间和详细列表
docker system df --verbose
# Linux 查看 Docker 数据目录所在分区容量
df -h /var/lib/docker
# inode 耗尽也会导致“磁盘有空间却无法写文件”
df -i /var/lib/docker
# 查看所有容器日志驱动和日志路径
docker inspect <container> \
--format 'driver={{.HostConfig.LogConfig.Type}} path={{.LogPath}}'
安全处理顺序:
- 找出增长来源:容器日志、应用数据、镜像还是构建缓存;
- 先控制继续增长,例如限制日志和异常重试;
- 备份并确认数据归属;
- 只删除明确无用的对象;
- 为容量、inode 和增长速率增加监控。
11. 镜像拉取失败
# 直接拉取并保留完整错误:可能是超时、鉴权、证书或不存在
docker pull nginx:1.27-alpine
# 查看 Engine 代理、Registry Mirrors 和 DNS 相关信息
docker info
# Linux 查看最近 Engine 日志中的拉取错误
sudo journalctl -u docker --since '15 minutes ago' --no-pager
# 检查到 Docker Hub Registry 的 TLS 和 HTTP 连通性
curl -I https://registry-1.docker.io/v2/
401 Unauthorized 对 Docker Hub /v2/ 探测可以是正常响应,说明 TLS 和网络已建立;超时、DNS 失败或证书错误才指向网络链路。私有仓库还需检查登录凭据、镜像路径、证书信任和代理 NO_PROXY。
12. 精简镜像没有排障工具
不要为了排障永久修改业务镜像。可以让临时工具容器共享目标网络命名空间:
# netshoot 包含 curl、dig、tcpdump、ss 等工具,并共享目标网络
docker run --rm --interactive --tty \
--network container:<container> \
nicolaka/netshoot
# 进入工具 shell 后检查监听端口和本机 HTTP
ss -lntup
curl -v http://127.0.0.1:<port>
或者只加入目标自定义网络:
# 在 app-net 中验证 db 名称能否解析
docker run --rm \
--network app-net \
alpine:3.20 \
getent hosts db
临时工具容器也拥有对应网络访问能力,只在排障期间运行,结束后立即删除。
13. 清理前检查
# 先列出待处理对象,不加 -f,让 Docker 请求确认
docker container prune
# 默认只删除 dangling 镜像,不删除所有未使用镜像
docker image prune
# 清理构建缓存前显示预计可回收空间并请求确认
docker builder prune
不要在没有备份和归属信息时执行 docker system prune -a --volumes。清理停止容器前,至少保存:
docker inspect输出;- 相关时间段日志;
- Compose 配置和环境变量来源;
- 必要的容器可写层文件;
- 卷备份或数据库逻辑备份;
- 宿主机 Engine、内核和资源日志。
14. 快速决策表
| 现象 | 第一证据 | 常见根因 |
|---|---|---|
| 容器不见了 | docker ps -a | 已退出,或被 --rm 自动删除 |
| 容器不断重启 | 日志、退出码、重启次数 | 配置错误、依赖失败、OOM |
| 宿主机访问失败 | docker port、容器内请求 | 未发布、映射反了、应用未监听 |
| 容器间访问失败 | network inspect、DNS 测试 | 不同网络、用了 localhost、名称错误 |
| 写文件失败 | Mounts、id、目录权限 | 只读挂载、UID/GID、SELinux、磁盘满 |
| 服务 unhealthy | .State.Health.Log | 检查命令缺失、端点错误、启动太慢 |
| 退出码 137 | OOMKilled、内核日志 | 内存不足、强制 kill |
| 拉取失败 | pull 原始错误、Engine 日志 | DNS、代理、认证、证书、镜像名错误 |
下一篇提供按对象组织的命令速查表。遇到不熟悉的参数时,仍应先运行 docker <命令> --help 查看本机版本支持情况。