
集群里跑着十几个Deployment升级一次却卡在滚动更新上ReplicaSet下的Pod一会儿Ready、一会儿又CrashLoopBackOff这种场景每个做过Kubernetes运维的人都遇到过。排查Deployment故障最怕的不是报错本身而是看着终端里一堆状态字段却不知道怎么串起来。这篇“可视化指南”不是让你装一套昂贵监控而是告诉你怎样用最朴素的状态命令、事件日志、资源指标和一张时间轴把一次故障从“看到现象”推进到“确认根因”。我把思路整理成一套可以反复用的排查路径适合刚接手集群的运维同学也适合那些被线上Deployment折磨过但还没有形成自己套路的开发者。1. 排障前的可视化准备先看清再动手1.1 两个终端窗口和一张“状态快照”为什么可视化第一步不是装监控很多人一提到可视化马上想到Grafana大屏。但Deployment故障排障里最基础的可视化其实是把Kubernetes集群当前的“事实”原样拉出来。我习惯的做法是开两个终端窗口一个专门跑状态查询一个专门跟着事件走。状态查询窗口必跑三条命令缺一不可kubectl get deploy -o wide kubectl get rs -o wide kubectl get pods -o wide三条命令分别回答“期望状态是什么”“控制器做到哪一步”“Pod实际落在哪台节点”。注意-o wide它比默认输出多给出了集群IP、节点名、就绪状态这些是判断网络和调度问题的第一手材料。另一个窗口里跑kubectl get events --sort-by.lastTimestamp -w-w一定要加否则看到的只是当前这一秒的事件启动故障往往有先后顺序先Pull镜像、再Create容器、再Start Container事件顺序本身就是一条时间线。我习惯把这四条命令之后的输出叫做“状态快照”因为它在某一时刻完整地反应了从Deployment到Pod的每一层现状。拿到快照之后再去看日志、看监控、动代码才不会东一榔头西一棒子。这里还有一个很少被提到的小习惯把快照存下来。每次排障前先执行kubectl get deploy -o yaml deploy_snapshot.yaml kubectl get pods -o yaml pod_snapshot.yaml存下来的YAML是对比配置漂移最有效的硬币。后面改了参数、重启了Pod前后一diff很多“奇怪问题”立刻现形。1.2 日志和指标用真实数据代替“脑补”有了状态快照接下来要把模糊的推断变成确定的证据。Kubernetes里有两个最容易获取、也最常被忽略的真实数据源。第一个是容器日志。很多新手的惯性是直接kubectl logs pod但容器如果反复崩溃当前这个Pod里的日志很可能是空白的因为进程已经退出日志随容器销毁。正确做法是先加--previouskubectl logs pod --previous -n namespace--previous拉出的是上一个实例的日志也就是崩溃前容器留下的最后遗言。这个参数在CrashLoopBackOff场景里几乎是必查项等会儿我在第2章再展开。第二个是指标数据。如果你的集群里没有部署metrics-server现在就该补上。它不占用多少资源但能让你执行kubectl top nodes kubectl top pods -A这两条命令输出的是每个节点和每个Pod的CPU、内存实时使用量。做可视化排障时指标的作用不是炫技而是回答一个关键问题这个故障是“应用自己坏了”还是“被资源挤死的”。如果Pod状态正常但响应超时kubectl top nodes一看节点内存接近上限那问题大概率在资源争抢而不是代码逻辑。1.3 可视化工具选型别在排障时临时建大屏很多人问我要不要上Prometheus和Grafana。我的观点是长期运行、多人协作的集群监控看板必须有但如果你今天正在排障临时去搭一套看板等于给伤口再补一刀。排障期的可视化优先级是终端命令的即时输出第一优先已有的监控看板里查最近15分钟趋势如果有日志聚合平台的检索先查ERROR级别再查特定TraceID我见过最浪费时间的操作就是故障发生时一群人聚在会议室里等一个看板刷出数据。排障的第一步永远是缩小范围而不是把范围放大。所以我的建议是平时花半天时间把Prometheus、Grafana和日志采集这类基础设施在测试环境落地把告警规则配上真正出故障时打开看板只看三个面板Pod重启次数、容器日志错误率、节点可用资源。这三个面板能覆盖90%的Deployment故障原因。其他花哨的可视化等故障处理完再慢慢建设。2. 看图说话Deployment启动故障的典型可视化路径2.1 ImagePullBackOff从一条错误消息定位到镜像仓库问题Deployment最常见的第一类故障是ImagePullBackOff。这个状态非常直白Pod被调度到了节点但kubelet在拉镜像时失败了。可视化排查的起点在kubectl describe pod pod输出的最底部那里有一块叫Events的内容它记录了kubelet对Pod的每一次尝试。Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 4m default-scheduler Successfully assigned default/my-app-7d8b5c5d76-abcde to node01 Normal Pulling 4m (x3 over 4m) kubelet Pulling image registry.example.com/my-app:v2 Warning Failed 4m (x3 over 4m) kubelet Failed to pull image registry.example.com/my-app:v2: rpc error: code Unknown desc failed to resolve reference registry.example.com/my-app:v2: pull access denied Warning Failed 4m (x3 over 4m) kubelet Error: ErrImagePull Normal BackOff 4m (x3 over 4m) kubelet Back-off pulling image registry.example.com/my-app:v2这条信息已经告诉你三个事实镜像地址是什么、拉取失败原因是什么、重试了几次。不少团队的镜像仓库不在公网这时要看几个容易混淆的点镜像Tag写错最常见的是v1和V1大小写问题还有latest标签但仓库里根本没有这个标签。私有仓库凭据没有配置到节点上或者Pod的imagePullSecrets写的是别的Namespace。本地缓存了旧镜像但新Tag拉取失败Deployment回滚时又拉到了旧缓存造成“等下明明Running了过一会儿又ImagePullBackOff”的假象。在终端里进一步确认用kubectl get pods -o wide看看Pod所在节点然后去那台节点上手工执行一次docker pull或crictl pull把容器运行时的真实报错拉出来。这条命令比任何可视化面板都直接因为它复现了kubelet的拉取过程。我经验里docker pull能成功而kubelet拉失败的情况极少一旦出现基本就是imagePullSecrets的顺序问题或者指向的镜像仓库域名被集群DNS解析到了老地址。2.2 CrashLoopBackOff日志、退出码、探针三张图对照看CrashLoopBackOff比ImagePullBackOff复杂因为镜像拉下来了但容器就是起不来。这也最能体现“可视化”的价值——你不能只靠一条日志下定论要把退出码、探针、日志开头结尾三个信息拼在一起看。退出码是第一个线索。把常见的退出码列成一张速查表排障时对照效率高很多退出码可能原因常见位置0正常退出但探针检查失败后容器被重启readiness/liveness探针1应用自身panic、初始化失败、配置错误业务日志、配置文件127命令找不到入口指令存在拼写或路径问题Dockerfile的ENTRYPOINT137被SIGKILL杀死通常是OOM或手动kill节点kubelet事件、dmesg139段错误通常是程序内部内存越界无业务日志需查运行时错误有了退出码再去看业务日志。kubectl logs --previous能把崩溃前的日志捞出来但你要看的不只是报错文本还有最后几行日志的时间间隔。如果日志里每隔几秒就出现一条FATAL并退出说明程序在启动阶段撞上了某种外部依赖数据库连接、配置中心、权限文件。如果退出码是137日志一切正常、突然中断那就要查OOMkubectl describe pod里有一行Last State: Terminated里面带着Reason: OOMKilled和Exit Code: 137。把这两条和节点上的dmesg | grep -i oom对照就能确认是不是内存限制给太小。第三种情况最隐蔽容器进程没退出但探针失败导致一直重启。describe里能看到Liveness probe failed但业务日志一片祥和。这时候要把readinessProbe和livenessProbe的配置翻出来看看initialDelaySeconds写的是几秒、periodSeconds又是几秒。一个典型的坑Spring Boot应用启动需要40秒探针initialDelaySeconds只写了5秒结果程序还在初始化就被liveness打死重启循环。这类问题的可视化线索都在kubectl get events -w里每一条Killing事件都对应一次探针失败事件间隔和periodSeconds一致的话基本就是探针参数配错了。2.3 Pending与调度失败为什么Pod一直“不在场”第三种现象是Pod一直Pending连Scheduled都看不见。这意味着调度器还没把这批Pod放到任何节点上。可视化排查的第一步还是看事件但要把范围扩大到整个集群kubectl get events --field-selector involvedObject.kindPod --sort-by.lastTimestamp这里可以加--field-selector过滤Pod相关的事件避免看到一堆Node事件造成干扰。Pending背后通常是四类原因节点资源不足kubectl describe node里的Allocated resources显示CPU或内存已经接近上限新Pod无法满足Request。节点亲和性或污点不匹配Pod上有nodeSelector、affinity但集群里没有匹配节点或者节点上有污点Pod没有对应的tolerations。持久卷无法被调度比如PVC是WaitForFirstConsumer的绑定模式但存储插件、StorageClass配置错误导致Pod无法调度。配额限制Namespace设置了ResourceQuota剩余额度不够调度器直接拒绝。第四类原因最容易被忽略因为事件里只有一条FailedCreate的错误信息但很多人习惯只盯Pod事件。在kubectl get events里看到Warning FailedCreate 11m deployment-controller Error creating: pods my-app-7d8b5c5d76 is forbidden: exceeded quota: default/object-counts这条事件是Deployment控制器打的不是kubelet打的如果你只看kubectl get pods会发现Pod列表里根本没有这个实例。所以排障时一定要把事件查询范围放到整个Namespace而不是只盯着某一个Pod。调度类问题的可视化图片本质上就是一张“资源账本”节点还剩多少cpu/内存、Namespace配额还剩多少、每个Pod声明了多少Request。先把这三行拉齐Pending的根因基本就浮出水面了。3. 深入网络、存储与资源三类高频故障的可视化排查3.1 Service与Endpoint从“端到端”视角找出不通的一环Deployment本身正常运行Pod全部Ready但外部访问还是超时。这种故障是网络可视化里最经典的一类。排查路径应该沿着一条链路走而不是瞎试端口。假设服务有这样一个访问链路Ingress → Service → Endpoints → Pod。那么执行顺序应该是kubectl get ingress -n ns kubectl get svc -n ns kubectl get endpoints -n ns kubectl get pods -n ns -o wide这四个对象就是四个“可视化节点”。哪个节点为空故障就在哪个节点后面。我遇到过最多的情况是kubectl get endpoints输出里没有PodIP列表是空的。这时要检查Service的selector和Pod的labels是否匹配。这类问题用YAML对比最直观kubectl get svc svc-name -o yaml | grep selector kubectl get pods --show-labels -n ns两边一对比selector写错一个字母Endpoint就少一个IP。还有一种情况是Endpoint里有IP但Pod不在这个节点上这通常和网络插件比如kube-proxy使用iptables还是IPVS有关。这时候把Service里所有Endpoints列出来和Pod实际IP一一比对如果出现旧Pod的IP残留最直接的办法是重建Service对象kubectl delete svc再kubectl apply让kube-proxy重新生成转发规则。网络故障的第二个排查入口是DNS。很多Deployment微服务之间互相调用依赖Service域名如果Pod里解析不到调用就会超时。检查方法是进入Pod里执行kubectl exec -it pod -n ns -- nslookup svc-name.ns.svc.cluster.local这一步能立刻确认CoreDNS是否正常。常见的问题是CoreDNS后端Pod被驱逐导致解析失败这时看kubectl top pods -n kube-system确认CoreDNS是否被限流或者Pod里用的是宿主机/etc/resolv.conf而非集群DNS配置这时要看Pod的dnsPolicy字段。网络排障一定要有层次感——先看链路对象再看DNS最后才查网络策略顺序反了容易在细枝末节里绕圈。3.2 PVC与挂载问题存储链条的可视化线索Deployment挂载持久化存储后起不来事件报错往往是FailedMount。这个错误的可视化链条比网络更明显StorageClass → PV → PVC → Pod的Volume。沿着这个链条逐层执行kubectl get sc kubectl get pv kubectl get pvc -n ns kubectl describe pod pod -n ns大多数情况是PVC的状态不是Bound而是Pending。PVC Pending时先看StorageClass对应的存储插件能不能动态创建PV。常见的坑是集群用的云厂商存储插件版本太旧或者权限不足创建卷时报了一个隐藏的IO错误。这时候看PV控制器的事件也就是查看存储插件的控制器日志比死盯PVC状态有用。还有一种情况是Pod能挂载但启动后才报权限错比如只读文件系统。这个现象在描述Pod时看不到要进入容器验证kubectl exec -it pod -n ns -- df -h kubectl exec -it pod -n ns -- touch /data/test.txt如果touch报Read-only file system多半是PV挂载选项、存储卷模式或安全上下文里的fsGroup配置问题。尤其要注意很多存储插件对目录权限很严格fsGroup不写会导致Pod里非root用户写不进存储目录。这类排障的可视化结论最后往往落在YAML配置上把StorageClass的mountOptions、PV的accessModes、Pod的securityContext三张配置表放在一起比对问题一目了然。3.3 资源配额与节点压力从指标趋势中找到“压垮”现场第2章聊过OOM导致的CrashLoopBackOff这里把资源类问题的可视化排查单独展开。因为资源问题有一个特点会话期性白天负载高的时候爆发夜里负载低的时候消失很难复现。我的做法是把三个指标同时拉出来看kubectl top nodes kubectl top pods -A kubectl describe nodes | grep -A15 Allocated resources第一行看节点整体水位第二行看具体哪个Pod在吃什么资源第三行看调度器计算出的“已分配资源”。第三行有一个容易误读的地方Kubernetes调度器关心的是requests不是limits也不是实际使用量。如果所有组件的requests加起来超过了节点真实容量新的Pod就一直Pending如果requests写得很小但实际负载很高节点会被打爆表现就是已有Pod频繁重启、节点Load Average飙高。这类问题最好有历史趋势图哪怕不是Grafana在终端里手动采样也凑合。我用的土办法是写一个循环while true; do kubectl top nodes --no-headers | date /tmp/metrics.log; sleep 30; done跑上一个小时这个文件就是最朴素的趋势可视化。故障发生后把时间点和曲线一对照马上能判断是“正常业务高峰压垮节点”还是“某个Pod异常占用资源”。如果发现单个Pod用量和它声明的requests差距非常大比如requests写了0.5核实际用到8核这就要看HPA配置与业务是否需要限流。资源排障最后不要只停留在“哦是OOM”这一层要继续问为什么这个容器会在那个时间点涨到那个水平把答案写进复盘文档而不是止步于重启容器。4. 排障速查与复盘技巧把经验沉淀成可复用文档4.1 Deployment常见故障状态速查表排障次数多了以后我发现最值得沉淀的是一张状态速查表。它把现象、原因、入口命令三列整理好团队共用一套语言排查效率会高很多。故障状态典型原因第一个可视化入口ImagePullBackOff镜像名错、Tag不存在、凭据失效kubectl describe pod的Events段CrashLoopBackOff应用启动失败、探针误杀、OOMkubectl logs --previousexit codePending资源不足、污点/亲和性不匹配、配额满kubectl get events --sort-by.lastTimestampContainerCreating存储挂载慢、网络插件初始化慢kubectl describe pod的Events段Ready但服务不通Service selector不匹配、Endpoint为空kubectl get endpoints -n ns间歇性超时节点资源争抢、DNS解析慢kubectl top nodeskubectl exec内nslookup这张表的价值不在于“正确”而在于统一团队排查起点。新人拿到表以后不会再用kubectl logs硬扛ImagePullBackOff也不会在CrashLoopBackOff时反复重启Pod。把表钉在团队Wiki首页新成员上手的门槛会明显降低。4.2 故障复盘的“时间轴画法”与协作要点故障处理完下一步是复盘。很多团队的复盘就是列一串聊天记录截图没有结构。我推荐的复盘格式是一张时间轴表格横向列时间点纵向列“现象、动作、结论、证据来源”。以一次滚动更新故障为例时间现象动作结论与证据14:00滚动更新触发ObservedGeneration落后执行kubectl rollout status新RS创建中14:02新Pod CrashLoopBackOff查看退出码1日志报数据库连接失败依赖的配置中心不可达14:05排查配置中心手工curl配置中心地址超时网络策略限制了新Namespace的出口14:08调整NetworkPolicy应用重新发布新Pod Ready滚动更新恢复这张时间轴的妙处在于每个“结论”旁都跟着“证据来源”。下次如果又有人问“当时为什么那样判断”直接翻表就能还原。协作时还有一个细节在集群终端里执行只读命令没问题但需要变更配置时尽量通过GitOps流程提交而不是现场kubectl edit。因为现场改配置可能解决眼前问题却在监控里留下一个没有记录的操作。我吃过这个亏一次网络策略临时放开了测试Namespace的端口事后没人知道七天后的故障复盘里所有人都在猜为什么流量路径变了最后靠集群审计日志才还原出来。可视化排障的上层思维是让每个操作都有痕迹。4.3 我踩过的三个坑和对应的检查习惯最后分享三个我用实际故障换来的经验都是常规文档不会写得太细的地方。第一个坑只查当前Pod的日志漏掉--previous。有一次Pod一直CrashLoopBackOff我盯着kubectl logs看了十分钟日志是空的以为程序什么都没打。后来用了--previous才发现崩溃前容器里其实打了一长串初始化异常。从那时起我的检查顺序永远是先--previous再查当前。如果两个都没有日志才考虑程序是否连stdout都没输出。第二个坑探针的failureThreshold被忽略。事件里只显示Liveness probe failed但没有把失败次数和阈值联系起来。后来发现容器每次启动都会在探针第2次失败时被杀而failureThreshold只写了1等于程序刚一起跑就被判了死刑。现在我看探针问题一定会同时看initialDelaySeconds、periodSeconds、failureThreshold三个参数并把它们换算成“从Pod启动到被判定失败的最长时间”这个数如果小于应用实际启动耗时探针配置一定有误。第三个坑在事件风暴里迷失重点。kubectl get events -w在故障高峰期会刷出一大堆Pulling、Started、Killing新手很容易被刷屏淹没。我的习惯是把事件按Reason归类只挑Failed和Killing开头看得或者使用jq过滤kubectl get events -n ns -o json | jq .items[] | select(.typeWarning) | {reason: .reason, message: .message, time: .lastTimestamp}过滤之后的Warning事件才是有效故障信号Normal事件暂时丢到一边等定位完再看也不迟。排障不是收集信息越多越好而是把信息数量压到最低、同时保留指向根因的关键证据。写到这里我又想起一次凌晨两点处理生产故障的经历滚动更新半小时都没完成团队轮流盯着终端每个人都在重复敲同样的命令。后来我把上面的速查表和时间轴方法发给队友把每个现象都要求附上证据十分钟内就锁定了是一条过期的镜像Tag。Kubernetes排障本身并不神秘它只是一个从现象到证据、从证据到根因的推理过程。把这套可视化思路用熟你会发现自己面对Deployment故障时越来越敢说“先别动我看一眼就知道方向”。