深夜救火!我亲历的10大K8s诡异故障排查实录:这些坑你的集群可能正在经历

loong
2026-01-21 / 0 评论 / 11 阅读 / 正在检测是否收录...

深夜救火!我亲历的10大大规模Kubernetes集群诡异故障排查实录

凌晨三点,手机突然狂震。监控告警:线上核心服务Pod批量重启,CPU负载曲线像心电图一样狂跳,但日志里干净得像个乖孩子。这不是我第一次被这种‘症状清晰、病因模糊’的K8s故障从床上薅起来,也绝不会是最后一次。

如果你正在管理或计划管理大规模K8s集群(比如几百个节点、上千个服务),这篇文章可能会让你少掉很多头发。我不会讲那些‘kubectl get pods’的基础操作,而是聚焦于那些真正让人抓狂、搜索无果、甚至反常识的诡异问题。这些都是我用真金白银的线上事故和无数个不眠之夜换来的经验。

诡异的表象背后:我们到底在排查什么?

大规模集群的复杂性呈指数级增长。问题往往不再是单个Pod起不来这么简单,而是表现为系统的、间歇的、难以复现的诡异行为。搜索这类问题的你,大概率已经过了‘新手村’,正处于‘深水区’:你懂基本操作,但面对集群级别的‘玄学’问题,仍然感到无从下手。你的痛点很明确:如何从海量噪音(metrics、日志、事件)中,精准定位那个导致业务抖动或中断的根因?

下面这十大故障场景,每一个我都踩过坑,希望能成为你的排查地图。

故障一:“幽灵”抢占:Pod明明资源充足,为何频繁被Evicted?

现象:监控显示节点资源(CPU/Memory)远未用满,但某些Pod(尤其是低优先级的Batch Job)频繁被K8s驱逐(Evicted),报错‘Node pressure’。

诡异点:kubectl describe nodeAllocatableAllocated都对不上,总觉得有‘看不见’的资源被用了。

根因与排查实录:

  1. 检查Kubelet配置的System ReservedKube Reserved:这部分资源是预留给系统和K8s组件的,不体现在Allocatable,但会占用实际物理资源。如果你的预留设置过高,或节点上系统/kubelet实际占用远超预留值,就会挤压Pod空间。
  2. 排查隐形杀手——ephemeral-storage:这是最容易被忽略的资源!容器日志、镜像层、emptyDir卷都会快速消耗临时存储。用df -hdu命令深入节点,查看/var/lib/kubelet/var/lib/docker(或containerd根目录)的大小。我遇到过因日志轮转配置错误,导致/var/log/pods目录暴涨触发驱逐的案例。
  3. 警惕内存杀手——内存不可压缩资源:Pod的内存请求(request)如果设置过低,当Pod实际使用内存超过其Limit时,会被OOM Killer干掉。但更诡异的是,如果节点总的内存使用(包括缓存和Buffer)接近物理限制,即使Pod未超Limit,也可能因为整个节点的内存压力而‘躺枪’。

关键命令:

# 查看节点资源详情,重点关注Events和Allocatable
kubectl describe node <node-name>
# 登录节点,检查实际磁盘使用
df -h /var/lib/kubelet /var/lib/containerd
du -sh /var/log/pods/*
# 查看kubelet配置中的预留资源
ps aux | grep kubelet | grep -E "system-reserved|kube-reserved"

故障二:网络“黑洞”:Service ClusterIP间歇性不通

现象:从Pod A访问Service B的ClusterIP,大部分时间正常,但偶尔会超时或失败,直接访问Pod IP却一直正常。问题随机出现,难以捕捉。

诡异点:核心网络组件(Calico/Flannel/Cilium)的日志和监控都‘岁月静好’,kubectl get endpoints也显示Endpoints正常。

根因与排查实录:

  1. IPTables/IPVS的Conntrack表满:这是经典问题。在高连接数(特别是短连接)场景下,Linux连接跟踪表可能被迅速打满,导致新连接被丢弃。检查net.netfilter.nf_conntrack_countnet.netfilter.nf_conntrack_max。解决方法包括增大conntrack_max、减小conntrack_tcp_timeout_*,或者对于K8s 1.14+且使用IPVS模式的kube-proxy,其连接跟踪压力会小很多。
  2. 节点级网络策略冲突:某些安全软件或手动配置的iptables规则,可能会干扰kube-proxy生成的规则链。用iptables-save | grep <service-ip>检查规则是否被意外丢弃。
  3. CoreDNS或kube-dns负载不均/缓存问题:Service域名解析失败。检查CoreDNS Pod的负载是否均衡,监控其QPS和延迟。我曾遇到CoreDNS Pod所在的节点网络异常,导致部分Pod解析超时。

关键思路:这种间歇性问题,必须抓包。在客户端Pod和服务端Pod同时用tcpdump抓包,对比时间戳,看包在哪一环丢失了。

故障三:调度“僵局”:高优Pod卡在Pending,节点却有空闲

现象:一个关键Deployment扩缩容,新Pod一直Pending,kubectl describe显示调度失败的原因为‘0/XX nodes are available’。但明明有节点上有足够的空闲资源(kubectl top node)。

诡异点:调度器日志No nodes available,但没说具体原因。

根因与排查实录:

  1. NodeSelector/NodeAffinity/Affinity硬性约束不满足:这是最常见原因。仔细检查Pod的配置。
  2. Taints and Tolerations的‘潜规则’:即使Pod有Toleration能容忍节点的Taint,如果该Taint的effectNoSchedule,且节点上还存在其他该Pod不能容忍的Taint,调度依然会失败。需要逐个核对。
  3. Pod拓扑分布约束(PodTopologySpread):这个用于实现Pod打散的特性,在结合selector时可能产生意想不到的调度死锁。例如,要求Pod在zone间均匀分布,但某个zone的节点都不满足其他选择器要求。
  4. 资源碎片化:节点有空闲CPU,但可能都是‘碎片’。比如节点剩4个核,但Pod请求requests.cpu: 2.5,无法满足。kubectl describe node看的是总量,但调度器看的是可分配块。

关键命令:

# 查看调度失败的具体详情
kubectl describe pod <pending-pod-name>
# 查看调度器日志(需开启相应日志级别)
kubectl logs -n kube-system <scheduler-pod-name> --tail=100 | grep <pending-pod-name>

故障四:存储“断联”:PVC突然只读,卷无法挂载

现象:运行了很长时间的有状态应用(如数据库),突然报‘Read-only file system’或新Pod因‘Unable to mount volume’启动失败。使用云盘或Ceph等网络存储时尤其常见。

诡异点:存储后端自身监控显示一切正常,节点dmesgjournalctl里可能有隐蔽的错误日志。

根因与排查实录:

  1. 云盘被意外卸载或节点漂移:在云环境中,节点VM发生维护性重启或迁移时,如果CSI驱动处理不当,可能导致磁盘从旧节点‘卸载’后,在新节点‘挂载’失败,状态卡住。
  2. iSCSI/FC连接中断与重连失败:网络闪断可能导致存储连接断开,重连协议超时后,卷被标记为故障。检查节点的multipath -ll状态和存储客户端日志
  3. 文件系统损坏:某些极端情况下(如节点突然断电),文件系统可能损坏,触发只读保护。需要fsck修复,但务必先完整备份数据!
  4. StorageClass的回收策略(reclaimPolicy)是Delete:这是高危配置! 删除PVC时,会联动删除后端PV和物理数据。务必确认生产环境StorageClass的reclaimPolicy设置为Retain

关键步骤:立即将业务切换到备用实例(如果有),然后对问题节点执行:dmesg -T | tail -100, journalctl -u kubelet --since "5 minutes ago",并与存储管理员联动。

故障五:镜像“拉取”迷思:ImagePullBackOff背后的隐藏错误

现象:Pod状态ImagePullBackOff,错误信息就是简单的‘pull access denied’。但用同样的imagePullSecret在其他命名空间或集群拉取都正常。

诡异点:kubectl describe pod的错误信息过于笼统,掩盖了真实原因。

根因与排查实录:

  1. 私有仓库证书问题:节点时钟不同步,导致HTTPS证书验证失败。或者,仓库使用的自签名证书未添加到节点的Docker/Containerd信任链中。
  2. ImagePullSecret格式或挂载问题:Secret中的.dockerconfigjson格式错误,或Secret未被正确引用/挂载到Pod使用的ServiceAccount上。检查kubectl get secret <secret-name> -o yaml,确认data字段的.dockerconfigjson内容完整且为base64编码的合法JSON
  3. 仓库网络策略或防火墙:某些集群网络策略(NetworkPolicy)可能限制了Pod对特定外部IP(仓库地址)的访问。或者节点所在主机的防火墙规则阻止了443端口。
  4. 镜像仓库限流或宕机:尤其是使用Docker Hub免费账号时,很容易触发限流。查看节点上容器运行时的拉取日志。

关键命令:

# 模拟节点拉取镜像,在节点上执行
crictl pull <your-image-name>
# 查看容器运行时日志(以containerd为例)
journalctl -u containerd --since "1 hour ago" | grep -i pull

故障六:API“心跳”骤停:kube-apiserver间歇性高延迟或中断

现象:kubectl命令间歇性变慢或超时,监控显示apiserver的P99延迟飙高,但CPU/内存使用率并不高。

诡异点:问题周期性出现,与业务高峰不重合,重启apiserver Pod能暂时缓解。

根因与排查实录:

  1. Etcd性能瓶颈:Apiserver的后端是etcd。etcd的磁盘IOPS/延迟是生命线。使用etcdctl endpoint status检查etcd集群健康度和leader状态。我曾遇到一个案例,是etcd所在的云盘性能达到上限,导致写请求堆积。
  2. Apiserver的请求过滤器(Filter)或认证/授权组件(Webhook)性能问题:如果启用了审计日志、动态准入控制(Mutating/Validating Webhook),这些外部Webhook服务响应慢会直接拖垮apiserver。检查apiserver日志中是否有大量超时警告
  3. 资源泄漏或客户端连接数暴增:某些失控的Controller或客户端可能会创建大量LIST/WATCH连接,耗尽apiserver资源。使用ssnetstat查看apiserver进程的连接数

关键工具:使用kubectl get --raw=/readyz/livez检查apiserver健康状态,并使用kube-apiserver的metrics接口(通常是/metrics)分析请求延迟和错误码分布。

故障七:DNS“风暴”:CoreDNS因解析请求过多而崩溃

现象:集群内服务发现时好时坏,CoreDNS Pod的CPU使用率持续100%,频繁重启。nslookup间歇性失败。

诡异点:业务流量并未显著增长,但DNS请求量异常飙升。

根因与排查实录:

  1. 应用程序的DNS查询策略不佳:某些Java应用(特别是使用JNDI的)或配置了不当DNS缓存的客户端,会对每个连接都发起一次DNS查询,产生海量请求。
  2. 就绪探针(Readiness Probe)使用主机名而非IP:如果大量Pod的Readiness Probe配置为http://service-name:port/health,那么每次执行探针都会触发一次DNS查询。在Pod数量巨大时,DNS请求量将是(Pod数量 * 探针频率)的恐怖级别。
  3. CoreDNS缓存配置不当或内存不足:缓存太小(cache插件)或未启用缓存,导致所有请求都穿透到上游。CoreDNS Pod的内存Limit设置过低,在请求量大时可能OOM。

解决方案:

  • 优化应用,使用连接池并缓存DNS结果。
  • 将就绪探针的地址改为ClusterIP或Pod IP。
  • 调整CoreDNS配置:增大缓存(cache [TTL] [ZONES...]),启用负载均衡(loadbalance),并适当增加其CPU/内存资源。

故障八:节点“假死”:Kubelet状态为Ready,但Pod无法通信

现象:kubectl get node显示节点状态为Ready,但节点上的Pod无法被访问(服务无响应),且kubectl exec进入Pod也失败。

诡异点:节点看起来是健康的,但网络平面或容器运行时层面已“脑裂”。

根因与排查实录:

  1. 容器运行时(Docker/Containerd)无响应:运行时进程僵死或陷入内核死锁。systemctl status containerddockerd可能显示active (running),但实际已无法处理请求。需要重启容器运行时(但要做好Pod中断的准备)。
  2. CNI插件故障:网络插件(如Calico的calico-node、Flannel的kube-flannel)的Pod在该节点上CrashLoopBackOff,导致网络配置无法下发。检查kubectl get pods -n kube-system -o wide | grep <node-name>
  3. 节点负载极高,进程被Stall:由于磁盘IO、内存交换(Swap)或内核Bug导致系统完全无响应,虽然kubelet的心跳线程还能工作,但其他所有进程都已停滞。登录节点(如果还能登录)执行top,并查看dmesg

紧急处理:将该节点标记为不可调度并驱逐Pod:kubectl cordon <node-name> 然后 kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data。如果drain失败,再考虑重启节点。

故障九:配置“漂移”:看似相同的YAML,在不同集群表现迥异

现象:同一份Deployment YAML,在开发集群运行完美,到了生产集群就出各种问题(调度失败、启动错误等)。

诡异点:Diff了YAML文件,确认完全相同。

根因与排查实录:

  1. Kubernetes版本差异:不同版本(特别是跨Minor版本,如1.23 vs 1.25)的API默认值、行为可能发生变化。例如,securityContext的默认值、Pod优先级等。
  2. 准入控制器(Admission Controllers)不同:生产集群可能启用了额外的Mutating Webhook(如Istio sidecar注入、Pod安全策略PSP的替代品等),它们动态修改了你的Pod Spec,而你没意识到。使用kubectl get pod <pod-name> -o yaml查看被实际创建的Pod的‘最终形态’
  3. 集群组件配置不同:kube-scheduler、kube-controller-manager的命令行参数(如--default-not-ready-toleration-seconds)可能不同,影响Pod的行为。

黄金法则:基础设施即代码(IaC)。不仅应用YAML要代码化,集群的初始化配置、插件部署、策略配置也应全部代码化,确保环境一致性。

故障十:日志“消失”:容器日志被神秘截断或丢失

现象:应用抛出一个关键错误后崩溃,但kubectl logs只看到崩溃前的几条普通日志,关键的报错信息不见了。或者,日志文件在容器内明明存在且完整,但kubectl logs就是看不到最新部分。

诡异点:日志像被一刀切断,不符合预期。

根因与排查实录:

  1. 容器运行时日志驱动配置:如果Docker/Containerd配置的日志驱动(如json-file)的max-sizemax-file设置过小,当日志写满最大文件数后,会回滚删除最老的日志文件,造成日志丢失。
  2. Sidecar日志采集器的‘激进’读取:如果使用了Filebeat、Fluent-bit等Sidecar容器通过共享卷方式采集日志,它们可能以‘tail’方式读取文件。某些情况下,如果主容器和Sidecar对文件的读写存在竞争,可能导致日志被跳过或读取不完整。
  3. 缓冲区未刷新:应用日志写入标准输出(stdout/stderr)时,如果未配置缓冲刷新(如Java的-XX:+DisableExplicitGC可能影响?不,这里更可能是-Xlog:async或日志框架的缓冲设置),在进程突然崩溃时,缓冲区中的数据来不及写入就被丢弃了。

解决之道:

  • 调整容器运行时的日志轮转策略(如增大max-size)。
  • 对于关键应用,考虑让应用直接将日志写入持久化卷上的文件,而非仅依赖标准输出。
  • 确保日志采集器配置的可靠性。

写在最后:诡异故障的通关秘籍

处理大规模K8s集群的诡异问题,像法医破案,也像老中医问诊。你需要:

  1. 全链路可观测性:Metrics(Prometheus)、Logs(Loki/ELK)、Traces(Jaeger)一个都不能少。让数据说话。
  2. 清晰的排查层次:从宏观(集群状态)到微观(节点进程、内核日志),逐层下钻,隔离变量。
  3. 怀疑一切默认配置:K8s默认配置是为通用性设计的,不一定适合你的规模和场景。资源配额、调度器参数、网络策略、日志轮转......都需要根据实际情况调整。
  4. 建立自己的知识库和SOP:把每次踩坑和解决过程记录下来,形成内部排查手册。团队的知识共享能极大提升救火效率。

最核心的一点:保持敬畏,保持好奇。Kubernetes生态庞大而复杂,没人能通晓所有细节。遇到问题时,敢于深入底层(容器运行时、Linux内核、网络协议),往往能找到最终答案。

你的集群最近遇到了什么‘诡异’问题?欢迎分享,或许我能提供一些排查思路。

0