跳到正文

Pod 生命周期与探针 ​

一句话:Pod 的 status.phase 只描述生命周期的大阶段,真正的故障原因藏在容器的 state.waiting.reason 里;探针则决定这个容器是「被摘流量」还是「被重启」,两者混着看就会修错方向。

适用版本:Kubernetes 1.29+。探针默认值以官方文档为准。

为什么需要 ​

kubectl get pods 的 STATUS 列会把不同层级的信息混在一起:Pending 是 Pod 阶段,CrashLoopBackOff 是容器等待原因,Evicted 是 Pod 被驱逐的原因。只看到 CrashLoopBackOff 就去重启,或者看到 Running 就以为服务可用,都会漏掉真正的问题。

把层级拆开,排障才有顺序:先看 Pod 阶段,判断是调度、启动还是运行;再看容器等待原因,判断是镜像、配置还是进程退出;最后看探针事件,判断是流量问题还是存活问题。

核心机制 ​

三个层级:phase、reason、container state ​

Pod 的 status.phase 只有五个值:

phase含义
Pending已接受,但还没调度成功或容器还没全部启动
RunningPod 已绑定节点,至少一个容器在运行
Succeeded所有容器正常退出(exit 0),不会再重启
Failed所有容器已终止,至少一个非零退出
Unknown无法从节点获取状态,通常是节点失联

Evicted、NodeLost、Shutdown 不是 phase,而是 status.reason。CrashLoopBackOff、ImagePullBackOff、ErrImagePull、CreateContainerConfigError 也不是 phase,而是 status.containerStatuses[].state.waiting.reason。

容器状态有三态:

yaml
status:
  phase: Running
  containerStatuses:
    - name: app
      ready: false
      restartCount: 5
      state:
        waiting:
          reason: CrashLoopBackOff
      lastState:
        terminated:
          reason: Error
          exitCode: 1

这条 Pod 的 phase 是 Running,但容器没 Ready、在退避重启。只看 phase 会误判为正常。

探针:三种检查,三种动作 ​

探针检查什么失败后的真实动作典型用途
startupProbe启动完成了吗kubelet 杀掉容器并按重启策略重启;成功前 liveness/readiness 不生效慢启动应用
readinessProbe能接流量吗从 Service Endpoint 摘除,容器不重启预热、依赖未就绪
livenessProbe进程还活着吗kubelet 杀掉容器,按 restartPolicy 重启死锁、无响应

探针的默认值:initialDelaySeconds=0、periodSeconds=10、timeoutSeconds=1、failureThreshold=3、successThreshold=1(liveness 和 startup 的 successThreshold 只能是 1)。这些默认值意味着:不配 initialDelaySeconds 时,容器一启动就开始探测;timeoutSeconds=1 对慢接口很苛刻。

几个容易混淆的点:

  • liveness 失败不会删除 Pod,只会杀掉容器并按 restartPolicy 重启;Pod 名称、IP 不变。
  • readiness 失败不会重启,Pod 仍是 Running,只是 READY 变成 0/1,Endpoint 里没有它。
  • startup 失败会重启容器,而且在它成功之前,liveness 不会介入,这正是它保护慢启动应用的方式。
  • 重启有退避:10s 起,逐次翻倍,最长 5 分钟;退避期间状态显示 CrashLoopBackOff。

Init Container 与重启策略 ​

Init Container 串行执行,全部成功后才启动主容器。任何一个失败,Pod 会停在 Init:Error 或 Init:CrashLoopBackOff,主容器根本不会创建。排查时要看 initContainerStatuses 和对应容器日志,而不是主容器。

restartPolicy 决定容器退出后的行为:Always(默认)总是重启,适合长期服务;OnFailure 只在非零退出时重启,适合 Job;Never 不重启。

实战场景 ​

一、phase=Running 的 Pod 一个都不 Ready,告警却全绿 ​

现象:凌晨一次配置变更后,order-service 的 12 个 Pod 里 9 个在反复重启。值班看板只显示“Running 12/12”,直到用户投诉才发现服务实际无可用实例。事后确认:状态是 phase: Running + containerStatuses[].state.waiting.reason: CrashLoopBackOff,ready: false。

排查:三个层级不能混用——status.phase、status.reason、containerState.reason。

层级取值常见误读
status.phase只有 Pending/Running/Succeeded/Failed/Unknown 五个值Evicted、NodeLost、Shutdown 不是 phase,是 status.reason
容器状态 reasonCrashLoopBackOff、ImagePullBackOff、ErrImagePull、CreateContainerConfigError这些都不是 phase,按 phase 统计永远看不到
ready由 Readiness 探针决定Running 不等于 ready,Service 只转发 ready 的 Pod
bash
# 正确的可用性口径:Ready 数,而不是 Running 数
kubectl get pods -l app=order-service \
  -o 'jsonpath={range .items[*]}{.metadata.name} phase={.status.phase} ready={.status.containerStatuses[0].ready} reason={.status.containerStatuses[0].state.waiting.reason}{"\n"}{end}'
