这几年我见过太多“线上炸了、老板冒烟、开发甩锅”的场面。说实话K8s这玩意儿部署起来可能半小时搞定但真到了生产环境出问题那可真是“一小时定位三小时复盘五小时背锅”。今天这篇文章不讲理论不画架构图就聊点实在的——生产环境真实遇到的排错案例 详细处理过程 踩过的坑。内容全来自我亲手处理过的故障有些甚至半夜三点被电话叫醒去救火。如果你也在搞K8s建议收藏说不定哪天就用上了。 场景一Pod 卡在 Pending你以为是资源不够前几天测试团队反馈新上线的服务一直起不来kubectl get pod看到状态是Pending。第一反应是不是节点资源不够赶紧kubectl describe pod xxx一看Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 2m default-scheduler 0/5 nodes are available: 5 Insufficient cpu.看起来确实是 CPU 不够。但奇怪的是集群明明还有两台空闲节点CPU 使用率不到30%。这时候很多人就懵了“资源明明够啊为啥调度不过去”真相其实是QoS 和 PriorityClass 在背后搞鬼。我们项目里为了保障核心服务给某些 Pod 设置了高优先级PriorityClass。而这个新上线的服务没配结果被调度器直接“降级”了——即使有资源也得等高优先级任务跑完再说。排查命令# 查看当前集群所有 PriorityClass kubectl get priorityclass # 查看 Pod 是否绑定了 PriorityClass kubectl get pod pod-name -o jsonpath{.spec.priorityClassName}解决办法如果是非核心业务可以临时调低其他高优 Pod 的副本数或者给这个新服务也配上合适的 PriorityClass更彻底的做法是在命名空间级别设置 ResourceQuota LimitRange避免资源被无限制抢占。 小提醒别只看kubectl describe的 Events 表面信息。有时候“Insufficient cpu”只是障眼法真正原因是亲和性affinity、污点taint或调度插件策略。️ 场景二服务间歇性 503日志却一切正常这是最让人抓狂的问题之一。用户访问接口偶尔返回 503 Service Unavailable但kubectl logs看应用日志完全正常连 error 都没有。重启 Pod暂时好了几小时后又来。一开始以为是应用 Bug后来发现根本不是应用的问题而是 conntrack 表溢出了K8s 默认用 iptables 做 Service 转发而 iptables 依赖内核的 conntrack 模块记录连接状态。当并发连接数暴增比如大促、压测、爬虫攻击conntrack 表满了新连接直接被丢弃表现为“服务不可达”但容器本身毫无感知。怎么确认登录到 Pod 所在节点执行# 查看当前 conntrack 连接数 sysctl net.netfilter.nf_conntrack_count # 查看最大限制 sysctl net.netfilter.nf_conntrack_max如果count接近max默认一般是 65536基本就是它了。解决方案临时扩容应急sysctl -w net.netfilter.nf_conntrack_max262144长期优化改用IPVS 模式性能更好不依赖 conntrack或者调整 kube-proxy 配置启用strictARP: true 合理设置conntrack参数对外暴露的服务前面加一层 Nginx 或 LVS 做连接收敛。 血泪教训有一次我们没注意大促当天凌晨 conntrack 满了导致支付回调失败损失几十万订单。从此以后监控里必加nf_conntrack_count指标。 场景三PVC 挂载失败Pod 卡在 ContainerCreating这种情况太常见了。你写了个 StatefulSet挂了个 PVC结果 Pod 一直卡在ContainerCreatingdescribe 一看MountVolume.SetUp failed for volume pvc-xxx : mount failed: exit status 32或者Unable to attach or mount volumes: unmounted volumes[data], unattached volumes[...]: timed out waiting for the condition这时候很多人第一反应是“存储后端挂了” 但其实90% 的情况是 StorageClass 配置问题或权限不对。举个真实例子我们用的是 NFS 作为后端存储StorageClass 里写了mountOptions: [vers4.1]但某台 NFS 服务器只支持vers3。结果新 Pod 调度到那台机器上就挂载失败。排查步骤看 PV 和 PVC 是否绑定成功kubectl get pvc -n your-ns kubectl get pv如果 PVC 状态是Pending说明没找到匹配的 PV。检查 StorageClass 是否存在且配置正确kubectl get storageclass kubectl describe storageclass your-sc-name手动在节点上测试挂载关键登录到目标节点用 root 执行mount -t nfs -o vers4.1,nolock,prototcp your-nfs-server:/path /mnt/test如果报错就能定位是网络、权限还是协议问题。如果是云厂商如 AWS EBS、阿里云云盘还要检查节点是否在同一可用区AZIAM 权限是否允许挂载磁盘是否已被其他实例占用修复建议统一 StorageClass 的 mountOptions对 NFS 存储建议用nolock,intr,soft参数避免死锁生产环境尽量用 CSI 驱动而不是 in-tree 插件已废弃。 场景四DNS 解析时有时无nslookup 正常但 curl 失败这个问题特别诡异。你在 Pod 里执行nslookup kubernetes.default返回 IP 正常但curl http://my-service却超时。很多人以为是 CoreDNS 崩了其实更可能是DNS 缓存 ndots 陷阱。K8s 默认给 Pod 的/etc/resolv.conf加了ndots:5意思是如果域名中点少于 5 个就先尝试拼接 search domain比如my-service.namespace.svc.cluster.local。但如果 search domain 太多或者网络延迟高就会导致 DNS 查询超时重试最终失败。验证方法# 进入 Pod kubectl exec -it your-pod -- sh # 查看 resolv.conf cat /etc/resolv.conf # 手动测试带完整域名的解析绕过 ndots nslookup my-service.namespace.svc.cluster.local # 对比短域名 nslookup my-service如果短域名慢或失败长域名正常基本就是 ndots 问题。解决方案应用层改用 FQDN完整域名比如代码里写http://my-service.namespace.svc.cluster.local:8080调整 Pod 的 dnsConfigspec: dnsConfig: options: - name: ndots value: 1 - name: timeout value: 2升级 CoreDNS 到最新版并开启缓存插件cache 30监控 CoreDNS 的 latency 和 error rate用 Prometheus Grafana 做告警。 冷知识K8s 1.27 已支持dnsPolicy: ClusterFirstWithHostNet对 hostNetwork Pod 更友好。⚙️ 场景五节点 NotReadykubelet 日志全是 “PLEG is not healthy”某天早上收到告警多个节点状态变成NotReady。登录节点一看systemctl status kubelet显示 active (running)但kubectl get node就是 NotReady。查 kubelet 日志journalctl -u kubelet -n 100 | grep -i pleg输出PLEG is not healthy: pleg was last seen active 3m agoPLEGPod Lifecycle Event Generator是 kubelet 用来监听容器生命周期变化的组件。它卡住通常是因为容器运行时响应太慢。我们当时用的是 containerd排查发现节点上跑了太多 Pod200containerd 的oom_score_adj没调被系统 OOM killer 干掉过一次磁盘 IO 高导致 containerd 状态同步延迟。处理流程先隔离节点防止调度新 Podkubectl cordon node-name kubectl drain node-name --ignore-daemonsets --delete-local-data --force重启 containerd 和 kubeletsystemctl restart containerd systemctl restart kubelet优化配置给 containerd 设置更高的 oom_score_adj比如 -999限制单节点 Pod 数量通过 kubelet 的--max-pods110使用 SSD 磁盘避免 IO 瓶颈。✅ 经验生产环境单节点 Pod 数别超过 100否则 kubelet 容易卡死。别信“理论上支持 110”那是理想值。 场景六镜像拉取失败但本地 docker pull 正常kubectl describe pod显示Failed to pull image registry.corp.com/app:v1: rpc error: code Unknown desc failed to pull and unpack image: failed to resolve reference ...: pull access denied但你在节点上手动crictl pull registry.corp.com/app:v1却成功了原因Pod 没配 imagePullSecretsK8s 拉镜像时会用 Pod 所在命名空间下的 secret 来认证。如果你没显式指定imagePullSecrets它就用默认的通常是空的。解决办法创建 secretkubectl create secret docker-registry regcred \ --docker-serverregistry.corp.com \ --docker-usernameuser \ --docker-passwordpass \ -n your-ns在 Deployment 中引用spec: imagePullSecrets: - name: regcred 坑点Helm chart 里经常漏掉 imagePullSecrets导致内网部署失败。建议在 values.yaml 里统一配置。️ 我的私藏排错工具箱最后分享几个我常用的“暗黑”命令关键时刻能救命绕过容器直接抓包不用进 Podkubectl debug node/node -it --imagenicolaka/netshoot -- tcpdump -i eth0 port 80查看 Pod 被哪个控制器管理kubectl get pod pod -o jsonpath{.metadata.ownerReferences}快速导出所有事件按时间排序kubectl get events --sort-by.metadata.creationTimestamp -A检查 kubelet 是否上报心跳kubectl get --raw/api/v1/nodes/node/proxy/stats/summary诊断镜像自己打包的FROM alpine:3.18 RUN apk add --no-cache curl jq bind-tools net-tools iproute2 tcpdump CMD [sleep, infinity]用法kubectl run diag --imagemy-diag-tools -it --rm --restartNever -- sh总结排错不是玄学是系统工程K8s 排错说难也难说简单也简单。关键在于别慌先看状态STATUS再看事件Events最后看日志Logs分层排查从 Pod → Node → Network → Storage → Control Plane一层层剥善用工具kubectl journalctl tcpdump Prometheus组合拳打满预防大于治疗做好监控、告警、资源限制、健康检查很多问题根本不会发生。生产环境没有“不可能”只有“还没遇到”。你今天省下的一个探针配置可能就是明天半夜三点的夺命连环 call。