K8S 生产排查台账:发版停机耗时满 30 秒 (PID 1 信号拦截与优雅退出失效)
01 前置理论与核心机制
1. K8s 的标准容器终止生命周期
- 终止流程:当 K8s 停止一个 Pod 时(如滚动更新、缩容),Kubelet 会向容器内部的 PID 1(1 号主进程)发送
SIGTERM(终止)信号。 - 宽限期与强制终止:发送信号后,K8s 会等待一段宽限期(
terminationGracePeriodSeconds,默认 30 秒)。如果 30 秒后容器依然未退出,Kubelet 会发送SIGKILL(强制终止)信号,直接结束进程。
2. 容器单进程模型与 Shell 的信号阻断
- PID 1 的职责:在容器环境中,占用 PID 1 的进程需要对容器的生命周期负责,必须能够接收并处理操作系统发来的系统信号。
- Shell 包装器的副作用:在编写 Dockerfile 时,如果采用
ENTRYPOINT sh -c "java -jar app.jar"的 Shell 模式,会导致sh外壳进程占用容器的 PID 1,而真正的 Java 业务进程变成了子进程(如 PID 2)。 - 信号未转发:
sh或bash进程作为 1 号进程时,收到SIGTERM后默认既不处理,也不会向子进程转发。底层的业务进程无法收到集群下达的停机指令,因此不会执行数据落盘或连接关闭动作,直到 30 秒宽限期结束被 K8s 强制终止。
02 故障排查与恢复闭环 SOP
遇到每次发版容器退出均固定耗时 30 秒,且观察到业务逻辑中的 @PreDestroy 或 ShutdownHook 完全未执行时,按以下步骤排查:
步骤一:观测停机耗时与确认现象
在执行部署或手动删除 Pod 时,观测退出耗时:
time kubectl delete pod <pod-name>
- 观测点:如果命令执行完毕耗时恰好在 30 秒左右,通常是
SIGTERM信号未生效,触发了兜底的SIGKILL强制终止逻辑。
步骤二:进入容器检查 PID 1 归属
直接进入容器查看进程树结构:
# 提取容器内的进程列表,重点关注 PID 为 1 的进程
kubectl exec -it <pod-name> -- ps -ef
- 观测点:如果看到
PID 1的进程是/bin/sh -c java ...,而真正的java -jar进程的 PID 是其他数字(如 7),即可确认是 Shell 进程阻断了信号传递链路。