Pod作为k8s的计算单元,当我们在部署新版本或者重启Pod时,k8s给我们提供了优雅退出Pod的机制,让我们有机会做一些事情,比如:

  • 获得更好的用户体验;
  • 保存数据,保护数据的完整性;
  • 完成其它善尾工作,保护相关状态。

k8s如何停止Pod

我们要清楚k8s在停止Pod时的流程。

首先在Pod模板里,有一个属性terminationGracePeriodSeconds,默认值是30,它代表Pod有30秒的宽限期,过了这个时间Pod便被当做死亡

示例流程

下面是一个示例流程:

  1. 用户发送命令删除Pod,默认terminationGracePeriodSeconds(宽限期)为30s;
  2. Pod会根据宽限期进行更新,同时被标记为Terminating
    • 通过命令行查询,可以看到PodTerminating状态;
    • Service会将Pod从它的Endpoints中移除,同时Replication Controller也会认为Pod不是运行状态。负载均衡也不会再将流量分配到该Pod中;
    • Pod处于Terminating状态时,Kubelet会执行Pod关闭流程:
      1. 如果Pod定义了preStop的勾子,便在Pod中执行它。
      2. 当宽限期结束后preStop还在执行时,会给Pod内的进程发送SIGTERM信号(相当于kill pid),增加等待时间是2秒(扩展宽限期)。
  3. 当宽限期结束后,任何还在Pod中运行的进程都会被SIGKILL杀死。
  4. Kubelet将通过设置宽限期为0(立即删除)来完成删除 API 服务器上的 Pod。Pod 从 API 中消失,从客户端也不再可见。

停止流程的重点

Pod的停止流程中,我们可以注意几个重点:

  1. terminationGracePeriodSeconds(宽限期):
    这个在上面提到过了,它代表着整个Pod停止流程的宽限期。
  2. preStop勾子:
    如果我们定义了preStop勾子,那在关闭Pod时,会先执行它。
  3. SIGTERM信号:
    如果没有定义preStop勾子,那在关闭Pod时,会直接发送SIGTERM信号。或者是有定义preStop,然而在宽限期结束了,preStop依旧没执行完,那么也会发送SIGTERM信号,并扩展2秒钟的宽限期。
  4. SIGKILL信号:
    当时间超过了宽限期,Pod中的所有进程都会被SIGKILL杀死。
  5. 如何强制删除Pod
    通过kubectl delete命令的--grace-period参数,可以覆盖掉宽限期,设置为「0」时会强制删除Pod。如果kubectl版本大于等于1.5,还需要增加一个--force参数。

我们要如何优雅地停止Pod

上面说了那么多,其实根据k8s提供的机制,我们只能做两件事情:

  1. 定义preStop操作;
  2. 监听SIGTERM信号。

对于preStop而言,我们可以提供两种 Hook: ExecHttpGet,配置位于Pod.spec.containers[].lifecycle.preStop中,可以为每一个container都做配置。
而监听SIGTERM信号,需要写在程序代码里面,当程序接收到信号后,做退出的操作。

两个注意(坑)

preStop接口Path

preStop如果使用的是httpGet的形式,要注意接口的路径问题,根据k8s目前代码

1
2
url := fmt.Sprintf("http://%s/%s", net.JoinHostPort(host, strconv.Itoa(port)), handler.HTTPGet.Path)
resp, err := hr.httpGetter.Get(url)

路径的拼接可能会出现问题,比如我们填写 path/shutdown时,接收到的请求将会是:{your.ip}//shutdown,有两个/,所以你的应用很可能会没有做对相应的响应。

SIGTERM信号的接收

SIGTERM信号的接收需要注意,在Pod中只有pid为1的进程会接收到信号,其他进程是接收不到的。

参考