kubectl get endpoints order-service          # 真实承接流量的后端数量

对症做法:

yaml
# 1. 监控指标换成 Ready 比例与 restartCount,而不是 Pod phase
rules:
  - alert: ServiceReadyRatioLow
    expr: |
      sum(kube_pod_status_ready{condition="true", namespace="prod", label_app="order-service"})
      / sum(kube_pod_labels{label_app="order-service"}) < 0.8
    for: 2m
  - alert: PodRestartStorm
    expr: increase(kube_pod_container_status_restarts_total{label_app="order-service"}[10m]) > 3
    for: 5m
text
2. 值班手册里写清“三个层级各看什么”:
   phase 看整体阶段,reason 看为什么卡住,ready 看是否接流量。
3. restartCount 增长速度是比“有没有重启”更有用的信号:
   一次重启可能是抖动,每 5 分钟一次是 CrashLoop 退避周期。
4. CrashLoopBackOff 的第一现场是容器日志与退出码:
   kubectl logs --previous 看上一次崩溃的输出,137 是内存 OOM、1 是应用异常。

复盘要点:判据是把所有服务级告警从“Pod 状态”改成“Ready 比例 + endpoints 数量”,并保留 restartCount 增速告警。这条改动的价值在“无可用实例但看起来正常”这类误报上——它是发布与变更期间最容易致命的假绿。

二、Java 应用启动 90 秒,被 liveness 反复杀死 ​

现象:一个 Spring Boot 服务迁移到新集群后进入死循环:Pod 起来约 30 秒被杀,重启次数不断累加,日志显示启动流程每次都停在同一处;运维误判为“应用启动崩溃”。

排查:启动阶段与运行阶段用同一套探针阈值。

定位层取证据判定
探针配置是否有 startupProbe;liveness 的 initialDelaySeconds/periodSeconds/failureThreshold无 startupProbe 且窗口小于启动耗时即命中
实际启动时长Started ... in Xs 日志与 Pod 首次 ready 时间90s 远大于 30s 判定窗口
杀死原因kubectl describe pod 的 Liveness probe failed 事件与退出信号探针杀的,不是应用崩的
探针端点内容liveness 是否包含 DB/Redis 检查下游抖动会造成整批重启(另一类事故)
依赖耗时启动期是否同步拉配置/预热让启动时长不可控
yaml
# 用 startupProbe 保护慢启动,成功之前不执行 liveness/readiness
startupProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }
  periodSeconds: 5
  failureThreshold: 30          # 最多容忍 150s,覆盖冷启动 P99 并留余量
livenessProbe:
  httpGet: { path: /actuator/health/liveness, port: 8080 }   # 只回答“进程是否该重启”
  periodSeconds: 10
  failureThreshold: 6
readinessProbe:
  httpGet: { path: /actuator/health/readiness, port: 8080 }  # 依赖就绪、预热完成放这里
  periodSeconds: 5
  failureThreshold: 2
text
1. 不要用巨大的 initialDelaySeconds 掩盖真实启动时间:
   它把启动期变成一个固定空白,应用变快也不会更早接流量,变慢则直接失败。
2. liveness 只放 ping(进程存活);依赖检查放 readiness,
   并判断“依赖挂了是否所有实例都该下线”。
3. 预热完成才能 ready:自定义 HealthIndicator 加入 warmup 状态,
   归入 readiness 组,避免冷实例被放进满流量。
4. startupProbe 的上限要有依据:
   failureThreshold × periodSeconds ≥ 冷启动 P99 × 1.5(经验值,需校准)。

复盘要点:判据是“liveness 触发的重启次数应长期为 0”,出现即说明探针阈值或应用健康检查内容有错。同时把“Pod 创建到首次 ready 的时长”做成服务级指标,它是容量规划与故障恢复时间的输入,不该靠 initialDelay 猜。探针与服务上下线的配合见 优雅上下线与启动检查。

三、CreateContainerConfigError 被当成应用 Bug 排查了两小时 ​

现象:某次发布后 Pod 一直不进入 Running,kubectl get pods 显示 CrashLoopBackOff/Error,开发查了应用日志毫无输出(应用根本没启动)。最终原因是 ConfigMap 里删掉了一个 key,而容器 envFrom 引用它并配了必填的 env 项——这是容器创建阶段就失败。

排查:先确定失败发生在哪一层,再看具体原因。

失败层典型 reason判定证据
镜像拉取ImagePullBackOff、ErrImagePulldescribe pod 的 Events + imagePullSecrets/标签是否存在
配置/密钥CreateContainerConfigErrorEvents 里 configmap "x" not found / key not found
存储卷FailedMount、FailedAttachVolumePVC 状态与存储类可用区
调度Pending + 0/N nodes are available见 K8s 核心概念与调度
容器启动退出码 1 / 137应用日志(--previous)或 OOMKilled
探针Liveness probe failed事件时间线与探针配置
bash
kubectl describe pod <pod> | sed -n '/Events:/,$p'        # 第一现场,不要先看应用日志
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0]}' | jq
kubectl get events --field-selector involvedObject.name=<pod> --sort-by=.lastTimestamp | tail -15

