☰
kubectl get pod状态解密:READY与STATUS列的计算逻辑与排障实战
2026/9/26 16:47:58 网站建设 项目流程

刚接手 Kubernetes 那阵,我对kubectl get pod的输出基本是无条件信任的:STATUS 是 Running,READY 是 1/1,就觉得这 Pod 稳了。直到有一次线上事故,所有 Pod 看起来全是 Running + 1/1,结果业务错误率猛涨,最后定位到根因是进程活着但业务线程池已经挂了,而那个服务压根没配就绪探针。从那时候起我开始较真:kubectl get pod里的 READY 和 STATUS 这两列,到底是从哪来的?

先说结论:它们都不是 API Server 里某个现成字段的直接透传,而是 kubectl 客户端把 Pod 对象里一堆状态字段汇总之后,按照一定优先级当场算出来的展示值。这篇文章我带你把这套计算逻辑彻底拆开——分别看它们读的是哪些底层字段、STATUS 列各种显示值背后的决策树是什么、以及怎么利用这套机制把排障效率提上去。适合所有被0/1、CrashLoopBackOff、ContainerCreating折磨过的 K8s 使用者和运维同学。

1. 先把 kubectl get pod 的输出和底层数据对上号

kubectl get pod默认展示的列是 NAME、READY、STATUS、RESTARTS、AGE。加-o wide之后会多出 IP、NODE、NOMINATED NODE、READINESS GATES 这几列。但不管显示几列,它们的源头都是同一个东西:API Server 返回的 Pod 对象 JSON。

想看到完整的原始数据,最直接的方式是:

kubectl get pod <pod-name> -o json

这段 JSON 里包含一个庞大的status结构。我们只挑和本文相关的字段列出来:

JSON 字段含义影响哪一列
metadata.deletionTimestampPod 被标记删除的时间戳STATUS(Terminating)
metadata.deletionGracePeriodSeconds优雅删除宽限期STATUS(Terminating)
status.phasePod 生命周期的大阶段STATUS(基础值,Pending/Running/Failed 等)
status.reason节点或控制器上报的具体原因STATUS(如 Evicted、NodeLost)
status.conditions[]Pod 级别的条件集合,包括 Ready、ContainersReady、Initialized、PodScheduled与 READY 列间接相关
status.containerStatuses[]每个业务容器的详细状态:state、ready、restartCount、lastStateREADY 列、STATUS 列
status.initContainerStatuses[]每个初始化容器的详细状态READY 列(初始化期间)、STATUS 列(Init 相关)

这里面最关键的一个认知是:kubectl get 显示的是客户端渲染出来的视图,不是服务端存了一个"显示用字段"。你进 etcd 里翻 Pod 对象,找不到一个叫 "STATUS 列" 的东西,能找到的只有上面这些结构化字段。换句话说,kubectl get pod的打印逻辑本质上是"把 Pod 状态翻译成人话"的过程。

kubectl 的这套打印逻辑对应源码里的staging/src/k8s.io/kubectl/pkg/printers/internalversion/printers.go,核心函数是printPod。接下来我按 READY 和 STATUS 两条线分别拆。

2. READY 列:容器就绪状态的聚合统计

READY 列的格式是X/Y,比如1/1、0/2。很多人以为它表示"Pod 里有多少容器在运行",其实不准确,它统计的是容器的 Ready 字段,也就是就绪状态,和"进程是否活着"是两个维度。

2.1 X 和 Y 分别是怎么算出来的

Y 的分母很简单:spec.containers数组的长度,也就是普通业务容器的数量。注意,Init 容器不算在内,这个后面细说。

X 的算法大概是这样的:

readyContainers := 0 for _, cs := range pod.Status.ContainerStatuses { if cs.Ready { readyContainers++ } } totalContainers := len(pod.Spec.Containers) // 如果 Pod 还在初始化阶段,READY 列固定显示 0/N

所以1/1表示:Pod 里定义了 1 个业务容器,且这个容器的ContainerStatuses[*].Ready为 true。

