Kubernetes Pod 接受 SIGTREM 信号并处理
在 Kubernetes 环境中,业务发版时经常会对 workload
进行滚动更新,当旧版本 Pod 被删除时,K8S 会对 Pod 中各个容器中的主进程发送 SIGTERM
信号,当达到超时时间进程还未完全停止的话,K8S 就会发送 SIGKILL
信号将其强制杀死。
没收到SIGTREM现象
业务在 Kubernetes 环境中实际运行时,有时候可能会发现在滚动更新时,我们业务的优雅终止
逻辑并没有被执行,现象是在等了较长时间后,业务进程直接被 SIGKILL
强制杀死了
通常都是因为容器启动入口使用了 shell
,比如使用了类似 /bin/sh -c application-binary
这样的启动入口。 或者使用 /entrypoint.sh
这样的脚本文件作为入口,在脚本中再启动业务进程:
$ vim entrypoint.sh
#! /bin/bash
./application-binary
这就可能就会导致容器内的业务进程收不到 SIGTERM
信号,原因是:
- 容器主进程是
shell
,业务进程是在shell
中启动的,成为了shell 进程的子进程
。
$ kubectl exec -it lxcfs-demo "/bin/sh"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # pstree -p
entrypoint.sh(1)
-application-binary(1)
-
shell 进程默认不会处理 SIGTERM 信号,自己不会退出,也不会将信号传递给子进程,导致业务进程不会触发停止逻辑。
-
当等到 K8S 优雅停止超时时间 (
terminationGracePeriodSeconds
,默认 30s),发送SIGKILL
强制杀死 shell 及其子进程。
解决方式
-
- 如果可以的话,尽量不使用 shell 启动业务进程。
$ vim dockerfile
...
CMD [ 'application-binary', '-f', '/data/config.ini' ]
...
-
- 如果
一定要通过 shell 启动
,比如在启动前需要用 shell 进程一些判断和处理,或者需要启动多个进程,那么就需要在 shell 中传递下 SIGTERM 信号.
- 如果
方式一:exec启动
$ vim start.sh
#! /bin/bash
...
exec /bin/yourapp # 脚本中执行二进制
方式二: trap 传递信号
在 shell 中使用 trap 来捕获信号,当收到信号后触发回调函数来将信号通过 kill 传递给业务进程
#! /bin/bash
/bin/app1 & pid1="$!" # 启动第一个业务进程并记录 pid
echo "app1 started with pid $pid1"
/bin/app2 & pid2="$!" # 启动第二个业务进程并记录 pid
echo "app2 started with pid $pid2"
handle_sigterm() {
echo "[INFO] Received SIGTERM"
kill -SIGTERM $pid1 $pid2 # 传递 SIGTERM 给业务进程
wait $pid1 $pid2 # 等待所有业务进程完全终止
}
trap handle_sigterm SIGTERM # 捕获 SIGTERM 信号并回调 handle_sigterm 函数
wait # 等待回调执行完,主进程再退出
方式三:使用 init 系统(推荐方式)
用脚本实现了一个极简的 init
系统 (或 supervisor
) 来管理所有子进程,只不过它的逻辑很简陋,仅仅简单的透传指定信号给子进程,其实社区有更完善的方案,dumb-init
和 tini 都可以作为 init 进程
,作为主进程 (PID 1)
在容器中启动,然后它再运行 shell 来执行我们指定的脚本 (shell 作为子进程),shell 中启动的业务进程也成为它的子进程,当它收到信号时会将其传递给所有的子进程,从而也能完美解决 SHELL 无法传递信号问题,并且还有回收僵尸进程
的能力。
这是以 tini
为例制作镜像的 Dockerfile
示例:
FROM ubuntu:22.04
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /tini /entrypoint.sh
ENTRYPOINT ["/tini", "--"]
CMD [ "/start.sh" ]
start.sh
脚本内容:
#! /bin/bash
/bin/app1-binary &
/bin/app2-binary &
wait
「如果这篇文章对你有用,请随意打赏」