Kubernetes 日常排障实战:从状态机到事件流的系统化 Debug 方法论

发布时间:2026/10/15 15:33:59
Kubernetes 日常排障实战:从状态机到事件流的系统化 Debug 方法论
一、引言做 Kubernetes 一线运维的人都有同感真正花在写 YAML上的时间远少于花在为什么没按我写的来上的时间。 声明式系统的好处是你只管说想要什么代价是出问题时你得去猜它卡在哪一步。Kubernetes 排错有两大难点组件多、链路长一个 Pod 从 kubectl apply 到真正跑起来要经过 API Server认证/鉴权/准入→ etcd持久化→ Scheduler决策→ kubelet执行→ 容器运行时CRI→ 网络插件CNI→ kube-proxy/DNS。任何一环出问题现象都可能在最末端才暴露。信息分散错误不会集中弹在你脸上而是散落在 Pod status、Events、kubelet 日志、容器日志、审计日志 里。本文的目标不是罗列XXX 报错的解决办法那种列表你记不住也搜得到而是给你一套可复用的方法论——先理解 K8s 是怎么失败的再带你把最高频的几类报错逐一复现、定位、修复最后沉淀一个排错工具箱和决策树。排错的底层逻辑只有一句话找到期望状态与实际状态之间的偏差并定位产生偏差的那一层。二、原理解析先搞懂 K8s 是怎么失败的不把机制讲清楚排错就只能靠试。这一章是后面所有场景的语法基础。2.1 声明式系统的排错本质K8s 是声明式的kubectl apply 写进去的是 Spec期望状态各控制器不断做 Reconcile调和把 Status实际状态 向 Spec 靠拢。你写的 Spec ──▶ API Server ──▶ etcd ──▶ 各 Controller/Scheduler/kubelet│└──▶ 持续 Reconcile ──▶ 实际 Status所以出错了通常意味着 某一段 Reconcile 卡住了或拒绝了。排错的第一步永远是判断这次失败卡在哪一环 后续每一类报错本质都对应一条特定的卡点。2.2 Pod 生命周期状态机Pod 的状态来自 kubectl get pod 的 STATUS 列不是随便显示的它来自 Pod 真实的状态机。把这张图刻在脑子里读状态就不再靠猜kubectl apply│▼┌──────────┐ 调度成功 ┌──────────────────┐│ Pending │─────────────▶│ ContainerCreating││ │ └────────┬─────────┘│ (等待调度/ │ │ 镜像拉取启动│ 资源/PVC)│ ▼└──────────┘ ┌──────────────────┐│ │ PodInitializing ││ (调度失败) └────────┬─────────┘▼ │┌──────────────────┐ ▼│ 长期 Pending │ ┌────────────────┐│ (需排查调度) │ │ Running │◀───┐└──────────────────┘ └───┬────────┬───┘ ││ │ │ 重启探针失败/退出│ │存活 │▼ ▼ │┌────────────┐ ┌─────────┐ ││ Succeeded │ │CrashLoop│─┘│ (正常结束) │ │ BackOff │└────────────┘ └─────────┘│▼┌────────────────┐│ ImagePullBackOff│ (镜像拉取失败)│ OOMKilled/Error │ (运行期崩溃)└────────────────┘常见状态的一句话成因速查状态含义通常卡在Pending已接受但还没调度/启动调度层资源、污点、PVCContainerCreating调度成功正在拉镜像/建沙箱节点/运行时层镜像、volume、CNICrashLoopBackOff容器反复启动即退出应用层启动报错、配置、依赖、OOMImagePullBackOff镜像拉不下来仓库认证、镜像名/Tag、网络OOMKilled内存超限被 kill属于 Error 子状态内存 limits 过低 / 泄漏Terminating正在删除但卡住finalizer、node 失联、volume 卸载Unknown节点失联状态上报不上来节点/网络层Running 但没流量探针未就绪探针配置错误或应用未 listen关键认知Running 不等于能用。 只有 readinessProbe 通过Pod 才会进 Service 的 Endpoint 拿到流量Running 但 readiness 不通过是最容易被忽略的一类假活。2.3 三道防线决定了错误长什么样按失败发生的时间顺序可以粗略分成三层错误现象天然不同① 准入层API Server 内PSA / Kyverno / RBAC│ 失败特征kubectl apply 直接报错被拒Pod 根本没创建▼② 调度层Scheduler资源、污点、亲和、PVC│ 失败特征Pod 处于 PendingEvents 里有 FailedScheduling▼③ 节点/运行时/网络层kubelet CRI CNI失败特征ContainerCreating / CrashLoop / Running但不通这条线也正好呼应本仓库前面几篇PSA 拒绝见《Pod 安全》镜像验签拒绝见《镜像供应链》RBAC 403 见《RBAC》网络不通见《NetworkPolicy》Pending 见《调度器剖析》。本文后面会把这些被拒/阻断类问题统一收口为场景 6。2.4 Events 与 Probe你的两个望远镜1Events——排错的第一现场Events 是集群内各组件向 API Server 打的带时间戳的流水账几乎每个异常都会在这里留下线索。谁产生Scheduler、kubelet、控制器、准入插件。看什么kubectl describe pod 底部的 Events: 段。坑Events 有存活窗口默认约 1 小时且只保留最近若干条老问题要趁早看或用 kubectl get events --sort-by.lastTimestamp 全局捞。级别Warning 才是异常Normal 是例行通知别被 Normal 干扰。2探针——决定活没活、通不通startupProbe启动慢的应用先靠它通过前不触发 liveness/readiness。livenessProbe失败 → 杀容器重启杀错了会雪崩。readinessProbe失败 → 摘流量但不重启不通过就不进 Endpoint。探针配置示例节选三个探头各司其职livenessProbe:httpGet: { path: /healthz, port: 8080 }initialDelaySeconds: 10 # 给应用留足启动时间periodSeconds: 10failureThreshold: 3 # 连续 3 次失败才杀readinessProbe:httpGet: { path: /ready, port: 8080 }periodSeconds: 5startupProbe: # 启动慢的服务必配避免被 liveness 误杀httpGet: { path: /healthz, port: 8080 }failureThreshold: 30periodSeconds: 5 # 允许最长 150s 启动经典翻车initialDelaySeconds 设太短 livenessProbe 太敏感 → 应用还没起来就被杀进入 CrashLoopBackOff 的死循环。这就是探针误杀。三、实战演示高频报错场景逐一复现与修复下面 6 个场景覆盖了日常 90% 的排错诉求。每个都按 现象 → 定位 → 修复 → 验证 展开。场景 1ImagePullBackOff / ErrImagePull镜像拉不下来现象kubectl get podNAME READY STATUS RESTARTS AGEweb-0 0/1 ImagePullBackOff 0 2m定位 —— 直接看 Events它几乎总会告诉你原因kubectl describe pod web-0 | sed -n ‘/Events:/,$p’Failed to pull image “registry.local/app:v2”: rpc error: code NotFound …or: pulling image … unauthorized: authentication requiredor: dial tcp: i/o timeout失败原因通常就四类镜像名 / Tag 拼错NotFound私有仓库未配 imagePullSecretsunauthorized仓库网络不通 / 域名解析失败timeout / no such host节点上已有该镜像但 digest 不匹配少见清本地缓存可解修复 —— 以私有仓库为例1) 先建拉取 secretkubectl create secret docker-registry regcred–docker-serverregistry.local–docker-usernameci --docker-password‘****’–docker-emailcicorp.local -n shop2) Pod/Deployment 里引用spec:imagePullSecrets:- name: regcredcontainers:- name: webimage: registry.local/app:v2 # 确认 tag 真实存在验证kubectl get pod web-0 -w # 状态从 ImagePullBackOff → Running 即成功排错口诀看到 ImagePullBackOff 先 describe 看 Events 的 Failed 原因别上来就改 YAML——80% 的情况下原因就在那一行。场景 2CrashLoopBackOff容器反复崩溃CrashLoopBackOff 是个包罗万象的状态它本身不是病根只是说容器启动后很快退出kubelet 在退避重试。真正的病根在容器退出码和日志里。定位步骤1) 看退出码与最近重启次数kubectl get pod app-0STATUS CrashLoopBackOffRESTARTS 在快速增长2) 看当前/上一次容器日志–previous 看崩溃那次的kubectl logs app-0 --previous常见配置文件缺失、连不上数据库、端口被占用、panic stack3) 看 Events 里的退出原因是否 OOMkubectl describe pod app-0 | grep -A3 -i ‘OOMKilled|Back-off|Error’子场景 A应用启动即报错配置/依赖日志里通常直接打印堆栈或 “connection refused”。修复修正 ConfigMap/Secret、确认依赖服务就绪注意启动顺序必要时用 initContainers 等依赖、修正环境变量。子场景 BOOMKilled内存超限kubectl describe pod app-0 | grep -i oomReason: OOMKilledExit Code: 137说明容器用了超过 memory limit 的内存被 cgroup 杀掉。排查真实用量进节点看该容器实际 RSS需节点权限kubectl debug node/ -it --imagebusybox – chroot /host crictl stats修复resources:requests: { memory: “256Mi”, cpu: “100m” }limits: { memory: “512Mi”, cpu: “500m” } # 内存 limit 调到真实峰值之上重要OOMKilled 调的是 limits.memory不是 requests。 只加 requests 不顶用。若调大 limit 后仍频繁 OOM多半是应用内存泄漏要从代码层定位heap profile而非一味加内存。子场景 C探针误杀liveness 太严表现为容器其实能跑但 RESTARTS 持续增加日志显示业务正常却被重启。看 describe 的 Last State / Events 里 Unhealthy: Liveness probe failed。修复放宽 initialDelaySeconds、failureThreshold或改用 startupProbe 兜住慢启动。验证kubectl get pod app-0 -w # RESTARTS 停止增长、STATUS 变 Runningkubectl logs app-0 # 业务日志正常输出场景 3Pod 一直 Pending调度不出去这一场景在《调度器剖析》里讲透了机制这里给最短排错路径。kubectl describe pod stuck-0 | grep -A20 ‘Events:’0/5 nodes are available: 3 Insufficient cpu, 2 node(s) had taint …常见卡点对应表Events 关键字含义修复方向Insufficient cpu/memory节点资源不足以满足 requests加节点 / 降 requests / 删闲置负载node(s) had taint … that … did not tolerate节点有污点不容忍加 tolerations 或换节点node(s) didn’t match Pod’s nodeSelectornodeSelector 无匹配放宽 selector 或打标签persistentvolumeclaim “x” not found/boundPVC 未绑定先修 StorageClass / PV1 node(s) had volume node affinity conflict本地卷地域不匹配调整拓扑 / 用延迟绑定快速佐证kubectl get nodes -o custom-columnsNODE:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memorykubectl describe node | sed -n ‘/Allocated resources/,/Events/p’场景 4Running 但访问不通Service / 网络Pod 是 Running 且 READY 1/1但业务访问不到。这类问题最容易让人抓狂按下面顺序查1) Service 的 Endpoint 是否为空空 selector 没匹配到 Podkubectl get endpoints web-svcNAME ENDPOINTS AGEweb-svc 10.244.1.23:8080 5m ← 有值才正常 就是问题2) 对比 Service 的 selector 和 Pod 的 labels大小写/连字符最易错kubectl get svc web-svc -o jsonpath‘{.spec.selector}’kubectl get pods --show-labels3) 端口是否对得上svc.port.targetPort 必须指向容器真正 listen 的端口kubectl get svc web-svc -o jsonpath‘{.spec.ports[0].targetPort}’4) 临时从集群内另起一个 Pod 直接打 Pod IP绕过 Service 验证应用本身kubectl run netshoot --rm -it --imagenicolaka/netshoot – curl -s 10.244.1.23:8080三类典型成因selector 标签错配最常见Endpoint 为空。核对 Service selector 与 Pod labels 完全一致K8s label 不认大小写模糊。targetPort 写错Service 转发到错误端口连接被拒。网络策略阻断呼应《NetworkPolicy》Pod 虽 Running但 Ingress 被默认拒绝策略挡掉。查kubectl get networkpolicy -n shop若启用了 default-deny需显式放通来自同 ns 或特定前端的流量排错顺序建议先确认 Endpoint 非空 → 再确认端口对得上 → 再用 netshoot 直连 Pod IP 排除应用本身 → 最后查 NetworkPolicy/DNS。这样能快速把网络问题和应用问题切开。场景 5节点 NotReady 与节点级排错当 kubectl get nodes 出现 NotReady该节点上 Pod 会失联、状态变 Unknown调度也会避开它。kubectl get nodesnode-a NotReady 120d v1.28.x看节点事件与条件kubectl describe node node-a | sed -n ‘/Conditions:/,/Addresses/p’节点级排查需 SSH / 节点权限kubelet 是否活着systemd 托管时journalctl -u kubelet -n 100 --no-pagersystemctl status kubelet容器运行时是否健康containerd 为例crictl info | head -n 20systemctl status containerd节点资源是否耗尽磁盘满会导致镜像/日志写入失败df -h /var/lib/containerd /var/log常见成因kubelet 进程挂掉/配置错、容器运行时崩溃、节点磁盘/内存/inode 耗尽、节点与控制面网络分区、kubelet 证书过期。场景 6准入被拒收口前面安全系列这类问题特征最明显kubectl apply 时直接报错Pod 根本没创建出来——说明卡在 2.3 节的准入层。aPSAPod Security Admission拒绝 —— 呼应《Pod 安全》kubectl apply -f bad.yamlError from server (Forbidden):violates PodSecurity “restricted:latest”:allowPrivilegeEscalation ! false (container “app” must set …)unrestricted capabilities (container “app” must drop ALL)修复按提示补齐 securityContextdrop ALL、runAsNonRoot、allowPrivilegeEscalationfalse 等。b镜像未签名被 Kyverno 拒 —— 呼应《镜像供应链》kubectl apply -f unverified.yamlError from server (Forbidden):policy “require-signed-images” validation failure:image registry.local/app:v2 is not signed修复先 cosign sign 再用 cosign verify 确认或用 audit 模式临时放通。cRBAC 403 —— 呼应《RBAC》kubectl get secrets -n kube-systemError from server (Forbidden):User “system:serviceaccount:shop:default” cannot list resource “secrets” …修复用 kubectl auth can-i --as… list secrets -n kube-system 验证按需补 Role/RoleBinding。通用定位技巧 —— 想看完整的准入拒绝响应加 verbositykubectl apply -f x.yaml -v8 # 在请求/响应里能看到 Webhook 返回的完整拒绝理由四、排错工具箱与通用方法论重点光记场景不够真正值钱的是一套固定动作。下面是我建议的肌肉记忆。4.1 kubectl 四件套任何异常都从这四个开始① 看状态加 -o wide 看节点/IP-w 看变化kubectl get pod -o wide -w② 看详情Events 是金矿永远先看这里kubectl describe pod③ 看日志-p 看上一次崩溃的-c 指定多容器kubectl logs --previous -c④ 进容器确认文件/配置/网络真的那样kubectl exec -it – sh顺序口诀get → describe → logs → exec。先看整体状态再看 Events 定位大方向再看日志看应用层面最后进容器实证。4.2 kubectl debug没有 shell 也能排错很多生产镜像为了瘦身没有 sh/bash传统 exec 进不去。kubectl debug 用 Ephemeral Containers临时容器注入一个排错镜像到运行中的 Pod给运行中的 Pod 注入一个排错容器共享同一 PID/网络命名空间kubectl debug -it --imagebusybox --target进去后可直接看目标容器的进程、/proc、网络栈节点级排错以特权容器寄生到节点上kubectl debug node/ -it --imagebusybox – chroot /host之后可查 /host/var/log、/host/etc/kubernetes 等4.3 全局 Events 与时间线单 Pod 的 Events 不够用时捞全局并按时间排序定位同一时刻一大批 Pod 出问题的系统性故障kubectl get events --sort-by.lastTimestamp -A | tail -n 40kubectl get events -A --field-selector typeWarning # 只看 Warning4.4 排错决策树一图速查Pod 状态├─ Pending ──────────────▶ describe 看 FailedScheduling ─▶ 场景3资源/污点/PVC├─ ImagePullBackOff ─────▶ describe 看 Failed 拉取原因 ──▶ 场景1secret/Tag/网络├─ ContainerCreating ────▶ describe 看 mount/CNI 报错 ──▶ volume/CNI/secret 挂载├─ CrashLoopBackOff ─────▶ logs --previous 看退出码 ────▶ 场景2应用/OOM/探针│ └─ OOMKilled? ──▶ 调大 limits.memory├─ Running 但 READY 0/1 ─▶ 查 readinessProbe / 应用 listen ─▶ 探针未通过├─ Running 但访问不通 ───▶ endpoints 是否空 → 端口 → netshoot 直连 → NP ─▶ 场景4├─ apply 直接被拒 ───────▶ 看 Forbidden 提示PSA/Policy/RBAC ─▶ 场景6└─ Unknown/节点 NotReady ▶ 查节点 kubelet/运行时/资源 ─────▶ 场景54.5 几个能救命的好习惯先 describe 再改 YAML80% 的错误原因在 Events 里明写着别盲改。区分起不来和起来但不对前者看调度/镜像/启动后者看探针/配置/网络。改完用 -v8 看原始响应特别是准入类拒绝完整理由只在 verbose 日志里。排错前先 kubectl get -o wide一眼看到节点、IP、重启次数往往省掉后面三步。Events 会过期问题发现后尽早看别等一小时后再去翻。五、总结Kubernetes 排错表面上是在跟各种 BackOff、Forbidden、Pending 搏斗本质上是在跟一个声明式系统期望 vs 实际的偏差搏斗。本文给你两样东西一份机制地图从状态机§2.2到三道防线§2.3让你读到任何异常状态都能立刻判断卡在哪一环。一套固定动作 场景库get → describe → logs → exec 四件套§4.1是永远的起点6 个高频场景§三覆盖了日常九成诉求决策树§4.4把两者串成可执行的路径。也正因为 K8s 的错误是分层暴露的本文刻意把前面几篇的排错线索收口为场景 6准入被拒当你看到 kubectl apply 直接 Forbidden那不是 Pod 的问题而是 PSAPod 安全/ Kyverno 验签镜像供应链/ RBAC授权 在准入层把不合规的负载挡在了门外——对应本仓库的《Pod 安全》《镜像供应链》《RBAC》三篇。把这几篇和本文连起来看就是一张从提交清单到Pod 真正健康运行的完整排错全景图。最后一句忠告不要去背特定错误的解决办法要去练定位偏差在哪一层的肌肉记忆。 方法对了新错误自然也能解。附录本文与系列其他文章的索引关系现象本文场景关联深度文章镜像拉取失败场景 1—容器崩溃 / OOM场景 2—长期 Pending场景 3《调度器全链路剖析》服务访问不通场景 4《NetworkPolicy 网络微隔离》节点失联场景 5—PSA 拒绝场景 6《Pod 安全上下文与 PSA》镜像验签拒绝场景 6《镜像供应链安全》RBAC 403场景 6《RBAC 最小权限》完全跑不通第一个应用—《从零跑通第一个应用》