这里有个容易踩的坑:Pod 在初始化的过程中,即使业务容器已经创建了,READY 也会显示0/N。因为初始化阶段有个硬逻辑——只要 Init 容器还没全部跑完,kubectl 就不去统计普通容器的就绪数,直接输出0/业务容器总数。所以看到0/2别急着下结论,先看 STATUS 列是不是Init:1/2或者PodInitializing。

2.2 容器 Ready 到底由谁决定

一个容器的 Ready 属性由 kubelet 维护,具体判断方式取决于你有没有配探针:

  • 没配任何探针:容器启动进入 Running 状态后,kubelet 会很快把 Ready 置为 true。进程活着就等于就绪。
  • 配了 readinessProbe(就绪探针):容器进程活着还不够,必须探针连续成功一次,Ready 才会变 true。探针一旦失败,Ready 立刻变 false。
  • 配了 startupProbe(启动探针):容器启动后先跑 startupProbe,成功之前 readinessProbe 根本不生效,Ready 保持 false。

这就解释了最常见的诡异现象:STATUS 是 Running,READY 却是 0/1。容器确实在跑,但 readinessProbe 一直失败,kubelet 只负责执行探针并同步 Ready 状态,并不会因为你业务没就绪就把 STATUS 改成别的。

所以 READY 列本质上是一个"业务是否被判定为可服务"的开关聚合,而不是"进程是否存活"的聚合。真正决定这个开关的是 Kubelet 的探针机制,这也是为什么很多正式环境强制要求核心服务必须配 readinessProbe 的原因——没有探针,一个依赖下游超时的服务依然会对外显示 1/1,但请求进来全挂在等待上。

2.3 readinessGates:Pod 级别的另类就绪门控

除了容器自身的 Ready,Pod 还可以通过spec.readinessGates声明额外的就绪条件。比如:

spec: readinessGates: - conditionType: "example.com/healthy"

如果声明了这个 gate,Pod 的 Ready condition 必须同时满足"所有容器 Ready"和"所有 gate 对应的 condition 为 True",才能算 Pod 就绪。这个机制常被用来让自定义控制器参与就绪判定,比如某些网络组件会在 CNI 配置没就绪时写一个 False 的 condition。

不过要注意一个细节:readinessGates 影响的是 Pod 的 Ready condition,不一定直接影响 READY 列的数字。因为 READY 列统计的是容器级containerStatuses[*].Ready,而不是 Pod 级 Ready 条件。你可能会看到 READY 1/1 但 Pod Ready condition 是 False 的情况,此时这个 Pod 依然不会进 Service 的 Endpoints。这种"显示正常但实际不服务"的场景,排查时很容易被忽略。

2.4 restarts 对 READY 的短暂影响

容器重启期间,旧容器被终止、新容器在创建,新容器的 Ready 会短暂为 false。所以 CrashLoopBackOff 里 Pod 的 READY 长期是 0/1,一旦某个周期内容器能撑够时间,READY 会短暂跳到 1/1,然后又掉回 0/1。这个变化用kubectl get pod -w看得非常清楚。

3. STATUS 列:一套客户端决策树算出来的"印象分"

STATUS 列是所有 K8s 新手最先看的列,也是误解最多的列。先说个让人意外的事实:STATUS 显示什么,并不是 API Server 告诉 kubectl 的,而是 kubectl 自己根据 Pod 的一堆字段按优先级现场决策的。

3.1 printPod 的决策优先级

把printPod的逻辑简化成一个决策树,大概是这样的:

1. 如果满足删除中条件: -> Terminating(节点失联场景可能显示 Unknown,而不是 Terminating) 2. 如果 Init 容器还没全部成功退出: -> 某个 init 容器失败:Init:Error / Init:ExitCode:x / Init:Signal:x -> 某个 init 容器正在运行:Init:N/M(N=已成功退出数,M=总数) -> 某个 init 容器在等待:PodInitializing 3. 上面都不满足时,遍历普通容器,但只看第一个容器: -> 如果它处于 Waiting 且 reason 非空:用这个 reason 当作 STATUS (比如 ContainerCreating、CrashLoopBackOff、ErrImagePull) -> 如果它处于 Terminated 且 reason 非空:用这个 reason 当作 STATUS (比如 Completed、Error、OOMKilled) 4. 如果上面都没拿到有意义的 reason,退回 Pod.Status.Reason,再退回 Pod.Status.Phase -> phase == Succeeded -> Completed -> phase == Failed -> Error -> phase == Running -> Running -> phase == Pending -> Pending

