IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。在第 21 篇中我们编写了 Pod YAML部署了包含 Flask 和 Redis 的双容器 Pod体验了共享网络和localhost互访。但关于 Pod还有几个关键问题没有回答Pod 从创建到销毁经历了哪些阶段Pending 和 Running 之间发生了什么容器退出了Pod 为什么还在RESTARTS列的数字是谁在控制回想第 6 篇我们学容器生命周期时Docker 容器的状态流转相对简单——created → running → paused → exited → deleted。而 K8s 的 Pod 生命周期在此基础上增加了更多阶段并引入了重启策略来定义“容器退出后该怎么办”。今天这篇就是要把这些规则彻底搞清楚同时引出Init Container这个启动前置机制为第 24 篇的探针健康检查做好铺垫。一、Pod 生命周期的完整阶段1.1 生命周期全景图一个 Pod 从创建到销毁会经历以下阶段┌─────────────┐ │ Pending │ ← 已提交等待调度 拉取镜像 └──────┬──────┘ │ 调度成功 镜像拉取完成 ┌──────▼──────┐ │ Running │ ← 至少一个容器在运行 └──────┬──────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐ │ Succeeded │ │ Failed │ │ Unknown │ │ 正常退出码0 │ │ 非零退出码 │ │ 节点失联等 │ └──────────────┘ └──────────────┘ └──────────────┘PendingPod 已被 API Server 接受并写入 etcd但尚未在任何节点上运行。这个阶段包含 Scheduler 选择节点、kubelet 拉取镜像的过程。如果 Pod 长时间卡在 Pending通常是因为资源不足或镜像拉取失败。RunningPod 已被调度到节点所有容器创建完成至少有一个容器仍在运行未退出。SucceededPod 内所有容器都已正常终止退出码为 0且不会再重启。典型场景是一次性 Job。FailedPod 内至少有一个容器以非零退出码终止且不会再重启。UnknownPod 状态无法获取通常是因为 Pod 所在节点失联网络分区、节点宕机。1.2 动手观察Pod 的状态变化部署一个临时 Pod观察它的状态变化过程kubectl run lifecycle-demo--imagenginx:alpine--restartNever用-w持续观察kubectl get pod lifecycle-demo-w输出NAME READY STATUS RESTARTS AGE lifecycle-demo0/1 Pending00s lifecycle-demo0/1 ContainerCreating01s lifecycle-demo1/1 Running05sContainerCreating不是官方 Pod 阶段之一而是 kubectl 在 Pending 阶段中展示的过渡状态——kubelet 正在拉取镜像并创建容器。如果镜像较大或网络较慢这个过渡状态会持续更长时间。1.3 Pod 状态 vs 容器状态Pod 状态和容器状态是两个不同的概念这一点经常被混淆Pod Status由 kubelet 汇总所有容器状态后上报Pending / Running / Succeeded / Failed / UnknownContainer State单个容器的具体状态Waiting / Running / Terminated受重启策略影响可以通过kubectl describe pod查看每个容器的详细状态信息kubectl describe pod lifecycle-demo|grep-A10Containers:1.4 容器终止流程与 TerminationGracePeriodSeconds当 Pod 被删除时K8s 会按照以下流程优雅终止容器Pod 进入Terminating状态kubelet 向每个容器的主进程发送SIGTERM信号等待terminationGracePeriodSeconds秒默认 30 秒如果容器仍未退出kubelet 发送SIGKILL强制终止从 API Server 中删除 Pod 对象你可以在 Pod spec 中自定义这个宽限期spec: terminationGracePeriodSeconds:60这与 Docker 中docker stop -t 30的机制完全一致——先 SIGTERM 优雅退出超时后 SIGKILL 强制终止。二、重启策略容器退出后怎么办K8s 提供了三种重启策略用restartPolicy字段指定。这三种策略和 Docker 的--restart参数有对应关系但适用层级不同——Docker 的 restart 作用于单个容器而 K8s 的 restartPolicy 作用于 Pod 内的所有容器。2.1 Always默认值行为容器退出无论退出码是否为 0kubelet 都会重启它。重启间隔由指数退避算法控制首次重启立即执行之后间隔 10 秒、20 秒、40 秒……最长不超过 5 分钟重置间隔需要容器正常运行至少 10 分钟。适用场景长期运行的服务Web 服务器、数据库、缓存这是 Deployment、StatefulSet 等控制器创建 Pod 时强制使用的策略。2.2 OnFailure行为只有当容器以非零退出码退出时才执行重启。容器正常退出退出码 0不会重启。适用场景Job 或 CronJob批处理任务失败时需要重试成功则停止。2.3 Never行为容器退出后永不重启。适用场景一次性初始化任务如数据库迁移或用于调试的临时 Pod。2.4 策略对比2.5 动手验证对比三种策略Always默认# 创建一个 Pod容器执行后立即退出退出码 0kubectl run always-demo--imagealpine--restartAlways--command--sh-cecho done exit 0# 观察 RESTARTS 列kubectl get pod always-demo-w# NAME READY STATUS RESTARTS AGE# always-demo 0/1 CrashLoopBackOff 3 90sRESTARTS数值不断增加——即使容器正常退出Always 策略也会重启它。CrashLoopBackOff是 kubelet 的一种保护机制当容器在短时间内被反复重启kubelet 会逐渐延长重启间隔避免陷入无限重启循环消耗节点资源。OnFailure# 模拟失败退出码 1kubectl run onfailure-demo--imagealpine--restartOnFailure--command--sh-cexit 1kubectl get pod onfailure-demo-w# NAME READY STATUS RESTARTS AGE# onfailure-demo 0/1 CrashLoopBackOff 3 90s容器退出码非零OnFailure 策略触发重启。Never# 正常退出kubectl run never-demo--imagealpine--restartNever--command--sh-cecho donekubectl get pod never-demo# NAME READY STATUS RESTARTS AGE# never-demo 0/1 Completed 0 5sSTATUSCompletedRESTARTS0——容器退出后不再重启Pod 进入 Succeeded 阶段。Completed是 kubectl 对 Succeeded 阶段的展示名称。2.6 清理实验 Podkubectl delete pod lifecycle-demo always-demo onfailure-demo never-demo三、Init Container启动前置任务3.1 什么是 Init Container在某些场景下应用容器启动前需要先完成一些准备工作——等数据库就绪、下载配置文件、检查外部依赖。如果把重试逻辑写进应用代码会使应用变得复杂K8s 提供的解决方案是Init Container。Init Container 是 Pod 中一种特殊的容器它在应用容器启动之前执行并且必须成功完成退出码 0后kubelet 才会启动应用容器。如果 Init Container 失败kubelet 会根据 Pod 的重启策略决定是否重试。Init Container 与普通容器的关键区别3.2 实战等待 Redis 就绪的 Init Container以下 YAML 定义了一个 Init Container在应用启动前轮询 Redis 是否可用apiVersion: v1 kind: Pod metadata: name: flask-with-init spec: restartPolicy: Always initContainers: - name: wait-for-redis image: redis:alpine command: -sh--c-|echo等待 Redis 就绪...untilredis-cli-hredis-service-p6379ping;doechoRedis 尚未就绪2 秒后重试...sleep2doneechoRedis 已就绪containers: - name: flask-app image: flask-redis-counter:2.0 ports: - containerPort:5000env: - name: REDIS_HOST value: redis-service - name: redis image: redis:alpine ports: - containerPort:6379initContainers字段与containers同级。这里定义的wait-for-redisInit Container 会循环执行redis-cli ping直到成功退出码 0 后 kubelet 才启动 Flask 容器。实际的 Redis 依赖应在 K8s 中用 Service 暴露——此时我们用redis-service指代这个 Service 名称。3.3 执行流程追踪部署这个 Pod 后查看事件kubectl describe pod flask-with-init|grep-A5Events:输出会清晰展示 Init Container 的启动和执行过程。Init Container 常用于等待数据库、下载配置模板、设置文件权限等场景。如果应用本身已包含重试逻辑比如我们 Flask 应用中连接 Redis 的get_hit_count函数Init Container 并非必须但在需要确保某个外部依赖完全就绪再启动的场景中它是最简洁的 K8s 原生方案。四、对比 Docker Compose 的启动控制学到这里做一个贯穿系列的知识串联。第 15 篇我们学过 Compose 用depends_oncondition: service_healthy控制启动顺序。K8s 中实现相同效果的手段有三层Init Container确保某个外部依赖如数据库可连接再启动应用容器。对应 Compose 中depends_on的条件等待。Readiness Probe告知 Service 该 Pod 是否可接收流量。对应 Compose 中healthcheck的“可用性”判定第 24 篇详解。Liveness Probe检测容器是否处于死锁/假死状态触发重启。对应 Compose 的healthcheck配合restart策略第 24 篇详解。五、命令速查表六、本篇总结Pod 生命周期五阶段Pending → Running → Succeeded / Failed / Unknown每个阶段有明确含义。三种重启策略Always默认服务类 Pod 必须使用、OnFailure批处理重试、Never一次性任务控制容器退出后的行为。TerminationGracePeriodSeconds控制优雅终止的超时窗口默认 30 秒SIGTERM → 等待 → SIGKILL。Init Container在应用容器前执行的初始化任务按顺序串行执行全部成功后才启动普通容器。对应 Compose 的depends_on逻辑。CrashLoopBackOffkubelet 的保护机制容器反复重启触发指数退避。这一篇让我们真正理解了 Pod 从创建到终止的完整过程以及如何通过重启策略和 Init Container 控制容器行为。下一篇文章——第 23 篇多容器 Pod 与设计模式Sidecar 等我们将深入 Pod 的常见设计模式看看如何在实际项目中优雅地组织多容器 Pod。想了解更多还可以去各个平台搜索「IT策士」一起升级 IT 思维
第 22 篇 k8s 之 Pod: 生命周期与重启策略
IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章助你少走弯路。在第 21 篇中我们编写了 Pod YAML部署了包含 Flask 和 Redis 的双容器 Pod体验了共享网络和localhost互访。但关于 Pod还有几个关键问题没有回答Pod 从创建到销毁经历了哪些阶段Pending 和 Running 之间发生了什么容器退出了Pod 为什么还在RESTARTS列的数字是谁在控制回想第 6 篇我们学容器生命周期时Docker 容器的状态流转相对简单——created → running → paused → exited → deleted。而 K8s 的 Pod 生命周期在此基础上增加了更多阶段并引入了重启策略来定义“容器退出后该怎么办”。今天这篇就是要把这些规则彻底搞清楚同时引出Init Container这个启动前置机制为第 24 篇的探针健康检查做好铺垫。一、Pod 生命周期的完整阶段1.1 生命周期全景图一个 Pod 从创建到销毁会经历以下阶段┌─────────────┐ │ Pending │ ← 已提交等待调度 拉取镜像 └──────┬──────┘ │ 调度成功 镜像拉取完成 ┌──────▼──────┐ │ Running │ ← 至少一个容器在运行 └──────┬──────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌───────▼──────┐ ┌───────▼──────┐ ┌───────▼──────┐ │ Succeeded │ │ Failed │ │ Unknown │ │ 正常退出码0 │ │ 非零退出码 │ │ 节点失联等 │ └──────────────┘ └──────────────┘ └──────────────┘PendingPod 已被 API Server 接受并写入 etcd但尚未在任何节点上运行。这个阶段包含 Scheduler 选择节点、kubelet 拉取镜像的过程。如果 Pod 长时间卡在 Pending通常是因为资源不足或镜像拉取失败。RunningPod 已被调度到节点所有容器创建完成至少有一个容器仍在运行未退出。SucceededPod 内所有容器都已正常终止退出码为 0且不会再重启。典型场景是一次性 Job。FailedPod 内至少有一个容器以非零退出码终止且不会再重启。UnknownPod 状态无法获取通常是因为 Pod 所在节点失联网络分区、节点宕机。1.2 动手观察Pod 的状态变化部署一个临时 Pod观察它的状态变化过程kubectl run lifecycle-demo--imagenginx:alpine--restartNever用-w持续观察kubectl get pod lifecycle-demo-w输出NAME READY STATUS RESTARTS AGE lifecycle-demo0/1 Pending00s lifecycle-demo0/1 ContainerCreating01s lifecycle-demo1/1 Running05sContainerCreating不是官方 Pod 阶段之一而是 kubectl 在 Pending 阶段中展示的过渡状态——kubelet 正在拉取镜像并创建容器。如果镜像较大或网络较慢这个过渡状态会持续更长时间。1.3 Pod 状态 vs 容器状态Pod 状态和容器状态是两个不同的概念这一点经常被混淆Pod Status由 kubelet 汇总所有容器状态后上报Pending / Running / Succeeded / Failed / UnknownContainer State单个容器的具体状态Waiting / Running / Terminated受重启策略影响可以通过kubectl describe pod查看每个容器的详细状态信息kubectl describe pod lifecycle-demo|grep-A10Containers:1.4 容器终止流程与 TerminationGracePeriodSeconds当 Pod 被删除时K8s 会按照以下流程优雅终止容器Pod 进入Terminating状态kubelet 向每个容器的主进程发送SIGTERM信号等待terminationGracePeriodSeconds秒默认 30 秒如果容器仍未退出kubelet 发送SIGKILL强制终止从 API Server 中删除 Pod 对象你可以在 Pod spec 中自定义这个宽限期spec: terminationGracePeriodSeconds:60这与 Docker 中docker stop -t 30的机制完全一致——先 SIGTERM 优雅退出超时后 SIGKILL 强制终止。二、重启策略容器退出后怎么办K8s 提供了三种重启策略用restartPolicy字段指定。这三种策略和 Docker 的--restart参数有对应关系但适用层级不同——Docker 的 restart 作用于单个容器而 K8s 的 restartPolicy 作用于 Pod 内的所有容器。2.1 Always默认值行为容器退出无论退出码是否为 0kubelet 都会重启它。重启间隔由指数退避算法控制首次重启立即执行之后间隔 10 秒、20 秒、40 秒……最长不超过 5 分钟重置间隔需要容器正常运行至少 10 分钟。适用场景长期运行的服务Web 服务器、数据库、缓存这是 Deployment、StatefulSet 等控制器创建 Pod 时强制使用的策略。2.2 OnFailure行为只有当容器以非零退出码退出时才执行重启。容器正常退出退出码 0不会重启。适用场景Job 或 CronJob批处理任务失败时需要重试成功则停止。2.3 Never行为容器退出后永不重启。适用场景一次性初始化任务如数据库迁移或用于调试的临时 Pod。2.4 策略对比2.5 动手验证对比三种策略Always默认# 创建一个 Pod容器执行后立即退出退出码 0kubectl run always-demo--imagealpine--restartAlways--command--sh-cecho done exit 0# 观察 RESTARTS 列kubectl get pod always-demo-w# NAME READY STATUS RESTARTS AGE# always-demo 0/1 CrashLoopBackOff 3 90sRESTARTS数值不断增加——即使容器正常退出Always 策略也会重启它。CrashLoopBackOff是 kubelet 的一种保护机制当容器在短时间内被反复重启kubelet 会逐渐延长重启间隔避免陷入无限重启循环消耗节点资源。OnFailure# 模拟失败退出码 1kubectl run onfailure-demo--imagealpine--restartOnFailure--command--sh-cexit 1kubectl get pod onfailure-demo-w# NAME READY STATUS RESTARTS AGE# onfailure-demo 0/1 CrashLoopBackOff 3 90s容器退出码非零OnFailure 策略触发重启。Never# 正常退出kubectl run never-demo--imagealpine--restartNever--command--sh-cecho donekubectl get pod never-demo# NAME READY STATUS RESTARTS AGE# never-demo 0/1 Completed 0 5sSTATUSCompletedRESTARTS0——容器退出后不再重启Pod 进入 Succeeded 阶段。Completed是 kubectl 对 Succeeded 阶段的展示名称。2.6 清理实验 Podkubectl delete pod lifecycle-demo always-demo onfailure-demo never-demo三、Init Container启动前置任务3.1 什么是 Init Container在某些场景下应用容器启动前需要先完成一些准备工作——等数据库就绪、下载配置文件、检查外部依赖。如果把重试逻辑写进应用代码会使应用变得复杂K8s 提供的解决方案是Init Container。Init Container 是 Pod 中一种特殊的容器它在应用容器启动之前执行并且必须成功完成退出码 0后kubelet 才会启动应用容器。如果 Init Container 失败kubelet 会根据 Pod 的重启策略决定是否重试。Init Container 与普通容器的关键区别3.2 实战等待 Redis 就绪的 Init Container以下 YAML 定义了一个 Init Container在应用启动前轮询 Redis 是否可用apiVersion: v1 kind: Pod metadata: name: flask-with-init spec: restartPolicy: Always initContainers: - name: wait-for-redis image: redis:alpine command: -sh--c-|echo等待 Redis 就绪...untilredis-cli-hredis-service-p6379ping;doechoRedis 尚未就绪2 秒后重试...sleep2doneechoRedis 已就绪containers: - name: flask-app image: flask-redis-counter:2.0 ports: - containerPort:5000env: - name: REDIS_HOST value: redis-service - name: redis image: redis:alpine ports: - containerPort:6379initContainers字段与containers同级。这里定义的wait-for-redisInit Container 会循环执行redis-cli ping直到成功退出码 0 后 kubelet 才启动 Flask 容器。实际的 Redis 依赖应在 K8s 中用 Service 暴露——此时我们用redis-service指代这个 Service 名称。3.3 执行流程追踪部署这个 Pod 后查看事件kubectl describe pod flask-with-init|grep-A5Events:输出会清晰展示 Init Container 的启动和执行过程。Init Container 常用于等待数据库、下载配置模板、设置文件权限等场景。如果应用本身已包含重试逻辑比如我们 Flask 应用中连接 Redis 的get_hit_count函数Init Container 并非必须但在需要确保某个外部依赖完全就绪再启动的场景中它是最简洁的 K8s 原生方案。四、对比 Docker Compose 的启动控制学到这里做一个贯穿系列的知识串联。第 15 篇我们学过 Compose 用depends_oncondition: service_healthy控制启动顺序。K8s 中实现相同效果的手段有三层Init Container确保某个外部依赖如数据库可连接再启动应用容器。对应 Compose 中depends_on的条件等待。Readiness Probe告知 Service 该 Pod 是否可接收流量。对应 Compose 中healthcheck的“可用性”判定第 24 篇详解。Liveness Probe检测容器是否处于死锁/假死状态触发重启。对应 Compose 的healthcheck配合restart策略第 24 篇详解。五、命令速查表六、本篇总结Pod 生命周期五阶段Pending → Running → Succeeded / Failed / Unknown每个阶段有明确含义。三种重启策略Always默认服务类 Pod 必须使用、OnFailure批处理重试、Never一次性任务控制容器退出后的行为。TerminationGracePeriodSeconds控制优雅终止的超时窗口默认 30 秒SIGTERM → 等待 → SIGKILL。Init Container在应用容器前执行的初始化任务按顺序串行执行全部成功后才启动普通容器。对应 Compose 的depends_on逻辑。CrashLoopBackOffkubelet 的保护机制容器反复重启触发指数退避。这一篇让我们真正理解了 Pod 从创建到终止的完整过程以及如何通过重启策略和 Init Container 控制容器行为。下一篇文章——第 23 篇多容器 Pod 与设计模式Sidecar 等我们将深入 Pod 的常见设计模式看看如何在实际项目中优雅地组织多容器 Pod。想了解更多还可以去各个平台搜索「IT策士」一起升级 IT 思维