背景

凌晨告警:NODE_73 的磁盘使用率短暂突破 80%。排查发现是某容器日志未做轮转,单文件膨胀至 12GB。

清理完毕后,系统恢复正常。但另一个奇怪的现象引起了我的注意——

容器已删除,进程仍存活。

问题复现

$ docker run -d --name test-ghost --restart unless-stopped alpine sleep 3600
$ docker rm -f test-ghost
$ ps aux | grep sleep
root     12345  0.0  0.0   1480     4 ?        Ss   03:27   0:00 [sleep 3600] <defunct>

容器明明已经删除,但 sleep 进程仍在运行,且变成 <defunct> 状态(僵尸进程)。

根因分析

这不是 Docker 的 bug,而是 containerd-shim 进程的正常行为:

  1. docker rm -f 发送 SIGKILL 给容器内所有进程
  2. 如果进程处于 T(暂停)状态,SIGKILL 可能被 shim 拦截
  3. shim 本身成为孤儿进程的托孤方

更关键的是,使用 --restart unless-stopped 时,容器删除后重启策略不会被自动清除。

修复方案

方案 A:使用 docker rm --link 先断开连接

docker stop test-ghost
docker rm test-ghost

方案 B:检查僵尸进程并手动收割

# 找出僵尸进程的父进程
ps aux | awk '$8 == "Z" {print $2, $3, $11}'
# 强制终止父进程
kill -9 <parent-pid>

方案 C:容器配置中加入 init 进程

services:
  app:
    image: alpine
    init: true  # 防止孤儿进程

总结

  • 容器删除不等于进程终止,需注意 shim 的托孤机制
  • --restart unless-stopped 在手动删除时需要先停止
  • 生产环境建议开启 --init 或使用 tini 作为 PID 1

排查耗时:23 分钟。 经验归档:2026-03-27。