冒险岛079

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4|回复: 0

Day 28 容器故障排查

[复制链接]

701

主题

13

回帖

2235

积分

管理员

积分
2235
发表于 前天 16:17 | 显示全部楼层 |阅读模式
Docker 30天实战系列 · Day 28

半夜三点,手机震得跟地震似的,一看监控告警:容器挂了。睡眼惺忪打开终端,面对一堆红色日志,脑子里只有一个念头——"这玩意儿昨天还好好的啊?"

别慌,容器故障排查其实是有套路的。就像老中医看病,望闻问切,先看症状再找病因。今天我们就来系统地聊聊容器故障排查的四大经典场景,让你以后遇到问题不至于手忙脚乱。

本文你将学到:

  • 容器启动失败的诊断决策树和排查步骤
  • OOM(内存溢出)被杀的识别与应对策略
  • 容器网络不通的分层排查方法
  • 磁盘空间不足导致的各种"灵异现象"
  • 一套通用的故障排查思维框架

    | 阅读时间 | 实操时间 | 难度等级 |
    |---------|---------|---------|
    | 15 分钟 | 30 分钟 | 中级 |

    ━━━━━━━━━━━━━━━━━━━━

    排查总纲:先别急着改,先搞清楚怎么回事

    在动手之前,记住一个原则:先诊断,后治疗。很多人一看到容器挂了,上来就重启,重启不行就删了重建,重建不行就重装 Docker。这就好比你头疼去医院,医生二话不说先给你开颅——方向完全错了。

    通用排查思路可以用一棵决策树来表示:
    1. 容器出问题了
    2.     |
    3.     +-- 容器能启动吗?
    4.     |       |
    5.     |       +-- 不能 --> 【场景一:启动失败】
    6.     |       |
    7.     |       +-- 能,但很快退出 --> 检查退出码
    8.     |               |
    9.     |               +-- 退出码 137 --> 【场景二:OOM 被杀】
    10.     |               +-- 退出码 1   --> 应用本身报错
    11.     |               +-- 退出码 0   --> 前台进程结束了
    12.     |
    13.     +-- 容器在运行,但功能不正常?
    14.             |
    15.             +-- 访问不了 --> 【场景三:网络不通】
    16.             +-- 写入失败/日志断了 --> 【场景四:磁盘满了】
    17.             +-- 响应慢 --> 检查 CPU/内存/IO
    复制代码

    带着这棵树,我们逐个击破。

    ━━━━━━━━━━━━━━━━━━━━

    场景一:容器启动失败

    这是最常见的问题,也是新手最容易懵的场景。容器死活起不来,
    1. docker ps
    复制代码
    里看不到它,但
    1. docker ps -a
    复制代码
    里能看到一堆 Exited 状态的"尸体"。

    症状描述
    1. $ docker run -d my-app
    2. # 容器 ID 闪了一下就没了
    3. $ docker ps
    4. # 空空如也
    5. $ docker ps -a
    6. # 状态显示 Exited (1) 5 seconds ago
    复制代码

    排查命令

    第一步:看日志,这是最重要的一步,90% 的启动失败都能从日志里找到答案。
    1. # 查看容器日志
    2. docker logs <container_id>
    3. # 如果日志太多,只看最后 50 行
    4. docker logs --tail 50 <container_id>
    5. # 实时跟踪日志
    6. docker logs -f <container_id>
    复制代码

    第二步:看退出码,退出码是容器留下的"遗言"。
    1. # 查看容器详细信息
    2. docker inspect <container_id> --format='{{.State.ExitCode}}'
    3. # 或者查看完整状态
    4. docker inspect <container_id> --format='{{json .State}}' | python3 -m json.tool
    复制代码

    常见退出码对照表:

    | 退出码 | 含义 | 常见原因 |
    |-------|------|---------|
    | 0 | 正常退出 | 前台进程执行完毕就结束了 |
    | 1 | 应用错误 | 代码抛异常、配置错误 |
    | 126 | 权限不足 | 入口脚本没有执行权限 |
    | 127 | 命令找不到 | CMD/ENTRYPOINT 写错了 |
    | 137 | 被 kill -9 | OOM 或者手动 kill |
    | 139 | 段错误 | 程序访问了非法内存 |

    第三步:检查镜像和配置
    1. # 检查镜像是否存在
    2. docker images | grep my-app
    3. # 检查 Dockerfile 中的 CMD/ENTRYPOINT
    4. docker inspect my-app:latest --format='{{json .Config.Cmd}}'
    5. docker inspect my-app:latest --format='{{json .Config.Entrypoint}}'
    6. # 尝试用交互模式进入容器排查
    7. docker run -it --entrypoint /bin/sh my-app
    复制代码

    根因分析

    启动失败最常见的几个原因:

  • 配置文件缺失或错误:环境变量没传、配置文件路径不对
  • 端口冲突:宿主机端口已被占用
  • 入口命令错误:CMD 或 ENTRYPOINT 写错了路径
  • 依赖服务未就绪:数据库还没启动,应用就开始连了

    修复方案
    1. # 端口冲突:查看端口占用
    2. ss -tlnp | grep :8080
    3. # 依赖未就绪:用 depends_on + healthcheck(Compose 场景)
    4. # 或者在入口脚本里加等待逻辑
    5. while ! nc -z db 3306; do
    6.   echo "Waiting for database..."
    7.   sleep 2
    8. done
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    场景二:OOM 被杀

    这个场景特别隐蔽。容器启动得好好的,运行一段时间后突然消失了,就像武侠小说里的"暗器伤人"——你都不知道啥时候中的招。

    症状描述
    1. $ docker ps -a
    2. # 状态显示 Exited (137)
    3. $ dmesg | grep -i oom
    4. # 能看到 OOM killer 的记录
    复制代码

    退出码 137 = 128 + 9,也就是进程收到了 SIGKILL 信号。而在容器场景下,最常见的 SIGKILL 来源就是 OOM Killer。

    排查命令
    1. # 查看容器的内存限制和使用情况
    2. docker stats <container_id> --no-stream
    3. # 查看容器的内存限制配置
    4. docker inspect <container_id> --format='{{.HostConfig.Memory}}'
    5. # 查看系统层面的 OOM 记录
    6. dmesg | grep -i "out of memory"
    7. dmesg | grep -i "oom"
    8. # 查看容器内存使用详情(cgroup v2)
    9. cat /sys/fs/cgroup/docker/<container_id>/memory.current
    10. cat /sys/fs/cgroup/docker/<container_id>/memory.max
    复制代码

    根因分析

    容器 OOM 通常有这么几个原因:

  • 内存限制设太小了:你给容器分了 256M,但应用启动就要 300M
  • 内存泄漏:应用跑着跑着内存越来越大,最终撑爆
  • 突发流量:平时够用,一到高峰期就不够了
  • JVM 类应用:堆内存 + 堆外内存 + 线程栈,加起来超限

    修复方案
    1. # 方案一:调整内存限制(治标)
    2. docker run -d --memory=512m --memory-swap=1g my-app
    3. # 方案二:限制应用本身的内存使用(治本)
    4. # Java 应用示例
    5. docker run -d --memory=512m \
    6.   -e JAVA_OPTS="-Xmx384m -Xms256m" \
    7.   my-java-app
    8. # 方案三:监控内存使用趋势
    9. docker stats --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
    复制代码

    有个小技巧:如果你怀疑是内存泄漏,可以用
    1. docker stats
    复制代码
    持续观察内存使用趋势。如果内存一直在涨从不回落,那八成就是泄漏了。就像家里的水龙头,正常情况下开了还会关,如果水表一直在转那肯定是漏了。

    ━━━━━━━━━━━━━━━━━━━━

    场景三:网络不通

    容器跑着呢,但就是访问不了。这种问题排查起来最磨人,因为网络链路上任何一个环节出问题都可能导致不通。

    症状描述
    1. # 从宿主机访问容器服务,连接超时或拒绝
    2. $ curl http://localhost:8080
    3. curl: (7) Failed to connect to localhost port 8080: Connection refused
    4. # 容器之间互相访问不通
    5. $ docker exec app1 ping app2
    6. ping: bad address 'app2'
    复制代码

    排查命令

    网络排查的核心思路是分层排查,从内到外
    1. 应用层(服务是否在监听?)
    2.     |
    3. 容器层(容器网络配置对不对?)
    4.     |
    5. Docker 网络层(网桥/overlay 正常吗?)
    6.     |
    7. 宿主机层(端口映射、防火墙)
    复制代码
    1. # 第一层:确认容器内服务是否在监听
    2. docker exec <container_id> ss -tlnp
    3. # 或者
    4. docker exec <container_id> netstat -tlnp
    5. # 第二层:检查容器网络配置
    6. docker inspect <container_id> --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool
    7. # 第三层:检查 Docker 网络
    8. docker network ls
    9. docker network inspect bridge
    10. # 第四层:检查宿主机端口映射
    11. docker port <container_id>
    12. ss -tlnp | grep docker
    13. # 检查 iptables 规则(Docker 靠 iptables 做端口转发)
    14. sudo iptables -t nat -L -n | grep <port>
    15. # DNS 排查(容器间通信)
    16. docker exec <container_id> nslookup <other_container_name>
    17. docker exec <container_id> cat /etc/resolv.conf
    复制代码

    根因分析

    网络不通的常见原因:

  • 服务监听了 127.0.0.1 而不是 0.0.0.0:这是最最常见的坑。容器内服务只监听 localhost,外面自然访问不到
  • 端口映射错误
    1. -p
    复制代码
    参数写反了,或者映射的端口不对
  • 容器不在同一网络:不同 Docker 网络的容器默认不能互通
  • DNS 解析失败:容器名解析不了,通常是没用自定义网络
  • 防火墙拦截:宿主机的 firewalld 或 iptables 把流量挡了

    修复方案
    1. # 问题一:监听地址不对
    2. # 修改应用配置,监听 0.0.0.0 而不是 127.0.0.1
    3. # 问题二:创建自定义网络让容器互通
    4. docker network create my-net
    5. docker run -d --network my-net --name app1 my-app1
    6. docker run -d --network my-net --name app2 my-app2
    7. # 现在 app1 和 app2 可以通过容器名互相访问
    8. # 问题三:防火墙放行
    9. sudo firewall-cmd --add-port=8080/tcp --permanent
    10. sudo firewall-cmd --reload
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    场景四:磁盘满了

    磁盘满了的表现五花八门,堪称容器故障界的"百变星君"。可能是日志写不进去了,可能是数据库报错了,也可能是新容器死活拉不下来。总之就是各种奇奇怪怪的问题,查到最后发现——磁盘满了。

    症状描述
    1. # 常见错误信息
    2. "no space left on device"
    3. "write /var/lib/docker/...: no space left on device"
    4. # 新镜像拉不下来
    5. $ docker pull nginx
    6. Error: write /var/lib/docker/...: no space left on device
    复制代码

    排查命令
    1. # 查看磁盘使用情况
    2. df -h
    3. # 查看 Docker 的磁盘占用
    4. docker system df
    5. # 详细查看各类占用
    6. docker system df -v
    7. # 查看哪些容器的日志最大
    8. find /var/lib/docker/containers -name "*.log" -exec ls -lh {} \; | sort -k5 -h
    9. # 查看悬空镜像和无用资源
    10. docker images -f "dangling=true"
    11. docker volume ls -f "dangling=true"
    复制代码

    根因分析

    磁盘满的元凶通常是这几个:

  • 容器日志失控:没有配置日志轮转,日志文件长到几十个 GB
  • 悬空镜像堆积:反复 build 产生大量
    1. <none>
    复制代码
    镜像
  • 无主数据卷:容器删了但数据卷还在
  • 大量停止的容器
    1. docker ps -a
    复制代码
    一看几百个"尸体"

    修复方案
    1. # 紧急处理:清理无用资源(一键大扫除)
    2. docker system prune -a --volumes
    3. # 注意:上面的命令会删除所有停止的容器、无用镜像和无主卷
    4. # 生产环境建议分步操作:
    5. # 1. 清理停止的容器
    6. docker container prune
    7. # 2. 清理悬空镜像
    8. docker image prune
    9. # 3. 清理无主数据卷(谨慎!确认不需要了再删)
    10. docker volume prune
    11. # 预防措施:配置日志轮转
    12. # 在 /etc/docker/daemon.json 中添加:
    13. {
    14.   "log-driver": "json-file",
    15.   "log-opts": {
    16.     "max-size": "10m",
    17.     "max-file": "3"
    18.   }
    19. }
    20. # 修改后重启 Docker
    21. sudo systemctl restart docker
    22. # 单个容器指定日志限制
    23. docker run -d \
    24.   --log-opt max-size=10m \
    25.   --log-opt max-file=3 \
    26.   my-app
    复制代码

    一个血泪教训:我曾经遇到过一台服务器,磁盘 200G,其中 180G 被一个容器的日志文件占了。那个容器每秒打几百行 DEBUG 日志,跑了两个月没人管。所以说,日志轮转不是可选项,是必选项

    ━━━━━━━━━━━━━━━━━━━━

    故障排查工具箱

    最后总结一下常用的排查命令,建议收藏备用:

    | 场景 | 命令 | 说明 |
    |------|------|------|
    | 查看日志 |
    1. docker logs -f --tail 100 <id>
    复制代码
    | 实时查看最近 100 行 |
    | 进入容器 |
    1. docker exec -it <id> /bin/sh
    复制代码
    | 交互式排查 |
    | 查看资源 |
    1. docker stats --no-stream
    复制代码
    | CPU/内存/网络/IO |
    | 查看详情 |
    1. docker inspect <id>
    复制代码
    | 完整配置信息 |
    | 查看事件 |
    1. docker events --since 1h
    复制代码
    | 最近 1 小时的事件 |
    | 查看进程 |
    1. docker top <id>
    复制代码
    | 容器内进程列表 |
    | 查看变更 |
    1. docker diff <id>
    复制代码
    | 容器文件系统变更 |
    | 系统信息 |
    1. docker system info
    复制代码
    | Docker 系统全局信息 |

    ━━━━━━━━━━━━━━━━━━━━

    常见问题 Q&A

    Q1:容器反复重启怎么办?

    先用
    1. docker inspect
    复制代码
    看重启策略(RestartPolicy),再用
    1. docker logs
    复制代码
    看每次重启前的报错。如果是
    1. restart: always
    复制代码
    ,容器会无限重启,你得先把重启策略改了或者
    1. docker update --restart=no <id>
    复制代码
    停掉自动重启,然后安心排查。

    Q2:docker exec 进不去容器怎么办?

    如果容器已经 Exited 了,
    1. exec
    复制代码
    自然进不去。这时候可以用同一个镜像启动一个新容器,挂载同样的卷,手动排查:
    1. docker run -it --entrypoint /bin/sh \
    2.   -v my-data:/data \
    3.   my-app
    复制代码

    Q3:怎么知道容器是被 OOM 杀的还是自己退出的?

    看两个地方:一是退出码 137 强烈暗示 OOM,二是
    1. dmesg | grep oom
    复制代码
    会有系统级日志记录。另外
    1. docker inspect
    复制代码
    里的
    1. OOMKilled
    复制代码
    字段如果是 true,那就是铁证。
    1. docker inspect <id> --format='{{.State.OOMKilled}}'
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    小结

    今天我们聊了容器故障排查的四大经典场景:

  • 启动失败:先看日志,再看退出码,最后检查配置
  • OOM 被杀:认准退出码 137,结合 dmesg 和 docker stats 确认
  • 网络不通:分层排查,从应用层到宿主机层逐步定位
  • 磁盘满了:docker system df 一看便知,日志轮转必须配

    记住一个心法:容器故障排查的核心不是记住所有命令,而是建立分层排查的思维方式。就像修水管一样,水不通了,你得从水龙头往回查——水龙头开了吗?阀门开了吗?管道堵了吗?水厂停水了吗?一层一层排查,总能找到问题所在。

    下一篇 Day 29,我们来聊聊存量应用容器化——如何把一个跑了多年的传统应用,优雅地搬进容器里。这可是很多团队在落地 Docker 时最头疼的事情,敬请期待。
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    果子博客
    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-3-31 05:52 , Processed in 0.123873 second(s), 19 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表