你是否遇到过这样的场景------大半夜被监控告警吵醒,打开电脑一看,Pod处在CrashLoopBackOff状态,或者某个Service突然访问不通了,然后你开始kubectl命令一通狂敲,半小时过去了还没定位到问题在哪?
如果你做过K8s运维,一定经历过这种抓狂的时刻。据Spectro Cloud的数据,IT团队平均每年要花34个工作日来解决K8s问题,超过60%的集群管理时间都耗在排查故障上。68%的生产环境事故跟网络相关------这个数字来自CNCF 2024年调查。
别慌。我干了10年运维,踩过无数坑,最后总结出一个分层排查路径,按下面这个顺序查,绝大多数问题都能在15分钟内定位:
Pod → 容器 → 应用 → Service → Ingress → 存储 → 节点 → 网络
好了,说干就干,下面按场景逐一拆解。
一、Pod层面的问题:为什么Pod起不来?
1.1 Pod一直Pending
先看一眼Pod到底在不在某个节点上:
kubectl get pods -o wide
如果Status是Pending,意味着调度器压根没把这个Pod扔到任何节点上。
很多人看到Pending就死盯着Pod,但我告诉你------90%的Pending最后查到的是节点问题。
先看节点:
kubectl get nodes
看到NotReady?常见原因就这几种:kubelet挂了、容器运行时跪了、节点磁盘满了、CNI插件崩了。
用小命令看详细事件:
kubectl describe pod <pod-name>
如果出现Insufficient CPU、Insufficient memory这种字眼,就是资源不够。怎么办?要么释放节点资源、要么调低Pod的request值、要么扩节点。如果命名空间配置了ResourceQuota,还得看一眼配额是不是用光了:
kubectl describe quota -n <namespace>
(顺便提一嘴,在一些老版本K8s里资源配额的命名可能叫ResourceQuota,这里统一用的quota简写,效果一样。)
只要涉及存储的Pod,PVC没绑好也会导致Pending。走两步:
kubectl get pvc
kubectl describe pvc <pvc-name>
常见坑:没有适配条件的PV、StorageClass的provisioner不存在、云盘的zone跟节点对不上。最后这个最坑爹,我之前在一个云环境里折腾了俩小时才发现是zone问题。
1.2 CrashLoopBackOff
CrashLoopBackOff意味着容器启动后立即崩溃,然后Kubelet用指数退避的方式不断重启,从10秒一直涨到5分钟。这个状态占到K8s支持工单的40%,我每天至少遇到一两个。
第一步,看日志------这是最直接的:
# 当前容器的日志
kubectl logs <pod-name>
# 崩溃的那个容器的日志(重要!因为当前正在重启的可能啥都没输出)
kubectl logs <pod-name> --previous
第二步,看事件和退出码:
kubectl describe pod <pod-name> | grep -A20 "Events:"
退出码会告诉你一些关键线索:
|-----|-----------------------------------|
| 退出码 | 含义 |
| 0 | 正常退出(但如果容器预期一直运行,这本身就是问题) |
| 1 | 应用错误,看日志 |
| 137 | SIGKILL --- 大概率是OOMKilled(被系统杀掉的) |
| 139 | SIGSEGV --- 段错误,内存访问越界之类 |
| 143 | SIGTERM --- 收到了终止信号 |
CrashLoopBackOff的四个常见根因:
- 应用启动就崩 ------ 看日志里的堆栈,修代码。我碰到过很多次入口脚本忘了写
#!/bin/sh,报错No such file or directory。 - 环境变量缺失 ------ ConfigMap或Secret没挂载好,报KeyError之类的。检查一下
kubectl get configmap。 - 配置文件路径错误 ------ 排查ConfigMap挂载路径,用
kubectl exec -it <pod> -- ls -la进容器看一眼。 - 端口已经被占用了 ------ 日志里会出现"address already in use"。
还有一种情况很有意思:多阶段构建的时候,后面的阶段忘了把文件从builder里copy过来,结果镜像里根本没有可执行文件。我同事上周刚中招。
二、容器内的应用问题
2.1 镜像拉取失败 ImagePullBackOff
ImagePullBackOff出现后,先检查:
-
镜像tag拼写错了没
-
registry地址对不对
-
私有仓库有没有配置imagePullSecret
-
节点能不能连上仓库(内网环境最常见------节点在私有子网,拉不了外网镜像)
kubectl describe pod <pod-name> | grep -A5 "Events:"
事件里会明确告诉你"Failed to pull image"以及具体原因。
2.2 探针把服务杀了
Pod显示Running但Ready是false?问题大概率出在Readiness或Liveness Probe上。最常见的错误:
- healthcheck的路径配错了(比如写了
/health实际是/healthz) - 端口写错了
- 探针阈值太低,网络抖动一下就挂了
验证一下端口到底对不对:
kubectl exec -it <pod-name> -- netstat -tlnp
或者直接进去curl:
kubectl exec -it <pod-name> -- curl localhost:8080/healthz
还有一个超实用的小技巧:如果你怀疑是Service层面的问题但不确定,先用port-forward绕过Service直接连Pod,看能不能通:
kubectl port-forward <pod-name> 8888:<pod-port>
这时候用curl localhost:8888测。通了说明Pod没问题,不通说明问题在Pod里面或应用本身。
三、Service访问问题
80%的人在这里栽过,真的不夸张。
先看Service的Endpoints:
kubectl describe svc <service-name>
如果看到Endpoints: <none>,说明Service的selector没有匹配到任何Pod。检查三项是否一致:
-
Service的
selector -
Pod的labels
-
命名空间
kubectl get pod --show-labels
kubectl get svc <svc-name> -o yaml | grep -A3 selector
别小看这个小问题------我见过有人把Pod的label从app: myapp改成了app: my-app,Service没跟着改,结果整个业务挂了20分钟。
Endpoints没问题但还是访问不通?那就要看kube-proxy了:
kubectl get pod -n kube-system | grep kube-proxy
systemctl status kube-proxy
也可以顺带检查iptables规则有没有被搞乱(某些网络插件或手动操作会导致):
iptables -t nat -L -n | grep <service-ip>
四、Ingress和DNS
Ingress能通过IP访问但用域名不行?这种情况一半是DNS解析问题,另一半是Ingress配置本身的问题。
DNS排查从最底层的resolv.conf开始:
kubectl exec -it <pod> -- cat /etc/resolv.conf
应该能看到类似nameserver 10.96.0.10的内容,这是CoreDNS的ClusterIP。
在Pod内部尝试解析K8s内置服务:
# 先测DNS本身
kubectl exec -it <pod> -- nslookup kubernetes.default.svc.cluster.local
# 再测Service
kubectl exec -it <pod> -- curl http://<service-name>.<namespace>.svc.cluster.local:port
如果DNS解析失败,但直接ping ClusterIP(CoreDNS的IP)能通,通常不是DNS本身的问题,而是Service配置错了------selector不对、targetPort不一致、或者是有意配置了Headless Service导致绕过了ClusterIP。
这里有个经典坑得提醒你:NGINX Ingress会在启动时解析一次后端Service的DNS,然后把结果缓存住。 如果后端是Headless Service,IP会变,DNS缓存没刷新,NGINX就会一直去连已经不存在的Pod。解法是改用serviceName而不是FQDN,或者在nginx配置里把resolver加上valid参数。
五、存储挂了
存储问题最直观的表现是Pod卡在ContainerCreating,这时候kubectl describe pod会告诉你Volume挂不上。
看看PVC状态:
kubectl get pvc
常见状态和含义:
|-----------|------------------------------------|
| PVC状态 | 解释 |
| Bound | 正常绑定了PV |
| Pending | 还在等------要么没PV匹配,要么StorageClass有问题 |
| Lost | PV丢了,基本免不了手工恢复 |
Pending状态的排查重点:
- 检查PVC的storageClassName和PV的storageClass是否匹配
- 检查对应StorageClass的provisioner是否存在且健康(有CSI driver的话确保它处在Running状态)
- 云盘场景下,看PVC要求的zone和节点的zone是否对得上
如果是动态provisioning,还得确认CSI Controller Pod是不是正常:
kubectl get pods -n kube-system | grep -E "csi|provisioner"
如果CSI driver本身处于CrashLoopBackOff或OOMKilled,那底层的PV根本没处创建。
六、节点NotReady
NotReady不一定是节点彻底挂了。用下面命令看详情:
kubectl describe node <node-name> | grep -A20 Conditions
你会看到几种conditions:
|----------------------|----------------|
| Condition | True表示 |
| MemoryPressure | 内存不够了 |
| DiskPressure | 磁盘快满了 |
| PIDPressure | 进程太多了 |
| NetworkUnavailable | CNI插件没就绪 |
| Ready=False | kubelet停止上报心跳了 |
磁盘压力:检查kubelet和容器运行时目录的剩余空间,清理多余镜像和退出状态的容器:
df -h /var/lib/kubelet /var/lib/containers
crictl rmi --prune # 清理没用到的镜像
crictl rm $(crictl ps -a -q --state exited) # 清理退出状态的容器
内存压力 :top看哪些进程吃内存,如果没法立刻加内存,考虑驱逐部分Pod。
网络插件没就绪(NetworkUnavailable):重启CNI pods试试:
kubectl delete pod -n kube-system -l k8s-app=calico-node
重启后等几十秒,节点应该会恢复Ready。
kubelet服务崩溃或证书过期:登录到节点上操作:
systemctl status kubelet
journalctl -u kubelet --no-pager -n 100 | grep -E "error|failed|certificate"
证书过期的典型错误信息是"certificate has expired"或"failed to run Kubelet: unable to load bootstrap kubeconfig"。解决方式是重新审批CSR:
kubectl get csr | grep Pending | awk '{print $1}' | xargs kubectl certificate approve
七、OOMKilled:让人头大的内存问题
OOMKilled是整个排查列表里最让人头疼的一个,因为它的根因不一定在K8s配置上,可能是应用有内存泄露。
确认是否OOMKilled:
kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
如果输出里有"exitCode":137,"reason":"OOMKilled",就是它了。
搞清楚是容器级别的OOMKilled还是节点级别的OOM:
- 容器OOMKilled → 容器超出了自己的memory limit,内核把它杀了
- 节点OOM → 节点整体没内存了,内核随便挑了个进程杀掉
怎么看?看Node上有没有OOMKilling事件:
kubectl get events --field-selector reason=OOMKilling -A
修复方向取决于模式:
-
内存随时间持续增长 → 内存泄露,修代码,别想着靠调Limit掩盖
-
流量上来就飙升 → Limit配低了,调高到实际用量的2倍左右
-
一直保持高位 → 应用本身就这样,用VPA算出合适的requests/limits值
调高Limit的示例
resources:
requests:
memory: "256Mi"
limits:
memory: "512Mi" # 保留burst余量,别刚好卡着用量设
彩蛋:关于CPU requests和limits,一个很多人都不知道的小细节------K8s官方文档明确说,CPU limit是"硬性强制执行"的,内核会直接节流(throttling),而内存limit是"响应式强制执行"的,只有在内核检测到内存压力时才会OOM kill,这意味着容器偶尔超过limit不一定立刻被杀。所以别再以为memory limit是"到了立刻杀"。
另外,我个人不太推荐在生产环境给CPU设limits,除非你跑的是完全不信任的任务。设requests保证调度就够了,limits设太低反而会导致莫名其妙的CPU节流,latency升高,最后你花半天时间排查才发现是limit的问题。内存方面必须设limits,因为不设的情况下OOM行为是"不可预测且通常灾难性的"。
总结一下核心要点:
- 按分层顺序排查:Pod → 容器 → 应用 → Service → 存储 → 节点 → 网络,别跳着来
- kubectl describe + logs --previous能解决90%的问题,别一上来就动配置
- Pending问题别只盯着Pod,90%的根因在节点或存储上
- Service不通先看Endpoints有没有
<none>,八成是selector写错了 - Always set memory limits(不能省),CPU limits谨慎用
- OOMKilled的退出码137永远是SIGKILL,别怀疑其他原因
如果觉得这篇对你有帮助,欢迎分享给更多被K8s折磨的同行。你遇到过最离谱的一次K8s故障是什么?花了多久才定位到?评论区见。