
刚接手 Kubernetes 那阵我对kubectl get pod的输出基本是无条件信任的STATUS 是 RunningREADY 是 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 被标记删除的时间戳STATUSTerminatingmetadata.deletionGracePeriodSeconds优雅删除宽限期STATUSTerminatingstatus.phasePod 生命周期的大阶段STATUS基础值Pending/Running/Failed 等status.reason节点或控制器上报的具体原因STATUS如 Evicted、NodeLoststatus.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 是 RunningREADY 却是 0/1。容器确实在跑但 readinessProbe 一直失败kubelet 只负责执行探针并同步 Ready 状态并不会因为你业务没就绪就把 STATUS 改成别的。所以 READY 列本质上是一个业务是否被判定为可服务的开关聚合而不是进程是否存活的聚合。真正决定这个开关的是 Kubelet 的探针机制这也是为什么很多正式环境强制要求核心服务必须配 readinessProbe 的原因——没有探针一个依赖下游超时的服务依然会对外显示 1/1但请求进来全挂在等待上。2.3 readinessGatesPod 级别的另类就绪门控除了容器自身的 ReadyPod 还可以通过spec.readinessGates声明额外的就绪条件。比如spec: readinessGates: - conditionType: example.com/healthy如果声明了这个 gatePod 的 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/MN已成功退出数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只要第一个容器在 RunningSTATUS 依然可能是 Running。这种多容器 Pod 的状态显示盲区是排障时最坑的细节。容器处于 Running 但 readiness 失败时STATUS 不会显示 NotReady。kubectl 没有为readiness 失败单独设计一种显示值所以 Running 0/1 是同时出现的。很多人看到 Running 就觉得 OK其实问题就藏在这里。3.2 各种 STATUS 显示值到底对应什么底层状态我整理了一张速查表基本覆盖日常最常见的 STATUS 值STATUS 显示底层状态常见诱因PendingphasePending没有其他 reason 覆盖调度失败、资源不足、等待存储ContainerCreating第一个容器 Waiting reasonContainerCreating镜像拉取、sandbox 创建、CNI 配置ErrImagePull容器 Waiting reasonErrImagePull镜像不存在、仓库认证失败、地址错误ImagePullBackOff容器 Waiting reasonImagePullBackOff镜像拉取连续失败后的退避阶段CrashLoopBackOff容器 Waiting reasonCrashLoopBackOff容器启动后反复崩溃kubelet 退避重启Running第一个容器处于 Running 状态正常或者探针未配置/探针失败但容器存活Completed容器 Terminated reasonCompleted 且 phaseSucceededJob 正常结束Error容器 Terminated reasonError或 phaseFailed启动脚本出错、探针失败导致重启等OOMKilled容器 Terminated reasonOOMKilled内存超过 cgroup 限制被内核杀掉TerminatingdeletionTimestamp 已设置执行了 delete等待优雅退出或卡删除Unknown节点失联且 Pod 设置了 NodeLost 相关 reasonkubelet 长时间不上报状态Evictedstatus.reasonEvicted节点资源压力驱逐这个表里的值不是随便拍的它们要么直接来自containerStatuses[*].state.waiting.reason要么来自state.terminated.reason要么来自status.reason/status.phase。所以你会发现STATUS 列其实是一个第一个异常状态的快速入口——它的设计目标是让你一眼看出最值得关注的容器问题而不是告诉你整个 Pod 的健康全貌。3.3 一个补充STATUSRunning 但 READY0/1 的官方解释回到开头那个现象。容器状态机里有一个独立的维度叫State分为 Waiting、Running、Terminated 三态。readiness 是另一个独立的布尔维度。kubectl 在渲染 STATUS 列时看的是 State 相关字段在渲染 READY 列时看的是 Ready 布尔值。两边各行其是所以会出现容器 StateRunningReadyfalse - STATUSRunningREADY0/1容器 StateWaiting(reasonCrashLoopBackOff)Readyfalse - STATUSCrashLoopBackOffREADY0/1理解了这个两套维度各算各的设计很多所谓的诡异显示就都能解释通了。比如容器刚被 OOM 杀掉State 变成 Terminated reasonOOMKilled但 kubelet 正在重启它STATUS 可能短时间闪过 OOMKilled 然后变成 CrashLoopBackOff。4. 三层状态模型phase、conditions、containerState 的关系如果你觉得上一节的决策树有点绕那可能是因为还没建立 Pod 状态的整体模型。我习惯把 Pod 状态拆成三层来看排障时一层一层剥。4.1 第一层Pod Phase阶段status.phase是 Pod 生命周期最粗粒度的描述只有五个值PendingPod 已被 API Server 接受但还没有完成调度或者某些容器还没启动。RunningPod 已绑定节点所有容器已被创建且至少有一个容器在运行或正在启动。Succeeded所有容器都以退出码 0 正常结束且不会被重启。Failed所有容器都已终止至少有一个容器以非 0 退出码结束。UnknownAPI Server 长时间无法从 kubelet 获取 Pod 状态一般是节点失联。Phase 是一个控制面视角的粗分类你不会从这里知道镜像拉到了没有探针过了没有它太粗了。4.2 第二层Pod Conditions条件status.conditions是一组更细的布尔条件标准的有四个PodScheduledPod 是否已成功调度到节点。Initialized所有 Init 容器是否已执行完成。ContainersReady所有业务容器是否都 Ready。Ready整个 Pod 是否就绪等价于 ContainersReady 且所有 readinessGates 满足。每个 condition 都有statusTrue/False/Unknown、reason和message。查看方式kubectl get pod pod-name -o jsonpath{.status.conditions[*].type}{.status.conditions[*].status}{\n}或者看 YAMLkubectl 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容器正常退出退出码 0Error容器异常退出除此之外lastState字段很有价值。它记录的是容器上一次的状态快照。当容器处于 CrashLoopBackOff 时当前 state 多半是 waiting但上一次的 terminated 信息里能看到 exitCode 和 OOMKilled 标志这是定位崩溃根因的钥匙。kubectl get pod pod-name -o json | jq .status.containerStatuses[0].lastState三层之间是递进关系容器 State 决定容器的 Ready 和 Phase 的走向容器 Ready 聚合出 ContainersReadyContainersReady 加上 readinessGates 得出 Pod Ready。所以当你看到 STATUS 列异常时往下一层找容器 State 的 reason往往比盯住 STATUS 文案更高效。5. 从这两列出发的实战排障链路知道数据来源之后排障思路就清晰了。下面按最常见的 5 个状态场景给出一步步的排查命令链。5.1 STATUS 是 ContainerCreatingREADY 是 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 是 CrashLoopBackOffREADY 是 0/1这个状态说明容器已经启动过但反复崩溃kubelet 进入退避。核心动作是看两样东西# 看上一次退出时的日志 kubectl logs pod-name --previous # 看终止时的退出码和 reason kubectl get pod pod-name -o json | jq .status.containerStatuses[0].lastState.terminated退出码很能说明问题127启动命令或依赖找不到检查 image 里的可执行文件路径。137SIGKILL通常是 OOMKilled或者被系统 cgroup 杀掉的信号检查内存 limit 和实际占用。143SIGTERM常见于探针失败触发 kill或者业务收到终止信号后没正常退出。退出码是 0 但仍在重启大概率是 livenessProbe 失败kubelet 认为容器不健康主动重启。这时候要去看探针配置kubectl get pod pod-name -o json | jq .spec.containers[0].livenessProbe5.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(.typeReady) # 手动模拟探针请求按你探针配置的协议来 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-period0 --force但要清楚强删可能造成容器进程残留、卷锁不释放等后遗症生产环境慎用。更稳妥的方式是先查 finalizer 归属等对应 controller 清理或者先把节点的问题处理掉。5.5 STATUS 是 Pending一直没有 RunningPending 最常见的是调度阶段就出问题。重点看调度事件kubectl describe pod pod-name | grep -A 20 Events kubectl get events --field-selector involvedObject.namepod-name --sort-by.lastTimestamp如果事件里持续刷FailedScheduling跟着 message 走常见的几类Insufficient cpu/memory节点资源不够扩容或减小 request。node(s) had taint节点有污点Pod 没有对应容忍。didnt 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[?(.typeReady)].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/5READY 一直是0/1持续十几分钟。这时候别急着重启或强删先看 Init 容器日志kubectl logs pod-name -c init-container-nameSTATUS 列显示 Unknown 的时候绝大多数情况是节点失联而不是 Pod 本身出问题。先去看节点状态kubectl get node kubectl get node node-name -o json | jq .status.conditions[] | select(.typeReady)节点 NotReady 会连带一堆 Pod 显示 Unknown 或 Terminating这时候先恢复节点再处理 Pod 状态。版本差异也要留个心眼。不同 Kubernetes 小版本之间kubectl 的打印逻辑偶尔会有细微调整比如原生 sidecar 容器K8s 1.28的 RESTARTS 统计方式、初始化阶段 READY 的分母算法都随版本演进改过。遇到和文档描述不一致的输出优先看当前 kubectl 对应版本的源码而不是怀疑自己的集群坏了。还有一个老生常谈但我必须再提一次的建议不要把 STATUS 和 READY 这两列当成业务健康度的最终答案。它们的设计目标是快速概览不是健康证明。真正判断服务能不能接流量的指标应该是Pod 的 Ready condition 是否为 TruePod 是否出现在 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% 的疑问。