在这之前先简单介绍一下SIGTERM和SIGKILL这两个信号。
• SIGKILL:立刻结束程序。该信号不能被阻塞、处理和忽略,不能在程序中被获取到。
• SIGTERM:程序结束(Terminate)信号,又叫请求退出信号,与SIGKILL不同的是该信号可以被阻塞和处理,我们可以通过在程序中注册该信号来实现服务的优雅停止。使用kill命令缺省会发出这个信号。
按照惯例,SIGKILL 是硬终止的信号,而 SIGTERM 是通知进程优雅退出的信号,因此很多微服务框架会监听 SIGTERM 信号,收到之后去做反注册等清理操作,实现优雅退出。
回到 Kubernetes(下称 K8s),当我们想干掉一个 Pod 的时候,理想状况当然是 K8s 从对应的 Service(假如有的话)把这个 Pod 摘掉,同时给 Pod 发 SIGTERM 信号让 Pod 中的各个容器优雅退出就行了。但实际上 Pod 有可能犯各种幺蛾子:
• 已经卡死了,处理不了优雅退出的代码逻辑或需要很久才能处理完成。
• 优雅退出的逻辑有 BUG,自己死循环了。
• 代码写得野,根本不理会 SIGTERM。
因此,K8s 的 Pod 终止流程中还有一个“最多可以容忍的时间”,即 grace period(在 Pod 的 .spec.terminationGracePeriodSeconds 字段中定义),这个值默认是 30 秒,我们在执行 kubectl delete 的时候也可通过 —grace-period 参数显式指定一个优雅退出时间来覆盖 Pod 中的配置。而当 grace period 超出之后,K8s 就只能选择 SIGKILL 强制干掉 Pod 了。
很多场景下,除了把 Pod 从 K8s 的 Service 上摘下来以及进程内部的优雅退出之外,我们还必须做一些额外的事情,比如说从 K8s 外部的服务注册中心上反注册。这时就要用到 PreStop Hook 了,K8s 目前提供了 Exec 、 HTTP 两种 PreStop Hook,实际用的时候,需要通过 Pod 的 .spec.containers[].lifecycle.preStop 字段为 Pod 中的每个容器单独配置,比如:
spec:
contaienrs:
- name: my-test
lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","/pre-stop.sh"]
/pre-stop.sh 脚本里就可以写我们自己的清理逻辑。
比如从consul中下线:
curl -X POST —data DOWN http://127.0.0.1:8080/service-registry/instance-status -H
“Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8”;sleep 30
其中:
• 127.0.0.1:8080代表consul地址
• service-registry代表注册中心
• DOWN代表下线操作
最后我们串起来再整个表述一下 Pod 退出的流程:
- 用户发送命令删除 Pod,使用的是默认的宽限期(30秒)
- API 服务器中的 Pod 会随着宽限期规定的时间进行更新,过了这个时间 Pod 就会被认为已 “死亡”。
当使用客户端命令查询 Pod 状态时,Pod 显示为 “Terminating”。
(和第 3 步同步进行)当 Kubelet 看到 Pod 由于步骤 2 中设置的时间而被标记为 terminating 状态时,它就开始执行关闭 Pod 流程。
- 如果 Pod 定义了 preStop 钩子,就在 Pod 内部调用它。如果宽限期结束了,但是 preStop 钩子还在运行,那么就用小的(2 秒)扩展宽限期调用步骤 2。
- 给 Pod 内的进程发送 TERM 信号。请注意,并不是所有 Pod 中的容器都会同时收到 TERM 信号,如果它们关闭的顺序很重要,则每个容器可能都需要一个 preStop 钩子。
- (和第 3 步同步进行)从服务的端点列表中删除 Pod,Pod 也不再被视为副本控制器的运行状态的 Pod 集的一部分。因为负载均衡器(如服务代理)会将其从轮换中删除,所以缓慢关闭的 Pod 无法继续为流量提供服务。
- 当宽限期到期时,仍在 Pod 中运行的所有进程都会被 SIGKILL 信号杀死。
- kubelet 将通过设置宽限期为 0 (立即删除)来完成在 API 服务器上删除 Pod 的操作。该 Pod 从 API 服务器中消失,并且在客户端中不再可见。