Pod 生命周期:从 Pending 到 Running 的完整路径
一个 kubectl apply -f pod.yaml 之后,终端里出现了 pod/my-app created。这行输出只意味着 API Server 接受了资源定义并写入 etcd——Pod 还没有运行在任何节点上,甚至还没有被任何节点知晓。从这个瞬间到容器真正响应流量,Pod 经历了一条由多个组件接力完成的状态转换链。
这条链不是黑盒。Kubernetes 把每个阶段都编码进 status.phase、status.conditions、status.containerStatuses 三个字段里,并通过 Events 记录关键节点的时间戳。理解这条链,就能在 Pod 卡住时精确定位是调度失败、镜像拉取失败、探针失败,还是 OOM Kill。
本文的核心问题是:一个 Pod 从 kubectl apply 到 Running 经历了哪些状态转换,每个状态背后是哪个组件在操作?
状态转换全景图
1 | |
phase 字段与 conditions 字段的区别
Pod 的 status.phase 是一个高层摘要,只有五个取值:
Pending:Pod 已被 API Server 接受,但还没有所有容器都处于运行状态。包含等待调度和等待镜像拉取两种情况。Running:至少一个容器正在运行,或者正在启动/重启过程中。Succeeded:所有容器都以状态码 0 退出,且restartPolicy不为Always。Failed:至少一个容器以非 0 状态退出或被系统终止,且restartPolicy不为Always。Unknown:通常意味着 API Server 无法与 kubelet 通信,节点可能宕机。
phase=Running 不代表应用已经可以处理请求。这是最常见的误解之一。一个刚刚拉起容器但 readinessProbe 还没通过的 Pod,phase 就是 Running,但它不会出现在任何 Service 的 Endpoints 里。
status.conditions 提供更细粒度的信息,四个关键 condition:
| Condition | 设置者 | 含义 |
|---|---|---|
PodScheduled |
Scheduler | Pod 已绑定到节点 |
Initialized |
kubelet | 所有 init containers 成功完成 |
ContainersReady |
kubelet | 所有容器通过了 readinessProbe |
Ready |
kubelet | Pod 可以接受流量(ContainersReady + readinessGates) |
用 kubectl get pod my-app -o jsonpath='{.status.conditions}' 可以看到每个 condition 的 status、reason 和 lastTransitionTime,这是诊断 Pod 卡在哪个阶段最直接的方式。
Pending 阶段:调度器的工作
Pod 进入 etcd 时,spec.nodeName 为空,status.phase 为 Pending,condition PodScheduled=False。Scheduler 通过 Informer 机制 watch 到这个新 Pod,将其放入优先级队列。
调度过程(Filter → Score → Bind)在05 调度器深入中详细展开。这里关注调度完成后发生了什么:Scheduler 向 API Server 发送一个 Binding 对象(或者直接 PATCH pod.spec.nodeName),API Server 更新 etcd,condition PodScheduled=True。
此时 Pod 仍然是 Pending。kubelet 还没有开始任何操作。
kubelet 接管:从镜像拉取到容器启动
kubelet 通过 Informer 监听 API Server,发现有 spec.nodeName 等于本节点名称的 Pod 进入,触发 syncPod 主循环。
镜像拉取
kubelet 通过 CRI(Container Runtime Interface)的 ImageService.PullImage gRPC 接口向容器运行时(如 containerd)发送拉取请求。拉取期间 Pod 的 status.containerStatuses[*].state 为 waiting,reason 为 ContainerCreating。如果镜像不存在于任何可达仓库,reason 会变成 ErrImagePull,随后变成 ImagePullBackOff(kubelet 引入指数退避)。
imagePullPolicy 控制行为:
Always:每次都拉取(无论本地是否有)IfNotPresent:本地有则跳过拉取Never:从不拉取,镜像必须预先存在于节点
Init Containers
Init containers 按数组顺序串行执行。第 N 个 init container 必须以状态码 0 退出,第 N+1 个才会启动。如果某个 init container 失败,kubelet 根据 restartPolicy 决定是否重试:
restartPolicy: Always或OnFailure:kubelet 重启失败的 init container,有指数退避(最长 5 分钟),在status.initContainerStatuses里能看到restartCount。restartPolicy: Never:init container 失败后 Pod 进入Failed状态。
condition Initialized=True 在所有 init containers 成功完成后由 kubelet 设置。
Sandbox 与主容器启动
kubelet 先通过 CRI RuntimeService.RunPodSandbox 创建 pause 容器(也叫 infra container)。pause 容器建立 Pod 的网络命名空间,CNI 插件在这一步被调用,为 Pod 分配 IP。
之后所有主容器并行启动(RuntimeService.CreateContainer + StartContainer),共享 pause 容器的网络和 IPC 命名空间。
三类探针的精确语义
探针通过三种机制执行检查:exec(在容器内执行命令)、httpGet(对容器 IP 发 HTTP GET)、tcpSocket(建立 TCP 连接)。三类探针控制不同的行为:
startupProbe
startupProbe 在容器启动后开始探测,直到连续成功 successThreshold 次。在此期间,livenessProbe 和 readinessProbe 都被挂起,不会执行。
作用:给启动慢的应用(如 JVM 应用)提供足够的启动时间,而不必将 livenessProbe.initialDelaySeconds 设置得很大。
失败结果:超过 failureThreshold * periodSeconds 后,kubelet 杀死容器并根据 restartPolicy 决定是否重启。
livenessProbe
startupProbe 成功(或未配置)后,livenessProbe 开始定期探测。失败达到 failureThreshold 次时,kubelet 向容器发送 SIGKILL(不经过 gracePeriod),然后根据 restartPolicy 重启容器。
livenessProbe 回答的问题是:容器是否还活着、能否继续服务?如果进程死锁但没有退出,操作系统不会自动重启它,livenessProbe 可以检测到这种状态。
readinessProbe
readinessProbe 失败时,kubelet 将该 Pod 从 Service 对应的 Endpoints 中摘除——Pod 不再接收通过 Service 路由的流量。探针恢复后,Pod 重新加入 Endpoints。整个过程中,Pod 保持 Running 状态,容器不会被重启。
readinessProbe 回答的问题是:容器是否准备好接受流量?这与容器是否活着是两个不同的问题。数据库连接池初始化期间,容器可以是活的但没有就绪。
condition ContainersReady 和 Ready 的变化由 readinessProbe 的结果驱动。
Pod 终止:优雅关闭路径
kubectl delete pod my-app 触发以下序列,在 terminationGracePeriodSeconds(默认 30 秒)内完成:
1 | |
preStop hook 的执行时间计入 terminationGracePeriodSeconds。如果 preStop 本身需要较长时间,需要相应调大 terminationGracePeriodSeconds。
一个常见的误解是认为删除 Pod 后容器会立即消失。实际上,Endpoints controller 和 kube-proxy 的更新存在延迟,如果没有 preStop hook 加一个短暂的 sleep,正在处理中的请求可能在容器收到 SIGTERM 时就被中断。标准做法是:
1 | |
这给 kube-proxy 更新 iptables 规则留出时间,在容器开始处理 SIGTERM 之前,新的连接就不会再路由过来。
可观测实验:用 kubectl 观察状态转换
准备 kind 集群
1 | |
观察 Pod 启动过程
创建一个带 init container 和 startupProbe 的 Pod:
1 | |
在另一个终端窗口运行:
1 | |
输出序列大致如下:
1 | |
注意 READY 列的变化:0/1 持续到 readinessProbe 通过,才变为 1/1。这是从 Endpoints 角度看到的就绪状态。
观察 conditions
1 | |
可以看到四个 condition 的状态,以及 PodScheduled 和 Initialized 先于 ContainersReady 变为 True。
观察 Events 的时间序列
1 | |
Events 部分会显示:
1 | |
Scheduled 到 Pulling(init container 的镜像)之间的间隔,是 kubelet 发现 Pod 并开始处理的时间。Pulling 到 Pulled 是镜像下载时间。Started(init container)到下一个 Pulling(主容器镜像)是 init container 的执行时间。
触发 livenessProbe 失败
1 | |
几秒后观察:
1 | |
当 livenessProbe 连续失败达到 failureThreshold 次,会看到 RESTARTS 列的计数增加,Pod 进入 Running 状态后 RESTARTS 从 0 变 1。
观察优雅终止
1 | |
用 -w 观察到 Pod 进入 Terminating 状态,等待约 30 秒(如果 nginx 没有其他进程在运行,实际上 nginx 会很快响应 SIGTERM 退出,不会等满 30 秒)。
将实验结果映射到 K8s 对象
kubectl get pod -w 输出的 STATUS 列不是直接来自 status.phase,而是 kubectl 基于多个字段计算的显示字符串:
Init:N/M:正在运行第 N 个(共 M 个)init container,来自status.initContainerStatusesPodInitializing:init containers 全部完成,主容器镜像正在拉取ContainerCreating:镜像拉取完成,容器正在创建Running:phase 为 RunningTerminating:deletionTimestamp已设置但 Pod 对象还在 etcd 中
READY 列(如 1/1)来自 status.containerStatuses 中 ready=true 的数量比总数量,由 readinessProbe 结果驱动。
Events 由 kubelet 通过 API Server 写入,存储在 etcd 中,默认保留 1 小时。每个 Event 对应 source.component(如 kubelet、default-scheduler),可以追溯是哪个组件触发了哪个操作。
模式提炼
Pod 状态机是"分层状态 + 多探针 + 优雅终止"三个机制的组合。
分层状态:phase 是粗粒度的外部可见状态,conditions 是细粒度的阶段检查点,containerStatuses 是每个容器的微观状态。三层叠加给出完整的 Pod 状态视图,而不是用单一字段表示所有情况。
多探针分离职责:startupProbe 解决启动时间不确定的问题,livenessProbe 解决死锁检测问题,readinessProbe 解决流量路由问题。三个探针回答三个不同的问题,混用或省略任一个都会带来不同的故障模式。
优雅终止:preStop + SIGTERM + gracePeriod + SIGKILL 的四层机制,给应用足够的时间完成正在处理的请求、刷新缓存、关闭连接。滚动更新期间,配合 Endpoints controller 的摘除时序,可以实现请求零中断的发布。
工程迁移表
| K8s 机制 | 类比概念 | 差异点 |
|---|---|---|
startupProbe |
Spring Boot Actuator /health 的 initialDelaySeconds |
K8s 的 startupProbe 期间 liveness 被完全挂起,Spring 的 delay 只是等待,liveness 检查仍然生效 |
livenessProbe |
JVM shutdown hook 检测 / watchdog 进程 | livenessProbe 由外部 kubelet 执行,不依赖进程自身;JVM shutdown hook 是进程内机制 |
readinessProbe |
数据库连接池的 testOnBorrow / 负载均衡器健康检查 |
readinessProbe 失败只影响 Endpoints,不影响 Pod 存活;数据库连接池的健康检查失败通常会丢弃连接 |
preStop + SIGTERM |
JVM shutdown hook / systemd ExecStop |
preStop 是容器生命周期钩子,在 SIGTERM 之前执行;JVM shutdown hook 在 JVM 收到 SIGTERM 后才触发 |
terminationGracePeriodSeconds |
数据库连接池 drain 超时 / 线程池 awaitTermination | 超时后 SIGKILL 强制杀死,不给进程任何处理机会 |
initContainers |
数据库 migration 脚本(在应用启动前运行)/ Spring ApplicationContextInitializer | init container 完全独立于主容器,可以用不同镜像,主容器失败不会重跑 init container |
Pod phase |
Spring Bean BeanDefinitionParserDelegate 状态 |
phase 是 kubelet 上报到 API Server 的摘要,不是状态机的内部状态;真实状态在容器运行时里 |
常见误解
误解一:phase=Running 说明应用已经可以接受请求
phase=Running 只意味着至少一个主容器正在运行(进程存在)。readinessProbe 可能还没有通过,Pod 还不在 Service 的 Endpoints 里。在滚动更新场景中,如果不配置 readinessProbe,新 Pod 一旦 Running 就会收到流量,但应用可能还在初始化。
验证方式:kubectl get endpoints <service-name>,看 Pod 的 IP 是否出现在 ENDPOINTS 列。
误解二:发送 SIGTERM 后容器会立即被杀死
发送 SIGTERM 后,kubelet 等待 terminationGracePeriodSeconds(默认 30 秒)。如果进程在这个时间内自然退出,就不会收到 SIGKILL。SIGTERM 是"请求终止"信号,进程可以捕获它并执行清理工作。
在 terminationGracePeriodSeconds 期间,Pod 的 phase 是 Running(直到容器全部退出),status.deletionTimestamp 已设置,这是 Terminating 状态的实际来源。
误解三:Init Containers 可以并行执行
Init containers 严格串行。数组中的 init container 按顺序执行,每个必须成功退出才运行下一个。这与主容器并行启动形成对比。
串行的设计是有意的:init containers 通常用于依赖检查(等待数据库就绪)、数据准备(从外部存储同步配置)等场景,这些场景需要确定的执行顺序。
如果需要并行初始化,只能通过主容器的 postStart hook 或者应用自身的启动逻辑实现,而不是 init containers。
练习
-
创建一个带三个 init containers 的 Pod,其中第二个 init container 故意失败(exit 1),观察
kubectl describe pod的 Events 和status.initContainerStatuses,确认restartCount在不同restartPolicy下的行为。 -
创建一个 Pod,配置
readinessProbe检查一个不存在的路径(如/healthz),并创建一个 Service 指向它,通过kubectl get endpoints确认 Pod 不在 Endpoints 里;然后 exec 进容器创建该路径,观察 Endpoints 的变化。 -
创建一个 Pod,将
terminationGracePeriodSeconds设为 60,在容器的preStophook 里执行sleep 10,然后 delete 这个 Pod 并用-w观察 Terminating 阶段的持续时间;修改sleep为 70(超过 grace period),观察 Pod 是否被强制杀死。 -
模拟 OOM Kill:在容器里运行一个分配大量内存的进程,观察
kubectl describe pod中的LastState,确认reason: OOMKilled,并观察restartCount的变化和phase是否保持 Running。
系列导航
- 00 核心概念导读
- 01 API Server 与声明式 API
- 02 etcd 与持久化
- 03 控制器模式与 Informer 机制
- 04 Pod 生命周期
- 05 调度器深入
- 06 kubelet 与容器运行时
- 07 网络模型与 CNI
- 08 Service 与 kube-proxy
- 09 Ingress 与 Gateway API
- 10 存储体系
- 11 配置与密钥管理
- 12 RBAC 与安全模型
- 13 资源管理与自动伸缩
- 14 CRD 与 Operator 模式
- 15 Helm 与应用打包
- 16 可观测性
- 17 生产集群运维