这个顺序里最容易被忽略的两点:

  • STATUS 列只看第一个普通容器。如果 Pod 里有多个容器,第二个、第三个容器即使 CrashLoopBackOff,只要第一个容器在 Running,STATUS 依然可能是 Running。这种多容器 Pod 的状态显示盲区,是排障时最坑的细节。
  • 容器处于 Running 但 readiness 失败时,STATUS 不会显示 NotReady。kubectl 没有为"readiness 失败"单独设计一种显示值,所以 Running + 0/1 是同时出现的。很多人看到 Running 就觉得 OK,其实问题就藏在这里。

3.2 各种 STATUS 显示值到底对应什么底层状态

我整理了一张速查表,基本覆盖日常最常见的 STATUS 值:

STATUS 显示底层状态常见诱因
Pendingphase=Pending,没有其他 reason 覆盖调度失败、资源不足、等待存储
ContainerCreating第一个容器 Waiting reason=ContainerCreating镜像拉取、sandbox 创建、CNI 配置
ErrImagePull容器 Waiting reason=ErrImagePull镜像不存在、仓库认证失败、地址错误
ImagePullBackOff容器 Waiting reason=ImagePullBackOff镜像拉取连续失败后的退避阶段
CrashLoopBackOff容器 Waiting reason=CrashLoopBackOff容器启动后反复崩溃,kubelet 退避重启
Running第一个容器处于 Running 状态正常,或者探针未配置/探针失败但容器存活
Completed容器 Terminated reason=Completed 且 phase=SucceededJob 正常结束
Error容器 Terminated reason=Error,或 phase=Failed启动脚本出错、探针失败导致重启等
OOMKilled容器 Terminated reason=OOMKilled内存超过 cgroup 限制被内核杀掉
TerminatingdeletionTimestamp 已设置执行了 delete,等待优雅退出或卡删除
Unknown节点失联且 Pod 设置了 NodeLost 相关 reasonkubelet 长时间不上报状态
Evictedstatus.reason=Evicted节点资源压力驱逐

这个表里的值不是随便拍的,它们要么直接来自containerStatuses[*].state.waiting.reason,要么来自state.terminated.reason,要么来自status.reason/status.phase。所以你会发现,STATUS 列其实是一个"第一个异常状态的快速入口"——它的设计目标是让你一眼看出最值得关注的容器问题,而不是告诉你整个 Pod 的健康全貌。

3.3 一个补充:STATUS=Running 但 READY=0/1 的官方解释

回到开头那个现象。容器状态机里有一个独立的维度叫State,分为 Waiting、Running、Terminated 三态。readiness 是另一个独立的布尔维度。kubectl 在渲染 STATUS 列时,看的是 State 相关字段;在渲染 READY 列时,看的是 Ready 布尔值。两边各行其是,所以会出现:

  • 容器 State=Running,Ready=false -> STATUS=Running,READY=0/1
  • 容器 State=Waiting(reason=CrashLoopBackOff),Ready=false -> STATUS=CrashLoopBackOff,READY=0/1

理解了这个"两套维度各算各的"设计,很多所谓的诡异显示就都能解释通了。比如容器刚被 OOM 杀掉,State 变成 Terminated reason=OOMKilled,但 kubelet 正在重启它,STATUS 可能短时间闪过 OOMKilled 然后变成 CrashLoopBackOff。

4. 三层状态模型:phase、conditions、containerState 的关系

如果你觉得上一节的决策树有点绕,那可能是因为还没建立 Pod 状态的整体模型。我习惯把 Pod 状态拆成三层来看,排障时一层一层剥。

4.1 第一层:Pod Phase(阶段)

