概述
Pod 销毁时,会停止容器内的进程,通常在停止的过程中我们需要执行一些善后逻辑,比如等待存量请求处理完以避免连接中断,或通知相关依赖进行清理等,从而实现优雅终止目的。本文介绍在 Kubernetes 场景下,实现容器优雅终止的最佳实践。
当 Kubernetes 杀死一个 pod 时,会发生以下 5 个步骤:
- Pod 切换到终止状态并停止接收任何新流量,容器仍在 pod 内运行。
- preStop 钩子是一个特殊的命令或 HTTP 请求被执行,并被发送到 pod 内的容器。
- SIGTERM 信号被发送到 pod,容器意识到它将很快关闭。
- Kubernetes 等待宽限期 (terminationGracePeriodSeconds)。此等待与 preStop hook 和 SIGTERM 信号执行并行(默认 30 秒)。因此,Kubernetes 不会等待这些完成。如果这段时间结束,则直接进入下一步。正确设置宽限期的值非常重要。
- 向 pod 发送 SIGKILL 信号,然后移除 pod。如果容器在宽限期后仍在运行,则 Pod 被 SIGKILL 强行移除,终止完成。
总结下大致分为两步,第一步定义 preStop,一般情况下可以休眠 30s,用于处理残余流量;第二步发送 SIGTERM 信号,服务收到信号后进行服务的收尾工作处理。比如:关闭连接、通知第三方注册中心服务关闭…..
Pods 生命周期的状态
phase表示一个Pod处于其生命周期的哪个阶段,一共有以下5个可能的取值:
- Pending:Pod已经被k8s系统接受,但Pod中还有容器没有被创建。Pod被调度前和下载容器镜像的时候都处于这个阶段
- Running:Pod已经被调度到Node上,所有的容器都已经被创建,并且至少有一个容器还在运行中(正在启动或重启中的容器也算)
- Succeeded:Pod中的所有容器都成功停止,并且不会再次重启
- Failed:Pod中的所有容器都已经停止,并且至少有一个容器是以失败停止的(以非0状态退出或被系统强制停止)
- Unknown:由于某种原因无法获得Pod的状态,一般是和Pod所在的Host出现通信问题导致
Pod phase的查看方式:
kubectl get pods whoami-78c854646d-nhgl9 -o yaml |grep 'phase:'
输出:
phase: Running
k8s lifecycle 用法
lifecycle 周期有两个hook钩子 postStart 与 preStop
- PostStart hook是在容器创建(created)之后立马被调用,并且PostStart跟容器的ENTRYPOINT是异步执行的,无法保证它们之间的顺序.
- PreStop hook是容器处于Terminated状态时立马被调用(也就是说要是Job任务的话,执行完之后其状态为completed,所以不会触发PreStop的钩子),同时PreStop是同步阻塞的,PreStop执行完才会执行删除Pod的操作
注意:
PostStart 会阻塞容器成为Running状态,PreStop 会阻塞容器的删除,但是过了 terminationGracePeriodSeconds时间后,容器会被强制删除,
如果PreStop或者PostStart失败的话, 容器会被杀死;
钩子的回调函数支持三种方式定义动作:
- exec:在容器内执行命令,如果命令的退出状态码是 0 表示执行成功,否则表示失败
lifecycle:
postStart:
exec:
command:
- cat
- /tmp/healthy
- httpGet:向指定 URL 发起 GET 请求,如果返回的 HTTP 状态码在 [200, 400) 之间表示请求成功,否则表示失败
lifecycle:
postStart:
httpGet:
path: /login # URI地址
port: 80 # 端口号
host: 192.168.126.100 # 主机地址
scheme: HTTP # 支持的协议,http或https
# http://192.168.126.100:80/login
- TCPSocket:在容器尝试访问指定的socket
lifecycle:
postStart:
tcpSocket:
port: 8080
Example:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
lifecycle:
postStart:
exec:
command: ["/bin/sh","-c","echo 11 >> /usr/share/nginx/html/index.html"] # 启动容器应用之后执行
preStop:
exec:
command: ["/bin/sh","-c","echo 'Hello from the preStop handler' >> /var/log/nginx/message"] ## 删除pod 完成之前执行
status: {}