容器排障实战手册:从日志到内核的分层排障

TL;DR(速览)

  • 排障六层:状态 → 日志 → 网络 → 资源 → 文件系统 → 内核
  • docker inspect 四大字段:State、Config、Mounts、NetworkSettings
  • 启动失败:命令错误或依赖缺失
  • 反复重启:重启策略叠加崩溃循环
  • 端口冲突:宿主机端口被占用
  • OOM 被杀:内存超限触发内核 Killer
  • DNS 失败:未配置 --dns 或搜索域
  • 卷权限/假死:UID 不匹配或线程阻塞
  • 内核工具:nsenter 进命名空间、strace 追系统调用、lsof 查占用、tcpdump 抓包
  • 三大陷阱:docker restart 掩耳盗铃、只看 docker ps、日志被轮转吞掉

1. 引言

容器化部署早已成为现代应用交付的主流方式,但"能跑起来"和"能稳定跑"之间,隔着一条排障能力的鸿沟。很多同学遇到容器故障,第一反应就是 docker restart,重启成功便当作问题解决------这恰恰是排障中最典型的"掩耳盗铃"。

本手册基于前置课程(06 容器网络、07 存储卷、08 资源限制)的知识体系,为你梳理一套从容器状态到内核的分层排障方法论。全文包含 6 个真实故障案例,每个案例都遵循"现象 → 排查 → 根因 → 预防"四段式结构,帮助你建立可复用的排障思维,而不是背命令。

2. 分层排障方法论

排障的第一原则是:从外到内、从易到难、先看现象再动刀。盲目进入容器抓包、strace,往往事倍功半。推荐按以下六层逐层排查:

  1. 容器状态层:容器是否在运行?退出码是什么?重启策略是否在反复拉起?
  2. 日志层 :应用日志、docker events、docker logs 是否暴露了直接错误?
  3. 网络层:端口映射、DNS 解析、容器间通信是否正常?
  4. 资源层:CPU、内存、文件句柄是否触顶?是否发生 OOM?
  5. 文件系统层:卷挂载、权限、只读层是否异常?
  6. 内核层 :系统调用、内核日志(dmesg)、cgroup 隔离是否成为瓶颈?

每一层都有对应的"第一命令",层层递进,直到定位根因。下面这张图概括了整体流程:
#mermaid-svg-SNEVqNTKkwYLIeyf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SNEVqNTKkwYLIeyf .error-icon{fill:#552222;}#mermaid-svg-SNEVqNTKkwYLIeyf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SNEVqNTKkwYLIeyf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SNEVqNTKkwYLIeyf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SNEVqNTKkwYLIeyf .marker.cross{stroke:#333333;}#mermaid-svg-SNEVqNTKkwYLIeyf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SNEVqNTKkwYLIeyf p{margin:0;}#mermaid-svg-SNEVqNTKkwYLIeyf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster-label text{fill:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster-label span{color:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster-label span p{background-color:transparent;}#mermaid-svg-SNEVqNTKkwYLIeyf .label text,#mermaid-svg-SNEVqNTKkwYLIeyf span{fill:#333;color:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf .node rect,#mermaid-svg-SNEVqNTKkwYLIeyf .node circle,#mermaid-svg-SNEVqNTKkwYLIeyf .node ellipse,#mermaid-svg-SNEVqNTKkwYLIeyf .node polygon,#mermaid-svg-SNEVqNTKkwYLIeyf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SNEVqNTKkwYLIeyf .rough-node .label text,#mermaid-svg-SNEVqNTKkwYLIeyf .node .label text,#mermaid-svg-SNEVqNTKkwYLIeyf .image-shape .label,#mermaid-svg-SNEVqNTKkwYLIeyf .icon-shape .label{text-anchor:middle;}#mermaid-svg-SNEVqNTKkwYLIeyf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SNEVqNTKkwYLIeyf .rough-node .label,#mermaid-svg-SNEVqNTKkwYLIeyf .node .label,#mermaid-svg-SNEVqNTKkwYLIeyf .image-shape .label,#mermaid-svg-SNEVqNTKkwYLIeyf .icon-shape .label{text-align:center;}#mermaid-svg-SNEVqNTKkwYLIeyf .node.clickable{cursor:pointer;}#mermaid-svg-SNEVqNTKkwYLIeyf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SNEVqNTKkwYLIeyf .arrowheadPath{fill:#333333;}#mermaid-svg-SNEVqNTKkwYLIeyf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SNEVqNTKkwYLIeyf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SNEVqNTKkwYLIeyf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SNEVqNTKkwYLIeyf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SNEVqNTKkwYLIeyf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SNEVqNTKkwYLIeyf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster text{fill:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf .cluster span{color:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SNEVqNTKkwYLIeyf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SNEVqNTKkwYLIeyf rect.text{fill:none;stroke-width:0;}#mermaid-svg-SNEVqNTKkwYLIeyf .icon-shape,#mermaid-svg-SNEVqNTKkwYLIeyf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SNEVqNTKkwYLIeyf .icon-shape p,#mermaid-svg-SNEVqNTKkwYLIeyf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SNEVqNTKkwYLIeyf .icon-shape .label rect,#mermaid-svg-SNEVqNTKkwYLIeyf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SNEVqNTKkwYLIeyf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SNEVqNTKkwYLIeyf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SNEVqNTKkwYLIeyf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 容器状态异常
docker ps -a 查看退出码
docker logs 查应用日志
docker inspect 查配置与挂载
网络/资源/文件系统专项排查
nsenter/strace 进入内核层
定位根因并修复