status.phase是 Pod 生命周期最粗粒度的描述,只有五个值:

  • Pending:Pod 已被 API Server 接受,但还没有完成调度,或者某些容器还没启动。
  • Running:Pod 已绑定节点,所有容器已被创建,且至少有一个容器在运行或正在启动。
  • Succeeded:所有容器都以退出码 0 正常结束,且不会被重启。
  • Failed:所有容器都已终止,至少有一个容器以非 0 退出码结束。
  • Unknown:API Server 长时间无法从 kubelet 获取 Pod 状态,一般是节点失联。

Phase 是一个控制面视角的粗分类,你不会从这里知道"镜像拉到了没有""探针过了没有",它太粗了。

4.2 第二层:Pod Conditions(条件)

status.conditions是一组更细的布尔条件,标准的有四个:

  • PodScheduled:Pod 是否已成功调度到节点。
  • Initialized:所有 Init 容器是否已执行完成。
  • ContainersReady:所有业务容器是否都 Ready。
  • Ready:整个 Pod 是否就绪,等价于 ContainersReady 且所有 readinessGates 满足。

每个 condition 都有status(True/False/Unknown)、reason和message。查看方式:

kubectl get pod <pod-name> -o jsonpath='{.status.conditions[*].type}={.status.conditions[*].status}{"\n"}'

或者看 YAML:

kubectl get pod <pod-name> -o yaml | grep -A 12 conditions

如果你的 Pod 被写入了自定义 condition,比如某些控制器会写example.com/healthy,那它也会出现在这个数组里。Pod 的 Ready condition 是控制器、Service Endpoints 是否收录该 Pod 的依据之一。

4.3 第三层:Container State(容器状态)

容器层状态是排障时信息量最大的一层。每个容器在status.containerStatuses[]里都有一个state字段,三选一:

state := { waiting: { reason, message } running: { startedAt } terminated: { exitCode, reason, signal, startedAt, finishedAt } }

值得专门做一张 reason 速查表:

state 字段的 reason含义
ContainerCreatingkubelet 正在创建容器,可能卡在 sandbox 或卷挂载
CrashLoopBackOff容器启动后崩溃,kubelet 正在退避等待下一次重启
ErrImagePull首次拉取镜像失败
ImagePullBackOff镜像拉取连续失败,进入退避
CreateContainerConfigError容器配置有问题,比如 ConfigMap/Secret 不存在
StartError容器运行时启动失败
OOMKilled容器因超限被杀
Completed容器正常退出,退出码 0
Error容器异常退出

除此之外,lastState字段很有价值。它记录的是容器上一次的状态快照。当容器处于 CrashLoopBackOff 时,当前 state 多半是 waiting,但上一次的 terminated 信息里能看到 exitCode 和 OOMKilled 标志,这是定位崩溃根因的钥匙。

kubectl get pod <pod-name> -o json | jq '.status.containerStatuses[0].lastState'

三层之间是递进关系:容器 State 决定容器的 Ready 和 Phase 的走向,容器 Ready 聚合出 ContainersReady,ContainersReady 加上 readinessGates 得出 Pod Ready。所以当你看到 STATUS 列异常时,往下一层找容器 State 的 reason,往往比盯住 STATUS 文案更高效。

5. 从这两列出发的实战排障链路

知道数据来源之后,排障思路就清晰了。下面按最常见的 5 个状态场景,给出一步步的排查命令链。

5.1 STATUS 是 ContainerCreating,READY 是 0/1

先看完整事件,别急着看日志:

kubectl describe pod <pod-name> | tail -30 kubectl get pod <pod-name> -o json | jq '.status.containerStatuses[0].state.waiting' kubectl get events --sort-by=.lastTimestamp | tail -30

这个状态最常见的两个原因:

  • 创建 sandbox 失败,事件里会看到类似failed to create pod sandbox: rpc error: code = unknown desc = failed to create...的信息。这种一般是容器运行时或 CNI 网络插件的问题,需要上节点看 containerd 日志。
  • 镜像拉取慢或被限流,事件里会有Failed to pull image ...或exceeded retry limit, last status: 429 too many requests之类。429 这类限流在镜像仓库层很常见,排查时留意 registry mirror、拉取凭证是否配置。

