Pod 生命周期

一个 Pod 从创建到销毁会经历若干阶段:先由各组件协作完成调度与创建,随后可能运行 init 容器完成初始化,主容器启动后由探针持续做健康检查,其状态通过 phase 对外呈现,最终经历优雅终止流程被删除。下面依次展开。
Pod 的创建过程
- 用户通过
kubectl客户端提交Pod Spec给API Server。 API Server尝试将Pod对象的相关信息存储到etcd中,等待写入操作完成后,API Server返回确认信息到客户端。API Server开始反映etcd中的状态变化。- 所有
Kubernetes组件通过watch机制跟踪检查API Server上的相关信息变动。 kube-scheduler(调度器)通过其watcher检测到API Server创建了新的Pod对象,但还没有绑定到任何工作节点。kube-scheduler为Pod对象挑选一个工作节点,并将结果信息更新到API Server。- 调度结果由
API Server更新到etcd,并且API Server也开始反馈该Pod对象的调度结果。 Pod被调度到的目标工作节点上的kubelet尝试在当前节点上调用docker engine启动容器,并将容器的状态结果返回给API Server。API Server将Pod信息存储到etcd系统中。etcd确认写入操作完成后,API Server将确认信息发送给相关的kubelet。
init 容器
init 容器与普通的容器非常像,除了:
init容器总是运行到成功完成为止;- 每个
init容器都必须在下一个init容器启动前完成。
tips:实际上最先生成的是 pause 容器。
如果 Pod 的 init 容器失败,kubernetes 会不断地重启该 Pod,直到 init 容器成功为止。如果 Pod 对应的 restartPolicy 为 Never,则 Pod 启动失败。
init 容器的优势
init 容器具有与应用程序容器分离的单独镜像,因此带来如下优势:
- 它们可以包含并运行实用工具,而出于安全考虑,这些工具并不适合放进应用程序容器镜像中。
- 它们可以包含用于安装的工具和定制化代码,而这些不必出现在应用程序镜像中。例如,创建镜像没必要
FROM另一个镜像,只需要在安装过程中使用类似sed、awk、python或dig这样的工具。 - 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
init容器使用Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问Secret的权限,而应用程序容器则不能。- 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以
init容器能够提供一种简单的阻塞或延迟应用容器启动的方法,直到满足一组先决条件。
init 容器示例
init-example.yaml:
1 | apiVersion: v1 |
应用后,由于 init 容器依赖的 myservice、mydb 尚未创建,Pod 会停留在 Init 状态:
1 | [root@k8s01 ~]# kubectl apply -f init-example.yaml |
创建 init 容器所等待的 Service:service-init-example.yaml:
1 | kind: Service |
1 | kubectl apply -f service-init-example.yaml |
此时 init 容器依次成功,Pod 最终进入 Running:
1 | [root@k8s01 ~]# kubectl get pods # 第一个 init 容器启动 |
通过 kubectl describe pod myapp-pod 可以看到两个 init 容器先后 Terminated / Completed,主容器随后 Running,Events 也清晰反映了这一先后顺序:
1 | [root@k8s01 ~]# kubectl describe pod myapp-pod |
容器探针
探针是由 kubelet 对容器执行的定期诊断。Kubernetes 提供三种探针:
livenessProbe(存活探针):指示容器是否正在运行。如果存活探测失败,kubelet会杀死容器,容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为Success。readinessProbe(就绪探针):指示容器是否准备好服务请求。如果就绪探测失败,端点控制器会将该Pod的IP从与之匹配的所有Service的endpoint中删除。初始延迟之前的就绪状态默认为Failure;如果容器不提供就绪探针,则默认状态为Success。startupProbe(启动探针,v1.16 新增):用于判断容器是否已启动完成。startupProbe 通过后,前两个探针才会开始检测。如果启动探测失败,kubelet会杀死容器,容器将受到其重启策略的影响。如果容器不提供此探针,则默认状态为Success。
要执行诊断,kubelet 调用由容器实现的 Handler。k8s 内置了三种类型的处理程序:
ExecAction:在容器内执行命令。命令退出返回码为0则认为诊断成功。TCPSocketAction:对指定端口上的容器IP地址进行TCP检查。端口打开则诊断成功。HTTPGetAction:对指定端口和路径上的容器IP地址执行HTTP GET请求。响应码为200~400(不包含 400)则诊断成功。
探测结果有三种:
- 成功:容器通过了诊断;
- 失败:容器未通过诊断;
- 未知:诊断失败,因此不会采取任何行动。
就绪检测(readinessProbe)
readinessProbe-http-get.yaml:
1 | apiVersion: v1 |
由于 /index1.html 不存在,就绪探测返回 404 而失败;手动创建该文件后返回 200,探测成功,Pod 变为就绪:
1 | [root@k8s01 ~]# kubectl describe pod readiness-httpget-pod |
当存在 index1.html 时会返回 200,探测成功。
存活检测(livenessProbe)
存活检测支持 exec、httpGet、tcpSocket 三种方式,下面分别举例。
exec 方式
livenessProbe-exec.yaml:
1 | apiVersion: v1 |
容器启动 60 秒后删除 /tmp/live,此后存活探测持续失败,Pod 周期性重启:
1 | [root@k8s01 ~]# kubectl get pod ## liveness-exec-pod 会周期性 restart |
httpGet 方式
livenessProbe-httpget.yaml:
1 | apiVersion: v1 |
将 /index.html 改名后,存活探测失败,容器会 restart:
1 | [root@k8s01 ~]# kubectl exec liveness-httpget-pod -it -- /bin/bash |
tcpSocket 方式
livenessProbe-tcp.yaml(探测监听中的 80 端口):
1 | apiVersion: v1 |
livenessProbe-tcp-81.yaml(探测未监听的 81 端口):
1 | apiVersion: v1 |
80 端口可以探测到,81 端口因无人监听导致连接被拒绝,容器一直重启:
1 | [root@k8s01 ~]# kubectl get pod # tcp 80 端口可以检测到,81 端口一直重启 |
查看日志可见 Liveness probe failed: dial tcp ...:81: connect: connection refused:
1 | # 节选 |
存活 + 就绪 + 启动检测(startupProbe,v1.16+)
liveness-readiness.yaml 同时配置三种探针。startupProbe 最先执行,连续失败达到 failureThreshold 次才会重启;通过之后才开始 livenessProbe 与 readinessProbe:
1 | apiVersion: v1 |
一旦容器通过了 startupProbe,kubelet 便按各探针各自的 periodSeconds 间隔,分别进行存活检测(livenessProbe)和就绪检测(readinessProbe)。
重启策略
PodSpec 中的 restartPolicy 字段适用于 Pod 中的所有容器,取值为 Always、OnFailure、Never,没有此字段时默认为 Always。
restartPolicy 仅指由同一节点上的 kubelet 重新启动容器。
Pod phase
Pod 的 status 字段是一个 PodStatus 对象,其中的 phase(相位)字段是对 Pod 在其生命周期中所处状态的简单宏观概述。phase 仅有以下取值:
Pending:Pod已被Kubernetes系统接受,但有一个或多个容器镜像尚未创建。Running:该Pod已绑定到一个节点,Pod中所有容器都已被创建,且至少有一个容器正在运行,或者正处于启动或重启状态。Succeeded:Pod中所有容器均已成功终止(退出码为 0),并且不会再被重启。Failed:Pod中所有容器都已终止,但至少有一个容器以非 0 状态退出或被系统终止。Unknown:因为某些原因无法取得Pod的状态,通常是与Pod所在主机通信失败。
常见场景分析
下面列举在不同 restartPolicy 下,几类典型场景对 Pod phase 的影响。
单容器 Pod,容器成功退出
- 记录完成事件;
restartPolicy的影响:Always:重启容器,Pod phase 仍为Running;OnFailure:Pod phase 变为Succeeded;Never:Pod phase 变为Succeeded。
单容器 Pod,容器退出失败
- 记录失败事件;
restartPolicy的影响:Always:重启容器,Pod phase 仍为Running;OnFailure:Pod phase 仍为Running;Never:Pod phase 变为Failed。
双容器 Pod,其中一个容器退出失败
- 记录失败事件;
restartPolicy的影响:Always:重启容器,Pod phase 仍为Running;OnFailure:Pod phase 仍为Running;Never:Pod phase 仍为Running。
- 若在此基础上另一个容器也退出,导致没有容器处于运行状态:
Always:重启容器,Pod phase 仍为Running;OnFailure:Pod phase 仍为Running;Never:Pod phase 变为Failed。
单容器 Pod,容器运行时内存超限(OOM)
- 容器以失败状态终止;
- 记录
OOM事件; restartPolicy的影响:Always:重启容器,Pod phase 仍为Running;OnFailure:Pod phase 仍为Running;Never:Pod phase 变为Failed。
Pod 正在运行,磁盘故障
- 杀掉所有容器,记录适当事件;
- Pod phase 变为
Failed; - 如果使用控制器来运行,Pod 将在别处重建。
Pod 正在运行,其节点被分区(partition)
- 节点控制器等待直到超时;
- 节点控制器将 Pod phase 设置为
Failed; - 如果使用控制器来运行,Pod 将在别处重建。
Pod hook(生命周期钩子)
Pod hook(钩子)由 kubelet 发起,在容器进程启动前或容器进程终止前运行,包含在容器的生命周期之中,可以为 Pod 中所有容器都配置 hook。
支持的 hook 类型:
postStart:容器创建后立即执行。注意由于是异步执行,它无法保证一定在ENTRYPOINT之前运行。如果执行失败,容器会被杀死,并根据restartPolicy决定是否重启。preStop:容器终止前执行,常用于资源清理与优雅退出(例如执行nginx -s quit)。如果执行失败,容器同样会被杀死。
钩子的回调函数支持两种方式:
exec:执行一段命令;HTTPGet:发送HTTP请求。
启动、退出动作示例
start_stop.yaml:
1 | apiVersion: v1 |
进入容器可以看到 postStart 已成功写入文件:
1 | [root@k8s01 ~]# kubectl exec lifecycle-demo -it -- /bin/bash |
Pod 的终止过程
- 用户发送删除
Pod的命令,默认宽限期是30秒; - 在超过宽限期后,
API Server会更新Pod的状态为dead; - 命令行上显示的
Pod状态变为Terminating,同时svc会从endpoint中移除该 Pod; - 与第 3 步同时,当
kubelet发现Pod被标记为Terminating状态时,开始停止Pod进程:- 如果在
Pod中定义了preStop hook,会在停止Pod前调用它;如果宽限期过后preStop hook仍在运行,则会再增加 2 秒宽限期; - 向
Pod中的进程发送TERM信号;
- 如果在
- 该
Pod从对应Service的endpoint列表中删除,不再是RS的一部分;关闭较慢的Pod将继续处理已由load balancer转发来的流量; - 过了宽限期后,向
Pod中依然运行的进程发送SIGKILL信号将其杀掉; kubelet在API Server中完成Pod的删除(将优雅周期设置为 0,即立即删除)。Pod在API中消失,客户端也不再可见。
宽限期由如下字段控制:
1 | spec: |
kubectl delete 命令支持 --grace-period=<seconds> 选项,允许用户设置自己的宽限期,也可以使用 --force --grace-period=0 来强制删除 Pod。
Pod 的强制删除是通过在 cluster 和 etcd 中将其标记为删除状态实现的。执行强制删除命令时,API Server 不会等待该 Pod 所在节点上的 kubelet 确认,就立即将该 Pod 从 API Server 中移除。这时节点上的 Pod 会被立即设置为 Terminating 状态,不过在被真正强制删除之前依然有一小段优雅删除周期。
1 | [root@k8s01 ~]# kubectl delete pod redis-master-0 --force --grace-period=0 |
如果删除一个 Pod 后再次查看发现它还在,这是因为在控制器中定义了 replicas,还需要删除对应的控制器才行。