15.4.5 容器进程与信号处理
容器中的进程模型以 PID 1 为核心,负责接收并处理信号。多数基础镜像以应用进程直接作为 PID 1 运行,若应用未正确处理信号,可能导致无法优雅退出或产生僵尸进程。因此需要明确容器内进程树、信号传递路径与退出策略。
进程与信号的原理草图(PID 1 作为信号与回收中心):
常用命令与含义(配合示例更易理解):
- docker top <container>:查看容器内进程树(宿主机视角)
- docker exec <container> ps -ef:容器内查看完整进程列表
- docker kill --signal=SIGTERM <container>:发送优雅退出信号
- docker kill --signal=SIGKILL <container>:强制终止
- docker stop -t <seconds> <container>:等待超时后强杀
- docker restart <container>:依赖应用对 SIGTERM/SIGINT 的处理
示例:构建一个能处理 SIGTERM 的容器
# 1) 创建示例目录
mkdir -p ~/demo/signal-app
cd ~/demo/signal-app
# 2) 编写应用脚本(演示信号处理与子进程回收)
cat > app.sh <<'EOF'
#!/usr/bin/env bash
set -e
echo "PID=$$"
# 启动一个子进程(模拟后台任务)
sleep 300 &
child=$!
# 处理 SIGTERM/SIGINT,优雅退出
trap 'echo "收到退出信号,清理资源..."; kill -TERM "$child"; wait "$child"; echo "已退出"; exit 0' TERM INT
# 主进程持续运行
while true; do
echo "工作中..."; sleep 5
done
EOF
chmod +x app.sh
# 3) 编写 Dockerfile,引入 tini 作为 PID 1
cat > Dockerfile <<'EOF'
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y tini bash && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY app.sh /app/app.sh
ENTRYPOINT ["tini","--"]
CMD ["/app/app.sh"]
EOF
# 4) 构建与运行
docker build -t signal-app:1.0 .
docker run -d --name signal-demo signal-app:1.0
验证进程与信号转发:
# 查看进程树:tini 作为 PID 1
docker exec signal-demo ps -ef
# 发送 SIGTERM,观察日志
docker kill --signal=SIGTERM signal-demo
docker logs -f signal-demo
预期效果:
- ps -ef 中 PID 1 为 /usr/bin/tini -- /app/app.sh
- docker kill --signal=SIGTERM 后日志出现“收到退出信号,清理资源...已退出”
- 容器状态从 running 变为 exited,退出码为 0
常见问题与排错步骤(含命令):
1. 容器停止缓慢
- 排查应用是否处理 SIGTERM:
bash
docker stop -t 5 signal-demo
docker logs signal-demo | tail -n 20
- 若无“清理资源”日志,说明应用未处理信号。
-
产生僵尸进程
- 查看进程状态:
bash docker exec signal-demo ps -o pid,ppid,stat,cmd
- 若看到Z状态,说明 PID 1 未回收子进程,可引入tini或dumb-init。 -
信号不生效
- 检查入口脚本是否转发信号:
bash docker inspect signal-demo --format '{{.Config.Entrypoint}} {{.Config.Cmd}}'
- 若 PID 1 是/bin/sh -c,需改为ENTRYPOINT ["tini","--"]或exec方式启动应用。
安装与替换建议:
- 若基础镜像是 Alpine,可安装 tini:
# Dockerfile 中
RUN apk add --no-cache tini
ENTRYPOINT ["tini","--"]
- 或使用官方
--init选项(自动启用 tini):
docker run --init -d --name signal-demo signal-app:1.0
运维实践要点:
1. 保证 PID 1 可转发信号、可回收子进程。
2. 应用实现 SIGTERM/SIGINT 处理,优雅关闭连接与刷盘。
3. 设置合理停止超时:docker stop -t 30 <container>,避免强杀数据不一致。
4. 多进程容器中明确主进程对子进程的生命周期管理。
练习:
1. 去掉 tini 后重新构建镜像,观察 ps -ef 中 PID 1 的变化,并对比 SIGTERM 的处理效果。
2. 修改 app.sh 使其不处理 SIGTERM,观察 docker stop 的行为与退出码变化。
3. 在容器中启动多个子进程,模拟僵尸进程场景,使用 ps -o stat 识别并解决。