5.2 STATUS 是 CrashLoopBackOff,READY 是 0/1

这个状态说明容器已经启动过,但反复崩溃,kubelet 进入退避。核心动作是看两样东西:

# 看上一次退出时的日志 kubectl logs <pod-name> --previous # 看终止时的退出码和 reason kubectl get pod <pod-name> -o json | jq '.status.containerStatuses[0].lastState.terminated'

退出码很能说明问题:

  • 127:启动命令或依赖找不到,检查 image 里的可执行文件路径。
  • 137:SIGKILL,通常是 OOMKilled,或者被系统 cgroup 杀掉的信号,检查内存 limit 和实际占用。
  • 143:SIGTERM,常见于探针失败触发 kill,或者业务收到终止信号后没正常退出。
  • 退出码是 0 但仍在重启:大概率是 livenessProbe 失败,kubelet 认为容器不健康主动重启。这时候要去看探针配置:
kubectl get pod <pod-name> -o json | jq '.spec.containers[0].livenessProbe'

5.3 STATUS 是 Running,但 READY 是 0/1

这就是我们前面讲的"假健康"状态。第一件事确认是不是探针问题:

# 查看就绪探针配置 kubectl get pod <pod-name> -o json | jq '.spec.containers[0].readinessProbe' # 查看容器就绪条件 kubectl get pod <pod-name> -o json | jq '.status.conditions[] | select(.type=="Ready")' # 手动模拟探针请求(按你探针配置的协议来) kubectl exec -it <pod-name> -- curl -sf http://127.0.0.1:8080/healthz

如果探针地址确实不通,那是应用没就绪,去查应用日志。如果应用地址通,但 READY 还是 0/1,检查一下探针的initialDelaySeconds和periodSeconds是否设置合理,以及探针是否写错了端口或路径。

另一个可能被忽略的情况是 readinessGates:如果 Pod 里定义了 gate,但对应的 condition 一直没被外部控制器置为 True,那 Pod Ready 也是 False。这时候去查:

kubectl get pod <pod-name> -o json | jq '.spec.readinessGates' kubectl get pod <pod-name> -o json | jq '.status.conditions[]'

5.4 STATUS 是 Terminating,一直在删除中

Pod 设置了 deletionTimestamp,但迟迟没消失。先用一条命令看清全貌:

kubectl get pod <pod-name> -o json | jq '{finalizers: .metadata.finalizers, deletionTimestamp: .metadata.deletionTimestamp, grace: .metadata.deletionGracePeriodSeconds, phase: .status.phase}'

常见的卡删除原因:

  • 容器主进程不响应 SIGTERM,超过 grace 期只能等 SIGKILL,但 SIGKILL 后还卡住一般是容器运行时问题。
  • 有 finalizer 挂在 metadata 上,需要对应的控制器清理完成后才会移除。
  • 底层卷还没完成 detach,或者节点失联导致状态更新停滞。

如果业务可以允许强删,再考虑兜底命令:

kubectl delete pod <pod-name> --grace-period=0 --force

但要清楚,强删可能造成容器进程残留、卷锁不释放等后遗症,生产环境慎用。更稳妥的方式是先查 finalizer 归属,等对应 controller 清理,或者先把节点的问题处理掉。

5.5 STATUS 是 Pending,一直没有 Running

Pending 最常见的是调度阶段就出问题。重点看调度事件:

kubectl describe pod <pod-name> | grep -A 20 Events kubectl get events --field-selector involvedObject.name=<pod-name> --sort-by=.lastTimestamp

如果事件里持续刷FailedScheduling,跟着 message 走,常见的几类:

  • Insufficient cpu/memory:节点资源不够,扩容或减小 request。
  • node(s) had taint:节点有污点,Pod 没有对应容忍。
  • didn't match node selector / affinity rules:调度约束无法满足。

如果调度已经成功,Pod 却还停在 Pending,那说明问题转移到卷挂载或容器创建阶段。这时再去翻 containerStatuses 的 message,看是不是FailedMount之类的问题。