3. docker inspect 大法

docker inspect 是容器排障的"瑞士军刀",它返回容器的完整 JSON 配置。排障时重点解析以下四个字段:

3.1 State:容器当前状态

bash 复制代码
docker inspect <container_id> --format '{{json .State}}' | jq

重点关注:

  • Status:running / exited / restarting / dead;
  • ExitCode:非 0 即异常退出,结合 Error 字段看退出原因;
  • Restarting:布尔值,为 true 说明重启策略正在反复拉起容器;
  • OOMKilled:为 true 说明容器因内存超限被内核杀死。

3.2 Config:启动配置

bash 复制代码
docker inspect <container_id> --format '{{json .Config}}' | jq

重点看 Env(环境变量是否缺失)、Cmd / Entrypoint(启动命令是否正确)、Image(镜像是否被意外覆盖)。

3.3 Mounts:卷挂载

bash 复制代码
docker inspect <container_id> --format '{{json .Mounts}}' | jq

重点看 Source 与 Destination 是否对应、RW 是否为 true(只读挂载会导致写入失败)、Type 是 bind 还是 volume。

3.4 NetworkSettings:网络配置

3.5 docker inspect 关键字段速查表

下面把四个核心字段的常用子字段、示例值与排障含义汇总成一张速查表,方便排障时快速对照:

字段 常用子字段 示例值 排障含义
State Status running / exited / restarting / dead 判断容器当前是否存活、是否在反复重启
State ExitCode 0 / 1 / 137 非 0 即异常退出;137 通常表示被 SIGKILL(如 OOM)
State Restarting true / false 为 true 说明重启策略正在反复拉起容器
State OOMKilled true / false 为 true 说明容器因内存超限被内核杀死
Config Env ["PATH=/usr/bin", "JAVA_OPTS=-Xmx512m"] 核对环境变量是否缺失或写错
Config Cmd / Entrypoint ["nginx", "-g", "daemon off;"] 核对启动命令是否正确、是否指向存在的脚本
Config Image nginx:1.25 确认镜像是否被意外覆盖或 tag 漂移
Mounts Source / Destination /host/data → /app/data 核对宿主机路径与容器内挂载点是否对应
Mounts RW true / false 为 false 说明只读挂载,写入会报 Permission denied
Mounts Type bind / volume 区分 bind mount 与命名卷,影响权限与持久化策略
NetworkSettings Ports {"8080/tcp": [{"HostPort": "8080"}]} 核对宿主机端口映射,排查端口冲突
NetworkSettings IPAddress 172.17.0.2 容器 IP,用于跨容器通信与抓包定位
NetworkSettings Networks {"bridge": {...}} 确认容器所属网络,排查网络模式错误

对应的 docker inspect 命令模板:

bash 复制代码
# 一次性查看全部四个字段
docker inspect <container_id> --format '{{json .State}} {{json .Config}} {{json .Mounts}} {{json .NetworkSettings}}' | jq

