1 号进程的职责
容器里的 1 号进程承担信号转发与孤儿进程回收:用 shell 当 1 号进程时,常把终止信号吞掉并留下僵尸进程。注入一个轻量 init 进程是代价很小的解决办法。
基本信息
| 症状 | 停止容器无响应、僵尸进程累积 |
|---|---|
| 机制 | 1 号进程需要转发信号并回收子进程 |
| 解决方案 | 注入 init 进程、用直接执行形式入口、或应用自带处理 |
| 排查 | 看进程列表与状态字段 |
| 典型环境 | 入口是 shell 脚本的镜像 |
典型使用场景
- 停止容器要等超时才退出甚至被强杀
- 容器内出现大量僵尸进程
- 排查应用升级后停止行为变化的原因
- 给无法修改入口的镜像做低成本的信号处理补救
- 梳理镜像入口写法是否合理
验证与观察
验证与观察
docker run -d --init --name app app/base:2.0
docker exec app sh -c 'ps -eo pid,ppid,stat,comm | head -15' # 观察僵尸进程
docker top app | head -8 # 进程视角
docker stop -t 5 app # 验证能否快速优雅退出有 init 与没有的差别
| 维度 | 有 init 进程 | 无 init 进程 |
|---|---|---|
| 1 号进程 | 轻量 init | 镜像入口命令 |
| 信号转发 | 由 init 负责 | shell 未处理则不转发 |
| 孤儿进程 | 自动回收 | 累积成僵尸 |
| 停止表现 | 收到信号即退出 | 可能等到超时 |
入口用直接执行形式(数组写法)能让应用直接成为 1 号进程,比 shell 包装更干净。
shell 形式的入口下信号只发给 shell,没写处理逻辑就永远不会转发。
既有镜像没法改入口时,注入 init 是代价很小的补救,但它本身也会占用一个进程。
僵尸进程的父进程通常是 1 号进程,排查时先看进程之间的父子关系。
停止无响应与僵尸进程往往同时出现,属于同一个根因的两种表现。
应用自身实现了信号处理时,优先让应用直接作为 1 号进程运行,减少中间层。
容器内进程很少、入口就是单个可执行文件时,多余包装反而增加排查成本,按实际需要决定是否引入 init。