bigywater.tech
Docker · 36

PID 1 与僵尸进程:--init 解决了什么

容器里为什么会出现一堆 <defunct> 进程?--init 到底解决了什么、没解决什么?这篇用会故意不 wait 的 Python 程序复现僵尸进程和孤儿进程,验证 init 的作用边界,以及僵尸进程怎样把 --pids-limit 用光。

孤儿 50 → 0
孤儿进程场景,PID 1 是 python 时 50 个僵尸,加 --init 后 0 个
50 个僵尸还在
父进程活着且不 wait 时,加 --init 也没用
docker top 看不到
僵尸进程用 docker top 看不见,容器里的 ps 才看得见

僵尸是父进程的事

实测 · 本机执行输出
  PID 1 创建 50 个子进程但不 wait:pid=1 mode=leak 僵尸进程数=50 总进程数=51
  同样,加 --init(PID 1 是 docker 自带的 init):pid=7 mode=leak 僵尸进程数=50 总进程数=52

子进程退出后要由父进程 wait 才会消失。PID 1 创建了 50 个子进程但不 wait,就剩 50 个僵尸;加了 --init 后,python 变成 PID 7,仍是这些子进程的父进程,所以 50 个僵尸照旧。

孤儿才轮到 init

实测 · 本机执行输出
  孤儿进程 50 个,PID 1 是 python 且不 wait:pid=1 mode=orphan 僵尸进程数=50 总进程数=51
  孤儿进程 50 个,加 --init:pid=7 mode=orphan 僵尸进程数=0 总进程数=2

子进程的父进程退出后,孤儿进程被挂到 PID 1 下面。PID 1 是不 wait 的 python 时,这些孤儿退出后变成 50 个僵尸;换成 init(--init)就一个都不剩。这就是容器里需要一个会收尸的 PID 1 的原因。

进程数上限

实测 · 本机执行输出
  --pids-limit 40,不停创建不 wait 的子进程:第 40 次创建子进程失败:Resource temporarily unavailable | pid=1 mode=forkbomb 僵尸进程数=39 总进程数=40
  --pids-limit 40,加 --init(父进程仍是 python,不是 PID 1):第 39 次创建子进程失败:Resource temporarily unavailable | pid=7 mode=forkbomb 僵尸进程数=38 总进程数=40

僵尸进程仍占着进程号。--pids-limit 40 下,不停创建不 wait 的子进程,攒到 39 个僵尸就再也创建不出子进程了(Resource temporarily unavailable)。

怎么发现

实测 · 本机执行输出
  docker top(PID 1 是 python,3 个子进程已退出但没被 wait):
    PID                 PPID                STAT                COMMAND
    38280               38256               Ss                  python
  docker exec 里用 ps 看(同一个容器):
    PID   PPID  STAT COMMAND
    1     0 S    python
    7     1 Z    true
    8     1 Z    true
    9     1 Z    true
    10     0 R    ps

docker top 只显示了 python,看不到 3 个已退出的子进程;进入容器用 ps 才看到 STAT 为 Z 的 true。我没有查 docker top 为什么不显示它们。

分步演示

僵尸进程与 init
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。测试程序是 python:3.12-alpine 里的小脚本:创建会立刻退出的 true 子进程,1 秒后读 /proc/*/stat 的状态字段统计 Z;--init 使用 Docker 内置的 init(PID 1,python 为 PID 7);每种情形运行一次(现象是确定性的);实验后已删除容器和镜像
    • 没有验证:tini 以外的 init(dumb-init、s6)、僵尸进程对宿主机的影响(进程表耗尽)、PID 1 是 sh 时的收尸行为、Kubernetes 的 shareProcessNamespace、docker top 不显示僵尸的原因
    京ICP备2026067502号-1京公网安备11010502063487号