前言:
在 Kubernetes 中,Pod 停止时 kubelet 会先给容器中的主进程发 SIGTERM
信号来通知进程进行 shutdown 以实现优雅停止,如果超时进程还未完全停止则会使用 SIGKILL
来强行终止。
容器终止流程:
1、Pod 被删除,状态置为 Terminating。 2、kube-proxy 更新转发规则,将 Pod 从 service 的 endpoint 列表中摘除掉,新的流量不再转发到该 Pod。 3、如果 Pod 配置了 preStop Hook ,将会执行。 4、kubelet 对 Pod 中各个 container 发送 SIGTERM 信号以通知容器进程开始优雅停止。 5、等待容器进程完全停止,如果在 terminationGracePeriodSeconds 内 (默认 30s) 还未完全停止,就发送 SIGKILL 信号强制杀死进程。 6、所有容器进程终止,清理 Pod 资源。
优雅退出,业务侧需要做的任务是处理SIGTERM信号:
要实现优雅终止,务必在业务代码里面处理下 SIGTERM 信号
注意事项:
要实现优雅退出,还需要注意的是如果业务容器的进程,是使用shell脚本启动的,需要进行特殊处理,业务容器才能接收到SIGTERM信号。建议尽量不使用shell脚本启动,如果确实需要,则需要特殊处理。
shell启动为什么接收不到SIGTERM信号呢?
1、容器主进程是 shell,业务进程是在 shell 中启动的,成为了 shell 进程的子进程。 2、shell 进程默认不会处理 SIGTERM 信号,自己不会退出,也不会将信号传递给子进程,导致业务进程不会触发停止逻辑。 3、当等到 K8S 优雅停止超时时间 (terminationGracePeriodSeconds,默认 30s),发送 SIGKILL 强制杀死 shell 及其子进程。
解决方案:
1、如果shell启动的是单进程,可以在shell 中启动二进制的命令前面加一个exec,这个命令可以让二进制启动的进程代替shell成为主进程,从而业务进程可以接收到SIGTERM
#! /bin/bash
exec /bin/myapp # 脚本中执行二进制
2、shell启动的是多个进程,则不能用exec来解决了,因为exec只能让一个进程成为主进程。可以使用trap或init系统实现多进程启动传递SIGTERM信号。
trap:
#! /bin/bash /bin/myapp & pid1="$!" # 启动第一个业务进程并记录 pid echo "app started with pid $pid1" /bin/myclient & pid2="$!" # 启动第二个业务进程并记录 pid echo "myclient 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:
dumb-init 和 tini 都可以作为 init 进程,作为主进程 (PID 1) 在容器中启动,然后它再运行 shell 来执行我们指定的脚本 (shell 作为子进程),shell 中启动的业务进程也成为它的子进程,当它收到信号时会将其传递给所有的子进程,从而也能完美解决 SHELL 无法传递信号问题,并且还有回收僵尸进程的能力
制作包含init系统的业务镜像:
FROM ubuntu:latest RUN apt-get update && apt-get install -y dumb-init ADD start.sh / ADD myapp /bin/myapp ADD myclient /bin/myclient ENTRYPOINT ["dumb-init", "--"] CMD ["/start.sh"]
start.sh:
#! /bin/bash /bin/app1 & /bin/app2 & wait
业务代码不方便处理或没有办法处理SIGTERM信号时,进程优雅退出的方法:
1、preStop-webhook
lifecycle: preStop: exec: command: - sleep - 5s
2、调整优雅终止时间,terminationGracePeriodSeconds 默认是30s。自己视情况而定