外观
项目 7:Docker 容器运维、日志监控与故障排查
约 3923 字大约 13 分钟
Docker运维日志监控
2026-06-07
项目目标
你在前面六节课中已经学会了构建镜像、配置网络、挂载数据卷。但容器上线后不可能一帆风顺——某天凌晨三点,用户投诉"网站打不开了"。你打开终端,面对的是一个跑了几周的生产容器。
本课模拟"线上容器服务异常排查"场景。你将掌握日志分析、资源监控、资源限制、自动重启、健康检查等运维"工具箱",并完成至少三个真实故障场景的定位与修复,最终形成一套可复用的容器排错方法论。
一、运维工具箱速览
容器运维的核心思路可以概括为四个字——"看"和"管"。
| 运维环节 | 核心命令 | 解决的问题 |
|---|---|---|
| 看日志 | docker logs | 应用出了什么错? |
| 看状态 | docker ps, docker inspect | 容器活着吗?配置对吗? |
| 看资源 | docker stats, docker system df | CPU/内存够吗?磁盘满了吗? |
| 管重启 | --restart | 容器崩了能自动拉起来吗? |
| 管健康 | HEALTHCHECK | 容器"活着"但服务正常吗? |
| 管资源 | --cpus, --memory | 一个容器会吃掉整台机器吗? |
| 管清理 | docker system prune | 垃圾镜像和停止的容器占了多少空间? |
本课逐一拆解这七个环节,最后组合成一套"先状态、再日志、后配置、最后资源"的排错流程。
二、日志分析:应用的"黑匣子"
日志是排错的第一入口。Docker 默认捕获容器的 stdout / stderr,所有 echo、print、console.log 输出都会进入 Docker 日志系统。
2.1 三种查看方式
# 查看全部日志(如果日志量大会刷屏)
docker logs nginx-test
# 只看最新的 20 行
docker logs --tail 20 nginx-test
# 实时跟踪日志(类似 tail -f)
docker logs -f nginx-test
# 按时间范围过滤(显示最近 30 分钟的日志)
docker logs --since 30m nginx-test2.2 实践:观察 Nginx 访问日志
先启动一个 Nginx 容器,然后观察日志:
docker run -d --name nginx-test -p 8081:80 nginx:alpine
sleep 3问题:查看容器日志的 Docker 命令是 docker 什么?(全小写,4个字母)
在另一个终端用 curl 访问几次:
curl http://127.0.0.1:8081/
curl http://127.0.0.1:8081/nonexistent回到第一个终端,你应该能在 docker logs -f nginx-test 的输出中看到 200 和 404 的访问记录。日志就是应用向你"说话"的方式。
2.3 什么时候日志不够用?
日志只能看到应用的标准输出。如果你需要确认运行时配置——比如容器绑定了哪个网络、挂载了什么目录、设置了哪些环境变量——就需要 docker inspect。
# 输出完整的容器配置 JSON(信息量非常大)
docker inspect nginx-test
# 用 --format 精准提取你关心的字段
docker inspect --format='{{range $name, $net := .NetworkSettings.Networks}}{{println $name $net.IPAddress}}{{end}}' nginx-test
docker inspect --format='{{json .Mounts}}' nginx-test | python3 -m json.tool
docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' nginx-test常用 inspect 提取模板
{{.State.Status}}— 容器运行状态{{range $name, $net := .NetworkSettings.Networks}}{{println $name $net.IPAddress}}{{end}}— 各网络中的容器 IP{{.HostConfig.PortBindings}}— 端口映射{{.Mounts}}— 挂载信息{{.Config.Env}}— 环境变量列表{{.State.ExitCode}}— 退出码(0正常退出,非0异常)
三、资源监控:容器吃了多少?
3.1 docker stats:实时资源面板
# 实时显示所有运行容器的 CPU、内存、网络、磁盘 IO
docker stats
# 只显示指定容器
docker stats nginx-test
# 一次性输出(不持续刷新)
docker stats --no-stream打开 docker stats 后,观察这几列:
| 列名 | 含义 | 报警信号 |
|---|---|---|
CPU % | 容器使用的宿主机 CPU 百分比 | 持续 > 80% |
MEM USAGE / LIMIT | 内存使用量 / 限制值 | 使用量接近 LIMIT |
NET I/O | 网络收发字节数 | 异常大流量 |
BLOCK I/O | 磁盘读写字节数 | 异常大 IO |
3.2 资源限制:防止一个容器拖垮整台机器
默认情况下,容器没有资源限制——它可以吃掉宿主机的全部 CPU 和内存。在多容器环境中,这是一个必须解决的问题。
# 启动一个最多使用 0.5 个 CPU 核心 + 256MB 内存的容器
docker run -d --name limited-nginx \
--cpus="0.5" \
--memory="256m" \
-p 8082:80 \
nginx:alpine
# 验证资源限制已生效
docker inspect limited-nginx --format='CPU: {{.HostConfig.NanoCpus}}, Memory: {{.HostConfig.Memory}}'
# CPU: 500000000 → 0.5 核,Memory: 268435456 → 256MB资源限制与 OOM Killer 演示
对比"无资源限制"与"限制内存"的容器在内存泄漏时的不同下场。
宿主机总内存 (1024 MB)
200 MB / 1024 MB
docker run nginxUp
无资源限制容器
应用发生内存泄漏,疯狂申请内存...
10 MB
docker run -m 256m nginxUp
限制 256M 容器
同样的内存泄漏应用...
10 MB
LIMIT (256M)
内存限制过小的后果
如果限制的 256MB 不够应用使用,容器会被 OOM Killer(Out of Memory Killer)杀死,状态变成 Exited (137)。137 = 128 + 9,其中 9 是 SIGKILL 信号编号——容器被系统强制终止了。
3.3 docker system df:磁盘空间概览
# 查看 Docker 占用的磁盘空间
docker system df
# 详细列出每种资源的占用
docker system df -v输出示例:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 5 2.5GB 1.8GB (72%)
Containers 8 3 150MB 120MB (80%)
Local Volumes 4 2 500MB 200MB (40%)RECLAIMABLE(可回收)这一列告诉你清理空间的最大收益在哪里。
3.4 安全清理:prune 的正确姿势
清理命令的破坏性从低到高:
# 最低风险:清理已停止的容器
docker container prune
# 中等风险:清理未使用的镜像(-a 包括未打标签的中间层)
docker image prune -a
# 中等风险:清理未被任何容器使用的数据卷
docker volume prune
# 最高风险:一键清理所有未使用资源(容器+镜像+网络+构建缓存)
docker system prune -a --volumes清理前必做
- 先
docker system df了解现状 - 确认没有"已停止但还需要"的容器和数据卷
prune默认只会清理未使用的资源——正在运行的容器及其镜像、数据卷是安全的--volumes会清理未使用的数据卷,这可能是你在第 6 课精心备份的数据库!
四、自动重启策略
容器不会永远不出错。应用可能因为内存泄漏崩掉,机器可能因为系统更新重启——你需要让 Docker 自动把服务拉起来。
| 策略 | 行为 | 适用场景 |
|---|---|---|
no(默认) | 容器退出后不自动重启 | 一次性任务 |
always | 无论什么原因退出,都自动重启 | 生产 Web 服务 |
on-failure[:N] | 仅当退出码非 0 时重启,最多 N 次 | 有 Bug 的应用,限制重启风暴 |
unless-stopped | 类似 always,但手动 docker stop 后不会在 Docker 重启时自动启动 | 需要手动维护窗口的服务 |
4.1 实践:验证 always 策略
# 启动一个带 always 重启策略的容器
docker run -d --name auto-restart --restart=always alpine \
sh -c 'sleep 10; exit 1'
# 观察:容器退出后会立即重启
watch -n 2 'docker ps -a --filter name=auto-restart --format "{{.Status}}"'
# 30 秒后停止观察,清理容器
docker rm -f auto-restartunless-stopped 与 always 的区别
always:即使你手动docker stop了容器,Docker 守护进程重启后它仍然会重新启动unless-stopped:你手动docker stop后,Docker 重启时它不会自动启动- 如果你的服务有维护窗口,用
unless-stopped更安全
五、健康检查:容器"活着"≠服务正常
一个容器状态是 Up,只说明它的主进程(PID 1)还在运行。但进程活着不代表它提供的服务是正常的——Nginx 可能因为配置文件损坏而 Up 但返回 500 错误。
5.1 在 Dockerfile 中定义健康检查
FROM nginx:alpine
# HEALTHCHECK:每 30 秒用 curl 检查一次本地 80 端口
# --interval=30s:检查间隔
# --timeout=3s:单次检查的超时时间
# --retries=3:连续失败 3 次后标记为 unhealthy
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -fsS -o /dev/null http://localhost/ || exit 15.2 在 docker run 时添加健康检查
不修改 Dockerfile 也能为容器添加健康检查:
docker run -d --name healthy-nginx \
--health-cmd="curl -fsS -o /dev/null http://localhost/ || exit 1" \
--health-interval=10s \
--health-timeout=3s \
--health-retries=3 \
-p 8083:80 \
nginx:alpine5.3 观察健康状态变化
# 查看健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"
# 你应该看到类似:
# healthy-nginx Up 10 seconds (healthy)现在模拟故障——进入容器把 Nginx 的配置文件改坏:
问题:容器健康检查连续失败后被标记为什么状态?(全小写,9个字母)
# 破坏 Nginx 配置(清空配置文件)
docker exec healthy-nginx sh -c 'echo > /etc/nginx/conf.d/default.conf'
# 重载 Nginx(会使 Nginx 失效)
docker exec healthy-nginx nginx -s reload 2>&1 || true
# 等 30 秒后检查状态
sleep 30
docker ps --filter name=healthy-nginx --format "{{.Status}}"
# 期望看到:Up 2 minutes (unhealthy)健康检查让 Docker 和编排工具(如 Docker Compose、Swarm、Kubernetes)知道容器何时需要被替换。
六、故障排查实战
以下是你需要完成的三类典型故障排查。
故障一:端口冲突
故障注入:教师或同学已在宿主机 8084 端口运行了某个服务。
# 先检查端口占用
ss -tlnp | grep 8084
# 或
lsof -i :8084
# 确认端口已被占用后,尝试启动新容器
docker run -d --name conflict-test -p 8084:80 nginx:alpine你会看到类似 port is already allocated 的错误。
排查思路:
docker ps确认是否有同名容器ss -tlnp | grep 8084确认端口占用情况- 解决方案:
- 换一个没被占用的端口:
-p 8085:80 - 或者停掉占用端口的进程
- 换一个没被占用的端口:
故障二:环境变量配置错误导致数据库连接失败
故障注入:启动一个 MySQL,但用错误的环境变量启动一个需要连接数据库的客户端容器。
# 启动正常的 MySQL
docker run -d --name troubleshoot-db \
-e MYSQL_ROOT_PASSWORD=correct123 \
-e MYSQL_DATABASE=appdb \
mysql:8.0
# 等待 MySQL 启动
sleep 20
# 故意用错误密码尝试连接(故障场景)
docker run --rm --name bad-client \
--link troubleshoot-db:db \
mysql:8.0 \
mysql -h db -uroot -pwrong123 -e "SHOW DATABASES;"排查思路:
docker logs troubleshoot-db查看数据库日志,确认是否收到连接请求docker inspect troubleshoot-db --format='{{range .Config.Env}}{{println .}}{{end}}'确认实际环境变量- 发现密码不匹配 → 修正为正确密码
故障三:挂载路径错误导致数据丢失
故障注入:把 MySQL 数据目录挂载到宿主机的 /tmp 临时目录,模拟误操作。
# 错误:把 MySQL 数据挂到 /tmp(系统可能会清理 /tmp)
docker run -d --name bad-mount \
-e MYSQL_ROOT_PASSWORD=secret123 \
-v /tmp/mysql-bad:/var/lib/mysql \
mysql:8.0
sleep 20
# 创建测试数据
docker exec bad-mount mysql -uroot -psecret123 -e "CREATE DATABASE important_data;"
# 删除容器
docker rm -f bad-mount
# /tmp 被清理或重启后丢失,重新创建容器发现数据没了排查思路:
docker inspect bad-mount --format='{{json .Mounts}}'查看绑定路径- 发现问题:数据挂到了不稳定路径
- 正确做法:使用 Docker Volume 而非
/tmp下的 Bind Mount
七、编写排错手册
根据以上实践,在 troubleshooting.md 中至少总结 7 类容器故障,每类包含:
- 现象:你看到什么?
- 原因:为什么发生?
- 排查命令:用哪些 Docker 命令定位?
- 处理步骤:怎么修?
推荐分类覆盖
端口冲突、环境变量错误、挂载路径问题、内存限制过小、健康检查误报、网络不通、镜像拉取失败。完整覆盖这七类,你就能应对大部分生产故障。
排错四步法(先记住这个顺序)
1. docker ps -a → 先看容器状态(Up?Exited?Restarting?)
2. docker logs --tail 50 → 再看容器日志
3. docker inspect → 再查运行时配置
4. docker stats --no-stream → 最后看资源使用排错四步法数据流:从状态到资源
模拟生产环境下的故障排查顺序,不要盲目猜错,要让数据说话。
Terminal - prod-server
1. 进程状态 (Status)
Exited (1)
↓
2. 标准输出 (Logs)
📄 stdout / stderr
↓
3. 运行时配置 (Inspect)
Env: MYSQL_PWD=***
Mounts: /var/lib/mysql
↓
4. 资源占用 (Stats)
CPU:
0%
不要一上来就改配置、删容器。看清楚再动手。
八、提交物清单
| 文件 | 说明 |
|---|---|
docker stats --no-stream 截图 | 显示 limited-nginx 容器的 CPU/Memory 限制 |
docker system df 截图 | 显示 Docker 磁盘占用概览 |
| 健康检查验证截图 | docker ps 显示 (healthy) 和 (unhealthy) 对比 |
| 三个故障排查截图 | 每个故障的排查过程和修复结果 |
troubleshooting.md | 至少 7 类容器故障的排错手册 |
当堂检测
- 容器状态
Exited (137)代表什么?什么情况会产生这个退出码? docker logs和docker inspect分别适合查什么问题?举例说明。--restart=always和--restart=unless-stopped的区别是什么?- 健康检查显示
unhealthy,但容器进程还活着——这意味着什么? - 执行
docker system prune -a --volumes前,你必须先做什么检查?
九、故障排查快速参考
| 现象 | 排查命令 | 常见原因 |
|---|---|---|
| 容器起不来 | docker logs、docker inspect --format='{{.State.Error}}' | 镜像不存在、端口冲突、命令错误 |
| 容器频繁重启 | docker logs --tail 20、docker inspect --format='{{.State.ExitCode}}' | 应用崩溃、OOM、健康检查失败 |
| 服务响应慢 | docker stats、docker system df | CPU/内存不足、磁盘满 |
| 容器间网络不通 | docker inspect --format='{{.NetworkSettings.Networks}}' | 不在同一网络、DNS 解析失败 |
| 数据丢失 | docker inspect --format='{{json .Mounts}}' | 未挂载 Volume、挂载路径错误 |
| 镜像拉取失败 | docker pull 输出 | 网络问题、镜像名错误、认证过期 |
| 端口映射不生效 | docker port、ss -tlnp | 端口冲突、防火墙拦截 |
十、深度探索任务
以下任务不强制提交,是拔高性质的进阶练习。
探索任务 1:压力测试与资源限制效果对比
目标:直观感受有/无资源限制时,一个失控容器对宿主机的影响。
# 1. 无限制:启动一个死循环容器(快速消耗 CPU)
docker run -d --name cpu-hog alpine sh -c 'while true; do :; done'
# 2. 观察 CPU 占用
docker stats --no-stream cpu-hog
# CPU 可能接近 100%
# 3. 清理
docker rm -f cpu-hog
# 4. 有限制:启动同一容器但限制 0.3 CPU 核心
docker run -d --name cpu-limited --cpus="0.3" alpine sh -c 'while true; do :; done'
# 5. 观察 CPU 占用
docker stats --no-stream cpu-limited
# CPU 稳定在 ~30%
docker rm -f cpu-limited确认问题
无限制的容器 CPU 占用多少?加了 --cpus="0.3" 后占用多少?如果生产环境有 20 个容器都没加资源限制,平均每个容器 CPU 需求 20%,会发生什么?
探索任务 2:日志驱动与日志轮转
目标:了解 Docker 日志的存储机制,防止日志撑爆磁盘。
# 查看当前日志驱动
docker info --format '{{.LoggingDriver}}'
# 默认是 json-file
# 启动一个带日志轮转的容器
docker run -d --name log-rotate \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:alpine
# 查看日志配置
docker inspect log-rotate --format='{{json .HostConfig.LogConfig}}' | python3 -m json.tool确认问题
如果不设置日志轮转,一个高流量 Nginx 容器运行一个月后可能产生多少日志?max-size=10m 和 max-file=3 的组合最多占用多少磁盘空间?
探索任务 3:构建完整的健康检查 + 自动重启方案
目标:组合 --restart、--health-cmd 实现自愈容器。
# 启动一个带健康检查 + 自动重启的 Nginx
docker run -d --name self-healing \
--restart=always \
--health-cmd="curl -fsS -o /dev/null http://localhost/ || exit 1" \
--health-interval=5s \
--health-timeout=3s \
--health-retries=3 \
-p 8086:80 \
nginx:alpine
# 观察:容器标记为 healthy
docker ps --filter name=self-healing --format "{{.Status}}"
# 破坏 Nginx(模拟故障)
docker exec self-healing nginx -s stop
# 观察:健康检查失败 → 容器被标记 unhealthy
# 但 restart=always 不会因 unhealthy 自动重启容器——它只管主进程退出
# (当前版本 Docker 的行为;在 Swarm/Docker Compose 中健康检查效果不同)
docker rm -f self-healing确认问题
--restart=always 会在健康检查失败时重启容器吗?如果不能,如何在生产环境中实现"健康检查失败 → 自动替换容器"的效果?(提示:Docker Swarm、Kubernetes 或外部监控工具)
