一次容量检查触发 OOM,网关开始返回 502¶
2026 年 8 月 11 日,一台 1.8 GiB 内存的服务器上执行 docker system df 后,dockerd 内存占用升到 1 GiB 以上,被 OOM Killer 终止。dockerd 自动重启,但已有容器用的是 unless-stopped 重启策略,守护进程重启后处于停止状态的容器不会自动拉起,反向代理的 upstream 全部失联,对外返回 502。
排查¶
502 只说明网关到 upstream 断了,断的原因可能在网关、容器或 dockerd。确认顺序自底向上:先查 dockerd 有没有被 OOM 杀过,再查容器状态,网关放在最后——网关日志里只有 upstream 拒绝连接,从它往下查得不到任何信息。
OOM Killer 是内核行为,不走 Docker 的日志,确认用内核日志:
journalctl -k --since '-15 min' | grep -iE 'oom|out of memory|killed process'
systemctl is-active docker
内核日志里能看到被杀的进程名和时间,与 502 出现的时间对照即可确认。
根因¶
两个机制叠加:
docker system df要遍历所有镜像层才能算出磁盘占用。这次故障里它扫描镜像层时把 dockerd 的内存占用顶到 1 GiB 以上,在 1.8 GiB 内存的机器上直接触发 OOM。unless-stopped的语义是守护进程重启后不恢复处于停止状态的容器。dockerd 被杀时容器进入停止状态,dockerd 自动重启后没有拉起它们,upstream 一直空着。
处理和恢复¶
预防:生产环境禁止 docker system df 和 docker system df -v(部署文档里加粗标注)。看容量只用不遍历镜像层的命令:
df -h <部署目录>
du -sh <检查点目录>
docker image ls --format '{{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}'
docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Image}}'
恢复按固定顺序:内核日志确认 OOM → systemctl 确认 dockerd 状态 → docker compose up -d 拉起服务容器 → 单独 docker start 监控容器(现存容器,dockerd 重启后需要手动恢复)→ docker ps 确认全部健康检查通过才算恢复完成。
部署文档记录了这次事故的现象、依据和恢复步骤。整理本文时没有登录服务器复核,也没有重新触发故障。
在生产环境执行任何观测命令前,先确认它的资源消耗在被观测系统上可承受——这次的故障源就是容量检查命令本身。