ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kubernetes故障排查分层定位方法论

Kubernetes故障排查分层定位方法论 1. 为什么Kubernetes故障排查不能只靠kubectl getKubernetes故障排查合集——这名字听起来像一本工具手册但实际用起来你会发现它更像一本“系统病理学诊断指南”。我带过三支不同行业的运维团队从金融核心交易系统到电商大促平台再到AI训练集群所有团队在刚接手Kubernetes生产环境时都曾陷入一个共同误区把kubectl get pods当成万能听诊器。结果呢Pod状态是Running日志里没报错监控曲线平滑可业务接口就是503Service明明存在Endpoint也列出来了curl却超时Ingress规则写了八条浏览器打开却是404。这种“一切看起来都正常但就是不工作”的状态才是Kubernetes故障最典型的表征。这背后的根本原因在于Kubernetes不是单体应用而是一套分层协作的控制平面数据平面系统。它的故障从来不是孤立发生的而是沿着声明式抽象→控制器 reconcile→API Server状态同步→etcd持久化→kubelet执行→容器运行时落地这条链路逐层传导、放大或掩盖。比如你看到Pod状态是Running那只是kubelet向API Server上报的“心跳快照”不代表容器进程真在跑Service的Endpoints为空可能不是Service配置错了而是Selector匹配的Pod标签根本没被Controller Manager成功创建Ingress 404根源可能在CoreDNS解析失败或者NodePort端口被宿主机防火墙拦截——这些环节彼此解耦又环环相扣。所以“排查合集”这个词的关键不在“合”而在“排”——是按逻辑顺序一层层排除可能性的过程。它要求你必须建立一套分层定位心智模型先确认Control Plane组件apiserver、scheduler、controller-manager是否健康再验证Data Plane节点kubelet、containerd是否在线接着检查网络插件CNI的Pod间连通性最后才深入到应用层容器进程、配置文件、依赖服务。跳过任何一层直接查日志就像医生不量血压、不听心音上来就开CT单——成本高、效率低、还容易误诊。提示很多团队一出问题就直奔kubectl logs这是最耗时的错误起点。真正高效的排查永远从kubectl get componentstatuses虽已弃用但仍有参考价值、kubectl get nodes -o wide、kubectl get events --sort-by.lastTimestamp这三条命令开始。它们不告诉你“哪里坏了”但会精准指出“哪一层最先失联”。我见过最典型的反面案例某支付系统凌晨告警订单创建失败。SRE同学花了2小时翻查应用日志发现数据库连接超时于是认定是DB问题联系DBA重启实例。结果DBA反馈一切正常而此时业务已中断47分钟。复盘发现真正原因是前一天升级了Calico版本新版本与内核模块不兼容导致Node间BGP路由未建立——所有Pod虽然Running但跨Node通信全断。kubectl get nodes显示全部Readykubectl get pods显示全部Running唯独kubectl get events里有一条被忽略的Warning“Failed to establish BGP peering with node X”。这个事件在滚动日志里一闪而过但它是整条排查链路上第一个、也是最关键的信号。因此本合集不提供“一键修复脚本”而是帮你重建一套可复用的诊断路径。接下来的内容将严格遵循Kubernetes架构分层从Control Plane到Data Plane再到网络与存储子系统每一步都附带真实场景、命令组合、输出解读和避坑要点。你不需要记住所有命令但必须理解每个命令在诊断链中的位置和意义——就像老司机不背交通法规全文但知道红灯亮起时该踩刹车而不是看后视镜。2. Control Plane瘫痪当kubectl命令集体失效时怎么办Control Plane是Kubernetes的大脑一旦它出问题整个集群就变成一座没有信号塔的机场——飞机Pod还在跑道上但调度指令Scheduler、状态维护Controller Manager、甚至航班查询API Server全都停摆。最直观的表现就是kubectl命令大面积超时或报错“The connection to the server X.X.X.X:6443 was refused”、“Unable to connect to the server: EOF”、“error: the server doesnt have a resource type pods”。这时候别急着重装集群先做三件事确认API Server是否存活、检查etcd数据一致性、验证证书有效期。这三步做完80%的Control Plane故障就能定位。2.1 API Server状态诊断不止是端口监听很多人认为“netstat -tlnp | grep 6443看到端口监听就代表API Server正常”这是致命误解。API Server是一个HTTP服务但它的健康不仅取决于端口更取决于其依赖的后端组件。正确做法是分层验证首先登录Master节点用curl绕过kubectl直接测试API Server的健康端点curl -k https://127.0.0.1:6443/healthz # 正常返回ok curl -k https://127.0.0.1:6443/livez # 正常返回ok curl -k https://127.0.0.1:6443/readyz # 正常返回ok这三个端点分别检测API Server自身进程、与etcd的连接、以及所有依赖组件如Aggregation Layer的就绪状态。如果/healthz失败说明API Server进程崩溃如果/livez失败大概率是etcd不可达如果/readyz失败可能是Metrics Server未启动或Webhook配置错误。其次检查API Server进程资源占用。我遇到过最隐蔽的故障API Server进程持续运行/healthz返回ok但所有kubectl命令超时。top一看CPU使用率99%ps aux | grep apiserver发现进程参数里漏掉了--max-requests-inflight500默认值太小导致高并发请求排队阻塞。解决方案不是重启而是动态调整——编辑/etc/kubernetes/manifests/kube-apiserver.yaml增加该参数后保存kubelet会自动滚动更新。注意修改Static Pod清单后务必执行kubectl get pods -n kube-system | grep apiserver确认新Pod已替换旧Pod且状态为Running。切勿手动kill进程否则可能导致API Server双写冲突。2.2 etcd数据一致性校验别让“假正常”骗过你etcd是Kubernetes的唯一真相源所有状态变更最终都落盘于此。但etcd集群可能“表面健康内在腐烂”——比如某个节点磁盘满导致无法写入但该节点仍能响应读请求造成数据不一致。诊断步骤如下检查集群健康状态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 # 输出应为https://127.0.0.1:2379 is healthy: successfully committed proposal验证成员列表与数据同步延迟etcdctl member list # 检查每个成员的Status是否为startedClientURLs是否可访问 etcdctl endpoint status --write-outtable # 关键列DBSize数据库大小突增可能意味着碎片、RaftTerm任期不一致说明分裂、Leader谁是主节点深度检查数据一致性生产环境慎用# 在所有etcd节点上执行比对hash etcdctl --write-outextended endpoint hashkv # 如果各节点hash值不同说明数据已不一致需从备份恢复我处理过一个案例某集群kubectl get nodes只返回2个节点但etcdctl member list显示有5个成员。排查发现3个节点的etcd进程因磁盘满被OOM Killer干掉但systemctl status etcd显示active因为进程重启后立即退出systemd未及时更新状态。endpoint health命令返回healthy是因为它只检测TCP连接不检测进程存活。真正的解决方法是journalctl -u etcd -n 100查看日志定位到No space left on device错误清理/var/lib/etcd/member/snap/目录后重启etcd。2.3 证书过期那个总在凌晨三点爆发的幽灵故障Kubernetes组件间通信全部基于TLS证书而证书有效期默认只有1年。很多团队在集群上线时忘了规划证书轮换结果第365天凌晨API Server突然拒绝所有请求kubectl报错x509: certificate has expired or is not yet valid。这不是Bug是设计使然。修复流程必须严格按顺序确认过期证书openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep Not After生成新CA证书仅当根CA过期kubeadm certs renew ca此操作会重建整个PKI体系需谨慎轮换所有组件证书kubeadm certs renew all重启所有Static Podkubectl delete pod -n kube-system -l componentkube-apiserver更新kubectl配置kubeadm init phase kubeconfig all --config /etc/kubernetes/kubeadm-config.yaml提示kubeadm certs check-expiration是日常巡检必备命令。建议将其加入Cron Job每周自动检查并邮件告警。另外kubeadm1.22版本支持--certificate-renewal参数可在kubeadm upgrade时自动轮换证书避免手动操作失误。3. Data Plane失联Node NotReady背后的七层真相当你执行kubectl get nodes看到某个Node状态是NotReady第一反应往往是kubectl describe node node-name。但这个命令输出的信息量巨大新手常被淹没在数百行Event和Conditions里抓不住关键线索。实际上“NotReady”只是一个聚合状态它由kubelet上报的多个Condition共同决定而每个Condition背后都对应着Data Plane的一条技术链路。要真正解决问题必须拆解这七个Condition逐层验证。3.1 MemoryPressure与DiskPressure资源水位线的精确解读MemoryPressure和DiskPressure是kubelet根据cgroup统计主动上报的Condition它们的阈值并非固定值而是动态计算的MemoryPressure当可用内存 node-status-update-frequency默认10s×eviction-hard参数中定义的内存阈值如memory.available100Mi时触发。DiskPressure当nodefs.available根分区可用空间或imagefs.available容器镜像存储分区低于eviction-hard设定值如nodefs.available10%时触发。诊断时不能只看kubectl describe node里的Condition描述必须登录Node执行# 查看实时内存使用排除缓存影响 free -h echo Available memory: $(awk /MemAvailable/{print $2/1024/1024 GiB} /proc/meminfo) # 查看磁盘使用详情注意区分nodefs和imagefs df -h / df -h /var/lib/containerd/ # 检查kubelet配置的驱逐阈值 cat /var/lib/kubelet/config.yaml | grep -A 5 eviction我处理过一个经典案例Node状态为NotReadydescribe node显示MemoryPressureTrue但free -h显示内存充足。深入排查发现该Node运行了大量短生命周期Job每次Job结束其容器的/dev/shm挂载点未被清理累积占用数GB内存。free命令不统计/dev/shm而kubelet的cgroup统计包含它。解决方案是在Job模板中添加securityContext: {shmVolume: {sizeLimit: 64Mi}}或定期执行find /dev/shm -name shm-* -type d -empty -delete。3.2 PIDPressure被忽视的进程句柄泄漏PIDPressureCondition在Kubernetes 1.20才被正式纳入Node Conditions但它引发的故障频率极高。当Node上进程数接近/proc/sys/kernel/pid_max通常32768时kubelet会标记此Condition为True并拒绝调度新Pod。问题在于kubectl describe node不会告诉你哪个进程占用了最多PID你需要手动排查# 统计各用户进程数 ps -eo user | sort | uniq -c | sort -nr | head -10 # 查看指定进程的线程数Java应用常见 ps -T -p $(pgrep -f java.*application) | wc -l # 检查是否存在僵尸进程堆积 ps aux | awk $8 ~ /Z/ {print} | wc -l一次生产事故中一个Python微服务因未正确关闭数据库连接池每秒创建数百个新线程2小时内耗尽PID。kubectl get pods显示所有Pod都是Running但新Pod始终Pending。describe node只显示PIDPressureTrue直到我们用ps -eo pid,ppid,comm,args | grep -v grep | sort -k2 | tail -20找到父进程PID再溯源到应用代码才发现connection.close()被遗漏。3.3 NetworkUnavailableCNI插件的静默死亡NetworkUnavailableCondition通常由CNI插件如Calico、Cilium的DaemonSet Pod设置。当该Pod异常退出或配置错误时kubelet无法为Pod分配IP导致Node被标记为NotReady。但诊断难点在于CNI Pod本身可能处于Running状态却无法提供网络服务。验证步骤检查CNI DaemonSet状态kubectl get daemonset -n kube-system | grep calico查看CNI Pod日志kubectl logs -n kube-system calico-pod-name | tail -50在Node上验证CNI二进制ls -l /opt/cni/bin/确认calico-ipam等文件存在手动执行CNI ADDecho {cniVersion:0.3.1,name:k8s-pod-network,type:calico,mode:ipam} | sudo CNI_COMMANDADD CNI_CONTAINERIDtest CNI_NETNS/dev/null CNI_IFNAMEeth0 CNI_PATH/opt/cni/bin /opt/cni/bin/calico最棘手的情况是CNI配置与内核模块冲突。例如Calico v3.21默认启用eBPF模式但某些云厂商定制内核禁用了bpf_jit_enable导致CNI初始化失败。日志里只会显示Failed to initialize bpf program而kubectl get pods看不到任何异常。解决方案是编辑Calico ConfigMap将mode: bird传统Felix模式而非ebpf。4. 网络连通性黑洞从Service到Ingress的逐跳验证法Kubernetes网络故障的恐怖之处在于它不像传统网络那样有明确的物理链路而是一张由iptables/ipvs规则、CNI插件、kube-proxy、CoreDNS、Ingress Controller共同编织的逻辑网。当curl http://my-service失败时你不知道问题出在DNS解析、Service负载均衡、Pod网络、还是Ingress路由。因此必须建立一套从客户端到Pod的逐跳验证法每一跳都用最小化命令验证避免信息过载。4.1 DNS解析层CoreDNS是否真的在工作Service名称解析失败是最常见的“假故障”。kubectl get service显示Service存在但应用内curl my-service超时。第一步永远是验证DNS# 在任意Pod内执行推荐用busybox kubectl run dns-test --imagebusybox:1.35 --rm -it --restartNever -- nslookup kubernetes.default.svc.cluster.local # 正常输出Server: 10.96.0.10 # Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local # Name: kubernetes.default.svc.cluster.local # Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local如果失败检查CoreDNS Pod是否Runningkubectl get pods -n kube-system -l k8s-appkube-dnsCoreDNS配置是否正确kubectl get configmap coredns -n kube-system -o yamlNode上的/etc/resolv.conf是否指向CoreDNS ClusterIP通常是10.96.0.10我遇到过一个诡异案例CoreDNS Pod状态正常nslookup在部分Pod成功部分失败。最终发现是某些Node的/etc/resolv.conf被Ansible脚本错误覆盖写入了nameserver 8.8.8.8导致Pod继承了外部DNS无法解析内部Service。修复只需kubectl edit cm coredns -n kube-system确保forward . /etc/resolv.conf指向正确的上游。4.2 Service转发层iptables/ipvs规则是否生效假设DNS解析成功下一步验证Service是否将流量正确转发到Endpoint。关键命令是iptables-save或ipvsadm -Ln# 查看Service对应的iptables规则以ClusterIP为例 iptables-save | grep my-service # 应看到类似-A KUBE-SERVICES -d 10.96.1.100/32 -p tcp -m comment --comment default/my-service:port -m tcp --dport 80 -j KUBE-SVC-XXXXXX # 再查KUBE-SVC链iptables-save | grep KUBE-SVC-XXXXXX # 应看到DNAT规则指向Endpoint IP # 如果使用ipvskubeadm 1.22默认 ipvsadm -Ln | grep 10.96.1.100:80 # 应看到RealServer列表- 10.244.1.5:80 Masq 1 0 0常见陷阱Endpoint为空kubectl get endpoints my-service返回空说明Service Selector未匹配到任何Pod。检查Pod标签是否与Service的selector完全一致包括大小写、拼写。iptables规则缺失kube-proxy Pod异常或--proxy-modeiptables参数未生效。kubectl logs -n kube-system kube-proxy-pod查找Starting kube-proxy日志确认模式。NodePort端口冲突kubectl get service my-service -o wide显示NodePort为30080但netstat -tlnp | grep :30080发现被其他进程占用。解决方案是删除Service并重新创建或指定nodePort: 30081。4.3 Ingress路由层Nginx Controller的配置热加载陷阱Ingress故障往往表现为404或503但根源可能不在Ingress资源本身。Nginx Ingress Controller采用ConfigMap驱动配置当Ingress资源变更时Controller会热加载Nginx配置。但热加载可能失败且无明显日志# 检查Ingress Controller Pod日志 kubectl logs -n ingress-nginx ingress-nginx-controller | grep -i reload\|error # 查看Nginx配置是否已更新 kubectl exec -n ingress-nginx ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A 5 server_name my-app.example.com # 验证Nginx进程是否重新加载 kubectl exec -n ingress-nginx ingress-nginx-controller -- nginx -t最隐蔽的问题是Ingress资源的host字段与实际请求Host头不匹配。例如Ingress定义host: app.example.com但浏览器访问http://localhost:30080未设置Host头Nginx会匹配到default server返回404。解决方案是在Ingress中添加spec.rules[0].host或使用kubectl patch ingress my-ingress -p {spec:{rules:[{host:localhost,http:{paths:[{path:/,backend:{service:{name:my-service,port:{number:80}}}}]}}]}}。5. 存储卷挂载失败PersistentVolumeClaim背后的权限迷宫PVC绑定失败是Kubernetes存储故障中最令人抓狂的一类。kubectl get pvc显示Pendingkubectl describe pvc只有一行Waiting for a volume to be created但你不知道是StorageClass配置错误、底层存储系统故障还是Pod安全策略阻止了挂载。必须按顺序排查四个关键环节StorageClass、PV动态供给、PVC绑定、Pod挂载权限。5.1 StorageClass验证Provisioner是否真的在运行StorageClass是PVC的“模板”它定义了如何创建PV。但kubectl get storageclass显示存在不代表Provisioner Pod在工作。例如AWS EBS Provisioner需要aws-ebs-csi-driverDaemonSet而Azure Disk Provisioner需要azuredisk-csi-driver。验证步骤# 查看StorageClass详情 kubectl get storageclass standard -o yaml # 关注provisioner字段e.g., ebs.csi.aws.com # 检查对应Provisioner的Pod kubectl get pods -n kube-system | grep ebs-csi-controller # 查看Provisioner日志 kubectl logs -n kube-system ebs-csi-controller-0 ebs-plugin常见错误Provisioner未安装kubectl get pods -n kube-system | grep csi无输出。需按官方文档部署CSI Driver。RBAC权限不足Provisioner Pod日志出现error updating pv: persistentvolumes pv-xxx is forbidden。需检查ClusterRoleBinding是否绑定到Provisioner ServiceAccount。参数错误StorageClass的parameters.type设为gp3但AWS账户不支持该类型。日志会提示InvalidParameterValue: Invalid volume type。5.2 PV动态供给为什么PVC卡在Pending当PVC Pending时kubectl describe pvc的Events会给出线索no persistent volumes available for this claim and no storage class is set未指定StorageClass且集群无Default StorageClass。waiting for a volume to be created, either by external provisionerProvisioner已收到请求但未创建PV。provisioning failed for claim default/my-pvcProvisioner创建PV失败。此时必须检查Provisioner日志。我处理过一个案例PVC Pending日志显示failed to create volume: context deadline exceeded。排查发现是CSI Driver的timeout参数过短默认30s而EBS创建需45s。解决方案是编辑CSI Driver的Deployment增加--timeout60s参数。5.3 Pod挂载权限SELinux与AppArmor的无声拦截即使PV成功绑定Pod启动时仍可能因挂载权限失败。kubectl describe pod显示ContainerCreatingEvents里有MountVolume.SetUp failed for volume my-pv。这通常不是Kubernetes问题而是宿主机安全模块拦截# 检查Node SELinux状态 getenforce # 应为Permissive或Disabled # 查看挂载日志 dmesg | grep -i avc | tail -20 # SELinux拒绝日志 # 检查AppArmor配置 aa-status | grep k8s_典型场景在RHEL/CentOS上SELinux默认启用而CSI Driver挂载的卷路径如/var/lib/kubelet/pods/xxx/volumes/kubernetes.io~csi/pv-name/mount未被赋予s0:c0.c1023上下文。解决方案是sudo semanage fcontext -a -t container_file_t /var/lib/kubelet/pods(/.*)?然后sudo restorecon -R /var/lib/kubelet/pods。6. 故障排查的终极心法从现象到根因的思维链所有技术细节终将过时但排查思维永不过时。在我十年Kubernetes实战中总结出一条铁律任何故障现象都必须映射到Kubernetes的声明式模型与控制器模式上。这意味着当你看到一个异常状态时不要问“怎么修”而要问“哪个控制器应该负责这个状态它的reconcile逻辑为何失败”。比如kubectl get pods显示Pod状态为CrashLoopBackOff新手会立刻kubectl logs看应用日志。但资深工程师会先问谁负责创建这个Pod是Deployment Controller。那么kubectl describe deployment my-app里是否有Replicas: 0/1如果有说明Deployment的replicas字段被意外修改为0或selector不匹配导致副本数为0。此时看日志毫无意义。再如kubectl get ingress显示ADDRESS为空describe ingressEvents里有LoadBalancer ingress has been assigned。这说明Ingress Controller已分配IP但未更新到Ingress资源。根源可能是Ingress Controller的--update-status-on-shutdownfalse参数未设置或Leader选举失败导致状态更新Pod非活跃。因此本合集的终极价值不是教你记住100条命令而是帮你建立一套控制器映射思维看到Node状态异常 → 想到Node Controller和kubelet的reconcile循环看到Service无Endpoint → 想到Endpoint Controller如何根据Pod标签生成Endpoint看到PV Pending → 想到Volume Controller如何协调Provisioner创建PV看到Ingress 404 → 想到Ingress Controller如何将Ingress资源编译为Nginx配置这套思维让我在处理一个跨云迁移故障时仅用15分钟定位根因客户将集群从AWS迁移到阿里云但忘记更新StorageClass的provisioner字段从ebs.csi.aws.com改为disk.csi.alibabacloud.com导致所有PVC Pending。kubectl describe pvc只显示no volume plugin matched而kubectl get storageclass输出里provisioner字段赫然写着AWS的地址——这就是控制器映射思维的胜利PVC Pending必然关联StorageClass而StorageClass的provisioner必须与当前云环境匹配。最后分享一个小技巧每次排查后用kubectl get events --sort-by.lastTimestamp | tail -20收集最近事件按时间倒序整理成故障时间线。你会发现绝大多数复杂故障其最早出现的Event就是整个链路的起点。就像破案一样找到那个最初的“犯罪现场”后面的故事自然清晰。
返回列表