对症做法:

yaml
# 1. 可选引用与必填分开:缺 key 时不要让容器创建失败
env:
  - name: FEATURE_FLAG
    valueFrom:
      configMapKeyRef: { name: app-config, key: feature.flag, optional: true }

# 2. 配置与消费方一起发布:ConfigMap 变更走同一 PR / 同一发布单
# 3. 关键配置在应用启动期显式校验,失败信息要能指回缺哪个 key(而不是靠 K8s 事件猜)
text
4. 发布顺序:先应用新配置(向后兼容),再发布使用新配置的版本;
   回滚时反过来。配置删除类变更必须先确认无人引用。
5. 镜像类问题与配置类问题的定位手段不同:
   前者看 Events 里的拉取错误与 tag 是否存在,后者看 not found 与 optional 设置。
   混在一起排查就会像本例一样查两小时应用日志。

复盘要点:判据是“发布流水线里包含 ConfigMap/Secret 的引用一致性检查”——把 Pod 卡在创建层的错误挡在部署阶段;另外值班流程要固化“先看 describe pod 的 Events,再看应用日志”,因为容器没起来时应用日志一定是什么都没有,从这里入手会绕远路。多集群场景下的配置分发与校验见 多集群容器平台。

最小上手 ​

先看一个 kubectl get 只显示等待原因、phase 仍是 Pending 的例子:

bash
kubectl run bad-image --image=nginx:does-not-exist
kubectl get pod bad-image
# STATUS: ErrImagePull 或 ImagePullBackOff

kubectl get pod bad-image -o jsonpath='{.status.phase}{" | "}{.status.containerStatuses[0].state.waiting.reason}{"\n"}'
# → Pending | ErrImagePull
kubectl describe pod bad-image | grep -A5 Events
kubectl delete pod bad-image

再验证 liveness 失败的真实动作:

bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: liveness-demo
spec:
  containers:
    - name: web
      image: nginx:1.27
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c",
              "echo OK > /usr/share/nginx/html/health; sleep 20; echo FAIL > /usr/share/nginx/html/health"]
      livenessProbe:
        httpGet: {path: /health, port: 80}
        periodSeconds: 5
        failureThreshold: 2
EOF

kubectl get pod liveness-demo -w
# READY 1/1 → 0/1,RESTARTS 增长;Pod 名称和 IP 不变
kubectl describe pod liveness-demo | grep -A5 Events
# → Liveness probe failed ... will be restarted
kubectl delete pod liveness-demo

生产动作 ​

  • 慢启动应用必配 startupProbe:窗口设为 P99 启动时间,例如 periodSeconds: 10、failureThreshold: 30 给 300s;不要把 initialDelaySeconds 当成启动保护,它只延迟第一次探测。
  • liveness 只判「死没死」:不要用它检查下游依赖或慢查询,否则下游抖动会让所有容器一起重启,放大故障。
  • readiness 用 failureThreshold: 1:快速摘流量,避免坏实例继续接请求。
  • 排障顺序:kubectl get pod -o wide → kubectl describe pod 看 Events → kubectl logs --previous 看上次退出 → 需要时 kubectl exec 进容器手动请求探针路径。
  • 看退出码:OOMKilled 是内存超 limits;Error + 非零退出是应用自己退出;Completed 是正常结束但被 restartPolicy: Always 反复拉起。

边界 ​

  • Running 不等于可用:phase 只看容器是否在运行,不看 readiness;面板上要同时看 READY 和 Endpoint。
  • readiness 不能修复进程:它只摘流量,死锁的容器仍然占着资源,需要 liveness 或人工介入。
  • liveness 阈值太紧会制造故障:GC 停顿、慢请求都可能超过 timeoutSeconds,连续失败就重启,形成「越重启越慢」的循环。
  • startupProbe 只保护启动阶段:启动成功后它不再执行,运行期的慢响应要靠 liveness 的合理阈值或独立健康检查。
  • exec 探针有开销:每次探测都在容器里起进程,高频探测会消耗 CPU;能用 HTTP 就用 HTTP。

讲清楚 ​

Pod 状态要分三层看:phase 是 Pod 的大阶段,只有 Pending、Running、Succeeded、Failed、Unknown;reason 是 Pod 被驱逐或失联的原因,比如 Evicted;容器的 waiting.reason 才是 CrashLoopBackOff、ImagePullBackOff 这类具体原因。kubectl get pods 的 STATUS 列把它们混在一起,排障时要用 jsonpath 拆开。

三种探针的动作不同:startup 失败重启,readiness 失败只摘流量,liveness 失败重启容器但不删 Pod。重启有 10s 起步、最长 5 分钟的退避。

最常见的配错是把 liveness 当业务健康检查。下游一抖,所有容器一起重启,故障被放大。liveness 只该判断进程是否死锁或无响应。

把一门技术讲透到既懂也会用