5.6 用 custom-columns 直接拿原始状态

排障时如果不想被 STATUS 列的展示值误导,可以绕过它,直接输出原始字段:

kubectl get pods -o custom-columns=\ NAME:.metadata.name,\ PHASE:.status.phase,\ POD_READY:.status.conditions[?(@.type=="Ready")].status,\ CONTAINER_READY:.status.containerStatuses[*].ready,\ WAIT_REASON:.status.containerStatuses[*].state.waiting.reason,\ RESTARTS:.status.containerStatuses[*].restartCount,\ DELETION:.metadata.deletionTimestamp

这能让你同时看到容器级 Ready 数组、Pod 级 Ready 条件、等待 reason 和删除时间戳,多容器 Pod 的状态盲区也一览无余。

6. 平时最容易忽略的细节和我的实操建议

最后聊几个分散在细节里的经验,都是我在实际维护中踩过或见过别人踩的。

多容器 Pod 的 STATUS 列盲区前面提过,值得再强调一次:kubectl 在选"代表容器"时只挑第一个普通容器,排在后面的容器就算炸了,STATUS 也可能纹丝不动。所以凡是多容器 Pod,我习惯直接-o json配合 jq 看全量容器状态,或者用custom-columns把所有容器的 reason 都打出来。肉眼盯着 STATUS 单列是真的会看漏。

RESTARTS 列和 READY 列的关系也要理解。RESTARTS 是所有containerStatuses[*].restartCount的累加值。容器被 OOM 杀掉后重启,RESTARTS 会 +1;如果后端是 sandbox 或网络重建,可能连累 Init 容器也计一次重启。所以看到 RESTARTS 很高,别只想到业务崩溃,也要看是不是基础设施层在不断重建。

初始化阶段 READY 恒为 0/N 这种行为,偶尔会引发误判。尤其是 Init 容器比较多、单个 Init 执行很慢的 Pod,你可能会看到 STATUS 一直是Init:2/5,READY 一直是0/1,持续十几分钟。这时候别急着重启或强删,先看 Init 容器日志:

kubectl logs <pod-name> -c <init-container-name>

STATUS 列显示 Unknown 的时候,绝大多数情况是节点失联,而不是 Pod 本身出问题。先去看节点状态:

kubectl get node kubectl get node <node-name> -o json | jq '.status.conditions[] | select(.type=="Ready")'

节点 NotReady 会连带一堆 Pod 显示 Unknown 或 Terminating,这时候先恢复节点,再处理 Pod 状态。

版本差异也要留个心眼。不同 Kubernetes 小版本之间,kubectl 的打印逻辑偶尔会有细微调整,比如原生 sidecar 容器(K8s 1.28+)的 RESTARTS 统计方式、初始化阶段 READY 的分母算法,都随版本演进改过。遇到和文档描述不一致的输出,优先看当前 kubectl 对应版本的源码,而不是怀疑自己的集群坏了。

还有一个老生常谈但我必须再提一次的建议:不要把 STATUS 和 READY 这两列当成业务健康度的最终答案。它们的设计目标是"快速概览",不是"健康证明"。真正判断服务能不能接流量的指标,应该是:

  • Pod 的 Ready condition 是否为 True;
  • Pod 是否出现在 Service 的 Endpoints 列表里;
  • 应用自身的健康检查接口、错误率、延迟是否正常。

如果你有监控系统,我建议直接把kube-state-metrics暴露的kube_pod_status_ready、kube_pod_container_status_ready这些指标接入告警,比每天对着终端刷kubectl get pod靠谱得多。

这两年排查 Kubernetes 问题下来,我最大的体会是:状态列是给人快速浏览用的,但每个奇怪的显示背后都有一套非常机械的计算逻辑。搞清楚kubectl get pod里 READY 和 STATUS 的每一层来源之后,很多"玄学"其实都是可预测、可复现的。遇到问题别盯着状态文案猜,拉着原始字段看——-o json加 jq,基本能回答你 80% 的疑问。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询