# 或按需单独查看某个字段
docker inspect <container_id> --format '{{json .State}}' | jq
docker inspect <container_id> --format '{{json .Config}}' | jq
docker inspect <container_id> --format '{{json .Mounts}}' | jq
docker inspect <container_id> --format '{{json .NetworkSettings}}' | jq
bash 复制代码
docker inspect <container_id> --format '{{json .NetworkSettings}}' | jq

重点看 Ports 的宿主机端口映射、IPAddress 容器 IP、Networks 所属网络。端口冲突、网络模式错误都能在这里找到线索。

4. 高频故障复现与排查

下面进入实战环节。6 个真实故障案例,每个都按"现象 → 排查 → 根因 → 预防"展开。

4.1 案例一:启动即失败(ExitCode 1)

现象 :docker run 后容器立即退出,docker ps 看不到容器。

排查:

bash 复制代码
docker ps -a          # 关键!只看 docker ps 会漏掉已退出的容器
docker logs <id>      # 查看应用启动日志
docker inspect <id> --format '{{.State.ExitCode}} {{.State.Error}}'

根因 :应用启动命令错误或依赖缺失。常见于 Entrypoint 指向不存在的脚本、环境变量未注入导致应用启动即抛异常。

预防 :本地先 docker run --rm 前台运行验证;启动脚本加入 set -e;关键环境变量在 docker inspect 中核对。

4.2 案例二:反复 Restarting

现象 :docker ps 显示状态为 Restarting (1) 3 seconds ago,容器不断重启。

排查:

bash 复制代码
docker inspect <id> --format '{{.State.Restarting}} {{.State.ExitCode}}'
docker logs <id> --tail 50
docker events --filter container=<id>   # 观察重启事件流

根因 :重启策略(--restart=always)与崩溃循环叠加。应用进程启动后立即崩溃,Docker 按策略反复拉起,形成死循环。

预防 :区分"一次性任务"与"常驻服务"的重启策略;为应用配置健康检查(HEALTHCHECK),避免崩溃循环被掩盖。

4.3 案例三:端口冲突

现象 :docker run -p 8080:80 报错 port is already allocated。

排查:

bash 复制代码
docker ps -a | grep 8080          # 找占用端口的容器
ss -lntp | grep 8080              # 找宿主机进程占用
docker inspect <id> --format '{{json .NetworkSettings.Ports}}'

根因 :宿主机端口被其他容器或进程占用。注意 docker ps 默认只显示运行中的容器,已退出但未删除的容器同样占用端口。

预防 :端口规划表化管理;使用 -p 前先 ss -lntp 确认;考虑动态端口映射(-P)配合服务发现。

4.4 案例四:OOM 被杀

现象 :容器运行一段时间后消失,docker ps -a 显示 Exited (137)。

排查:

bash 复制代码
docker inspect <id> --format '{{.State.OOMKilled}}'   # true 即 OOM
dmesg | grep -i oom                                   # 内核 OOM 记录
docker stats --no-stream                              # 观察内存实时占用

根因 :容器内存超过 --memory 限制,被内核 OOM Killer 杀死。退出码 137 = 128 + 9(SIGKILL)。

预防 :合理设置 --memory 与 --memory-swap;为 JVM 等应用显式设置堆内存上限;用 docker stats 持续监控,提前扩容。

4.5 案例五:DNS 解析失败

现象 :容器内 curl http://api.internal 报 Could not resolve host。

排查:

bash 复制代码
docker exec <id> cat /etc/resolv.conf    # 查看 DNS 配置
docker exec <id> nslookup api.internal   # 测试解析
docker network inspect <net>             # 查看网络 DNS 配置

根因 :容器默认继承宿主机 DNS;自定义网络未配置 --dns;或应用依赖的域名不在当前 DNS 搜索域内。

预防 :自定义网络时显式指定 --dns;使用 Docker 内置 DNS(容器名即主机名);跨网络通信优先用服务名而非 IP。

4.6 案例六:卷权限与假死容器

现象 :容器内写入挂载目录报 Permission denied;或容器 ps 显示 running 但应用无响应。

排查:

bash 复制代码
docker inspect <id> --format '{{json .Mounts}}' | jq   # 看挂载 RW 与源路径
docker exec <id> ls -l /data                          # 看容器内权限
docker exec <id> ps aux                               # 看进程是否存活
docker exec <id> cat /proc/1/status                   # 看主进程状态

根因 :宿主机目录权限与容器内 UID 不匹配(常见于 --user 指定非 root);假死容器则是主进程存活但内部线程阻塞(如死锁、IO 卡死)。

