Kubernetes 基础体系 · 第 14/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Pod 生命周期:Phase、Condition、RestartPolicy、终止和 Eviction
Pod 不是“运行一个容器”的简单包装。它是 Kubernetes 调度、容器运行时、kubelet 和控制器共同维护的对象:Pod 先被调度到节点,节点为它创建 sandbox 和网络,再按顺序运行 init 容器,最后启动应用容器;运行期间,容器可能被单独重启,Pod 也可能因为删除、节点压力或节点故障而终止。
理解 Pod 生命周期时,必须区分四类信息:
- Phase:Pod 的粗粒度生命周期阶段。
- Condition:Pod 是否满足某个具体条件。
- RestartPolicy:容器退出后,kubelet 是否以及何时重新启动容器。
- 终止与 Eviction:Pod 如何结束,以及节点资源压力如何导致 Pod 被驱逐。
这些概念彼此相关,但不能互相替代。例如,Pod 处于 Running 并不表示业务已经可以接收流量;Pod 处于 Ready=False 也不表示容器已经退出。
一、先建立完整的生命周期模型
一个典型 Pod 的生命周期可以抽象为:
flowchart LR
A[Pod 对象创建] --> B[Pending]
B --> C[调度到节点]
C --> D[创建 Pod sandbox]
D --> E[运行 init 容器]
E --> F[启动应用容器]
F --> G[Running]
G --> H{容器是否退出}
H -->|按 RestartPolicy 重启| G
H -->|全部成功退出| I[Succeeded]
H -->|至少一个失败且不再重启| J[Failed]
G --> K[删除或 Eviction]
K --> L[终止流程]
L --> J
这是概念上的主路径,实际状态还可能受到以下事件打断:
- 调度失败,Pod 长时间停留在
Pending; - 镜像拉取失败,Pod 仍可能是
Pending; - sandbox 创建失败,Pod 可能不断重建 sandbox;
- liveness probe 失败,kubelet 重启单个容器;
- 节点失联,Pod 可能进入
Unknown或长期没有新状态; - Pod 被删除时,即使容器仍在运行,API 对象也会进入终止过程;
- 节点资源压力触发 kubelet eviction,Pod 通常以
Failed结束,原因标记为Evicted。
因此,Pod 状态不是一条只向前移动的简单枚举。尤其是 Running 内部可以发生多次容器创建、退出、重启和 sandbox 重建。
二、Pod Phase:粗粒度、单值、不可替代的生命周期摘要
Pod 的 status.phase 是 Kubernetes API 定义的有限集合:
| Phase | 含义 |
|---|---|
Pending |
Pod 已被 Kubernetes 接受,但尚未完成运行准备,例如等待调度、拉取镜像或运行 init 容器 |
Running |
Pod 已经绑定节点,所有容器已经创建,并且至少有一个容器正在运行或正在重启 |
Succeeded |
Pod 中所有容器都已成功终止,并且不会再重启 |
Failed |
Pod 中所有容器都已终止,至少一个容器失败,并且不会再重启 |
Unknown |
控制面无法获得 Pod 当前状态,通常与节点或 kubelet 通信失败有关 |
1. Pending 不等于“调度失败”
例如:
kubectl get pod demo
可能得到:
NAME READY STATUS RESTARTS AGE
demo 0/1 Pending 0 20s
此时至少有三种可能:
- 调度器还没有找到满足资源、亲和性、污点等条件的节点;
- Pod 已调度,但镜像尚未拉取完成;
- init 容器正在运行,应用容器尚未启动。
要区分这些情况,应查看事件和详细状态:
kubectl describe pod demo
kubectl get pod demo -o yaml
例如事件可能是:
Warning FailedScheduling 0/3 nodes are available: 3 Insufficient cpu.
这说明问题发生在调度阶段,而不是容器进程启动阶段。另一种情况可能是:
Normal Scheduled Successfully assigned default/demo to node-a
Warning Failed Failed to pull image "example/app:v9"
此时 Pod 已经调度成功,Pending 的原因变成镜像获取失败。
2. Running 不等于业务可用
Pod 进入 Running 的条件比“业务准备完成”宽松。只要 Pod 已绑定节点、容器已经创建,并且至少一个容器运行或重启,就可以处于 Running。
例如,应用容器虽然进程已经启动,但还没有完成数据库连接初始化:
NAME READY STATUS RESTARTS AGE
demo 0/1 Running 0 30s
这里的 READY 0/1 表明容器尚未通过就绪判断。Service 通常只会把通过就绪条件的 Pod 作为可用后端,因此:
phase=Running描述生命周期;Ready=True描述是否可以接收正常流量。
把 STATUS=Running 当作“服务已经可用”是常见误判。
3. Succeeded 与 Failed 主要用于一次性任务
假设 Pod 的 restartPolicy: Never,容器退出码为 0,则可能得到:
status:
phase: Succeeded
containerStatuses:
- name: worker
state:
terminated:
exitCode: 0
reason: Completed
如果退出码为 2:
status:
phase: Failed
containerStatuses:
- name: worker
state:
terminated:
exitCode: 2
reason: Error
Succeeded 和 Failed 不是“容器当前状态”的直接替代品,而是 Pod 级别的最终摘要。Job 控制器通常根据 Pod 的成功或失败来推进任务重试和完成状态。
4. Unknown 不代表容器真的处于未知业务状态
Unknown 的核心含义是:控制面无法可靠知道节点上的实际状态。常见原因包括:
- 节点断电;
- kubelet 停止工作;
- 节点到 API Server 的连接中断;
- 网络分区;
- 节点压力严重导致状态上报失败。
这时不能从 Unknown 推断“容器一定还在运行”或“一定已经退出”。实际容器可能仍然运行,也可能已经消失。控制器和运维系统必须结合节点状态、租约、事件和业务侧观测进行判断。
三、Pod Condition:把生命周期拆成多个可观察条件
status.phase 只有一个值,而 status.conditions 是多个带名称的条件。一个条件通常包含:
type: Ready
status: "False"
observedGeneration: 3
lastProbeTime: "2025-01-01T10:00:00Z"
lastTransitionTime: "2025-01-01T09:59:30Z"
reason: ContainersNotReady
message: "containers with unready status: [app]"
其中:
type:条件名称;status:通常是True、False或Unknown;reason:机器可读性较强的原因标识;message:给人阅读的补充信息;lastTransitionTime:该条件最近一次发生 True/False/Unknown 转换的时间。
条件是“某个命题当前是否成立”,不是一个互斥状态机。
1. 常见 Pod Conditions
PodScheduled
表示 Pod 是否已经被调度器绑定到节点。
type: PodScheduled
status: "True"
如果它是 False,应优先检查:
- 资源请求是否超过节点可用资源;
- nodeSelector、nodeAffinity 是否匹配;
- 节点污点是否缺少 toleration;
- 拓扑分布约束是否无法满足;
- PVC 是否影响调度。
Initialized
表示 init 容器阶段是否完成。对于没有 init 容器的 Pod,通常很快变为 True。
init 容器必须按顺序执行。后一个 init 容器只有在前一个成功退出后才会启动。因此,一个失败并不断重启的 init 容器会阻止应用容器启动。
ContainersReady
表示 Pod 中所有常规容器是否都报告为 ready。它主要由容器状态和 readiness probe 等因素决定。
Ready
表示 Pod 是否满足对外提供服务的就绪条件。通常它依赖于 ContainersReady,还可能受到 readinessGates 的影响。
因此,以下关系通常成立:
Ready=True
⇒ Pod 能满足所有就绪门槛
⇒ 通常 ContainersReady=True
但 Running=True 并不能推出 Ready=True:
Running=True
⇏ Ready=True
这就是为什么 Service 不应仅依据 Pod phase 选择流量。
PodReadyToStartContainers
较新的 Kubernetes 版本还提供用于表达 sandbox、网络等基础运行环境是否已经准备好的条件。它通常用于区分:
- Pod 已经调度到节点;
- sandbox 和网络还没有准备好;
- sandbox 已准备好,可以开始启动容器。
该条件与 Kubernetes 版本和相关功能门控有关,使用前应检查目标集群版本。它不能替代 Ready:sandbox 已准备好,只代表容器具备启动基础,不代表应用已经可以接收流量。
DisruptionTarget
在某些版本和场景中,Pod 可能被标记为将因驱逐、抢占或其他中断而终止。该条件的可用性和具体触发场景受版本影响,不能把它当作所有终止原因的统一标志。诊断时仍应结合 deletionTimestamp、事件、Pod reason 和控制器行为。
2. readinessGates 会改变 Ready 的最终结果
Pod 可以声明额外的就绪条件:
spec:
readinessGates:
- conditionType: "example.com/DatabaseReady"
此时,即使所有容器都通过 readiness probe,Pod 仍可能因为缺少:
type: "example.com/DatabaseReady"
status: "True"
而保持:
type: Ready
status: "False"
这提供了让外部控制器参与流量准入的机制,但也增加了失败路径:如果负责写入该条件的控制器停止工作,Pod 可能永远无法 Ready。
3. Condition 与容器状态不是一回事
容器状态位于:
status.containerStatuses[].state
其主要形态为:
WaitingRunningTerminated
例如:
state:
waiting:
reason: CrashLoopBackOff
CrashLoopBackOff 不是 Pod Phase,也不是一个独立的容器生命周期阶段。它表示 kubelet 发现容器反复失败,并在重启前执行退避等待。
Pod 可能同时出现:
status:
phase: Running
conditions:
- type: Ready
status: "False"
containerStatuses:
- state:
waiting:
reason: CrashLoopBackOff
这组状态并不矛盾:Pod 仍满足 Running 的粗粒度定义,但容器正在反复失败,业务显然不可用。
四、RestartPolicy:决定“退出后是否重启”,不决定“为何退出”
spec.restartPolicy 是 Pod 级字段,取值为:
AlwaysOnFailureNever
默认值是 Always。它在 Pod 创建后不能随意修改;对于 Deployment、ReplicaSet 等控制器,通常必须使用 Always。Job 常用 OnFailure 或 Never。
1. 三种策略的形式化含义
设容器在时刻 退出,退出码为 ,并令:
success(c) ⇔ c = 0
这只是一个常见的进程退出成功判定,实际还要考虑信号、运行时原因和控制器语义。
容器退出后的重启判定可以简化为:
Always:
restart = true
OnFailure:
restart = not success(c)
Never:
restart = false
但“是否重启”还不是“立即重启”。kubelet 还会考虑容器是否仍处于终止流程、Pod 是否正在删除、节点是否可用,以及失败重启退避。
2. Always 并不表示 Pod 永远存在
下面这个 Pod 使用默认的 Always:
apiVersion: v1
kind: Pod
metadata:
name: always-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo done; exit 0"]
容器第一次成功退出后,kubelet 会再次启动它。结果可能是:
NAME READY STATUS RESTARTS AGE
always-demo 0/1 CrashLoopBackOff 5 40s
如果它持续快速退出,Pod 通常仍是 Running,但容器会进入反复重启和退避状态。
另一方面,如果用户删除 Pod:
kubectl delete pod always-demo
Always 不会阻止删除。重启策略只在 Pod 仍然属于节点管理范围时生效,不能抵消 API 删除、控制器缩容或节点故障。
3. OnFailure 只根据退出是否成功决定重启
apiVersion: v1
kind: Pod
metadata:
name: onfailure-demo
spec:
restartPolicy: OnFailure
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "exit 0"]
其预期结果是:
NAME READY STATUS RESTARTS AGE
onfailure-demo 0/1 Completed 0 5s
如果改成 exit 1,kubelet 会尝试重启容器,而不是立即将 Pod 变成 Failed。只有在容器最终不再重启、Pod 被删除或控制器结束这条执行路径时,Pod 才会形成相应的最终结果。
4. Never 适合把一次容器执行结果保留下来
apiVersion: v1
kind: Pod
metadata:
name: never-demo
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "echo processing; exit 2"]
查看结果:
kubectl apply -f never-demo.yaml
kubectl get pod never-demo -o wide
kubectl describe pod never-demo
预期可见:
STATUS RESTARTS
Failed 0
Never 只禁止 kubelet 在同一个 Pod 内重启容器。Job 控制器仍可以根据 backoffLimit 创建新的 Pod。也就是说:
同一 Pod 内的重启 ≠ 控制器创建新的 Pod
不能通过观察 Job 创建了多个 Pod,就推断原 Pod 的 restartPolicy 是 Always。
5. 重启退避不是 RestartPolicy 的另一种取值
容器反复失败时,kubelet 通常会逐渐增加重启间隔,从而避免死循环式地高频拉起进程。命令:
kubectl get pod always-demo
kubectl describe pod always-demo
kubectl logs always-demo --previous
其中:
RESTARTS观察重启次数;describe常能看到退出码、信号和事件;--previous查看上一次已经退出的容器日志。
退避时间、重置行为等细节属于 kubelet 实现行为,不应将某个具体秒数写成跨版本的 API 保证。实践中,容器连续稳定运行一段时间后,退避状态可能被重置;具体行为应以目标 Kubernetes 版本和 kubelet 实现为准。
五、Probe 如何影响生命周期,但不直接改变 Phase
Probe 是生命周期判断的重要前置工具:
- Startup probe:应用启动完成前,保护应用免受 liveness/readiness 检查干扰;
- Readiness probe:决定容器是否可以接收流量;
- Liveness probe:失败后由 kubelet 重启容器;
- gRPC probe:用 gRPC Health Checking Protocol 检查服务健康状态。
它们对状态的影响路径不同:
sequenceDiagram
participant K as kubelet
participant C as 容器
participant S as Service/EndpointSlice
K->>C: readiness probe
C-->>K: 失败
K->>K: ContainersReady=False
K->>K: Ready=False
K->>S: 移除或标记为不可用后端
K->>C: liveness probe
C-->>K: 连续失败
K->>C: 终止容器
K->>C: 按 RestartPolicy 重启
关键因果关系是:
readiness 失败
⇒ Ready=False
⇒ 通常不接收 Service 流量
⇏ 容器被重启
liveness 失败
⇒ kubelet 终止容器
⇒ 容器按照 RestartPolicy 处理
⇏ Pod phase 必然变为 Failed
例如,应用依赖一个外部数据库。若数据库短暂不可用,应优先考虑 readiness 失败,使 Pod 暂停接收流量;如果把同一个检查配置成 liveness,数据库故障可能导致所有 Pod 同时重启,反而放大故障。
Startup probe 存在时,通常在它成功前不会执行 liveness 和 readiness 的正常判定。这解决的是“启动慢”和“运行后失活”之间的时间尺度冲突,但错误的 failureThreshold × periodSeconds 仍可能让启动过程被过早判定失败。
六、终止流程:删除对象不等于进程立即消失
当执行:
kubectl delete pod demo
API Server 通常不会只做一个瞬间的“对象消失”动作,而是启动一个带宽限期的删除流程。Pod 会出现 deletionTimestamp,kubelet 根据终止宽限期停止容器。
典型顺序如下:
sequenceDiagram
participant U as 用户/控制器
participant A as API Server
participant K as kubelet
participant R as 容器运行时
participant E as EndpointSlice/Service
U->>A: DELETE Pod
A-->>A: 设置 deletionTimestamp 和 grace period
A->>E: 观察 Pod 终止并停止正常流量
K->>R: 执行 preStop(如配置)
K->>R: 发送 TERM
K->>R: 等待剩余宽限期
K->>R: 发送 KILL(仍未退出时)
R-->>K: 容器退出
A-->>U: 最终删除 Pod 对象
实际实现中,EndpointSlice 更新、容器停止和 API 对象删除存在并发,不能把上图当作严格的全局事务顺序。它表达的是主要因果路径。
1. terminationGracePeriodSeconds
例如:
apiVersion: v1
kind: Pod
metadata:
name: graceful-demo
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: nginx:1.27
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
删除该 Pod:
kubectl delete pod graceful-demo
通常过程是:
- API 对象获得删除时间戳;
- kubelet 开始终止流程;
- 执行
preStop; - 向容器主进程发送终止信号,通常是
SIGTERM; - 等待剩余宽限期;
- 宽限期结束仍未退出时发送强制终止信号,通常对应
SIGKILL; - 容器被运行时清理,Pod 对象最终消失。
宽限期的默认值通常为 30 秒,但生产配置不应依赖“默认值一定存在”,应显式设置并验证。
preStop 不是一个可以无限执行的清理任务:
- 它消耗 Pod 的终止宽限期;
- 如果 hook 花费 5 秒,应用实际用于处理
SIGTERM的时间会减少; - hook 失败不会自动保证业务已经完成清理;
- kubelet 或节点崩溃时,不能保证 hook 一定执行。
容器必须正确处理终止信号。若应用把业务逻辑放在 shell 子进程中,而 shell 没有正确转发信号,Pod 即使配置了宽限期,主进程也可能直到最后被强制杀死。
2. 删除宽限期不是可靠消息传递机制
应用收到 SIGTERM 后可能需要:
- 停止接收新请求;
- 等待正在处理的请求完成;
- 刷新日志或消息;
- 关闭连接;
- 写入一致性状态。
但这些操作都受限于:
- Pod 的宽限期;
- 节点是否仍然存活;
- 容器进程是否正确处理信号;
- 外部系统是否及时完成;
- kubelet 和容器运行时是否正常。
因此,宽限期是“尽力而为的优雅终止窗口”,不是事务提交协议。关键数据不能只依赖 preStop 或 SIGTERM 才持久化。
3. 强制删除的风险
kubectl delete pod demo --grace-period=0 --force
这会让 API Server 尽快删除对象,但在节点失联等情况下,不能证明节点上的旧进程已经停止。于是可能出现:
- 控制器快速创建同名或等价的新副本;
- 旧 Pod 仍在节点上运行;
- 两个进程同时访问同一外部资源;
- Stateful 工作负载发生重复写入或数据损坏风险。
强制删除适用于确认旧实例不可能继续运行,或已经接受重复执行风险的场景。它不是普通删除的“更快版本”。
4. Finalizer 可能让 Pod 对象迟迟不消失
如果对象带有 finalizer,API Server 可以在删除请求后保留对象,等待相关控制器完成清理:
kubectl get pod demo -o jsonpath='{.metadata.deletionTimestamp}{"\n"}'
kubectl get pod demo -o jsonpath='{.metadata.finalizers}{"\n"}'
出现 deletionTimestamp 但对象长时间存在,不能立即认为 kubelet 没有杀死容器,也可能是 finalizer 没有完成。盲目移除 finalizer 会跳过其负责的外部清理逻辑,应先确认资源所有者和清理责任。
七、Eviction:节点压力下的 Pod 驱逐
Eviction 是在资源不足或主动维护场景下,让 Pod 离开节点的机制。需要区分两个概念:
- API Eviction 子资源:用户或控制器主动请求驱逐,例如节点维护;
- kubelet node-pressure eviction:节点本地资源压力达到阈值,kubelet 为保护节点而驱逐 Pod。
它们都叫 eviction,但决策者、保护机制和故障路径不同。
1. API Eviction 与 PodDisruptionBudget
可以通过客户端调用 eviction 子资源,常见命令是:
kubectl drain node-a --ignore-daemonsets
节点排空通常会尝试通过 eviction API 驱逐 Pod。此类主动中断通常会考虑 PodDisruptionBudget(PDB),例如要求某个工作负载至少保留一定数量的可用副本。
但 PDB 不是“任何 Pod 都不能被杀”的保护:
- 它主要限制自愿中断;
- 它不阻止节点断电;
- 它通常不阻止 kubelet 因节点压力进行的本地驱逐;
- 它不能防止容器自身崩溃;
- 它不能保证业务请求已经完成。
因此,“配置了 PDB,所以节点压力时 Pod 不会被驱逐”是错误理解。
2. kubelet node-pressure eviction 的触发对象
kubelet 监控的节点压力可能包括:
- 内存不足;
- 根文件系统空间不足;
- 镜像文件系统空间不足;
- inode 不足;
- PID 资源不足。
这些资源压力会反映到节点条件和相关 taint,例如内存压力、磁盘压力或 PID 压力。调度器可以利用这些信息减少新的 Pod 被调度到问题节点,但已经运行的 Pod 是否被驱逐,是 kubelet 的本地决策。
“容器收到 OOMKilled”与“Pod 被 Evicted”也不是同一回事:
容器或 cgroup 超出内存限制
⇒ 可能是容器级 OOMKilled
节点整体内存压力达到 kubelet 驱逐条件
⇒ 可能触发 Pod eviction
前者可能只重启一个容器,后者通常会终止整个 Pod,并在状态中记录驱逐原因。
3. 驱逐选择不是简单的“先杀内存最大的 Pod”
kubelet 的驱逐排序会综合考虑 Pod 的优先级、资源使用是否超过 requests,以及相对请求的超额程度等因素。实际排序还受到 QoS、资源类型和版本实现影响。
QoS 类别通常为:
GuaranteedBurstableBestEffort
一个简化但不能替代实现细节的理解是:
BestEffort 通常缺少资源请求和限制,压力下风险较高;
Burstable 可能在超过 requests 后成为优先候选;
Guaranteed 通常具有更强资源保障,但并非绝对不会被驱逐。
Guaranteed Pod 仍可能因为:
- 节点整体不可用;
- 磁盘或 inode 压力;
- 系统级资源压力;
- 节点维护或管理员删除;
- 其他不可控故障;
而终止。QoS 不是持久化、可用性或不被删除的保证。
4. 硬阈值与软阈值
kubelet eviction 配置可以包含硬阈值和软阈值:
- 硬阈值:压力达到后立即采取驱逐动作,实际可用优雅终止时间可能为零;
- 软阈值:压力持续超过指定时间后才触发,并可配置软驱逐宽限期。
这些阈值是 kubelet 配置,不能假定所有云厂商集群都使用相同值。检查目标节点配置和事件比背诵默认值更可靠。
5. 驱逐后的状态
查看被驱逐 Pod:
kubectl get pod demo -o yaml
可能看到:
status:
phase: Failed
reason: Evicted
message: "The node was low on resource: memory."
或者使用:
kubectl describe pod demo
检查:
Reason: Evicted;- eviction message;
- 最后状态;
- 节点事件;
- Pod 的资源 requests/limits;
- 同一时刻节点的
MemoryPressure、DiskPressure或PIDPressure。
Pod 被驱逐后,Deployment、ReplicaSet 等控制器通常会在其他可用节点创建替代副本;裸 Pod 没有控制器负责重建,驱逐后不会自动恢复。
八、完整诊断算例:从 CrashLoopBackOff 到真实原因
创建一个会反复失败的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
restartPolicy: Always
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
echo "start"
sleep 2
echo "exit with failure"
exit 1
执行:
kubectl apply -f lifecycle-demo.yaml
kubectl get pod lifecycle-demo -w
可能依次看到:
lifecycle-demo 0/1 Pending 0 1s
lifecycle-demo 1/1 Running 0 3s
lifecycle-demo 0/1 CrashLoopBackOff 1 8s
lifecycle-demo 0/1 CrashLoopBackOff 2 18s
逐步解释:
Pending:Pod 对象已经创建,但节点侧准备还未完成;Running:容器第一次启动,Pod 满足 Running 的粗粒度条件;- 容器退出码为 1;
- 因为
restartPolicy=Always,kubelet 尝试重新启动容器; - 容器再次快速失败;
- kubelet 进入重启退避,于是显示
CrashLoopBackOff; - Pod phase 仍可能是
Running,因为它属于“至少有容器正在运行或重启”的生命周期摘要。
诊断命令:
kubectl get pod lifecycle-demo -o jsonpath='
phase={.status.phase}{"\n"}
ready={.status.conditions[?(@.type=="Ready")].status}{"\n"}
restartCount={.status.containerStatuses[0].restartCount}{"\n"}
state={.status.containerStatuses[0].state}{"\n"}
lastState={.status.containerStatuses[0].lastState}{"\n"}'
kubectl logs lifecycle-demo --previous
kubectl describe pod lifecycle-demo
应重点查看:
phase:Pod 级摘要;Ready:是否对外可用;restartCount:当前容器重启次数;lastState.terminated.exitCode:上一次退出码;reason:例如Error、OOMKilled;- Events:镜像、挂载、探针、节点和运行时错误。
如果看到 OOMKilled,应进一步比较:
kubectl get pod lifecycle-demo -o jsonpath='{.spec.containers[0].resources}{"\n"}'
kubectl describe node <node-name>
因为这说明问题可能是容器限制过低,也可能是节点发生整体内存压力;不能只依据 CrashLoopBackOff 判断应用代码有 bug。
九、容易混淆的状态组合
误解一:STATUS=Completed 表示容器一定只执行了一次
不一定。对于 Job,控制器可能创建多个 Pod;每个 Pod 可能只执行一次,但整个 Job 的尝试次数由 Job 策略控制。另一方面,restartPolicy=OnFailure 可能在同一 Pod 内重启同一个容器多次,最终仍然得到成功结果。
误解二:readiness 失败会重启容器
通常不会。readiness 失败主要影响 Ready Condition 和 Service 后端选择。只有 liveness 或容器自身退出等路径,才会触发容器重启。
误解三:Pod 删除后,所有进程立即停止
不一定。正常删除有宽限期;节点失联时,控制面也无法立即确认节点上的进程;强制删除更可能造成旧实例和新实例重叠。
误解四:restartPolicy=Never 可以防止重复执行
它只能防止 kubelet 在同一个 Pod 内重启容器。Deployment、Job 或其他控制器仍可能创建新的 Pod;人为重新 apply、节点恢复或控制器重试也可能导致新的执行实例。
误解五:Pod 被驱逐是应用退出码导致的
通常不是。Eviction 是节点或主动维护路径,常见标志是:
status:
reason: Evicted
而应用主动退出、liveness 重启、OOMKilled、节点断电,分别属于不同故障路径,应通过 reason、事件和节点状态区分。
十、生产诊断的正确顺序
遇到 Pod 异常时,可以按状态层级逐层缩小范围,而不是只看 kubectl get pod 的 STATUS:
kubectl get pod <pod> -o wide
kubectl describe pod <pod>
kubectl get pod <pod> -o yaml
kubectl logs <pod> -c <container>
kubectl logs <pod> -c <container> --previous
kubectl get events --sort-by=.lastTimestamp
kubectl describe node <node>
推荐的判断顺序是:
- 先确认 Pod 是否真的存在以及是否正在删除:检查
deletionTimestamp; - 看 Phase:判断是未准备、运行中、已成功、失败还是状态未知;
- 看 Conditions:确认是未调度、未初始化、未就绪还是基础 sandbox 未准备;
- 看每个容器的
state、lastState和退出码; - 看 RestartPolicy 和重启次数;
- 看事件:区分调度、镜像、挂载、探针、运行时和驱逐;
- 看节点状态:确认是否存在内存、磁盘、inode、PID 或 kubelet 故障;
- 最后检查控制器:确定是否会创建替代 Pod,以及重试上限是什么。
形式化地说,下面这些推断都不成立:
phase=Running ⇒ service 可用
Ready=False ⇒ 容器已退出
restartCount>0 ⇒ Pod phase=Failed
phase=Failed ⇒ 应用退出码非零
Pod 被删除 ⇒ 节点上的旧进程已经停止
配置了 PDB ⇒ 节点压力时不会被驱逐
正确做法是沿着“谁改变了什么状态”的因果链检查:
调度器决定节点
→ kubelet 创建 sandbox 和容器
→ probe 与容器退出改变容器状态
→ kubelet 根据 RestartPolicy 重启容器
→ kubelet 或用户启动终止/驱逐
→ 控制器根据最终结果决定是否创建替代 Pod
十一、版本偏差与生产边界
本文使用当前稳定 Kubernetes API 中长期存在的 Pod 字段和行为:status.phase、status.conditions、restartPolicy、terminationGracePeriodSeconds、容器状态以及删除宽限期。以下部分需要特别注意版本和实现差异:
PodReadyToStartContainers、DisruptionTarget等条件的可用性、默认启用情况和具体触发场景可能随 Kubernetes 版本变化;- kubelet 的重启退避细节、驱逐排序实现和默认阈值不是应用可以依赖的稳定业务协议;
- 云厂商托管集群可能修改节点镜像、kubelet 参数、运行时和节点自动修复策略;
- 节点断电、内核崩溃、网络分区和强制删除都可能绕过正常优雅终止路径;
- PDB 只约束部分自愿中断,不能替代多副本、跨节点分布、持久化和应用级故障恢复;
- Pod phase 和 Condition 都是 API Server 观察到的状态,不是对节点上每一个瞬间事实的强一致快照。
Pod 生命周期的核心不是记住几个 STATUS 字符串,而是区分“Pod 级摘要”“具体条件”“容器重启决策”和“终止原因”。只有把这四层状态放回调度器、kubelet、容器运行时和控制器之间的故障路径中,才能正确解释 Pending、Running、CrashLoopBackOff、Completed、Evicted 以及看似矛盾的 Condition 组合。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Pod 完整模型:共享 Namespace、容器、Sandbox 和生命周期
- 下一篇:Init、Sidecar 与 Ephemeral Container:启动顺序、共享和调试边界
- 延伸:Kubernetes Probe:Startup、Readiness、Liveness、gRPC 和误配置风险
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论