K8s故障诊断:用状态机思维拆解Pod/Node/Control Plane三层失联

发布时间:2026/10/9 3:39:25
K8s故障诊断:用状态机思维拆解Pod/Node/Control Plane三层失联
简介本资源是一份面向Kubernetes运维工程师与云计算从业者的实战排错指南系统梳理k8s集群中连接异常、网络通信故障、节点异常、应用异常及Pod常见状态ContainerCreating/Pending/ImagePullBackOff等五大类典型问题的根因分析与处理流程。文档结构清晰按故障类型分章展开每章均包含现象识别、诊断命令链kubectl describe/logs、systemctl、日志追踪、定位逻辑与实操修复步骤如Node重置、Ceph插件安装、PV绑定排查等辅以关键截图与配置要点说明。资源为单个11.58MB的Word文档.docx内容详实、步骤可复现适合作为日常运维速查手册或进阶学习笔记。目前已有2879人下载学习覆盖Linux系统管理、容器编排与云原生运维等技术场景。1. K8s 故障处理不是“查日志重启”而是用状态机思维拆解 Pod、Node、Control Plane 的三层失联现场你刚收到告警生产环境一个 Deployment 的 Pod 持续 Pendingkubectl get pods显示0/1 RunningEvents里只有一行FailedScheduling你顺手kubectl describe node发现 Node 状态是Ready但Allocatable资源比Capacity少了一半你再kubectl get events --all-namespaces翻到 3 分钟前一条被刷走的 Warningnode.kubernetes.io/unreachable: Node became unreachable——可ping和ssh都通。这不是网络问题也不是配置写错而是 kubelet 与 apiserver 的心跳超时阈值、节点污点自动添加、以及调度器缓存未同步三者在 45 秒窗口内咬合出的“幽灵故障”。K8s 常见故障处理总结的本质不是堆砌命令清单而是建立一套可观测性锚点 → 状态跃迁定位 → 控制面责任归属的诊断链路。它适合两类人刚通过kubeadm init搭起集群却卡在CoreDNS CrashLoopBackOff的运维新人也适合已能手写 Admission Webhook但面对etcd leader change后 Service DNS 解析间歇性失败仍要翻 3 小时日志的老兵。本文不讲“K8s 是什么”只聚焦你敲下kubectl后该看哪一行输出、该查哪个组件日志、该调哪个参数、为什么调这个值而不是那个值——所有结论来自真实压测集群v1.26.11 containerd 1.7.13中复现并验证过的 7 类高频故障闭环路径。2. 从 Pod Pending 到 Running调度层故障的三阶定位法含 scheduler 日志解析技巧Pod 卡在 Pending 状态是 K8s 最高频入口故障但FailedScheduling这个事件本身只是结果不是原因。必须穿透调度器kube-scheduler内部状态而非仅依赖kubectl describe pod的摘要。2.1 先确认调度器是否真在工作检查 scheduler 的健康与选举状态很多团队在多 master 架构下误以为 scheduler 是无状态服务其实它依赖 etcd 实现 leader 选举。若当前 scheduler 实例不是 leader它根本不会处理任何 Pod 调度请求——此时kubectl get pods依然显示 Pending但 scheduler 日志里连一条Attempting to schedule pod都没有。# 查看 scheduler pod 是否 running 且 ready kubectl -n kube-system get pods -l componentkube-scheduler # 查看 scheduler leader 信息需有 etcd 访问权限 kubectl -n kube-system exec -it $(kubectl -n kube-system get pods -l componentkube-scheduler -o jsonpath{.items[0].metadata.name}) -- \ curl -s http://127.0.0.1:10259/metrics | grep scheduler_scheduler_leader_status # 输出示例scheduler_scheduler_leader_status{leaderip-10-0-1-100.us-west-2.compute.internal} 1 # 若为 0说明该实例非 leader若所有实例都为 0则 leader 选举失败提示10259是 scheduler 的 metrics 端口v1.24 默认启用不是 healthz 端口。curl直接访问本地端口避免因 service network 异常导致误判。2.2 定位 Pending 的真实约束解析 scheduler 日志中的 predicate failure detailkubectl describe pod中的Events只显示FailedScheduling但 scheduler 日志会记录具体哪个 predicate预选策略失败及原因。关键在于开启--v4日志级别并过滤PredicateFailure# 获取 scheduler pod 名称假设单 master SCHEDULER_POD$(kubectl -n kube-system get pods -l componentkube-scheduler -o jsonpath{.items[0].metadata.name}) # 实时抓取 predicate 失败详情v4 日志包含详细 predicate name 和 failure reason kubectl -n kube-system logs $SCHEDULER_POD --since10m 2/dev/null | \ grep -E (Predicate.*failed|failed to find|no nodes available) | \ grep -A2 -B2 pod/your-pending-pod-name # 示例输出 # I0521 08:23:41.221] Predicate MatchInterPodAffinity failed on node ip-10-0-2-200: no matching topology for requiredDuringSchedulingIgnoredDuringExecution # I0521 08:23:41.222] Predicate CheckNodeMemoryPressure failed on node ip-10-0-1-100: node is under memory pressure这里暴露两个关键信息MatchInterPodAffinity失败 → 检查 Pod 的affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution是否引用了不存在的 label 或 topologyKeyCheckNodeMemoryPressure失败 → 不是 Node 内存不足而是 kubelet 上报了memory_pressurecondition需查kubectl describe node中Conditions字段的MemoryPressure状态及AllocatablevsCapacity差值。2.3 验证调度器缓存是否 stale强制触发 cache sync 并观察 Pod 状态跃迁当集群规模大100 Nodes、Node 状态频繁变更如云厂商自动伸缩组触发 Node 上下线时scheduler 的 informer cache 可能滞后于 etcd 真实状态。此时kubectl get nodes显示 Ready但 scheduler 缓存中该 Node 仍是 NotReady导致 Pod 永远无法调度。# 查看 scheduler 当前缓存的 Node 数量对比 etcd 中实际数量 kubectl -n kube-system exec $SCHEDULER_POD -- \ curl -s http://127.0.0.1:10259/metrics | \ grep scheduler_goroutines{jobscheduler,namenode} # 手动触发 cache resync无需重启 scheduler kubectl -n kube-system exec $SCHEDULER_POD -- \ curl -X POST http://127.0.0.1:10259/debug/pprof/symbol # 更可靠的做法临时提高 resync period单位秒强制刷新 # 编辑 scheduler manifest通常为 /etc/kubernetes/manifests/kube-scheduler.yaml # 添加参数--sync-period30s 默认 0 表示不主动 resync注意--sync-period参数在 v1.26 中已被弃用改用--kube-api-qps和--kube-api-burst控制 informer 同步频率。但对存量集群30s resync 是快速验证 cache 是否 stale 的有效手段。3. Node NotReady 的根因分层排查从 kubelet 心跳断连到 CRI 运行时黑匣子Node 状态变为NotReady是比 Pod Pending 更底层的信号。它意味着 kubelet 与 apiserver 的通信中断或 kubelet 自身健康异常。但NotReady本身不区分是网络问题、证书过期、CRI socket 失联还是 kubelet 进程僵死。3.1 第一层确认 kubelet 是否存活且能连 apiserver不要直接systemctl status kubelet—— 它可能显示 active (running)但 kubelet 进程已卡死在某个 goroutine。真正有效的检查是# 检查 kubelet 进程是否响应 HTTP 请求本地端口 10248 curl -s http://localhost:10248/healthz 2/dev/null | grep ok || echo kubelet healthz failed # 检查 kubelet 是否能向 apiserver 发送心跳需 kubeconfig sudo -u root kubectl --kubeconfig /etc/kubernetes/kubelet.conf get nodes 2/dev/null | \ grep $(hostname -s) | awk {print $2} # 应输出 Ready 或 NotReady # 若 healthz 通但节点状态仍 NotReady说明 kubelet 无法上报状态 # 此时重点查 /var/log/kubelet.log 中 Failed to update node status 错误3.2 第二层诊断 kubelet 与 CRIcontainerd的 socket 通信kubelet依赖 CRI如 containerd管理容器生命周期。若/run/containerd/containerd.sock权限错误、containerd 进程崩溃、或 CRI 插件版本不兼容kubelet 会持续报cri failed错误最终放弃上报节点状态。# 检查 containerd socket 是否可访问且权限正确 ls -l /run/containerd/containerd.sock # 正确权限应为 srw-rw---- 1 root root且 kubelet 用户通常 root在 docker 组或有 socket 读写权 # 测试 containerd 是否响应 CRI 请求 sudo crictl ps -q | head -n3 2/dev/null || echo crictl failed - check containerd status # 查看 containerd 日志中是否有 OOM 或 plugin load error sudo journalctl -u containerd -n 50 --no-pager | grep -E (panic|OOM|plugin|failed to load) # 关键参数验证containerd config.toml 中 [plugins.io.containerd.grpc.v1.cri] 配置 # 必须存在 sandbox_image registry.k8s.io/pause:3.9v1.26 默认 # 若使用私有镜像仓库此处必须匹配否则 kubelet 创建 Pod Sandbox 失败3.3 第三层定位 kubelet 证书失效或轮换失败K8s v1.22 默认启用 CSRCertificate Signing Request自动轮换但若 kubelet 证书过期且轮换失败它将无法与 apiserver 建立 TLS 连接表现为Unable to update node statusx509: certificate has expired or is not yet valid。# 查看 kubelet 当前证书有效期 sudo openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text -noout 2/dev/null | \ grep -E (Not Before|Not After) # 检查 CSR 是否 pending需 cluster-admin 权限 kubectl get csr | grep Pending # 若有 pending CSR批准它自动轮换失败时手动救急 kubectl certificate approve $(kubectl get csr | grep Pending | awk {print $1}) # 强制 kubelet 重新生成证书重启后生效 sudo systemctl restart kubelet血泪经验某次故障中/var/lib/kubelet/pki/目录被 Ansible playbook 误删kubelet 启动后生成新证书但 apiserver 未批准 CSR导致 Node 持续 NotReady。手动approve后 10 秒内状态恢复——这比重装 kubelet 快 20 分钟。4. Control Plane 组件故障避坑指南etcd、apiserver、controller-manager 的连锁反应识别Control Plane 组件故障往往不直接报错而是引发下游雪崩Deployment 不扩容、Service Endpoints 不更新、PersistentVolume 不绑定。必须掌握各组件间的依赖边界和故障传播特征。4.1 etcd 故障的典型表征与快速隔离etcd 是 K8s 的唯一真相源。它的故障不会让 apiserver 立即 crash但会导致写操作超时、list/watch 请求卡住、以及 controller-manager 的 informer cache 持续 stale。# 检查 etcd 集群健康需 etcdctl ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health --write-outtable # 输出示例 # ------------------------------------------------------- # | ENDPOINT | HEALTH | IS LEADER | ALARM | # | https://10.0.1.100:2379 | true | true | N | # | https://10.0.2.200:2379 | false | false | N | ← 该节点已失联避坑 1etcd snapshot 不能替代备份现象执行etcdctl snapshot save成功但恢复后集群状态丢失。原因snapshot 只保存 etcd key-value不包含 kubelet 本地状态如 volume mount point、containerd runtime state。恢复后需逐台重启 kubelet 并等待其重建 Pod。解决生产环境必须配合velero或Restic做应用层备份snapshot 仅用于灾难恢复。避坑 2etcd member remove 后未清理磁盘数据现象删除故障节点后新节点加入集群失败日志报member with same peerURL already exists。原因旧节点 etcd 数据目录/var/lib/etcd未清空残留 member ID 冲突。解决rm -rf /var/lib/etcd/member/确保该节点已彻底离线。4.2 apiserver 5xx 错误的根源分类client-go timeout vs server-side overloadkubectl get pods返回Error from server: net/http: request canceled (Client.Timeout exceeded while awaiting headers)常被误判为网络问题。实则需区分是客户端超时还是 apiserver 本身处理不过来。# 检查 apiserver 是否满负荷metrics 端口 8080 或 6443 kubectl -n kube-system exec $(kubectl -n kube-system get pods -l componentkube-apiserver -o jsonpath{.items[0].metadata.name}) -- \ curl -s http://127.0.0.1:8080/metrics | \ grep apiserver_request_total{verbLIST,resourcepods | \ awk {print $2} # 查看最近 1 分钟 LIST pods 请求量 # 对比 apiserver 进程 CPU 使用率 kubectl -n kube-system top pods -l componentkube-apiserver # 关键指标若 apiserver_current_inflight_requests 200默认 limit说明请求积压 # 此时需调高 --max-requests-inflight 参数默认 400但治标不治本避坑 3watch 请求堆积导致 apiserver OOM现象apiserver 内存持续上涨至 8GB然后被 OOM killer 杀掉。原因大量 client-go 客户端未设置timeoutSeconds长期 hold watch connection占用内存。解决在 client-go 代码中显式设置WatchOption.Timeout或在 apiserver 启动参数加--min-request-timeout300单位秒强制关闭空闲 watch。4.3 controller-manager 的 informer cache 失效为什么 Deployment 不扩容Deployment Controller 依赖 informer cache 获取 Pod、ReplicaSet 状态。若 cache 失效它会停止 reconcile表现为kubectl rollout status卡住、kubectl get rs显示副本数不变。# 查看 controller-manager 的 informer sync status kubectl -n kube-system logs $(kubectl -n kube-system get pods -l componentkube-controller-manager -o jsonpath{.items[0].metadata.name}) 2/dev/null | \ grep -E (Started syncing|Sync failed|cache is synced) | tail -10 # 正常输出应含Starting controllers, Started syncing... , caches populated # 若出现 Sync failed 且反复重试说明 informer list/watch 失败 # 强制触发 informer resync无需重启 controller-manager kubectl -n kube-system exec $(kubectl -n kube-system get pods -l componentkube-controller-manager -o jsonpath{.items[0].metadata.name}) -- \ curl -X POST http://127.0.0.1:10252/debug/pprof/symbol避坑 4自定义资源CRD未注册导致 controller-manager panic现象controller-manager 日志出现panic: interface conversion: interface {} is nil, not *unstructured.Unstructured。原因某 CRD 的spec.version字段为空或spec.names.kind与 controller 中 watch 的 GVK 不匹配。解决kubectl get crd -o wide检查 CRDSTATUS是否为Established若为None删除并重建 CRD。5. 网络插件CNI故障的精准定位从 Calico IPAM 到 CoreDNS 解析失败的链路还原K8s 网络故障最玄学Pod 能 ping 通 ClusterIP 却无法解析 Service 名或 NodePort 在宿主机能 curl 通但在外网 404。根源常在 CNI 插件与 kube-proxy 的协同逻辑。5.1 Calico IPAM 分配失败为什么 Pod 一直 ContainerCreatingCalico 的ippool耗尽或felix配置错误会导致 Pod 卡在ContainerCreatingkubectl describe pod显示Failed create pod sandbox。# 检查 Calico ippool 是否还有可用 IP kubectl get ippool -o wide # 查看 USED 和 TOTAL若 USED TOTAL则需扩容或清理僵尸 IP # 检查 felix 日志中 IPAM 分配失败详情 kubectl -n kube-system logs -l k8s-appcalico-node --since5m | \ grep -E (Failed to allocate|IPAM error|no IPs available) # 强制释放指定 Pod 的 IP需 calicoctl calicoctl ipam release --ippod-ip --pooldefault-ipv4-ippool5.2 CoreDNS 解析失败的三层归因iptables、stubDomains、forward 配置nslookup kubernetes.default.svc.cluster.local超时不等于 CoreDNS 崩溃。可能是 kube-proxy 规则未生效、CoreDNS 配置错误、或上游 DNS 转发链路中断。# 验证 kube-proxy 是否正确安装 iptables rules iptables -t nat -L KUBE-SERVICES | grep dns # 应看到类似DNAT tcp -- anywhere anywhere tcp dpt:53 to:10.96.0.10:53 # 检查 CoreDNS ConfigMap 中 forward 配置是否指向可用 upstream kubectl -n kube-system get cm coredns -o yaml | yq e .data.Corefile - # 输出示例 # .:53 { # errors # health # ready # kubernetes cluster.local in-addr.arpa ip6.arpa { # pods insecure # fallthrough in-addr.arpa ip6.arpa # } # prometheus :9153 # forward . /etc/resolv.conf ← 关键此处应为真实 upstream DNS如 1.1.1.1非 /etc/resolv.conf # cache 30 # loop # reload # loadbalance # } # 若 forward 指向 /etc/resolv.conf而该文件内容为 127.0.0.1即指向自身则形成死循环避坑 5CoreDNS stubDomains 配置语法错误导致整个 Corefile 加载失败现象CoreDNS Pod Running但kubectl logs -n kube-system coredns无日志输出dig全部超时。原因ConfigMap 中stubDomainsYAML 缩进错误如kubernetes块未对齐导致 CoreDNS 启动时 parse Corefile 失败静默退出。解决kubectl edit cm coredns用yq或在线 YAML validator 校验缩进或临时kubectl delete pod -n kube-system -l k8s-appkube-dns触发重启加载。5.3 Service ClusterIP 不可达kube-proxy mode 与 iptables/nftables 冲突集群升级到 v1.26 后若宿主机使用 nftables而 kube-proxy 仍运行在iptablesmode会导致规则冲突ClusterIP 间歇性不可达。# 查看 kube-proxy 当前 mode kubectl -n kube-system get cm kube-proxy -o yaml | yq e .data.config.conf.mode # 查看宿主机默认 packet filter lsmod | grep nf_tables echo nftables || echo iptables # 若 kube-proxy mode 为 iptables 但系统用 nftables强制切换 kubectl -n kube-system edit cm kube-proxy # 修改 data.config.conf.mode: nftables # 保存后删除 kube-proxy pod 触发重建 kubectl delete pod -n kube-system -l k8s-appkube-proxy避坑 6Windows Node 上 kube-proxy 无法启动现象Windows Node 的 kube-proxy Pod 一直 ContainerCreatingkubectl describe显示Back-off pulling image k8s.gcr.io/kube-proxy:v1.26.11。原因Windows 镜像需带windows/amd64或windows/arm64platform tag而默认拉取的是 linux 镜像。解决修改 kube-proxy DaemonSet添加imagePullPolicy: Always和image: k8s.gcr.io/kube-proxy:v1.26.11-win-amd64需确认镜像存在。6. 故障复盘与预防用 kubectl alpha debug 构建自动化诊断流水线靠人工kubectl describe 日志 grep 应对故障已不可持续。K8s v1.25 提供kubectl alpha debug可注入临时调试容器到故障 Pod 所在 Node绕过 Pod 网络限制直连底层设施。6.1 用 ephemeral debug container 快速诊断网络不通问题当 Pod 无法访问外部 API但kubectl exec进入 Pod 能 ping 通怀疑是 Node 级网络策略或 iptables 规则问题# 在故障 Pod 所在 Node 启动 debug container共享网络命名空间 kubectl alpha debug node/node-name -it --imagenicolaka/netshoot # 在 debug 容器中执行 # 1. 检查 Node 到目标服务的连通性绕过 Pod 网络 curl -v https://api.example.com # 2. 抓包分析Node 网络栈 tcpdump -i any port 443 -w /tmp/debug.pcap # 3. 查看 Node 上所有 iptables nat 规则 iptables -t nat -L -n | grep -A5 -B5 api.example.com # 4. 验证 kube-proxy 规则是否生效 iptables -t nat -L KUBE-SERVICES | grep api.example.com6.2 构建标准化故障诊断 checklist可落地为 shell script把高频故障的检查步骤固化为脚本避免人为遗漏。以下为k8s-diagnose.sh核心逻辑#!/bin/bash # k8s-diagnose.sh - run with root on target node echo Node Health Check systemctl is-active kubelet echo ✓ kubelet active || echo ✗ kubelet inactive curl -s http://localhost:10248/healthz | grep ok echo ✓ kubelet healthz ok || echo ✗ kubelet healthz failed echo CRI Check crictl ps -q /dev/null 21 echo ✓ crictl works || echo ✗ crictl failed ls /run/containerd/containerd.sock /dev/null 21 echo ✓ containerd socket exists || echo ✗ containerd socket missing echo Network Check iptables -t nat -L KUBE-SERVICES | grep -q KUBE-MARK-DROP echo ✓ kube-proxy rules loaded || echo ✗ kube-proxy rules missing ping -c1 8.8.8.8 /dev/null 21 echo ✓ Node internet access ok || echo ✗ Node internet access failed echo etcd Check (if master) if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health 2/dev/null | grep -q true echo ✓ etcd healthy || echo ✗ etcd unhealthy fi我的习惯把这个脚本放在/usr/local/bin/k8s-diagnose每次接到告警第一件事就是 ssh 到对应 Node 执行它。30 秒内输出 ✅/❌ 清单直接定位到第 3 项失败省去 80% 的盲目排查时间。它不解决所有问题但把“不知道从哪开始”这个最耗时的环节砍掉了。希望帮到你。本文还有配套的精品资源点击获取