☰
Kubernetes Deployment故障可视化排障:从状态快照到根因定位
2026/10/22 12:03:20 网站建设 项目流程

集群里跑着十几个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里的日志很可能是空白的,因为进程已经退出,日志随容器销毁。正确做法是先加--previous:

kubectl 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。我的观点是:长期运行、多人协作的集群,监控看板必须有;但如果你今天正在排障,临时去搭一套看板,等于给伤口再补一刀。排障期的可视化优先级是:

  1. 终端命令的即时输出(第一优先)
  2. 已有的监控看板里查最近15分钟趋势(如果有)
  3. 日志聚合平台的检索(先查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的ENTRYPOINT
137被SIGKILL杀死,通常是OOM或手动kill节点kubelet事件、dmesg
139段错误,通常是程序内部内存越界无业务日志,需查运行时错误

有了退出码,再去看业务日志。kubectl logs --previous能把崩溃前的日志捞出来,但你要看的不只是报错文本,还有最后几行日志的时间间隔。如果日志里每隔几秒就出现一条FATAL并退出,说明程序在启动阶段撞上了某种外部依赖(数据库连接、配置中心、权限文件)。如果退出码是137,日志一切正常、突然中断,那就要查OOM:kubectl 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.kind=Pod --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 --previous+exit code
Pending资源不足、污点/亲和性不匹配、配额满kubectl get events --sort-by=.lastTimestamp
ContainerCreating存储挂载慢、网络插件初始化慢kubectl describe pod的Events段
Ready但服务不通Service selector不匹配、Endpoint为空kubectl get endpoints -n <ns>
间歇性超时节点资源争抢、DNS解析慢kubectl top nodes+kubectl 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(.type=="Warning") | {reason: .reason, message: .message, time: .lastTimestamp}'

过滤之后的Warning事件才是有效故障信号,Normal事件暂时丢到一边,等定位完再看也不迟。排障不是收集信息越多越好,而是把信息数量压到最低、同时保留指向根因的关键证据。

写到这里,我又想起一次凌晨两点处理生产故障的经历:滚动更新半小时都没完成,团队轮流盯着终端,每个人都在重复敲同样的命令。后来我把上面的速查表和时间轴方法发给队友,把每个现象都要求附上证据,十分钟内就锁定了是一条过期的镜像Tag。Kubernetes排障本身并不神秘,它只是一个从现象到证据、从证据到根因的推理过程。把这套可视化思路用熟,你会发现自己面对Deployment故障时,越来越敢说“先别动,我看一眼就知道方向”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询