深入解析Kubernetes Pod生命周期(含示例代码)
Kubernetes 是一个强大的容器编排平台,其核心功能之一是管理 Pod 生命周期。本文将详细介绍 Pod 的生命周期管理机制,包括 initContainer、探针、钩子以及调度流程,帮助您深入理解 Kubernetes 的运行原理。
提示:文章较长建议收藏后仔细阅读。
Pod 生命周期
关键概念
• initC:全称 init Container,用于在主容器启动之前运行一次或多次,常用于准备工作。
• MainC:全称 Main Container,主要执行应用程序逻辑的容器。
• postStart:启动后钩子,在容器启动后立即执行的操作,常用于初始化任务。
• preStop:停止前钩子,在容器停止前执行的操作,常用于清理任务。
• 容器探测(探针):
• 启动探测:用于在容器启动时执行一次的探测,确保容器启动成功。
• 就绪探测:用于在容器启动后定期执行的探测,确保容器处于就绪状态。
• 存活探测:用于在容器运行期间定期执行的探测,确保容器持续处于健康状态。
initC介绍
Init 容器的概念
• 定义:Init 容器是一种特殊的容器,用于在 Pod 的应用容器启动之前运行。它们可以包含一些初始化任务或脚本,这些任务或脚本不需要在应用容器中存在。
• 特性:
• Init 容器总是运行到成功完成为止。
• 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。
• Init 容器与应用容器可以使用不同的镜像,因此可以在 Init 容器中放置一些危险的工具进行使用。
• Init 容器之间是线性启动的,可以做一些延迟性的操作。
• 如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的
restartPolicy
为Never
,它不会重新启动。
Init 容器的使用场景
• 初始化任务:可以在 Init 容器中运行一些初始化任务,如配置文件的生成、依赖项的安装等。
• 延迟启动:由于 Init 容器之间是线性启动的,可以利用这一特性进行一些需要延迟的操作,如等待某个服务启动完成等。
Init 容器与应用容器的区别
特性 | Init 容器 | 应用容器 |
运行时机 | 主容器启动之前 | Pod 启动后 |
探针支持 | 不支持 readinessProbe | 支持 |
失败处理 | Pod 重启 | 视重启策略而定 |
使用样例
这里我们用到busybox
容器,busybox
是一个非常小巧的 Linux 发行版,包含了一组常用的 Unix 工具。它常用于 Docker 镜像中,因为其体积小、启动快,适合用于测试、调试和简单的任务执行。
成功的 init 容器示例
配置文件
apiVersion: v1
kind: Pod
metadata:
name: init-container-success-example
spec:
containers:
- name: main-app
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'echo The main app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'echo Init container is running! && sleep 5']
这个示例展示了一个成功的 init 容器配置。init-myservice
容器运行一个简单的命令,打印 "Init container is running!" 并休眠 5 秒钟。由于这个命令能够成功完成,init 容器也会成功完成。之后,主应用容器 main-app
将启动并运行。
失败的 init 容器示例
配置文件
apiVersion: v1
kind: Pod
metadata:
name: init-container-failure-example
spec:
containers:
- name: main-app
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'echo The main app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'exit 1']
这个示例展示了一个失败的 init 容器配置。init-myservice
容器运行一个简单的命令 exit 1
,这会导致容器立即退出并返回非零状态码。由于 init 容器必须成功完成才能继续启动主应用容器,因此 Kubernetes 会不断地重启这个 Pod,直到 init 容器成功完成。然而,由于 exit 1
命令始终会失败,这个 Pod 会一直处于重启状态。
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的
restartPolicy
为Never
,它不会重新启动。
示例启动方法
• 将配置文件内容保存至
init-container-failure-example.yaml
• 启动运行
kubectl apply -f init-container-failure-example.yaml
• 查看运行状态
kubectl get pods
,再次步骤会发现失败的 initC 示例一直再重启。• 删除pod:
kubectl delete pod init-container-success-example
通过这两个示例,可以看到 init 容器在 Kubernetes 中的工作方式,以及如何处理成功和失败的情况。如果需要更多详细信息或进一步的解释,请告诉我。
容器探测(探针)
探针类型介绍
• startupProbe: 启动探针:开始检测吗?用于在容器启动过程中执行一次的探测,确保容器能够成功启动。如果启动探针失败,那么容器会被始终视为未就绪。
• livenessProbe:存活探针:还活着吗?用于检测容器是否仍然在运行。如果存活探针失败,kubelet 会杀死容器,并根据其重启策略决定是否重启容器。
• readinessProbe:就绪探针:准备提供服务了吗?用于检测容器是否已经准备好开始运行。如果就绪探针失败,容器将从服务负载均衡器中移除。
探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。有三种类型的处理程序:
• ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
• TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
• HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
• 成功:容器通过了诊断。
• 失败:容器未通过诊断。
• 未知:诊断失败,因此不会采取任何行动。
就绪探针(readinessProbe)
介绍:k8s 通过添加就绪探针,解决尤其是在扩容时保证提供给用户的服务都是可用的。选项说明:
• initialDelaySeconds:容器启动后要等待多少秒后探针开始工作,单位“秒”,默认是 0 秒,最小值是 0。
• periodSeconds:执行探测的时间间隔(单位是秒),默认为 10s,单位“秒”,最小值是 1。
• timeoutSeconds:探针执行检测请求后,等待响应的超时时间,默认为 1s,单位“秒”,最小值是 1。
• successThreshold:探针检测失败后认为成功的最小连接成功次数,默认值为 1。必须为 1 才能激活和启动。最小值为 1。
• failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,默认值为 3,最小值是 1。
成功示例
apiVersion: v1
kind: Pod
metadata:
name: readiness-probe-success
spec:
containers:
- name: nginx
image: nginx:1.21.6
readinessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
在这个示例中,就绪探针检查 TCP 端口 80 的连接,等待 5 秒后开始探测,每 5 秒探测一次,如果 3 次失败则视为未就绪。假设端口 80 正常开放,探针将会成功,容器被认为是就绪的,可以接受流量。
失败示例
apiVersion: v1
kind: Pod
metadata:
name: readiness-probe-failure
spec:
containers:
- name: nginx
image: nginx:1.21.6
readinessProbe:
tcpSocket:
port: 9999
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
在这个示例中,就绪探针检查 TCP 端口 9999 的连接,等待 5 秒后开始探测,每 5 秒探测一次,如果 3 次失败则视为未就绪。由于端口 9999 并未开放,探针将会失败,容器将不会接收流量。
存活探针(livenessProbe)
介绍:k8s 通过添加存活探针,解决虽然活着但是已经死了的问题。选项说明:
• initialDelaySeconds:容器启动后要等待多少秒后探针开始工作,单位“秒”,默认是 0 秒,最小值是 0。
• periodSeconds:执行探测的时间间隔(单位是秒),默认为 10s,单位“秒”,最小值是 1。
• timeoutSeconds:探针执行检测请求后,等待响应的超时时间,默认为 1s,单位“秒”,最小值是 1。
• successThreshold:探针检测失败后认为成功的最小连接成功次数,默认值为 1。必须为 1 才能激活和启动。最小值为 1。
• failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,默认值为 3,最小值是 1。
成功示例
apiVersion: v1
kind: Pod
metadata:
name: liveness-probe-success
spec:
containers:
- name: nginx
image: nginx:1.21.6
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
在这个示例中,存活探针执行 cat /tmp/healthy
命令,等待 5 秒后开始探测,每 5 秒探测一次,如果 3 次失败则视为存活失败。假设 /tmp/healthy
文件存在且内容正确,探针将会成功,容器继续运行。
失败示例
apiVersion: v1
kind: Pod
metadata:
name: liveness-probe-failure
spec:
containers:
- name: nginx
image: nginx:1.21.6
livenessProbe:
exec:
command:
- cat
- /tmp/non-existent-file
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
在这个示例中,存活探针执行 cat /tmp/non-existent-file
命令,等待 5 秒后开始探测,每 5 秒探测一次,如果 3 次失败则视为存活失败。由于文件不存在,探针将会失败,kubelet 会杀死容器并根据其重启策略决定是否重启容器。
启动探针(startupProbe)
介绍:k8s 在 1.16 版本后增加 startupProbe 探针,主要解决在复杂的程序中 readinessProbe、livenessProbe 探针无法更好的判断程序是否启动、是否存活。选项说明:
• initialDelaySeconds:容器启动后要等待多少秒后探针开始工作,单位“秒”,默认是 0 秒,最小值是 0。
• periodSeconds:执行探测的时间间隔(单位是秒),默认为 10s,单位“秒”,最小值是 1。
• timeoutSeconds:探针执行检测请求后,等待响应的超时时间,默认为 1s,单位“秒”,最小值是 1。
• successThreshold:探针检测失败后认为成功的最小连接成功次数,默认值为 1。必须为 1 才能激活和启动。最小值为 1。
• failureThreshold:探测失败的重试次数,重试一定次数后将认为失败,默认值为 3,最小值是 1。
成功示例
apiVersion: v1
kind: Pod
metadata:
name: startup-probe-success
spec:
containers:
- name: nginx
image: nginx:1.21.6
startupProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 10
在这个示例中,启动探针检查 /
路径上的 HTTP GET 请求,端口为 80,等待 5 秒后开始探测,每 5 秒探测一次,如果 10 次失败则视为启动失败。如果探针成功,容器将继续运行。
失败示例
apiVersion: v1
kind: Pod
metadata:
name: startup-probe-failure
spec:
containers:
- name: nginx
image: nginx:1.21.6
startupProbe:
httpGet:
path: /invalid-path
port: 80
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
在这个示例中,启动探针检查 /invalid-path
路径上的 HTTP GET 请求,端口为 80,等待 5 秒后开始探测,每 5 秒探测一次,如果 3 次失败则视为启动失败。由于路径错误,探针将会失败,导致容器被认为启动失败,kubelet 会不断重启容器直到探针成功,如果超过failureThreshold
设置的次数将失败。
Pod Hook(钩子)
Pod hook(钩子)是由 Kubernetes 管理的 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。可以同时为 Pod 中的所有容器都配置 hook。
Hook 的类型包括两种:
• postStart:在容器启动后立即执行,可用于初始化任务。
• preStop:在容器停止前执行,可用于执行清理操作。
执行的方法分为:
• exec:执行一段命令。
• HTTP:发送 HTTP 请求。
在 Kubernetes 中,理想的状态是 Pod 优雅释放,但是并不是每一个 Pod 都会这么顺利:
• Pod 卡死,处理不了优雅退出的命令或者操作。
• 优雅退出的逻辑有 BUG,陷入死循环。
• 代码问题,导致执行的命令没有效果。
Kubernetes 的 Pod 终止流程中有一个称为**优雅终止时间(grace period)**的设定,定义在 pod.spec.terminationGracePeriodSeconds 字段,默认值为 30 秒。当我们执行 kubectl delete 命令时,可以通过 --grace-period 参数显式指定这个优雅终止时间,以覆盖 Pod 的默认配置。
如果在设定的优雅终止时间内 Pod 仍未成功终止,Kubernetes 将被迫强制杀死(kill)该 Pod。需要注意的是,preStop Hook 和 SIGTERM 信号的触发与优雅终止时间是并行发生的,Kubernetes 不会等待 preStop Hook 的完成。如果你的应用程序在 terminationGracePeriod 结束之前成功关闭并退出,Kubernetes 将立即进入下一步操作。
PostStart 和 PreStop 钩子
• PostStart:在容器启动后立即执行的操作,常用于初始化任务。
• 示例:
apiVersion: v1
kind: Pod
metadata:
name: poststart-example
spec:
containers:
- name: my-container
image: my-image
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler"]• PreStop:在容器停止前执行的操作,常用于清理任务。
• 示例:
apiVersion: v1
kind: Pod
metadata:
name: prestop-example
spec:
containers:
- name: my-container
image: my-image
lifecycle:
preStransform: translateY(
exec:
command: ["/bin/sh", "-c", "echo Goodbye from the preStop handler"]
PreStop
钩子的作用是在容器终止前执行一些清理任务。它在以下情况下生效:
1. 正常终止容器:当使用
kubectl delete pod
命令删除 Pod 时,Kubernetes 会发送 SIGTERM 信号给容器进程,触发PreStop
钩子。2. 更新或缩减 Pod:在 Kubernetes 进行滚动更新或缩减副本数量时,也会触发
PreStop
钩子。3. 容器重启策略:如果容器的重启策略是
Always
或OnFailure
,在容器被重启前也会执行PreStop
钩子。
但是,如果你使用 kill -9
(SIGKILL) 信号来直接终止容器进程,PreStop
钩子将不会被执行,因为 SIGKILL 信号是不可捕获和无法忽略的。
pod的调度运行流程
pod 的生命周期我们知道了,这里再简单补充一下 pod 的调度流程
K8S Master 组件
• ApiServer: 接收用户请求并存储到 ETCD。
• ETCD: 保存整个集群的状态。
• Controller Manager: 监视 ETCD 并确保 Pod 的期望状态。
• Scheduler: 为未分配的 Pod 选择合适的节点。
K8S Node 组件
• Kubelet: 在节点上创建并管理容器。
• Container Runtime: 实际创建和运行容器。
• Kube-Proxy: 管理网络规则,确保服务间通信。
流程总结
• 用户通过 ApiServer 提交 Pod 创建请求。
• ApiServer 验证请求并存储到 ETCD。
• Controller 监视 ETCD,确保 Pod 的期望状态。
• Scheduler 选择合适的节点来运行 Pod。
• Kubelet 在选定的节点上创建并管理容器。
总结
在 Pod 生命周期中,initContainers、startupProbe、livenessProbe、readinessProbe 和 Lifecycle hooks 都可以使用,你可以根据需求选择全部、部分或完全不使用这些机制。
欢迎关注我的公众号“编程与架构”,原创技术文章第一时间推送。