Skip to content

第 9 章 运维调试:日志、资源限制与排障 ​

本章目标:掌握日志查看与日志上限配置,学会用 CPU/内存限制保护宿主机,会用 stats/top/inspect/events 定位容器问题,能识别并处理 OOM。

1. 日志:容器在说什么 ​

容器的日志 = 容器内主进程的 stdout/stderr。应用只要把日志打到标准输出,Docker 就会捕获并按"日志驱动"收集(12-factor 原则:应用不写日志文件,只写 stdout)。

bash
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           # 带时间戳

日志驱动(决定日志存哪、怎么存):

bash
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 可能连宿主机关键进程一起杀。限制是部署的基本功:

bash
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:

bash
docker inspect app --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
# 输出示例:137 true

Compose 里声明限制:

yaml
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):

bash
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}}'

典型故障排查套路:

text
容器起不来/秒退
  ├─ 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:

python
# examples/09-ops-and-debugging/memhog.py:不断吃内存直到被杀
chunks = []
while True:
    chunks.append(b"x" * 1024 * 1024)   # 每次吞 1MB
bash
docker 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。

练习 ​

  1. 运行 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。
  2. 用 docker stats 实时观察第 3 章 nginx 容器的 CPU/内存占用,再用 docker update --cpus 0.5 修改限制后再次观察。
  3. 启动一个循环打印日志的容器(docker run -d --name logger alpine sh -c "while true; do echo tick; sleep 1; done"),分别用 --tail 5、-f、--since 1m 查看日志,最后清理。
  4. 用 docker events 开启事件监听,在另一个终端启动/停止一个容器,观察事件流中的 create/start/stop 记录。
  5. 思考题:为什么生产容器要求"日志写 stdout 而不是写文件"?(提示:结合 docker logs、日志驱动与日志中心的采集方式。)

可运行示例见 examples/09-ops-and-debugging/,按其中的 README 步骤执行。

📖本文阅读--次|📊全站访问--次|👥访客--人