使用Pulumi更新Kubernetes Deployment时Pod无法优雅停机问题排查
问题结论
该问题不是Pulumi的Bug,根因为容器启动进程的信号转发逻辑不符合Kubernetes优雅停机的要求。
原因说明
- Kubernetes优雅停机的核心逻辑是:销毁Pod时,会先给容器内的PID 1进程发送SIGTERM信号,等待
terminationGracePeriodSeconds(默认30秒)后进程仍未退出的话,再发送SIGKILL强制销毁进程。 - 你使用
go run作为容器启动命令时,会触发两层问题:go run不会直接执行业务代码,会先编译源码生成临时二进制文件,再fork子进程运行该二进制,此时go run本身是容器的PID 1进程,你实际的业务代码是它的子进程。go run原生不支持将收到的系统信号转发给它生成的子业务进程,Kubernetes发送的SIGTERM信号被PID 1的go run进程接收后直接退出,不会通知到业务进程,业务逻辑还没执行优雅停机流程,就被后续的SIGKILL强制销毁了。
验证信息
本地IDE运行测试时,信号直接发给了业务进程,所以可以看到包含"Signal":"interrupt"标识和Worker has been stopped的停机日志:
{"level":"info","msg":"trying to activate gcloud service account","time":"2021-06-17T12:19:25-05:00"} {"level":"info","msg":"env var not found","time":"2021-06-17T12:19:25-05:00"} {"Namespace":"default","TaskQueue":"main-task-queue","WorkerID":"37574@Paymahns-Air@","level":"error","msg":"Started Worker","time":"2021-06-17T12:19:25-05:00"} {"Namespace":"default","Signal":"interrupt","TaskQueue":"main-task-queue","WorkerID":"37574@Paymahns-Air@","level":"error","msg":"Worker has been stopped.","time":"2021-06-17T12:19:27-05:00"} {"Namespace":"default","TaskQueue":"main-task-queue","WorkerID":"37574@Paymahns-Air@","level":"error","msg":"Stopped Worker","time":"2021-06-17T12:19:27-05:00"}
容器内进程列表也验证了进程层级:PID 1为go run进程,实际业务进程为PID 3782的二进制程序:
root@worker-ffzpxpdm-78b9797dcd-xsfwr:/gadic# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.3 0.3 2046200 30828 ? Ssl 18:04 0:12 go run src/cmd/worker/main.go --temporal-host temporal-server.temporal.svc.cluster.local --temporal-port 7233 --grpc-port 6789 --grpc-hos root 3782 0.0 0.5 1640772 43232 ? Sl 18:06 0:00 /tmp/go-build2661472711/b001/exe/main --temporal-host temporal-server.temporal.svc.cluster.local --temporal-port 7233 --grpc-port 6789 -- root 3808 0.1 0.0 4244 3468 pts/0 Ss 19:07 0:00 /bin/bash root 3817 0.0 0.0 5900 2792 pts/0 R+ 19:07 0:00 ps aux
手动执行kill -TERM 1时信号不会转发给下层二进制程序,优雅停机逻辑不触发;执行kill -TERM 3782时优雅停机逻辑可以正常触发,也符合上述逻辑。
解决方案
- 生产环境Docker镜像不要使用
go run启动服务,在镜像构建阶段完成源码编译,直接将编译后的二进制文件作为容器启动命令,保证业务进程就是PID 1,可以直接接收Kubernetes发送的停机信号。 - 如有特殊场景必须使用
go run启动,可以引入dumb-init等轻量初始化进程作为PID 1,由它管理子进程并转发收到的系统信号。
内容的提问来源于stack exchange,提问作者Paymahn Moghadasian
相关产品推荐
相关产品推荐

