Docker默认仅向PID 1进程发送SIGTERM,不自动转发给子进程;需通过tini/docker-init或手动信号捕获实现转发,确保多进程优雅退出。

在多进程容器中,Docker 默认只向 PID 1 进程发送 STOP(实际是 SIGTERM)信号,而不会自动转发给子进程。若容器内有多个长期运行的进程(如主服务 + 日志收集器 + 健康检查守护进程),仅靠默认行为会导致部分进程无法优雅退出,引发资源泄漏或数据丢失。
理解 PID 1 的特殊性
Linux 容器中,PID 1 进程承担 init 系统职责:接收孤儿进程、处理信号、执行僵尸进程回收。但普通应用(如 Python 脚本、Node.js 服务)通常不实现信号转发逻辑。因此,当 Docker 发送 SIGTERM 给它时,只有它自己响应,子进程继续运行,最终被强制 SIGKILL(超时后)终止。
使用轻量级 init 进程(推荐)
在启动命令前插入 tini 或 docker-init,可让 PID 1 具备信号转发与僵尸回收能力:
- 基础镜像中安装
tini:RUN apt-get update && apt-get install -y tini(Debian/Ubuntu)或apk add --no-cache tini(Alpine) - Dockerfile 中指定入口:
ENTRYPOINT ["/sbin/tini", "--"],再用CMD启动主进程 - 或直接在
docker run中启用:docker run --init ...(Docker 1.13+ 内置tini)
手动实现信号捕获与转发
若无法引入外部 init,可在主进程脚本中显式监听并分发信号:
- Bash 示例:用
trap捕获SIGTERM,向子进程组发送相同信号(kill -TERM -${PGID}) - Python 示例:用
signal.signal(signal.SIGTERM, handler)注册回调,调用os.killpg(os.getpgid(0), signal.SIGTERM) - 注意:需确保所有子进程未忽略该信号,且避免在 handler 中做阻塞操作
验证信号是否正常传递
部署后可通过以下方式确认行为符合预期:
- 进入容器:
docker exec -it <container> ps aux,确认主进程 PID 为 1,且子进程与其同属一个进程组 - 手动触发停止:
docker stop --time=30 <container>,观察日志中各组件是否输出“shutting down”类信息 - 检查退出码:
docker inspect <container> | grep -i exit,正常应为 0;非零值可能表示进程未响应SIGTERM
不复杂但容易忽略。关键在于让 PID 1 承担起信号中转站的角色,而不是把它当成普通应用进程对待。










