第 9 章 运维调试:日志、资源限制与排障
本章目标:掌握日志查看与日志上限配置,学会用 CPU/内存限制保护宿主机,会用 stats/top/inspect/events 定位容器问题,能识别并处理 OOM。
1. 日志:容器在说什么
容器的日志 = 容器内主进程的 stdout/stderr。应用只要把日志打到标准输出,Docker 就会捕获并按"日志驱动"收集(12-factor 原则:应用不写日志文件,只写 stdout)。
docker logs web # 查看全部日志
docker logs --tail 100 web # 只看末尾 100 行
docker logs -f web # 跟随(tail -f 效果)
docker logs --since 5m web # 最近 5 分钟
docker logs -t web # 带时间戳日志驱动(决定日志存哪、怎么存):
docker info --format '{{.LoggingDriver}}' # 当前全局驱动(默认 json-file)
docker run -d --log-driver json-file --log-opt max-size=10m --log-opt max-file=3 nginx:alpine| 驱动 | 说明 |
|---|---|
json-file(默认) | 写成本机 JSON 文件,支持 max-size/max-file 轮转 |
local | 更高效的本地存储,同样支持轮转 |
journald | 交给 systemd journal |
gelf / fluentd / splunk | 汇集到日志中心(ELK 等) |
必须配日志轮转:不限制的话,json-file 日志会无限增长直到撑爆磁盘。容器运行时配置 --log-opt max-size=10m --log-opt max-file=3(每个日志文件最大 10MB,最多保留 3 个);daemon 级可在 /etc/docker/daemon.json 设 "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"} 全局生效。
2. 资源限制:别让一个容器拖垮宿主机
不设限制的容器理论上可用满宿主机全部 CPU/内存,一旦内存耗尽,内核 OOM killer 可能连宿主机关键进程一起杀。限制是部署的基本功:
docker run -d --name app \
--memory 256m \ # 内存上限 256MB
--memory-swap 512m \ # 内存+swap 上限(缺省为 memory 的 2 倍)
--cpus 1.5 \ # 最多 1.5 个 CPU 核
--cpuset-cpus 0-1 \ # 限定在 0、1 号 CPU 上跑(可选)
nginx:alpine
docker update --memory 512m app # 对运行中的容器改限制
docker stats # 实时观察所有容器资源占用内存超限会怎样:容器进程尝试分配超过上限的内存时,内核 OOM killer 直接杀掉容器主进程,容器退出,退出码为 137(128 + SIGKILL 9),docker inspect 里 State.OOMKilled 为 true:
docker inspect app --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
# 输出示例:137 trueCompose 里声明限制:
services:
app:
image: nginx:alpine
deploy:
resources:
limits:
cpus: "1.0"
memory: 256M注意:
deploy.resources在docker compose up(非 swarm 模式)下对单机也生效。
3. 排障工具箱
| 命令 | 用途 |
|---|---|
docker logs | 应用说了什么(排障第一步) |
docker inspect <容器> | 完整配置 JSON;--format 提取单字段 |
docker top <容器> | 容器内进程(PID 映射) |
docker stats | 实时资源占用 |
docker events | 实时事件流(create/start/die 等) |
docker system df | 磁盘占用总览(镜像/容器/卷/缓存) |
docker system prune | 清理停用容器、悬空镜像、无用网络与构建缓存 |
常用 --format 提取(排障时不用翻整段 JSON):
docker inspect web --format '{{.State.Status}}'
docker inspect web --format '{{.NetworkSettings.IPAddress}}'
docker inspect web --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
docker inspect web --format '{{json .Mounts}}'典型故障排查套路:
容器起不来/秒退
├─ docker logs <容器> → 应用启动报错?缺依赖?端口被占?
├─ docker inspect <容器> → ExitCode/OOMKilled/健康状态
└─ docker run 前台重跑 → 直接看原始输出
容器间不通
├─ 同一网络?docker network inspect
├─ 按服务名/容器名?docker exec 里 ping
└─ 端口发布对了?-p 宿主:容器
容器被杀(退出码 137)
├─ docker inspect → OOMKilled=true?→ 加大 --memory 或排查内存泄漏
└─ 被 docker stop/kill?→ 检查是否有人/策略操作4. 完整示例:资源限制与 OOM(本机可复现)
配套 examples/09-ops-and-debugging/,用 python 写一个"内存膨胀"程序触发 OOM:
# examples/09-ops-and-debugging/memhog.py:不断吃内存直到被杀
chunks = []
while True:
chunks.append(b"x" * 1024 * 1024) # 每次吞 1MBdocker run -d --name hog --memory 64m \
-v "$PWD/memhog.py:/app/memhog.py" \
python:3.12-alpine python /app/memhog.py
# ↑ 64MB 上限,几秒内就会被 OOM killer 杀掉
docker ps -a --filter name=hog --format '{{.Names}} {{.Status}}'
docker inspect hog --format 'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}'
docker logs --tail 3 hog
docker rm -f hog预期结果:容器状态 Exited (137)、OOMKilled=true——这就是"内存超限被杀"的标准特征,也是排查"容器莫名退出"的第一线索。
常见误区
- 误区:应用日志写在文件里,用
docker logs看不到。 正确:docker logs只看stdout/stderr;应用应把日志打到标准输出(12-factor),要落文件就配日志驱动/日志代理。 - 误区:退出码 137 = 程序崩溃。 正确:137 = 128+SIGKILL,最常见原因是 OOM(看
OOMKilled),其次是外部 kill。 - 误区:容器不限制资源没风险。 正确:内存失控的容器可能触发内核 OOM killer,甚至波及宿主机其它进程;生产必须设 limits。
- 误区:
docker logs日志量不用管。 正确:不配max-size轮转,日志会撑爆磁盘;容器日志默认无限增长。 - 误区:
docker inspect只有"高级用户"才用。 正确:--format提取状态/网络/挂载/环境变量是日常排障基本功。
小结
- 日志看 stdout/stderr:
docker logs(-f/--tail/--since);配--log-opt max-size/max-file防撑爆磁盘。 - 资源限制:
--memory/--cpus,运行中可用docker update;compose 用deploy.resources.limits。 - OOM 特征:退出码 137 +
OOMKilled=true;先看日志和限制再下结论。 - 工具箱:logs(应用说了啥)→ inspect(配置与状态)→ stats/top(资源)→ events(事件流)→ system df/prune(磁盘)。
- 排查套路:起不来看 logs+inspect,不通看网络,被杀看 137/OOMKilled。
练习
- 运行
docker run -d --name hog --memory 64m python:3.12-alpine python -c "c=[]; [(c.append(b'x'*1024*1024)) for _ in range(10000)]",等待退出后用docker inspect确认ExitCode=137与OOMKilled=true。 - 用
docker stats实时观察第 3 章 nginx 容器的 CPU/内存占用,再用docker update --cpus 0.5修改限制后再次观察。 - 启动一个循环打印日志的容器(
docker run -d --name logger alpine sh -c "while true; do echo tick; sleep 1; done"),分别用--tail 5、-f、--since 1m查看日志,最后清理。 - 用
docker events开启事件监听,在另一个终端启动/停止一个容器,观察事件流中的 create/start/stop 记录。 - 思考题:为什么生产容器要求"日志写 stdout 而不是写文件"?(提示:结合
docker logs、日志驱动与日志中心的采集方式。)
可运行示例见
examples/09-ops-and-debugging/,按其中的 README 步骤执行。