K8S优雅终止方案

1.容器保证能接受SIGTERM信号

2.pod使用preStop处理残存流量

镜像中的处理

docker容器停止的流程

对于容器来说,init 系统不是必须的,当你通过命令 docker stop mycontainer 来停止容器时,docker CLI 会将 TERM 信号发送给 mycontainer 的 PID 为 1 的进程。

如果 PID 1 是 init 进程 - 那么 PID 1 会将 TERM 信号转发给子进程,然后子进程开始关闭,最后容器终止。
如果没有 init 进程 - 那么容器中的应用进程(Dockerfile 中的 ENTRYPOINT 或 CMD 指定的应用)就是 PID 1,应用进程直接负责响应 TERM 信号。这时又分为两种情况:

应用不处理 SIGTERM - 如果应用没有监听 SIGTERM 信号,或者应用中没有实现处理 SIGTERM 信号的逻辑,应用就不会停止,容器也不会终止。

容器停止时间很长 - 运行命令 docker stop mycontainer 之后,Docker 会等待 10s,如果 10s 后容器还没有终止,Docker 就会绕过容器应用直接向内核发送 SIGKILL,内核会强行杀死应用,从而终止容器。

容器进程收不到 SIGTERM 信号?

如果容器中的进程没有收到 SIGTERM 信号,很有可能是因为应用进程不是 PID 1,PID 1 是 shell,而应用进程只是 shell 的子进程。而 shell 不具备 init 系统的功能,也就不会将操作系统的信号转发到子进程上,这也是容器中的应用没有收到 SIGTERM 信号的常见原因。

问题的根源就来自 Dockerfile,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT ./popcorn.sh

ENTRYPOINT 指令使用的是 shell 模式,这样 Docker 就会把应用放到 shell 中运行,因此 shell 是 PID 1。

  1. 脚本中处理信号

    #!/bin/sh
    
    # catch the TERM signal and then exit
    trap "exit" TERM
    
    while true
    do
        date
        sleep 1
    done
    
  2. 使用tini作为init进程保证能接受SIGTERM信号

    FROM alpine:3.7
    COPY popcorn.sh .
    RUN chmod +x popcorn.sh
    RUN apk add --no-cache tini
    ENTRYPOINT ["/sbin/tini", "--", "./popcorn.sh"]
    

K8s中preStop

有三种preStop方式:

  • exec
  • httpGet
  • tcpSocket

示例

$ kubectl apply -f pod.yaml
# pod.yaml文件内容:
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["/bin/sh", "-c", "sleep 10m"]
      lifecycle:
        preStop:
          exec:
            command:
              [
                "/bin/sh",
                "-c",
                "echo this pod is stopping. > /stop.log && sleep 10s",
              ]

删除pod:

$ kubectl delete pod busybox

在新终端窗口(因为删除pod会占用终端窗口)获取pod内文件内容,需要在pod完全删除之前(10s内,也可以将该值设置稍长一点):

$ kubectl exec busybox -c busybox -- cat /stop.log
# 可以得到日志内容
this pod is stopping.

这说明,preStop确实生效了。

Pod的终止过程:

删除Pod => Pod被标记为Terminating状态 => Service移除该Pod的endpoint => kubelet筛别Terminating状态的pod,执行pod的preStop钩子 => 如果执行preStop超时(grace period) ,kubelet发送SIGTERM并等待2秒 => ...

具体可以使用strace -p pid去跟踪服务调用情况。

使用场景

  • 你的请求已经到达了当前Pod,硬终止会导致请求失败,我们希望已经到达了当前Pod的请求处理完成再将其停止掉,尽可能避免请求失败;
  • Pod已经本身已经注册到了服务中心,我们希望在Pod停止之前,主动向服务注册中心通知下线;

扩展

与preStop相对应,有一个postStart的概念,在容器创建成功后执行,可用于初始化资源,准备环境等;

posted @ 2022-04-22 13:54  carlrxu  阅读(1813)  评论(0)    收藏  举报