TIPS之 Kubernetes Pod 接受 SIGTREM 信号并处理

Kubernetes Pod 接受 SIGTREM 信号并处理

Posted by 董江 on Sunday, July 24, 2022

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 及其子进程。

解决方式

    1. 如果可以的话,尽量不使用 shell 启动业务进程。
$ vim dockerfile
...
CMD [ 'application-binary', '-f', '/data/config.ini' ]
...
    1. 如果一定要通过 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-inittini 都可以作为 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

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付