K8s这个系列写到第六篇,按我自己的规划,该聊点生产环境真正会疼的东西了。前几篇我们更多是在讲概念、搭集群、部署服务,但一个集群真正跑起来之后,你会发现80%的时间都耗在一件事上:排查故障。网上搜k8s相关的内容,热度高的除了安装部署教程,就是各种报错,比如"k8s控制节点master初始化显示the api server is not healthy"、"k8s生产环境中常见的故障影响到用户",这些词条的火爆程度,本身就说明大家遇到的坑高度集中。
这篇文章我不会去翻译官方文档,也不打算写那种"收藏了等于学会了"的清单,而是把我在实际工作中处理过的故障、踩过的坑、以及事后总结出来的排查套路,完整拆开讲给你听。适合几类读者:刚把集群搭起来准备上生产的运维同学,已经被Pod驱逐、节点NotReady折磨过的朋友,以及想搞清楚k8s和docker在故障场景下到底有什么区别、namespace和GPU这类资源怎么隔离和调度的人。就算你目前只是在学k8s、在记k8s学习笔记,这篇也能帮你把零散的知识点串成一条实战链路。
先说清楚一件事:k8s的学习曲线陡,陡不在概念多,而在故障链长。一个服务不可用,可能是业务代码问题,可能是Pod被驱逐,可能是节点磁盘满了,可能是CoreDNS挂了,可能是网络插件抽风,甚至可能是etcd慢了一下。任何一层出问题,最终都会表现为"用户访问失败了"。所以故障排查的核心不是背命令,而是建立一套"分层定位"的思维框架。
1. 生产环境的故障全景:先搞清楚K8s到底会坏在哪里
1.1 故障分层:控制面、数据面、应用面
我一直喜欢把K8s集群在逻辑上切成三层来看:控制面、数据面、应用面。控制面就是etcd、kube-apiserver、kube-scheduler、controller-manager这几个核心组件,它们负责存储状态、提供API、调度Pod。数据面是每台节点上的kubelet、容器运行时和网络插件,它们负责真正把容器拉起来、跑起来、连起来。应用面则是你部署的业务Pod、Service、Ingress、PVC这些业务资源。
生产环境里的故障,绝大多数不是单一层面的问题,而是层与层之间的连锁反应。举个例子,节点磁盘使用率超过85%,kubelet会根据驱逐阈值把Pod赶走,应用层看到的就是Pod被删了、服务闪断,但根因其实在数据面。再比如etcd性能变差,apiserver响应变慢,所有kubectl命令都卡,业务层反而是最后感知到的。所以我排障的第一反应永远是问一句:用户侧到底看到了什么?然后顺着这条线索往前推,先定层,再定位。
k8s和docker的区别,很多人从技术角度能说出一堆,但在故障排查里这个区别会非常直观:docker daemon是一个独立的守护进程,容器归它管,出了问题你看docker的日志基本能找到答案。K8s的容器运行时虽然是containerd这类组件,但真正负责"要不要重启、要不要驱逐"的决策者是kubelet。你习惯了用docker ps去看容器状态,上到k8s之后一定要改掉这个习惯,因为kubelet不会直接告诉你"这个Pod为什么起不来",你得去看它的事件、看API Server里的状态。
1.2 用户视角的故障影响面
热词里有"k8s生产环境中常见的故障影响到用户",这句话其实点破了排障的优先级。业务跑在K8s上,用户不关心你用的是Deployment还是StatefulSet,不关心你的节点是不是NotReady,他们关心的只有一件事:页面还能不能打开,接口响应快不快,数据会不会丢。
所以我的故障分级从来不是按照组件重要性来的,而是按照"用户影响面"来排的:影响所有用户的功能肯定先处理,影响单个用户的可以缓一缓;服务完全不可用比响应变慢更紧急,但响应从200ms涨到3秒其实比偶发5xx更隐蔽,也更容易被忽略。生产环境最常见的几类用户侧表现,一是白屏和5xx,说明服务不可用;二是超时和重试,说明链路里有瓶颈;三是数据不一致,说明有状态服务出了问题。每一种表现对应的排查入口完全不同。
这里特别想提醒一句:不要让命名空间namespace成为故障盲区。很多线上事故不是集群坏了,而是业务团队把测试环境和生产环境放在同一个集群的不同namespace里,某个测试服务占满了节点资源,把生产Pod挤爆了。namespace是隔离逻辑隔离,不是资源隔离,如果你不配ResourceQuota和LimitRange,它什么都拦不住。我见过不止一次因为namespace里没加资源配额,导致一个跑批任务把整台节点内存吃光、同节点其它生产Pod全部被驱逐的事故。
1.3 GPU资源是另一套玩法
顺带提一下热词里的"k8s调用gpu"。普通CPU和内存资源,kubelet调度时就看requests和limits就可以。GPU不一样,它要通过Device Plugin机制把GPU作为一种扩展资源上报给kubelet,然后在Pod里声明nvidia.com/gpu的limits,才能被调度到。排查GPU相关故障时,很多人第一步就跑偏了,去查Pod日志报什么CUDA错误,实际上大多数情况的根因是:GPU节点没打上nvidia.com/gpu=true的label、device plugin没跑起来、或者Pod的limits里没声明nvidia.com/gpu。后面第三章我会专门拆一个相关的案例。
2. "the api server is not healthy":master初始化踩坑实录
2.1 复现现场与报错背后逻辑
先把这个高频报错单独拿出来讲,因为搜索热词里出现最多的就是"k8s控制节点master初始化显示the api server is not healthy after 4m0.00747357s"。这行信息我太熟悉了,几乎每个用kubeadm装集群的人都见过,尤其在Rocky这类系统上装新版K8s(不管你是装1.28还是1.30,还是网上流传的所谓1.36,踩坑套路基本一样)。
第一次见到这行报错的人会慌,因为kubeadm init在前面一直显示节点初始化步骤,突然卡了几分钟然后抛出这么一句,看起来像系统出了什么不可挽回的问题。其实这句话的本质非常简单:kubeadm init执行到最后,会通过健康检查接口去访问kube-apiserver,也就是执行curl -k https://localhost:6443/healthz,如果这个接口在4分钟之内一直返回失败,kubeadm就说"the api server is not healthy after 4m0.00747357s"。4m是它默认等待的超时时间,0.00747357s只是程序自己计的精度,这两部分合在一起构成了报错文本。
关键问题就变成:apiserver为什么起不来?记住,kubeadm init创建的kube-apiserver是一个静态Pod,配置写在/etc/kubernetes/manifests/kube-apiserver.yaml里,由kubelet直接拉起。所以apiserver起不来的根因,要么是kubelet本身没有正常运行,要么是容器的静态Pod被创建了但容器启动失败,要么是启动之后连不上etcd或者健康检查没通过。排查链路就顺着这三条走。
2.2 排查链路:kubelet到静态Pod再到容器运行时
排查的第一步永远是看kubelet的状态,而不是去看apiserver的日志。kubelet是整个静态Pod的执行者,如果它在报错,后面全白搭。执行systemctl status kubelet看状态,如果显示active (running)就继续看日志,如果显示failed,直接journalctl -u kubelet -f -n 100看最近的报错。
然后不管kubelet状态如何,我都习惯用crictl而不是docker去查容器,因为K8s新版本默认用containerd作为运行时。crictl ps -a看一下节点上所有容器,重点看有没有kube-apiserver、etcd、kube-controller-manager、kube-scheduler这几个静态Pod对应的容器,以及它们的状态是不是Exited。如果容器已经退出,crictl logs 直接看容器日志,这里的报错往往才是真正的根因。比如我遇到过的几次情况,日志明确显示apiserver创建证书时找不到/etc/kubernetes/pki/ca.key,或者etcd因为数据目录权限不对起不来,这些问题只看kubeadm的输出是永远看不出来的。
如果容器还没被创建,那就去看/etc/kubernetes/manifests/目录下的几个yaml文件是否完好。有时候用户手欠改了里面的配置,或者之前的集群reset不干净,残留了旧配置,都会导致kubelet一直崩溃循环,镜像拉不下来,容器永远处于ContainerCreating。
2.3 实操命令与参数修正
把一套标准的排查命令整理出来,以后照着敲就行:
swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab systemctl status kubelet --no-pager journalctl -u kubelet -f -n 100 crictl ps -a crictl logs <container-id> cat /etc/kubernetes/manifests/kube-apiserver.yaml cat /etc/kubernetes/manifests/etcd.yaml df -h free -h除了命令之外,有几个高频参数要格外留意。第一是cgroup driver,Kubelet默认期望SystemdCgroup,而containerd的默认配置往往是cgroupfs,这两边不一致时容器能创建但运行态会异常。修改/etc/containerd/config.toml里SystemdCgroup = true,重启containerd,然后kubeadm reset之后重新init,问题多半就解决了。第二是容器镜像仓库的访问,初始化需要拉取十来个镜像,包括registry.k8s.io下的kube-apiserver、kube-controller-manager、kube-scheduler、etcd、pause等,如果节点处在内网环境、访问不了外网镜像仓库,kubelet日志里会反复出现镜像拉取超时,容器一直拉不下来。解决思路是配置registry mirror或者提前离线导入镜像,而不是死等4分钟超时。第三是swap没有完全关闭,很多系统里执行swapoff -a只是临时的,重启之后swap又起来了,kubelet会因为检测到swap直接拒绝启动,这类报错特征非常明显,日志里会写unexpected swap。
2.4 "4m0.00747357s"只是表象
顺便说一句,网上的教程里总有人把这个4分钟当成一个神秘现象,反复猜测是不是系统性能不行。真不是。kubeadm的默认超时就是4分钟,它是故意给你时间看日志的。所以你看到这行报错时,千万别再去初始化一次,然后就盯着屏幕等它再次失败。正确做法是立刻开另一个终端去查日志,查完再决定是kubeadm reset重来,还是修好问题等它自己恢复。
这个报错还有一个衍生场景:集群明明昨天还好好的,今天突然kubectl get nodes显示master节点NotReady,然后你去控制台上看到kube-apiserver容器反复重启。这种和初始化时的问题经常是同一个根源——节点上磁盘写满了,或者kubelet又因为cgroup配置被系统更新覆盖而挂掉。所以初始化踩过的坑,在生产环境里还会以另一种面目出现,提前理解这套逻辑,后面能省很多事。
3. 生产环境常见故障案例拆解
3.1 资源耗尽型故障:OOMKilled、Eviction、节点NotReady
如果说生产环境K8s故障里只能选一类代表,那一定是资源耗尽型。它覆盖了热词里的"k8s生产环境中常见的故障影响到用户"这个大方向,而且表现形式五花八门。
最常见的现象是Pod被杀,你去kubectl describe pod看到Last State: Terminated, Reason: OOMKilled。很多人第一反应是"业务代码内存泄漏了",这当然是一种可能,但更常见的原因是Pod的内存limit设得太小,或者压根没设limit。我遇过一个大坑:有个Java服务,JVM堆内存初始给了4G,但Pod的limits.memory只有2Gi,结果JVM启动没一会儿就整容器被内核OOM杀掉,重启了又杀,陷入CrashLoopBackOff。这个问题的本质是JVM参数和容器资源声明互相矛盾,不是代码的问题。
比OOMKilled更难受的是节点级驱逐。当节点内存或磁盘空间触发了kubelet的驱逐阈值(默认memory压力是100Mi或5%,磁盘是85%),kubelet会先尝试回收,回收不了就按优先级驱逐Pod。这时候有多少个Pod在跑根本不重要,重要的是节点的总资源容量已经被打满。比如一台32C64G的节点,上面20个Pod的requests加起来刚好是60G,看着还有4G余量,但某个Pod突然内存涨到12G,节点立刻进入MemoryPressure状态,所有Pod的QoS优先级重新洗牌,低优先级的BestEffort Pod第一批被赶走。所以给业务配置合理的requests和limits,不是字面意义上的限定资源,而是给kubelet一个"该杀谁、该留谁"的决策依据。
另一个与此相关的经典现象是节点NotReady。节点上的kubelet如果在持续报磁盘压力、内存压力,或者长时间没有上报心跳,控制面会把节点标记为NotReady。排查时先df -h看磁盘、free -h看内存,再用kubectl describe node确认Condition里写了什么。我处理过一个案例:/var/lib/containerd所在的根分区被日志塞满,kubelet自己都打不出日志了,节点状态当然也是NotReady。清理日志、把容器日志放到独立分区并配置logrotate之后才彻底解决。
3.2 网络与DNS故障的连锁反应
网络类故障是用户感知最直接的。应用层最常见的现象是服务间调用偶发超时、接口响应变慢,但业务代码没有报错。这种"软故障"比硬宕机更让人头疼,因为现场往往已经过去了,你只能靠监控日志回放。
我处理过一个印象很深的案例:一个微服务调用另一个服务的成功率从99.99%掉到了98%,看起来不致命,但用户侧明显感知到了卡顿。排查K8s层面时发现CoreDNS只有一个副本,而且那个副本所在节点的网络插件重启过一次,Pod漂移后DNS解析偶尔超时。业务方在配置里使用了主机名而不是Service域名,解析动作全压在CoreDNS上,单点故障被放大了。后来我们把CoreDNS扩到两个副本,加了podAntiAffinity让两个副本落在不同节点,又顺手配了node-local-dns做缓存,此后再没出过同类问题。
朋友,这里有个值得记住的规律:几乎所有的"应用偶发超时"都可以按这个顺序排查——先看业务日志有没有上游超时和连接拒绝,再看Pod重启次数,再看CoreDNS实例状态,再看网络插件Pod状态,最后看节点网卡和负载。90%的所谓"玄学网络问题"都能在链路里找到具体的慢点。
3.3 有状态服务:Redis集群在K8s里的坑
热词里有个"k8s redis集群",这个主题太容易踩坑了,值得单独聊。Redis Cluster在物理机上部署,节点标识是固定的IP和端口,但在K8s里Pod的IP是漂移的,重启一次IP就变了。很多人第一次在K8s里跑Redis集群,直接用Deployment管Redis Pod,数据一挂就全乱了,根本没有固定标识可用。正确玩法必须用StatefulSet,每个Pod有稳定的序号和稳定的域名,比如redis-0.redis-headless.namespace.svc.cluster.local,然后通过headless service暴露,集群节点之间用这个稳定域名互相通信。
但我遇到过比这更隐蔽的问题:有状态的集群和K8s的驱逐机制天生有摩擦。Redis节点一旦被节点级驱逐,数据可能没来得及刷盘就丢了,即使Pod自动拉起来,也是带着旧数据的内存镜像重启,主从同步差异很难收敛。生产环境里如果业务数据重要,我不建议让K8s自动管理Redis的持久化目录混乱问题——PVC一定要绑定到独立存储,用local PV或者云盘,不要用宿主机本地目录,否则Pod一旦被调度到别的节点,数据就读不到了。等到真发生"数据丢失、从节点落后主节点几万条命令"这种事故时,你就会明白什么叫有状态服务的分量。
3.4 GPU调度故障:设备明明在,Pod就是起不来
再拆一个高频点:k8s调用gpu。这个场景在AI训练和推理服务里非常常见。典型的故障表现是Pod状态卡在Pending,事件里写Failed to schedule,原因是0/4 nodes are available and 4 node(s) didn't match pod anti-affinity rules,或者明明GPU节点上有设备,但调度器认为这个节点不满足nvidia.com/gpu=1的资源要求。
排查步骤我一般固定这么走:先到GPU节点上执行nvidia-smi,确认物理驱动正常,显示不了设备那就是宿主机层面的问题,和K8s无关。然后确认device plugin是否以DaemonSet方式运行在所有GPU节点上,kubectl get pods -n kube-system | grep nvidia,看Pod状态是不是Running。再然后看节点资源是否上报,kubectl describe node ,在Capacity里找nvidia.com/gpu字段,如果没找到,说明device plugin上报失败,常见原因是nvidia-container-runtime没装好,或者kubelet没有开放扩展资源API。都正常之后,再检查你的Pod定义里limits是否声明了nvidia.com/gpu: 1,注意GPU资源只能放到limits里,不能混用requests和limits不一致的配置。
最后分享一个只有在生产环境才会遇到的细节:GPU资源的超卖问题。Nvidia的device plugin是按照物理卡数量上报资源的,一张卡就是1个nvidia.com/gpu,所以它在K8s眼里是"整卡粒度的"调度。如果你想两个Pod共享一张卡,靠K8s原生机制做不到,需要引入显存虚拟化方案或者MIG切分。否则就会出现在同一张卡上跑了两个任务但显存互相挤爆的黑天鹅。这种问题排起来非常痛苦,因为K8s没有任何一个层面会告诉你"你的Pod在物理设备上互相干扰"。
4. 一次生产故障的完整排查复盘
4.1 从用户投诉出发的现场还原
有些故障不是监控先报警,而是用户先炸了。我经历的一次典型事故,早上八点刚过,客服那边传来消息说订单接口开始报错,用户下单成功但页面一直转圈,然后陆续有人反馈支付回调失败。当时值班的人下意识去看数据库压力,因为接口频繁报超时,这种直觉不能说错,但方向偏了。
我接手后第一步不是去数据库,而是先看两个东西:kubectl get pods -A看全局Pod状态,再kubectl get events -A看最近有没有异常事件。结果发现核心订单服务有3个副本,其中2个处于CrashLoopBackOff,1个还勉强Running,但性能已经严重劣化。问题非常清晰了:服务只剩一个副本在干活,还扛着所有流量,不超时才有鬼。
4.2 排查看板与验证顺序
当时我的验证顺序大致是这样的:先kubectl describe pod确认了两个CrashLoopBackOff的Pod事件里写的是OOMKilled还是Error,如果是OOMKilled,直接看内存limits有没有设置、业务占用为什么超了;如果是其他错误,看业务日志定位具体异常。那次事件显示是OOMKilled,我立刻kubectl top node看整机内存和每台节点的实际用量,发现订单服务所在节点的内存使用率已经到92%,kubelet早就发出了MemoryPressure事件,但我们的告警规则里没有覆盖MemoryPressure,所以没人看见。
继续往下追了一层:订单服务的requests.memory只写了1Gi,但limits.memory是4Gi,两者差距过大。Kubernetes调度只看requests来判断节点能不能装下这个Pod,所以调度器认为这台节点内存非常充裕,一口气把多个Pod调度了上去,但Pod真正运行时的内存上限却是4Gi。结果多个Pod同时涨内存,节点物理内存被打爆,kubelet按优先级驱逐了一批Pod,订单服务直接被殃及。这个机制很多人不理解,简单说:requests是调度依据,limits是运行时资源上限,两者的差距越大,节点资源的真实水位越不可控。优先级低的业务的requests写得小、limits写得大,就会成为生产环境的"吸血鬼"。
4.3 恢复动作与止血措施
故障恢复阶段,我没有直接去改那3个Pod的资源声明,因为改动Deployment后Pod会重建,重建期间可能把仅剩的那个Running副本也搞挂。我先选择把订单服务的副本数临时扩到5个,让流量分散到更多节点,同时调整了节点的驱逐阈值,让kubelet更早介入磁盘和内存回收,避免等到物理内存真正耗尽。
等线上稳定下来之后才动刀改资源声明:把requests.memory从1Gi提升到2Gi,limits.memory保持在4Gi不变,这样节点不会被"看起来还剩很多、实际上随时会爆"的假象欺骗。同时给订单服务所在的namespace加了ResourceQuota,限制总内存上限,防止跑批任务再把资源挤爆。这些操作听起来不复杂,但每一步都要想清楚"动了之后会影响什么"——比如ResourceQuota如果设得太紧,可能新的Pod直接无法创建,所以我先设了一个比较宽的数值,观察一周后再收紧。
4.4 事后复盘:这个故障本来可以避免
复盘时我发现两件事。第一,集群里已经配置了Prometheus监控,但告警规则只有CPU使用率、内存使用率和节点状态,完全没有Pod OOMKilled事件、节点MemoryPressure事件这类"间接异常"指标。结果硬件还没到上限,集群的调度状态已经病了很久,监控却一片绿。后来我补了一组事件类规则:Pod重启次数5分钟超过5次告警,节点MemoryPressure持续30秒告警,etcd同步延迟超过50ms告警。第二,订单服务的JVM堆参数是开发团队直接从物理机部署时代拷贝过来的,启动参数-Xmx4g和容器limit完全脱节,这在容器化改造过程中非常普遍,隐蔽性极高。排查时看到容器limit是4Gi,但JVM实际可能因为识别不到cgroup限制而使用了宿主机内存总量,一启动就吃掉几个G,这是"k8s与docker在底层资源视图上的经典区别"。
5. 故障预防体系:别等事故发生了再学排查
5.1 监控指标与告警阈值的选择
故障排查做得再好,也只是事后补救。生产环境真正拉开差距的是预防体系。我自己的监控指标清单经过好几年迭代,现在基本稳定,不需要特别多,但要覆盖关键路径。
节点层我监控的是CPU使用率、内存使用率、根分区磁盘使用率、inode使用率,以及节点的MemoryPressure和DiskPressure状态。Pod和容器层监控的是重启次数、OOMKilled事件、ImagePullBackOff事件、CrashLoopBackOff事件。网络层重点盯CoreDNS的请求错误率、网络插件Pod(Calico或Cilium)的健康状态、节点网卡丢包率。存储层盯PVC的Pending事件和卷的IO延迟。有状态服务额外盯etcd的同步延迟指标(etcd_server_leader_applied_index等)。应用层我一般不去Prometheus里折腾响应时间,直接用业务自带的链路追踪和中台监控。
告警阈值我踩过太多坑,太灵敏会被噪音淹没,太迟钝等于没设。目前的一套经验值供参考:CPU使用率75%持续15分钟、内存使用率80%持续10分钟、根分区磁盘75%持续15分钟触发警告,85%持续5分钟触发严重;节点NotReady持续30秒必须触发严重;Pod单次OOMKilled即触发;Pod重启次数5次/10分钟触发严重;CoreDNS错误率超过1%持续5分钟触发严重。每个阈值都要根据业务调,但大方向是"可以接受短时间毛刺,不能容忍长时间恶化"。
5.2 容量规划:预留空间怎么算才合理
容量规划这块,我强烈建议在集群设计阶段就算清楚,而不是等报警了再补。以一个8节点集群为例,每个节点32C64G,总共256C512G。系统组件每节点预留2C4G,kubelet和容器运行时每节点再预留1G内存,这是硬性开销。剩下的可用计算量大约是240C480G。
然后看业务侧的requests总和,我个人习惯是控制在集群总容量的60%到70%之间,极限不超过75%。为什么留这么多?因为Pod的实际使用量会超过requests,再加上突发流量、节点故障时的Pod重调度、以及未来业务的自然增长,不留40%的缓冲就是在赌博。如果你算完发现requests总需求已经占到80%,那别犹豫,要么扩节点,要么推动业务优化资源声明。
另外Pod的requests与limits比例也要治理,我见过最夸张的是一个业务requests写1m、limits写1000m,它自己倒是永远不会被驱逐,但一台节点上能堆出几千个这种Pod,把节点负载打到天上。建议在每个namespace配置LimitRange,强制要求limits与requests的比值不得超过某个上限(比如3倍),否则拒绝创建Pod。这种规则一开始推起来总有业务方抱怨,但出事之后所有团队都会觉得真香。
5.3 演练与巡检清单
预防体系里第三块是演练。每年至少做一次节点故障模拟:把一台核心节点直接shutdown,观察控制面几分钟内完成Pod的重调度,服务是否恢复正常。再做一次网络故障模拟:停掉一个节点的网络插件DaemonSet,看同节点的CoreDNS、业务Pod是否跟着异常,验证网络拓扑的容错边界。这类演练不需要复杂的混沌工程工具,K8s原生就有节点drain、cordon机制,本质上和真实故障的干扰程度接近。
巡检清单我固定每周过一遍:kubectl get nodes看所有节点是否Ready,kubectl top nodes看资源水位,kubectl get pods -A快速扫一遍有没有非Running状态的Pod,再看kube-system里核心组件的Pod是否都在运行、镜像版本是否一致。巡检不是走过场,我要求值班的人必须对"发现异常后第一步做什么"有肌肉记忆,而不是把问题截图发群里就完事。
5.4 别把宝押在单个组件上
最后给一个偏架构层面的建议:任何自认为"核心"的组件,都不要以单体方式跑在集群里。etcd如果是单节点,整个集群的命脉就悬在一根线上;CoreDNS如果只有单副本,全集群的域名解析就存在单点;Ingress Controller如果只部署了一个副本,入口流量全压在一个容器上。生产环境的哲学是"在成本允许的范围内,把关键路径上所有可能单点的东西都拆掉"。这不是让基础设施无脑冗余,而是说在性能允许的前提下,多一个副本、多一层隔离,就能把很多潜在故障的影响面缩小一半。
6. 常见问题速查表
把前面所有内容沉淀成一张速查表,我平时也贴在自己工位上,遇到类似问题直接对照着查。
| 故障现象 | 可能原因 | 首选排查命令 | 解决思路 |
|---|---|---|---|
| kubeadm init报apiserver not healthy | kubelet异常、静态Pod起不来、镜像拉取失败 | journalctl -u kubelet -f -n 100; crictl ps -a | 按cgroup driver、swap、镜像源顺序排查 |
| 节点NotReady | 资源压力、kubelet宕了、网络插件异常 | kubectl describe node; df -h; free -h | 解除资源压力,修复网络插件,必要时重启kubelet |
| Pod一直Pending | 调度资源不足、PVC未绑定 | kubectl describe pod | 检查节点资源水位、PVC状态、污点和容忍度 |
| Pod反复CrashLoopBackOff | 业务启动异常、OOMKilled、配置错误 | kubectl logs; kubectl describe pod | 先确认是OOM还是业务错误,分别处理 |
| 镜像一直ImagePullBackOff | 镜像不存在、仓库认证失败、拉取超时 | kubectl describe pod | 检查image名称、registry配置、镜像源 |
| 应用偶发超时 | CoreDNS问题、网络插件抖动、节点负载高 | kubectl get pods -n kube-system; kubectl top nodes | 排查DNS与网络链路,扩副本与缓存 |
| Service访问不通 | 选择器不匹配、endpoints为空、Ingress配置错 | kubectl get endpoints; kubectl describe svc | 核对service selector与Pod标签 |
| PVC一直Pending | 存储类不存在、容量不足、驱动未安装 | kubectl describe pvc | 检查StorageClass和存储插件状态 |
| GPU资源不生效 | device plugin未运行、label缺失、limits未声明 | kubectl describe node; nvidia-smi | 确认驱动、插件、资源上报、Pod声明 |
| etcd leader频繁切换 | IO延迟高、网络抖动、etcd存储压力大 | kubectl logs -n kube-system etcd | 检查磁盘IO与网络,限流规避 |
| Pod被驱逐、节点MemoryPressure | 节点内存总量不足、requests与limits差距过大 | kubectl describe node; kubectl top nodes | 治理资源声明,配置驱逐阈值和ResourceQuota |
我个人的一点收尾
关于网上下载各种"k8s权威指南第五版pdf"这类资源,我这么看:书可以当字典查,但真正让你长本事的一定是线上故障。我学K8s这几年,最明显的进步都发生在处理完一个棘手的故障之后,那种"原来这个报错背后是这个机制"的感觉,是看多少PDF都换不来的。所以如果你正在学K8s,我的建议是:把基础概念过一遍之后,赶紧找一套环境去部署、去制造故障、去修复故障,踩坑越早,代价越小。
另一个小技巧,也是我最后想分享的:排障时把时间线记下来。几点几分出现什么现象、几点几分执行了什么命令、几点几分做了什么变更,全部写到记事本里。这个习惯在故障发生的时候看不出价值,但在复盘的时候价值巨大——因为你会发现很多所谓"莫名其妙的故障",其实是某个时间点的一个小操作埋下的地雷,时间线会直接指到那颗雷的位置。很多时候,高度紧张的排查现场是记不住这么多细节的,但白纸黑字的时间线会替你的记忆兜底。这篇文章里讲的所有排查思路,说到底就是这一件事:冷静、分层、记录、再动手。