1. 故障排查的底层逻辑设计——先给大脑装一张“地图”
先说个我一直坚持的观点:排查 Kubernetes Deployment 故障,真正难的不是某个具体命令不会敲,而是面对一坨报错时不知道从哪里下手。Kubernetes 的报错信息天生就“分层”,有时候问题明明出在 Deployment,报错却先出现在 Pod 上;有时候 Pod 一切正常,流量却进不来。如果脑子里没有一张“故障地图”,很容易被表象带偏,在错误的方向上浪费几个小时。
所以这篇指南不打算只教你几条命令,而是想把排查过程“可视化”:把 Deployment 从声明到运行的全链路拆开,让每个故障都对应到链路上的一个节点。这样你下次遇到问题,不是“猜”哪坏了,而是顺着链路“定位”哪坏了。
这篇文章适合谁?刚接触 Kubernetes、被 Deployment 各种状态搞晕的新手,也适合已经上手了一段时间、但排查仍然靠“重启大法”的开发者。我会把关键命令、事件解读、状态判断全部串起来,给你一套能直接抄作业的排查路径。
1.1 Deployment 的“三层结构”决定了排查顺序
要理解故障,先理解结构。一个 Deployment 往下拆,其实有三层:
Deployment(声明期望状态) └── ReplicaSet(管理 Pod 副本数) └── Pod(真正跑容器的最小单元)这个层级关系不是摆设,它直接决定了排查顺序。Deployment 本身不直接创建 Pod,它只负责声明“我想要几个副本”,然后由控制器创建出对应的 ReplicaSet,再由 ReplicaSet 负责把 Pod 拉起来。
所以很多新手犯的第一个错误是:看到 Pod 报错,就去查 Deployment 的 YAML,改一通,发现没用。为什么?因为 Deployment YAML 只是“蓝图”,Pod 才是“施工结果”。要搞清楚施工为什么失败,你得先看 Pod,再看 Deployment。
我推荐的排查顺序是固定的:
- 先看 Deployment 是否 Ready,确认 ReplicaSet 是否存在;
- 再看 ReplicaSet 的状态,确认它是否成功创建了 Pod;
- 最后看 Pod 的具体状态和事件,定位容器层的故障。
这个顺序就像查水管漏水:先看总阀门开没开,再看分阀门,最后看漏水点。跳层排查,大概率会被表象误导。
1.2 故障排查的“可视化思维”
标题里说“可视化”,并不只是画几张图那么简单。我理解的可视化,是把抽象的集群状态转化成人类容易理解的“信号灯”思维。
Kubernetes 本身已经给出了一套信号灯,就是各种状态字段:
- Deployment 的
Available字段,绿了就是副本数足够且可用; - ReplicaSet 的
FullyLabeledReplicas,绿了就是 Pod 的标签匹配没问题; - Pod 的
Status字段,Running就是运行中,Pending就是还没调度成功。
你不需要记住每一个字段的含义,但你要养成一个习惯:每次排查前,先用一条命令把这三个对象的状态同时拉出来,像看仪表盘一样扫一遍。
kubectl get deploy,rs,pods -o wide这一条命令输出 Deployment、ReplicaSet、Pod 的全部状态。我几乎所有排查都是从这一行开始的。它会把“红绿灯”一次性摆在你面前,哪个环节亮红灯,就从哪里往下挖。
2. 可视化排查第一站:Deployment 与 ReplicaSet
从 Deployment 开始排查,不是因为它最容易出问题,而是因为它是“第一道信号灯”。Deployment 的状态直接告诉你:期望状态和实际状态是否一致。
2.1 版本发布失败的信号灯
先看一个最常见的场景:你更新了 Deployment 的镜像版本,发现滚动更新一直没完成。这时候kubectl get deploy的输出很可能长这样:
NAME READY UP-TO-DATE AVAILABLE myapp 0/1 1 0这几个字段我把它们翻译成人话:
READY表示实际就绪的副本数 / 期望副本数;UP-TO-DATE表示已经更新到新版本的副本数;AVAILABLE表示正在对外提供服务的副本数。
如果READY一直卡在0/1,说明新版本 Pod 根本没起来。如果UP-TO-DATE是 1 但READY是 0,说明 Pod 创建了但不健康,大概率是启动探针失败或者容器启动崩溃。
这时候要做的不是盯着 Deployment 看,而是顺着链路往下查 ReplicaSet:
kubectl rollout status deployment/myapp这条命令会阻塞并等待滚动更新完成,但更好用的其实是加一个--timeout参数,比如:
kubectl rollout status deployment/myapp --timeout=60s如果 60 秒内没完成,命令会超时退出,告诉你“再等下去也是浪费时间,快去看事件吧”。这比干等着强多了。
2.2 ReplicaSet 状态的可视化排查
Deployment 下面是 ReplicaSet。看起来多了一层很麻烦,但其实这是个很好的“回溯机制”。
举个例子:有一次我把某个服务的镜像版本从 v1 升级到 v2,部署之后发现服务异常。我怀疑是 v2 镜像的问题。怎么验证?我先看 ReplicaSet 列表:
kubectl get rs输出里会同时存在两个 ReplicaSet:一个对应 v1,一个对应 v2。如果 v2 的 ReplicaSet 下面 Pod 一直报错,我立刻就能通过修改 Deployment 的镜像版本回滚到 v1,整个过程不到一分钟。
ReplicaSet 相关的排查,我建议重点关注两个字段:
DESIRED——期望的副本数;CURRENT——当前创建的副本数。
如果CURRENT一直小于DESIRED,说明 Pod 创建失败了。Pod 创建失败的原因,常见的有:资源配额不足、镜像拉取失败、存储卷挂载失败。这些具体原因,单看 ReplicaSet 是看不出来的,必须进一步看 Pod。
所以 ReplicaSet 阶段的排查诀窍是:把 ReplicaSet 当作“分赃不均”的信号灯,它亮红灯的瞬间,就是 Pod 往下走的初始状态。
2.3 事件的可视化排序
Kubernetes 里几乎所有重要操作都会产生事件(Event)。但事件是个“实时流”,过一段时间就被清理了。如果你等 Pod 已经 Crash 了好几个小时才去看事件,大概率什么都看不到。
所以我给自己定了一个规矩:任何异常,第一件事就是拉事件,不要先查日志。
kubectl describe deploy myapp kubectl describe pod myapp-xxxxdescribe输出的底部就是事件列表。事件是倒序排列的,最新的在最上面。我习惯把事件从下往上读,相当于把整个生命周期倒着播放一遍,看它是怎么一步步走到现在这个状态的。
可视化指南里,事件列表就像飞机上的“黑匣子记录仪”。你不需要记住每个事件的编号,只需要关注两种事件:Warning类型的事件,以及带有Failed关键字的描述。
3. 可视化排查第二站:Pod 生命周期状态全解码
Pod 是 Kubernetes 里最核心的排查对象,也是故障的“重灾区”。Pod 的状态字段就像人的“体温指标”,不同状态对应不同的病因。我把常见状态整理成一张速查表,排查时对照着看,效率能翻倍。
3.1 Pod 状态的“八字”速查表
| 状态 | 含义 | 常见原因 | 下一步操作 |
|---|---|---|---|
| Pending | 等待中,未调度成功 | 资源不足、节点不可调度、亲和性不满足 | kubectl describe pod查看事件 |
| Running | 容器已启动 | 正常状态 | 进一步看 Ready 是否 1/1 |
| CrashLoopBackOff | 启动后崩溃,反复重启 | 镜像启动命令错误、环境变量缺失、配置错误 | 查看日志kubectl logs |
| ImagePullBackOff | 镜像拉取失败 | 镜像不存在、仓库认证失败、网络不通 | 检查镜像名和拉取策略 |
| ErrImageNeverPull | 镜像拉取策略拒绝 | imagePullPolicy设置为Never | 检查 YAML 中拉取策略 |
| ContainerCreating | 容器创建中 | 正在拉镜像或挂载卷 | 等待,或查看事件判断卡在哪 |
| Terminating | 终止中 | 正在删除,或卡在优雅终止 | 检查terminationGracePeriodSeconds |
| Unknown | 节点异常 | 节点宕机或网络分区 | 检查节点状态 |
这张表怎么看?我推荐的做法是“先定位状态,再找原因”。不要一上来就去翻日志,日志和状态其实是两回事:状态告诉你“现状是什么”,日志告诉你“为什么会这样”。先看状态,再决定要不要看日志。
3.2 CrashLoopBackOff 的深入排查
在所有状态里,CrashLoopBackOff可能是出现频率最高的。这个状态的中文直译是“崩溃循环退避”,意思是容器启动后立刻崩溃,Kubernetes 会按指数退避策略反复重启它,比如第一次等 10 秒,第二次等 20 秒,第三次 40 秒,上限 5 分钟。
排查这个状态,我有一套固定的“三连”操作:
- 看启动日志和上一个实例的日志:
kubectl logs myapp-xxxx # 当前实例日志 kubectl logs myapp-xxxx --previous # 上一个崩溃实例的日志- 看事件里有没有具体报错:
kubectl describe pod myapp-xxxx- 如果症状是“进程启动了但马上退出”,还要看启动命令是否正确:
kubectl get pod myapp-xxxx -o yaml在 YAML 里spec.containers[].command和args是否写对了?最常见的一个坑是:基础镜像默认没有你要执行的命令,或者 entrypoint 被覆盖了。
我有一次排查一个 Java 应用,日志里反复出现“Unrecognized option: -jar”,折腾了很久才发现是启动命令写错了。command写成了java -jar app.jar,但实际应该拆成command: ["java"]和args: ["-jar", "app.jar"]。这个知识点很重要,因为在 Dockerfile 里这种写法没问题,但在 Kubernetes YAML 里,command和args的优先级和拼接逻辑跟 Docker 不一样。
3.3 Pod 调度失败的可视化诊断
另一个高频状态是Pending卡住不动。这个状态的本质是“调度器找不到合适的节点”,或者“调度器找到了节点但条件不满足”。
可视化排查的思路是这样:把节点想象成一个停车场,Pod 是一辆车。车停不进来,要么是停车场满了(资源不足),要么是管理员不让停(Taint 容忍问题),要么是车位条件不匹配(节点亲和性、标签选择器问题)。
实际排查命令:
kubectl describe pod myapp-xxxx看 Events 部分,通常会有这样一条信息:
0/3 nodes are available: 1 Insufficient cpu, 2 Insufficient memory.翻译一下:一共 3 个节点,1 个 CPU 不够,2 个内存不够。这基本就是资源配额的问题。
如果看到的是:
0/3 nodes are available: 3 node(s) had taint {node-role.kubernetes.io/control-plane: }, that the pod didn't tolerate.这是说 3 个节点都有污点(Taint),但 Pod 没有对应的容忍(Toleration)。这种情况通常发生在你往控制平面节点上调度任务时。
遇到 Pending,我的建议是:先describe,再根据事件里的关键词分方向。资源不足就调大节点或者缩小 request;污点问题就在 YAML 里加tolerations字段。切忌盲目把节点删了重加。
4. 从状态到根因:三种经典故障的可视化拆解
光看状态还不够,状态只是“现象”,真正的排查价值在于透过现象找到“根因”。这一节我挑三个出现频率最高的故障场景,从事件、日志、根因三个维度完整拆一遍。
4.1 镜像拉取失败的完整链路
镜像拉取失败,对应的状态是ImagePullBackOff或ErrImagePull。很多人一看到这个状态,第一反应是“镜像不存在”。但实际排查下来,原因五花八门:
- 私有仓库认证失败(最常见);
- 镜像仓库地址写错了;
- 镜像 tag 不存在;
- 集群无法访问外网仓库(网络策略限制);
imagePullSecrets配置缺失。
排查路径我建议这样走:先看 Webhook 层面有没有拉取凭证的问题。
kubectl get pod myapp-xxxx -o yaml | grep -A4 imagePullSecrets如果imagePullSecrets是空的,而你的镜像在私有仓库里,那一定拉取失败。解决方法是先创建 Secret:
kubectl create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=user \ --docker-password=pass然后在 Deployment 的spec.template.spec下加上:
imagePullSecrets: - name: regcred如果imagePullSecrets已经配置了还是失败,再看事件描述,通常会有明确提示,比如401 Unauthorized说明凭证错了,404 Not Found说明镜像不存在,connection refused说明网络不通。
这一条链路走完,基本能把镜相关故障隔离到具体环节。我统计过自己遇到的镜像问题,大概 60% 是认证问题,25% 是 tag 打错,15% 是网络问题。先把认证检查完,能少走很多弯路。
4.2 资源不足引发的连环故障
资源不足(Insufficient memory/Insufficient cpu)是 Kubernetes 集群最常见的问题,常常表现为 Pod 刚启动就被 OOM Kill,或者一直 Pending 无法调度。
可视化排查的第一步,是看集群的整体资源水位:
kubectl top nodes kubectl top podstop nodes会显示每个节点的 CPU 和内存占用率。如果某个节点内存占用已经到了 90% 以上,那么新 Pod 很可能调度不进来。
这时如果你硬要调度,可以临时把节点上的其他 Deployment 副本数降一降,或者直接扩容节点。但我不建议长期靠扩容解决问题,压根问题在于 Deployment 的 request 可能设置得太高或太低。
关于 request 和 limit,我在这里分享一个很重要的实操经验:
- request 是“最低保障”,调度器按它来分配节点资源;
- limit 是“最高上限”,超出会被强制杀掉(OOM Kill);
- 如果 Pod 里跑的进程有内存尖峰,request 设置过低会导致多个 Pod 挤在同一节点,互相抢内存,最终集体 OOM。
排查时如果发现容器反复 OOM Kill,登录节点看内核日志,能确认内存是否被打满:
journalctl -k | grep -i oom如果内核日志显示某进程被 OOM Killer 杀掉,那就说明内存确实不够。这种问题靠调代码不一定立刻生效,最直接的缓解方式是调大 limit 或者减少副本数。
4.3 CrashLoopBackOff 的根因可视化
CrashLoopBackOff 我已经提过一次,这里展开讲根因。这个状态本质上是一个“症状”,不是独立的一个病。我在实际项目里发现,CrashLoopBackOff 最常见的根因有四种:
- 启动命令或参数错误,导致应用启动即退出;
- 配置缺失,应用读取不到环境变量或配置文件;
- 探针配置过于激进,容器虽然启动了,但健康检查失败;
- 依赖服务(如数据库)未就绪,应用连接失败后主动退出。
排查顺序我推荐“由近及远”:先看应用自身的启动日志,再看探针配置,最后检查依赖服务。
启动日志这个步骤很多人会漏掉--previous参数,导致看到的是“当前正在重启的实例日志”,这个实例可能根本没来得及打印任何日志就退出了。正确姿势是上一个实例的日志。
kubectl logs myapp-xxxx --previous --tail=200--tail=200只看最近 200 行,避免日志刷屏。
如果日志显示“connect: connection refused”,那说明依赖服务没起来或地址不对。这里有个很容易踩的坑:在本地开发时服务间调用用的是localhost,但进了 Kubernetes,你得改用 Service 名称,比如mysql或redis。如果你忘了改,应用在容器里永远连不上。
探针问题我会在 5.3 单独讲,这里先提醒一句:livenessProbe和readinessProbe的periodSeconds不要设置得太小,否则应用启动稍有波动就会被误杀。
5. 运维环境故障排查的“可视化工具包”
看完前几节,你可能已经有了排查思路,但距离“高效”还差一步:工具。Kubernetes 自带的kubectl是最基础的,但有些信息光靠kubectl很难看清。这一节分享几个我在实战中常用的工具和技巧,它们能让故障排查真正“可视化”。
5.1 让事件实时可见:kubectl 的插件生态
先推荐一个最基础的组合拳:
kubectl get events --sort-by='.lastTimestamp' -A这个命令按时间排序显示所有命名空间的事件。但在事件较多时,输出会非常杂乱。所以我更喜欢用一个开源小工具kubectl-tree,它能把 Deployment、ReplicaSet、Pod 之间的从属关系画成一棵“树”,层次一目了然。
安装方式也很简单(某开源项目,读者可自行检索):
kubectl krew install tree kubectl tree deploy myapp输出类似:
NAMESPACE NAME READY STATUS default deploy/myapp 1/1 Ready └─ default rs/myapp-7d8f9c 1/1 Ready └─ default pod/myapp-7d8f9c-x2k3l 1/1 Running这个输出让我一眼就能看到三层结构的对应关系。相比传统的get deploy,rs,pods,它多了“谁是爸爸谁是儿子”的视觉呈现,排查层级问题时非常友好。
5.2 一键收集现场信息:kubectl describe 的批量用法
出故障的时候,时间和现场同样重要。人手不够的时候,我会用一行命令把关键对象的describe一次性导出来:
kubectl describe deploy,rs,pods -l app=myapp > debug-$(date +%s).txt这个命令的好处有三个:
- 用
-l app=myapp只导出目标应用的对象,不刷屏; - 输出带时间戳的文件名,方便事后对比;
describe自带事件,相当于把黑匣子下载下来。
曾经有两次我在排查线上问题时,都是先跑了这条命令存档,然后才慢慢分析。事后复盘时,档案里的事件时间线帮了大忙——因为你永远不知道自己会不会在排查过程中把现场搞乱。
5.3 探针与滚动更新相关的可视化避坑指南
探针(Probe)配置是 Kubernetes 里最容易被低估的“故障制造机”。它不像镜像问题那么显眼,但它一旦配置不当,就会让 Pod 状态反复在Running和CrashLoopBackOff之间横跳,非常隐蔽。
我的建议是三种探针分开理解:
livenessProbe:问“容器还活着吗?”如果挂了就杀掉重启;readinessProbe:问“容器能对外服务了吗?”如果没准备好,就从 Service 端点列表里摘除;startupProbe:问“应用启动完成了吗?”特别适合启动慢的应用,配合前两者可以避免启动期间被误杀。
有一次排查一个启动需要 30 秒的 Java 应用,它反复被 livenessProbe 杀死。后来我在启动阶段加了startupProbe,并把initialDelaySeconds设成 20 秒,问题就彻底解决了。
startupProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 20 periodSeconds: 10 failureThreshold: 30 livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 45 periodSeconds: 15failureThreshold: 30,periodSeconds: 10,意味着最多给 300 秒的启动时间。这是确保启动慢的应用不被误杀的关键。还有一个小细节:httpGet探测的路径一定要选择应用本身提供的健康检查接口,不要随便拿一个静态页面糊弄,否则探针会失真。
滚动更新相关的避坑也值得一提。kubectl rollout restart deployment/myapp在需要强制重新拉取镜像时很有用,但如果你用的是latest标签,即使执行了 restart,Kubelet 也可能因为本地已经有镜像而跳过拉取。这种情况下,把imagePullPolicy改成Always,或者换一个明确的版本号 tag,都能避免“改了镜像却不生效”的假象。
6. 故障速查表与实操心得
最后这部分,我把前面所有内容浓缩成一张故障速查表,并且分享几个我自己踩过的坑。这些坑不是什么高端技巧,但它们确实能节省排查时间。
6.1 高频故障速查表
| 故障现象 | 排查命令 | 最可能的原因 | 处理建议 |
|---|---|---|---|
| Deployment 死循环更新 | kubectl rollout status deploy/xxx | 新版本 Pod 一直不 Ready | 查看新 RS 对应的 Pod 事件 |
| Pod Pending 超时 | kubectl describe pod xxx | 资源不足/污点未容忍 | 扩节点或调整 request |
| Pod 反复 Crash | kubectl logs pod xxx --previous | 启动命令/环境变量错误 | 对比上个实例日志 |
| 副本数不足 | kubectl get rs | ReplicaSet 创建 Pod 失败 | 看 ReplicaSet 的事件 |
| 流量异常但 Pod 正常 | kubectl get endpoints | Service 标签选择器不匹配 | 确认 Service 的 selector |
| 服务 DNS 解析失败 | kubectl exec pod -- nslookup svc | Service 未创建/命名空间不对 | 检查 Service 名称和命名空间 |
这张表我打印出来贴在自己工位上。排查的时候先对照一圈,能覆盖 80% 的日常故障。
6.2 我踩过的三个坑
第一个坑:kubectl logs看不到日志,不代表容器没日志。很多时候应用把日志写到文件而不是标准输出,你需要先kubectl exec进容器看文件。还有一种情况是容器崩溃太快,logs来不及抓到输出,这时--previous参数比什么都管用。
第二个坑:错误地修改旧版本的 ReplicaSet。Deployment 滚动更新后,旧 ReplicaSet 会保留一定数量的副本用于快速回滚。有人觉得这些旧 Pod 占资源,手动把旧的 ReplicaSet 删了或者把副本数改成 0。结果下次回滚时发现找不到旧版本,只能痛苦地重新构建镜像。后来我在团队里立了一条规矩:除非确认永远不需要回滚,否则不手动删 ReplicaSet。
第三个坑:忽略命名空间。这个坑最基础但也最隐蔽。排查时命令里忘了加-n 指定命名空间,结果看的是 default 命名空间的对象,而你实际部署的应用在另一个命名空间。你会看到“一切正常”的假象,然后陷入无限迷茫。现在我执行任何命令前都会先在脑子里过一遍:这个对象在哪个命名空间?
6.3 最后一招:理清楚事件时间线再动刀
排查故障最忌讳“边看边改”。我见过不少同事看到 Pod 报错,立刻就去改 Deployment 配置,改完发现还不行,再改回来,来回折腾几小时。真正高效的排查顺序应该是:
- 先获取全局状态(get deploy,rs,pods);
- 再拉事件(describe),理清时间线;
- 根据时间线和状态锁定一个可疑点;
- 针对可疑点查看日志或 YAML;
- 确认根因后,一次性修复,再观察一段时间。
这个顺序看起来简单,但真正做到的人不多。它最大的价值在于避免了“瞎改碰运气式”的排查,让每一次操作都有依据。
我个人在实际操作中的体会是,Kubernetes 的故障排查与其说是技术活,不如说是“流程活”。只要顺着 Deployment → ReplicaSet → Pod 这条链路一层层往下看,把每个对象的状态当作信号灯,大多数故障都能在十分钟内定位。真正难的从来不是命令,而是你能不能忍住不跳过中间层、不凭感觉去猜。按这套可视化路径走,我自己的线上故障平均修复时间,从刚开始接触 Kubernetes 时的两三个小时,已经压缩到了二十分钟以内。希望你也能养成自己的排查节奏,少踩一些我曾经踩过的坑。