核心问题是容器缺乏真正的init进程,导致僵尸进程堆积和子进程逃逸出cgroups监控;用--init启动可自动注入tini或dumb-init作为pid 1,实现僵尸回收、信号转发与进程收养。

核心问题是:容器内缺乏真正的 init 进程,导致子进程退出后变成僵尸,或父进程退出后子进程被内核转交宿主机 PID 1——在容器命名空间中“失管”,从而逃逸出 cgroups 的生命周期监控与资源统计范围。
用 --init 启动容器,让轻量级 init 接管 PID 1
这是最直接有效的解决方式。Docker 1.13+ 原生支持 --init 标志,自动注入 dumb-init(部分发行版为 tini)作为 PID 1:
- 它会收养所有子进程(包括 fork 出的后台任务),确保无孤儿进程
- 自动调用
wait()回收已退出子进程,杜绝僵尸残留 - 正确转发信号(如 SIGTERM)给整个进程组,保障优雅退出
- 命令示例:
docker run --init -d nginx:alpine或带脚本:docker run --init alpine sh -c "sleep 10 & echo done; wait"
避免 shell 层级过深,优先 exec 主进程
Shell 脚本启动多进程时,若不加 exec,主服务会成为 shell 的子进程,而 shell 又可能提前退出,导致主进程脱离 init 管理:
- 错误写法:
/bin/sh -c "nginx -g 'daemon off;'"→ nginx 是 shell 的子进程,shell 退出后 nginx 可能被“遗弃” - 正确写法:
/bin/sh -c "exec nginx -g 'daemon off;'"或更简洁:nginx -g "daemon off;"(直接作为 ENTRYPOINT) - 这样 nginx 直接由 init(PID 1)收养,cgroups 统计、OOM Killer 判定、资源回收全部生效
验证是否真正受控于 cgroups
进入容器后,不只是看进程树,更要确认其资源归属是否在当前容器 cgroup 路径下:
- 检查 PID 1 类型:
ps -o pid,comm | head -2—— 应显示tini或dumb-init - 查任意子进程的 cgroup 所属:
cat /proc/$(pgrep sleep)/cgroup—— 输出中应含docker/xxx或system.slice/docker-xxx.scope - 对比宿主机视角:
cat /sys/fs/cgroup/memory/docker/*/memory.usage_in_bytes 2>/dev/null | sort -n | tail -3,确认数值与docker stats基本一致
补充:对已有镜像无法改启动参数时的兼容方案
若必须使用旧版 Docker 或无法加 --init,可手动替换 ENTRYPOINT:
- 基础镜像含
tini(如官方alpine:latest已内置):docker run --entrypoint /sbin/tini your-image sh -c "sleep 5 & wait" - 无 tini 时,COPY 官方二进制并设为入口:
COPY tini /tini && ENTRYPOINT ["/tini", "--"] - 注意:不要用
bash -c "tini -- ..."套一层,否则 bash 成为 PID 1,失去效果











