1. 整体设计思路:先定位,再动手,最后收尾复盘
每当有人问我Kubernetes环境出问题该怎么办,我的第一反应都是:别急着去重启Pod。Kubernetes排障和传统运维最大的区别在于,故障往往不是单点的。一个应用起不来,背后可能是镜像仓库拉不动、调度器把Pod分到了一个不健康的节点、存储卷没有挂上、网络策略拦了流量,或者干脆是配置清单里有个字段写错了。这套系统里角色太多,链路太长,如果一上来就凭直觉乱试,大概率会把问题扩得更大。
所以,我通常会把排障过程拆成三层:现象层、资源层、能力层。现象层是用户或者监控告诉你的症状,比如Pod一直Pending、应用反复CrashLoopBackOff;资源层是Kubernetes的各个核心对象,包括Pod、Deployment、Service、Node、PV/PVC、Ingress等;能力层则是底层基础设施提供的支持,比如容器运行时能不能跑、CNI网络插件是否连通、CSI存储插件是否正常挂载。整个排查过程就是沿着这个分层框架逐级下钻,每层只解决这一层的问题,不做跨层猜测。
这个思路的背后有三个实际原因。第一,Kubernetes的API是一套声明式模型,我们看到的资源状态往往是最终状态的快照,而不是故障的直接原因,比如Pod显示ImagePullBackOff,真实原因可能在消息里。第二,控制面和数据面分离的架构决定了某些问题要从不同入口查,比如访问Service不通,可能需要分别看kube-proxy的规则、DNS解析、节点网络策略等多个环节。第三,排障本身也是一次测试,不同的观察手段(kubectl describe、事件、日志、实时抓包)各自能看到一个侧面,组合起来才能拼出全貌。
这篇文章不会按照某个特定故障的单一排查过程来写,而是把Kubernetes容器环境里最常见的几类故障场景单独拆开,每类都讲清楚排查入口、关键命令、注意点,以及我自己在实际环境里踩过的坑。你在读的时候最好带着一个真实问题来看,比如“我的Pod为什么一直Pending”,那么直接跳到对应章节,会比从头到尾通读更快。
2. 前置准备:环境信息、工具集与排障模型
2.1 排障前需要收集哪些基础信息
不管是什么故障,我建议你先把“案件现场”的信息固定下来,否则排查到一半发现缺少关键信息再回头补,浪费的不只是时间,还可能错过现场痕迹。需要收集的核心信息有四类。
第一类是集群和节点的版本信息。Kubernetes的版本差异有时候会直接影响API行为和资源解释方式,尤其是从1.20到1.30之间的版本,调度逻辑、驱逐策略、端口命名都有不少变化。执行kubectl version和kubectl get nodes -o wide拿到版本、内核、容器运行时类型和版本,顺便确认各节点状态是否Ready。
第二类是工作负载的定义和当前状态。这里不只是看Pod状态,还要看引用它的控制器类型,比如是Deployment、StatefulSet还是有状态应用、DaemonSet还是Job。执行kubectl get deploy/xxx -o yaml,kubectl get pod -o wide,然后记录Pod所在的节点、重启次数、IP分配情况。
第三类是最近的变更记录。这个问题很多时候是昨天还好好的,今天就崩了,中间做过什么变更往往是破案关键。尽量去翻一下最近一次发布的时间点、镜像tag有没有变、环境变量或者ConfigMap有没有改动、节点有没有扩缩容。如果集群有审计日志,那更省事了。
第四类是业务层面的预期。排障不是为了“让Pod跑起来”,而是要让应用恢复服务。所以你得知道当前流量是多少、可用性阈值是多少、有没有别的降级方案。把业务预期和技术状态放在一起对照,才能决定是快速止血还是深入根治。
2.2 常用工具的安装与基础用法
Kubernetes排障的默认工具自然是kubectl,但只有kubectl往往不够。我这里列几个我自己常用的命令行工具,它们的定位各不相同。
- kubectl:必需,日常操作全靠它,建议配好shell自动补全和kubeconfig上下文切换。
- k9s:终端UI工具,适合先快速浏览大量资源的状态,再定位到具体对象深入查看,效率比纯命令行高不少。
- jq:处理
kubectl get ... -o json输出的神器,尤其是从Pod列表里筛选字段、解析状态信息时,手写一条jq表达式比来回grep靠谱得多。 - stern:多Pod日志聚合工具,可以按标签同时跟踪多个Pod的日志,排查多副本应用问题时非常实用。
- nsenter、tcpdump等宿主机工具:很多网络问题必须进到宿主机网络命名空间里才能看明白,后面讲网络排障时会细说。
安装它们都不复杂,优先用系统包管理器或者各自的构建脚本。有一点要提醒你,工具本身解决的是“看得见”的问题,真正决定排障效率的还是对Kubernetes模型的熟悉程度。工具只是让你更快地收集信息,不能替代分析。
2.3 我常用的四层排障模型
这套模型是我自己在生产环境中反复调整后固定下来的。它在前面提到的三层框架基础上再细分成四步:确认现象 → 检查状态 → 查看事件 → 深入日志/抓包。每次排障都可以把这四步走一遍,每一步都争取拿到明确结论,不糊弄过去。
第一步,确认现象。别管监控怎么报警,先亲自看一下当前实际状态。比如服务报错访问不了,先用curl探测一次,看看是连接超时、连接拒绝还是返回5xx,这三种情况指向的方向完全不同。再比如Pod状态显示Running,但业务就是处理不了请求,那就要进到应用内部看健康检查接口是不是异常。
第二步,检查状态。用kubectl get看一遍相关资源的状态列、年龄列和重启次数,再配合-o wide拿到IP和节点信息。这一步能快速排除一些低级问题,比如Pod没调度到节点上、Service没有关联到端点、PVC还处于Pending状态。
第三步,查看事件。kubectl describe是Kubernetes排障里最核心的命令,它把控制器、节点、kubelet、卷插件等组件的事件都汇总展示出来。很多时候故障的根因就藏在事件最后面的几条Warning里,比如FailedScheduling、FailedMount、Back-off pulling image,这些信息比Pod状态本身开放得多。
第四步,深入日志和抓包。这一步需要根据前三步的结果来决定,是看kubelet日志、容器内应用的日志,还是在节点上用tcpdump抓包。日志和抓包属于“实锤”环节,用于确认前面的怀疑,或者在前三步没有结论时找到新的线索。
这四步走完,大多数问题都能定位到某一个具体环节。如果还没有结论,那通常说明我们怀疑错了方向,回头重新校正第一步的“现象”定义,往往更有效。
3. Pod生命周期类故障:定位并解决调度、拉镜像与启动失败
3.1 Pod一直Pending,怎么查调度到底卡在哪里
Pod处于Pending状态,通俗讲就是调度器还没给它找到一个合适的节点。看到这个状态,第一反应应该是问:是没有节点可用,还是节点不满足条件。最快的方式是执行kubectl describe pod xxx,然后在Events里找FailedScheduling。它通常会用一句简洁的话告诉你原因,比如0/3 nodes are available: 1 node(s) had taint, 2 node(s) had resource pressure。
遇到这种情况,常见原因有几种。资源不足是最常见的,节点CPU或内存不够,或者可分配资源已被其他Pod占满,这时可以用kubectl describe node查看各节点的资源分配情况和已分配量。另一个常见原因是节点上有污点(taint),而Pod没有对应的容忍(toleration)。结合咱们运营的集群来看,有些节点可能特意打了污点用来隔离混部任务,新业务Pod如果没有加上容忍,调度器无论如何都不会把Pod放上去。
还有一类隐蔽问题是拓扑分布约束,比如Pod要求必须调度到某些可用区内,或者需要与某个Pod分布在不同节点,如果当前节点规模不足以满足这些条件,同样会Pending。排查这类问题,建议把调度器日志也打开,开启-v=4级别的日志能看到调度流程里每个环节的耗时和失败原因。有时候调度器明明有空闲节点,但Pod还是Pending,那就去检查scheduler.kubeconfig的连通性,以及调度器自身是否处于Leader状态。
注意:如果集群采用了自定义调度器或者调度器插件,比如基于节点资源超卖的自研插件,那
FailedScheduling可能不会给出完整原因,这时候需要结合自定义调度器的日志一起看,别只盯着kube-scheduler的日志。
3.2 ImagePullBackOff与ErrImagePull:镜像拉取失败的完整排查路径
镜像拉取失败在Kubernetes环境里实在是太常见了,特别是对于私有镜像仓库的自建集群。你看到Pod状态是ImagePullBackOff或者ErrImagePull,第一件事永远是kubectl describe pod看事件。事件里面通常会有Failed to pull image,后面跟着一段比较关键的失败细节。
常见的失败原因和应对方式,我整理成一个速查表。
| 失败原因 | 典型报错片段 | 处理思路 |
|---|---|---|
| 镜像不存在或tag写错 | manifest unknown / not found | 检查镜像名称和tag拼写,去仓库确认 |
| 仓库认证失败 | authentication required / pull access denied | 检查imagePullSecrets是否正确配置并引用 |
| 网络无法到达仓库 | dial tcp: lookup xxx on ... no such host | 检查节点DNS、防火墙、HTTP代理设置 |
| 镜像仓库证书问题 | x509: certificate signed by unknown authority | 在节点上配置仓库CA证书到可信目录 |
| 镜像过大导致超时 | context deadline exceeded / i/o timeout | 优化镜像体积、预热节点镜像缓存、增加拉取超时 |
如果事件里没有明确信息,就去节点上手动拉一次镜像试试,用crictl pull或者nerdctl pull,这能直接暴露真正的底层错误。比如证书问题、认证问题还是网络问题,手动一拉就明白了。
我还记得有一次,所有节点都设置了HTTP代理,但代理服务某天到期了,Pod先是ImagePullBackOff,后来整个节点上所有新Pod全部拉取失败。当时describe拿到的错误是“proxyconnect tcp: dial tcp: lookup xxx on xxx:53: no such host”,一看就知道是代理域名解析挂了。这类问题在纯内网环境尤其要重视,镜像仓库的白名单、代理的可用性、DNS解析的健康,都值得做常态化的拨测。
3.3 CrashLoopBackOff:容器一直重启的定位思路
CrashLoopBackOff意味着容器启动后马上崩溃,kubelet反复重启但还是失败。这可能是整个Kubernetes排障里最需要耐心的一类问题,因为根因往往藏在应用的日志、启动参数、环境变量或者探针里。
第一步,看当前容器的退出码。kubectl describe pod会显示容器的Last State,包括Exit Code。如果退出码是0,说明容器是正常退出但Exited,大概率是启动后主动结束,比如一次性任务或者应用收到什么信号后退出;非0的退出码要和具体的错误日志结合起来判断,比如28是OOMKilled,137是SIGKILL,126是命令找不到,127是命令不存在。
第二步,看日志。执行kubectl logs xxx --previous,这个命令用于查看容器上一次退出前打印的内容,对CrashLoopBackOff特别有效。很多应用启动时崩溃前会打印异常堆栈,这些信息就是答案。如果容器里没有把日志写到stdout,那就要在节点上找容器文件系统的日志目录,但这属于比较特殊的情况。
第三步,检查启动命令和参数。应用在Kubernetes里启动失败,很多时候是因为镜像里默认的启动方式和你给的command/args不匹配。比如镜像本身通过入口脚本启动,脚本依赖某个环境变量,你忘了配置,直接裸跑java -jar就崩了。这时候拿镜像在本地用docker run手动起一遍,用同样的环境变量和参数复现问题,通常很快能找到差异。
还有一类容易被忽略的原因是启动探针或者就绪探针写的太严格,应用本身没有Crash,而是因为探针失败被kubelet判定为不健康,进而被反复重启。这种情况要区分好,去Events里看是否有Unhealthy,在kubectl describe的事件里会提示探针失败的具体原因,比如超时或者HTTP返回码不对。探针问题不能靠盲目调大超时去掩盖,要理解探针的本意是保护流量的,如果你的探针比应用真实启动时间还快,那必然会导致重启风暴。
3.4 Pod实际运行但业务异常时,如何借助事件与日志定位
这属于最让人头疼的情况:Pod明明Running,但流量进来后报错、超时、或者数据不对。首先要明确一个前提:Kubernetes不会替你处理应用内部的逻辑错误,它只负责保证Pod的“存活”。所以查看的顺序要先从Kubernetes层面排除,再进入应用层面。
先从Pod状态和重启计数判断是否存在间歇性崩溃。用kubectl get pod -w持续观察一段时间的状态变化,重启次数如果不断上涨,说明应用还在周期性崩溃,回到3.3。如果重启次数稳定,那就确认Pod的副本数、归属Service和Endpoint是否正常;用kubectl get endpoints xxx看Service后面挂的Pod IP是否和kubectl get pod -o wide输出的IP一致。
接下来进到应用日志。推荐先用kubectl logs查看当前最新日志,再用stern按标签聚合多个Pod的日志,对比各副本之间的行为是否一致。如果所有副本都在报同样错误,那就是配置或资源层面的问题;如果只有个别副本报错,那大概率是节点差异或者Pod间数据不一致。
这里要提醒一个经验:不要把“Pod能启动”当作“应用健康”的唯一标准。Kubernetes的Liveness探针只会探活,不会探应用依赖。如果应用依赖数据库、Redis、消息队列,这些外部依赖出问题时应用往往日志还在正常输出,但业务接口已经不可用。所以业务层面要建立自己的健康检查接口,比如/health里包含依赖组件的探测结果,这样Kubernetes的探针才能反映真实可用状态。
4. 网络与配置类故障:Service不通、DNS解析异常与存储挂载问题
4.1 Service不通,是链路里哪一段出了问题
Service访问不通,很多人在“Service对象”里找原因,其实Service本身只是个API对象,真正的数据通路是由kube-proxy、节点iptables/ipvs规则、Endpoint、Pod网络等共同组成的。排查思路应该先拿拓扑图。
第一步,先确定客户端从哪里访问。是集群内Pod访问、集群外节点访问,还是通过外部负载均衡访问。不同场景下要检查的链路节点不同。集群内Pod访问的话,先确认Service的ClusterIP、端口、协议,再在Pod里用curl或nc测试连通性,比如curl -v http://<ClusterIP>:<Port>。
第二步,检查Endpoint。kubectl get endpoints <svc>,如果显示<none>,说明Service的selector和Pod标签不匹配,这是很常见也容易被忽略的点。比如Pod标签写的是app: myapp,但Service的selector写成了app: my-app,结果Endpoint空。另一个可能是Pod处于未就绪状态,Kubernetes默认不会把未就绪Pod纳入Service端点,需要检查Pod的ReadinessProbe。
第三步,验证kube-proxy规则。进入节点,先看kube-proxy的模式和运行状态。较新的集群默认用iptables模式或者ipvs(取决于kube-proxy启动参数),可以用iptables-save | grep <ClusterIP>或ipvsadm -Ln看看规则有没有生成。如果规则存在但访问还是不通,继续查节点内核参数net.ipv4.ip_forward是否开启,以及conntrack表是否满。
第四步,排查网络策略。如果你使用了Calico、Cilium等CNI插件并启用了网络策略,Service本身可能没问题,但流量被策略拦了。kubectl get networkpolicy -A,检查当前Pod的入站和出站策略,必要时用kubectl describe networkpolicy查看选择器和规则明细。
有一个在生产环境反复遇到的教训:跨节点通讯时需要保证Pod CIDR在节点路由表和云厂商路由表中都正确配置。某次加新节点后,新节点上的Pod能访问同一节点的Service,但访问其他节点的Pod就超时,最终发现是节点上没加路由规则,导致数据包发出去没有回包。所以当你确认Service、Endpoint、kube-proxy规则都正常但仍然不通时,一定要检查节点间的路由连通性,而不是一直盯着Kubernetes对象。
4.2 DNS解析失败,CoreDNS相关问题的排查要点
在Kubernetes里,服务间访问大多通过域名完成,尤其是包含了一堆微服务之后,DNS就成了一条动态业务链。DNS出问题的主要现象是:Pod里nslookup或curl解析不了Service名称,或者偶尔超时。这种感觉很像家里路由器有一天突然不派发IP,全屋设备都上不了网。
首先检查CoreDNS的Pod状态。kubectl get pod -n kube-system -l k8s-app=kube-dns,如果Pod一直重启,去看日志,可能是CoreDNS版本与上游DNS配置不兼容,或者是配置了无效的上游地址导致转发失败。如果Pod正常,再订阅域名测试:进入一个业务Pod里执行nslookup <service_name>.<namespace>.svc.cluster.local,看返回结果。
如果解析失败,去查看CoreDNS的ConfigMap配置。kubectl get configmap -n kube-system coredns -o yaml,重点看forward指令的upstream地址。自建集群中如果这一步配的是内网DNS地址,那么服务发现能不能通完全取决于内网DNS对那些域名有没有正确A记录。有时我们会在CoreDNS中做hosts和rewrite规则,如果改动了这部分但没重载配置,也是坑。
还有一个常见坑是ndots问题。应用容器里默认的resolv.conf会设置ndots:5,域名里只要点的数量少于5,就会先走search domain,造成额外查询超时。典型表现是业务里访问同一个命名空间里的短域名没问题,但偶尔很慢。这时候可以把应用的DNS策略改成Default,让Pod继承节点DNS配置,或者在Pod yaml里自定义dnsConfig。
DNS排障最后一步要记得看节点上的DNS通信是否通畅。CoreDNS的Pod在节点上通过NodeLocal DNSCache(如果部署了)提供转发,整个链路里任意一环断了,表现都是解析失败。如果业务Pod能解析公网域名但不能解析Service名称,重点检查CoreDNS的Pod网络和kube-dns service的Endpoint是否正常。
4.3 存储挂载失败:PVC Pending与VolumeMount超时的常见偏差
存储相关故障的排查难度通常不在于原理,而在于存储插件和底层存储系统的种类太多。我见过的问题里,七八成都是PVC Pending,剩下两三成是Pod能启动,但挂载卷一直没有Ready。
PVC Pending的排查,看kubectl describe pvc事件就够了。如果事件里提示waiting for a volume to be created, either by external provisioner,说明StorageClass对应Provisioner没有正常工作。先去确认StorageClass是否指定了provisioner,比如kubernetes.io/no-provisioner表示本地静态存储,而这不能自动创建PV;ebs.csi.aws.com等则要求环境中CSI控制器运行正常。查看StorageClass事件,或检查Provisioner Pod的日志,往往能找出底层创建卷失败的原因。
VolumeMount超时看起来则隐蔽很多。kubectl describe pod的Events里可能会看到FailedMount,提示mount超时,但真正的根因要看kubelet日志,在/var/log/messages或者systemd journal中搜索kubelet和mount相关字段。常见问题包括:节点上缺少nfs-client、iscsi等mount依赖工具;SMB/CIFS卷认证失败;节点无法访问存储网关;存储可以接上,但PV的mountOptions写错了。
对于这种问题,我的建议是先在节点上手动执行一遍mount命令。比如对于NFS卷,直接mount -t nfs <server>:<path> /mnt,能成功挂上说明kubelet之前失败可能有其他原因,比如SELinux、AppArmor限制;挂不上就顺着系统报错直接解决,效率高得多。
另外,容器运行时的cgroup和卷挂载之间还有一个隐蔽的坑:很多存储插件在挂载时要求Pod的启动容器内有权限访问挂载点,如果你的Pod使用securityContext设置了readOnlyRootFilesystem: true,而应用需要往挂载卷里写临时文件,那就会产生应用“看起来在跑但不停报错”的怪现象。这时候去检查应用日志里的权限错误,或者临时关掉readOnlyRootFilesystem试验一次,很快就能确认。
5. 特殊场景与常见问题的实战排查速查
5.1 节点异常:NotReady、资源压力与驱逐机制的影响
节点状态从技术角度属于kubelet上报给控制面的状态汇总。节点NotReady,最常见的原因是kubelet心跳超时,也就是节点上kubelet与API Server之间的网络断了,或者kubelet进程异常。排查时先用kubectl get node看节点状态与Age,然后SSH到节点上执行systemctl status kubelet,再查看kubelet日志。
在云厂商环境里,还有一种可能被忽视的情况:节点由于底层虚拟机内存/CPU超卖或磁盘IO飙高,导致kubelet上报状态延迟,控制面在超时后把节点标记为NotReady。监控指标里往往能看到节点负载和node_load1在异常前有一段攀升。
节点资源压力的问题则分为内存压力、磁盘压力和PID压力。一旦触发压力,kubelet会对节点上的Pod进行驱逐(eviction),优先驱逐超出requests的Pod或BestEffort类Pod。如果发现某些Pod突然被全部杀掉,且事件里有Evicted字样,那就是资源压力驱逐无疑。此时应检查节点资源使用率,并结合kubectl describe node查看Allocatable和Allocated resources,判断是否有Pod占用了过多资源。
注意:生产环境里不建议用“重启节点”来应对NotReady。重启会让节点上的Pod全部迁移,在有状态应用场景里破坏性极大。正确的做法是先通过kubelet日志定位根因,比如网络故障、容器运行时故障、磁盘故障等,再决定是恢复kubelet服务还是临时驱逐节点。
5.2 快速定位Pod一直被重新创建的控制器问题
Deployment与StatefulSet的控制器差异是造成“为什么Pod一直重新创建”的关键。Deployment管理的Pod是有序替换、滚动更新,但StatefulSet则保证Pod的编号和存储卷绑定。如果你发现某个StatefulSet的Pod一直在创建,但每次创建都失败,则需要重点查看StatefulSet的serviceName和PVC是否可复用。
Deployment滚动更新卡住,常见原因是新版本Pod一直处于Pending或CrashLoopBackOff,导致旧Pod不能被替换。这时候用kubectl rollout status deployment/xxx查看进度,再用kubectl rollout undo回滚。如果遇到更新策略为Recreate的,那么发布新版本会先把旧Pod全部删除,新Pod如果起不来,整个服务就中断了,所以生产环境里我一般建议把更新策略改为RollingUpdate,并配置合理的maxUnavailable和maxSurge。
另一个让人迷惑的场景是Pod被驱逐后,控制器会根据副本数自动重新创建,但新Pod永远调度不到合适的节点。这其实是控制器工作正常,但集群资源或污点不满足导致的。排障时应同时查看控制器状态、Pod状态和节点资源,三者结合才能判断是控制器的问题还是资源池的问题。
5.3 使用kubectl的实用技巧与有效日志获取方式
kubectl本身暗藏很多实用技巧,熟练使用能大幅缩短排障时间。先说输出定制。kubectl get pod -o wide能看到节点和IP;-o custom-columns可以精确提取指定列,例如-o custom-columns=NAME:.metadata.name,STATUS:.status.phase,IP:.status.podIP。如果你要过滤某个状态,可以用kubectl get pods -A -o json | jq '.items[] | select(.status.phase=="Running")',jq在这个场景下非常顺手。
日志获取方面,kubectl logs基本命令之外,-f跟随输出、--tail看末尾行数、--since看某时间段日志,都是日常必备。多容器Pod里看指定容器用-c参数,看前一个实例加--previous。有些应用日志量很大,建议先把日志输出到文件再分析,避免终端滚动刷屏。
在节点上查看容器日志的另一种途径是crictl。Kubernetes从1.20以后逐步放弃docker作为运行时,很多集群都切换到containerd,此时在节点上用crictl ps查看容器,crictl logs <containerid>看日志,crictl inspect <containerid>看容器状态和挂载信息。学会crictl之后,即便API Server不可达,也能在节点层独立排查容器状态,这在生产环境的脱机应急中是个很硬核的技能。
5.4 从三天生产故障中提炼的排障经验
讲一个我印象很深的案例。某个集群在三天的不同时段出现零星5xx错误,监控一直报警,但每次报错持续几分钟就自动恢复,完全找不到规律。当时团队先怀疑是应用代码问题,但看应用日志只看到超时异常,没有并发错误。后来我们把视线转向DNS和网络层,才发现节点上部署的CoreDNS在高并发下出现spurious response,部分域名查询会返回空结果,而Kubelet自身的健康状态完全正常。
这个案例带来的经验是:故障排名不一定是顺序发生,可能同时有多个环节在恶化。比如节点上磁盘IO高会拖慢容器创建,同时又会拖慢CoreDNS响应,最终表现为偶发性服务不可用。这种情况下,如果只关注单一指标很容易陷入误判。建议在排障初期就同时采集多种维度的数据,比如节点层面、网络层面、应用层面,画一条时间线,把异常数据点对齐,这样能明显提高判断精度。
另外,排障时要有“回滚意识”。如果某次变更导致了故障,能最快恢复系统的方式不是修改新版本,而是回滚到之前稳定版本。虽然这听起来很简单,但很多团队会因为不想承认发布有问题而继续在新版本上调试,结果小问题拖成大故障。遇到生产问题,先把业务恢复,再聊根因,永远是第一原则。
6. 写在最后:几个我必须再三强调的排障习惯
最后我想分享的不是某个具体的命令,而是几个长期积累下来的习惯,它们对解决Kubernetes故障的帮助比任何单一工具都大。
第一,习惯用时间线记录排查过程。从收到报警开始,把每个时间点观察到的状态、执行过的命令、获取到的报错都记在纸上或者文档里。排查到后来,你会发现自己经常需要通过对比不同时间段的状态来判断变化趋势,这份记录就是你的“时间目击证人”。
第二,养成分层验证的习惯。无论问题看起来多像一个应用代码bug,都不要跳过Kubernetes层面的检查。也许问题确实是代码bug,但如果你没有先确认网络、存储、配置都正常,那么后续所有基于“代码问题”的分析都可能建立在一个错误假设之上。用最快的方式把基础层排查完,往往只需要十几分钟,却能让之后的分析方向不偏航。
第三,为故障准备好标准操作手册。不是所有故障都有时间在发生时才从头想,提前针对常见故障场景写好SOP,比如“节点NotReady怎么处理”“Pod ImagePullBackOff怎么处理”“Service访问不通怎么处理”,把这些SOP沉淀到团队知识库里,并且每半年根据实际故障更新一遍。这样事故发生时,你就不必在慌乱中回忆命令,而是按照既有的路径稳扎稳打。我见过很多团队靠一两个技术骨干顶着,等骨干休假出问题就手足无措,有了SOP,情况就会完全不同。
第四,别低估日志的价值。这条听起来很简单,但真正实践起来,很多团队的应用日志都是一团乱麻:没有结构化、没有请求ID、没有上下文。Kubernetes提供的Pod日志能力虽然已经很方便,但如果应用本身不打印关键业务链路信息,那排障时你将不得不在集群和代码两个世界里反复跳跃。在平时开发阶段就把日志标准定好,比如统一采用JSON格式,输出traceId和userId标准字段,在故障时就能直接按traceId串联起整条链路的日志,效率会提升一个量级。
如果你现在正遇到一个Kubernetes容器环境的疑难杂症,按我上面的四层模型从现象出发逐步深入,大概率能让你少走很多弯路。当然,Kubernetes的工具链和生态还在快速演进,新的镜像构建方式、新的CNI插件、新的运行时方案会不断改变故障的现象和排查方式,但排查问题的底层思路——先确认、后定位、再深挖,永远值得坚持。