预防:挂载前统一 UID/GID;使用命名卷而非 bind mount 规避权限问题;为应用配置健康检查与超时,假死时自动重启。

5. 排障工具清单

当分层排查推进到内核层,以下四个工具是容器排障的"终极武器"。

5.1 nsenter:进入容器命名空间

docker exec 依赖容器内存在 shell 和工具,而 nsenter 直接从宿主机进入容器的命名空间,即使容器内没有 bash 也能操作:

bash 复制代码
PID=$(docker inspect <id> --format '{{.State.Pid}}')
nsenter -t $PID -n -m -u -p

-n 进入网络命名空间、-m 挂载、-u UTS、-p PID。排障时常用 -n 在宿主机上直接查看容器网络栈。

5.2 strace:追踪系统调用

当应用"卡死"或行为诡异时,strace 能揭示它到底在等什么:

bash 复制代码
PID=$(docker inspect <id> --format '{{.State.Pid}}')
strace -p $PID -f -e trace=network,file

关注 connect、open、read 等调用的返回值,ETIMEDOUT、EACCES 往往直接指向根因。

5.3 lsof:查看文件与网络占用

bash 复制代码
PID=$(docker inspect <id> --format '{{.State.Pid}}')
lsof -p $PID | grep -E 'TCP|LISTEN'

排查端口监听异常、文件句柄泄漏时非常高效。

5.4 tcpdump:抓包定位网络问题

bash 复制代码
PID=$(docker inspect <id> --format '{{.State.Pid}}')
nsenter -t $PID -n tcpdump -i eth0 -nn -s 0 port 8080

结合 nsenter 进入容器网络命名空间抓包,可定位连接被拒、半开连接、DNS 超时等网络层问题。

6. 坑位总结

最后,把最常见的三个"排障陷阱"单独拎出来,避免你重蹈覆辙。

6.1 docker restart 掩耳盗铃

重启能掩盖问题,但不能解决问题。每次 docker restart 前,先问自己:根因是什么? 重启后问题复现,只会浪费更多时间。正确姿势是:先 docker inspect + docker logs 定位,再决定是否重启。

6.2 只看 docker ps 不看 -a

docker ps 默认只显示运行中的容器。已退出、被 OOM 杀死、反复重启的容器,全都被它"藏"起来了。排障第一步永远是 docker ps -a,看到完整状态再下手。

6.3 日志被轮转吞掉

docker logs 默认读取的是 JSON 文件日志,但日志量过大时会被轮转(log-opt max-size),历史日志可能丢失。排查长时间运行的容器时,务必确认日志驱动与轮转配置,必要时改用 journald 或挂载日志卷持久化。

7. 总结

容器排障不是背命令,而是一套分层递进的方法论:从容器状态到应用日志,再到网络、资源、文件系统,最后深入内核。每层都有对应的第一命令,层层收敛,直到根因浮出水面。

记住三个关键词:先看现象、再查配置、最后动刀 。配合 docker inspect 大法与 nsenter / strace / lsof / tcpdump 四大工具,绝大多数容器故障都能在十分钟内定位。更重要的是,每个故障都要沉淀为"现象 → 排查 → 根因 → 预防"的案例,让排障经验真正变成团队资产。

相关推荐
梦想不只是梦与想4 小时前
Docker 常用命令
docker
wzq11_6664 小时前
Kubernetes集群——Pod篇
云原生·容器·kubernetes
wzq11_6665 小时前
Kubernetes集群——命令篇
云原生·容器·kubernetes
小白的码BUG之路6 小时前
Docker -- 构建ruoyi-gateway镜像
docker·容器·gateway
小白的码BUG之路7 小时前
Docker -- 构建ruoyi-system镜像
运维·docker·容器
LRL_7 小时前
【云原生】Oracle Linux 9.6 离线(Air-Gapped)环境下安装 Longhorn 及其底层依赖(iSCSI/NFS)完全指南
linux·云原生·oracle
小白的码BUG之路7 小时前
Docker -- 构建redis镜像
运维·docker·容器
江湖有缘7 小时前
Docker实战 | 使用Docker部署EspoCRM开源客户关系管理平台
docker·容器·开源
Henry-SAP7 小时前
SAP MMBE跨工厂库存层级展示解析
人工智能·云原生·sap·erp