冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 28 容器故障排查

浏览:14次阅读
没有评论

共计 5799 个字符,预计需要花费 15 分钟才能阅读完成。

Docker 30 天实战系列 · Day 28

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

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

本文你将学到:

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

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

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

通用排查思路可以用一棵决策树来表示:

容器出问题了
    |
    +-- 容器能启动吗?|       |
    |       +-- 不能 -->【场景一:启动失败】|       |
    |       +-- 能,但很快退出 --> 检查退出码
    |               |
    |               +-- 退出码 137 -->【场景二:OOM 被杀】|               +-- 退出码 1   --> 应用本身报错
    |               +-- 退出码 0   --> 前台进程结束了
    |
    +-- 容器在运行,但功能不正常?|
            +-- 访问不了 -->【场景三:网络不通】+-- 写入失败 / 日志断了 -->【场景四:磁盘满了】+-- 响应慢 --> 检查 CPU/ 内存 /IO

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


场景一:容器启动失败

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

症状描述

$ docker run -d my-app
# 容器 ID 闪了一下就没了
$ docker ps
# 空空如也
$ docker ps -a
# 状态显示 Exited (1) 5 seconds ago

排查命令

第一步:看日志,这是最重要的一步,90% 的启动失败都能从日志里找到答案。

# 查看容器日志
docker logs 

# 如果日志太多,只看最后 50 行
docker logs --tail 50 

# 实时跟踪日志
docker logs -f 

第二步:看退出码,退出码是容器留下的 " 遗言 "。

# 查看容器详细信息
docker inspect  --format='{{.State.ExitCode}}'

# 或者查看完整状态
docker inspect  --format='{{json .State}}' | python3 -m json.tool

常见退出码对照表:

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

第三步:检查镜像和配置

# 检查镜像是否存在
docker images | grep my-app

# 检查 Dockerfile 中的 CMD/ENTRYPOINT
docker inspect my-app:latest --format='{{json .Config.Cmd}}'
docker inspect my-app:latest --format='{{json .Config.Entrypoint}}'

# 尝试用交互模式进入容器排查
docker run -it --entrypoint /bin/sh my-app

根因分析

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

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

修复方案

# 端口冲突:查看端口占用
ss -tlnp | grep :8080

# 依赖未就绪:用 depends_on + healthcheck(Compose 场景)# 或者在入口脚本里加等待逻辑
while ! nc -z db 3306; do
  echo "Waiting for database..."
  sleep 2
done

场景二:OOM 被杀

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

症状描述

$ docker ps -a
# 状态显示 Exited (137)

$ dmesg | grep -i oom
# 能看到 OOM killer 的记录

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

排查命令

# 查看容器的内存限制和使用情况
docker stats  --no-stream

# 查看容器的内存限制配置
docker inspect  --format='{{.HostConfig.Memory}}'

# 查看系统层面的 OOM 记录
dmesg | grep -i "out of memory"
dmesg | grep -i "oom"

# 查看容器内存使用详情(cgroup v2)cat /sys/fs/cgroup/docker//memory.current
cat /sys/fs/cgroup/docker//memory.max

根因分析

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

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

修复方案

# 方案一:调整内存限制(治标)docker run -d --memory=512m --memory-swap=1g my-app

# 方案二:限制应用本身的内存使用(治本)# Java 应用示例
docker run -d --memory=512m \
  -e JAVA_OPTS="-Xmx384m -Xms256m" \
  my-java-app

# 方案三:监控内存使用趋势
docker stats --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"

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


场景三:网络不通

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

症状描述

# 从宿主机访问容器服务,连接超时或拒绝
$ curl http://localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

# 容器之间互相访问不通
$ docker exec app1 ping app2
ping: bad address 'app2'

排查命令

网络排查的核心思路是 分层排查,从内到外

应用层(服务是否在监听?)|
容器层(容器网络配置对不对?)|
Docker 网络层(网桥 /overlay 正常吗?)|
宿主机层(端口映射、防火墙)
# 第一层:确认容器内服务是否在监听
docker exec  ss -tlnp
# 或者
docker exec  netstat -tlnp

# 第二层:检查容器网络配置
docker inspect  --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool

# 第三层:检查 Docker 网络
docker network ls
docker network inspect bridge

# 第四层:检查宿主机端口映射
docker port 
ss -tlnp | grep docker

# 检查 iptables 规则(Docker 靠 iptables 做端口转发)sudo iptables -t nat -L -n | grep 

# DNS 排查(容器间通信)docker exec  nslookup 
docker exec  cat /etc/resolv.conf

根因分析

网络不通的常见原因:

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

修复方案

# 问题一:监听地址不对
# 修改应用配置,监听 0.0.0.0 而不是 127.0.0.1

# 问题二:创建自定义网络让容器互通
docker network create my-net
docker run -d --network my-net --name app1 my-app1
docker run -d --network my-net --name app2 my-app2
# 现在 app1 和 app2 可以通过容器名互相访问

# 问题三:防火墙放行
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

场景四:磁盘满了

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

症状描述

# 常见错误信息
"no space left on device"
"write /var/lib/docker/...: no space left on device"

# 新镜像拉不下来
$ docker pull nginx
Error: write /var/lib/docker/...: no space left on device

排查命令

# 查看磁盘使用情况
df -h

# 查看 Docker 的磁盘占用
docker system df

# 详细查看各类占用
docker system df -v

# 查看哪些容器的日志最大
find /var/lib/docker/containers -name "*.log" -exec ls -lh {} \; | sort -k5 -h

# 查看悬空镜像和无用资源
docker images -f "dangling=true"
docker volume ls -f "dangling=true"

根因分析

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

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

修复方案

# 紧急处理:清理无用资源(一键大扫除)docker system prune -a --volumes

# 注意:上面的命令会删除所有停止的容器、无用镜像和无主卷
# 生产环境建议分步操作:# 1. 清理停止的容器
docker container prune

# 2. 清理悬空镜像
docker image prune

# 3. 清理无主数据卷(谨慎!确认不需要了再删)docker volume prune

# 预防措施:配置日志轮转
# 在 /etc/docker/daemon.json 中添加:{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
# 修改后重启 Docker
sudo systemctl restart docker

# 单个容器指定日志限制
docker run -d \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  my-app

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


故障排查工具箱

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

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

常见问题 Q&A

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

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

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

如果容器已经 Exited 了,exec 自然进不去。这时候可以用同一个镜像启动一个新容器,挂载同样的卷,手动排查:

docker run -it --entrypoint /bin/sh \
  -v my-data:/data \
  my-app

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

看两个地方:一是退出码 137 强烈暗示 OOM,二是 dmesg | grep oom 会有系统级日志记录。另外 docker inspect 里的 OOMKilled 字段如果是 true,那就是铁证。

docker inspect  --format='{{.State.OOMKilled}}'

小结

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

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

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

下一篇 Day 29,我们来聊聊 存量应用容器化——如何把一个跑了多年的传统应用,优雅地搬进容器里。这可是很多团队在落地 Docker 时最头疼的事情,敬请期待。

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计5799字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。