深夜两点,手机震动的声音在寂静的办公室里显得格外刺耳。监控大屏上,红色警报像血一样蔓延——核心业务服务的可用性从99.99%瞬间跌落到85%,几十个Pod同时处于CrashLoopBackOff或ContainerCreating状态。你揉了揉眼睛,意识到这不仅仅是“重启一下就好”的小事,而是一场典型的云原生架构下的危机。
作为一名在云原生领域摸爬滚打多年的“老炮儿”,我太熟悉这种场景了。Kubernetes(K8s)虽然被捧为容器编排的王者,但它复杂的依赖关系——网络、存储、调度、调度器、kubelet——任何一个环节出问题,都可能引发雪崩。今天,我不想给你讲枯燥的理论,而是想带你走进真实的故障现场,把那些让人头秃的Pod起不来、服务中断问题,掰开了、揉碎了讲清楚。
第一阶段:紧急止血——为什么我的Pod就是起不来?
当告警响起,第一反应不是慌,而是冷静地收集信息。Pod起不来,通常有几种“经典死法”,我们逐一拆解。
1. ImagePullBackOff与ErrImagePull:镜像拉取失败
这是新手最常遇到的坑,但老手也可能栽跟头。
典型场景:Pod状态卡在ContainerCreating,日志显示ImagePullBackOff。
排查思路:
- 检查镜像名称和标签:是不是拼写错误?
nginx:latest和nginx:laster可是两个不同的东西。 - 检查私有仓库凭证:如果你用的是Harbor、阿里云ACR等私有仓库,检查
imagePullSecrets是否配置正确。 - 网络问题:节点是否能连到镜像仓库?在节点上手动执行
docker pull或ctr images pull试试。
实战示例:
# 查看Pod详情
kubectl describe pod <pod-name> -n <namespace>
# 关注Events部分,你会看到类似这样的信息:
# Failed to pull image "registry.example.com/my-app:v1.2": rpc error: code = Unknown desc = Error response from daemon: pull access denied for registry.example.com/my-app, repository does not exist or may require 'docker login'
# 解决方案:创建并绑定Secret
kubectl create secret docker-registry my-registry-secret \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypassword \
--docker-email=myemail@example.com \
-n <namespace>
# 更新Pod Spec,引用Secret
spec:
imagePullSecrets:
- name: my-registry-secret
2. CrashLoopBackOff:容器反复崩溃
这是最让人头疼的问题之一,因为原因五花八门。
常见原因:
- 应用启动失败(代码bug、配置错误)
- 健康检查失败(livenessProbe配置不当)
- 资源不足(OOMKilled)
排查思路:
- 查看日志:
kubectl logs <pod-name> -n <namespace> --previous(看上一个容器的日志)和kubectl logs <pod-name> -n <namespace>(看当前容器日志)。 - 检查资源限制:
kubectl describe pod中看是否有OOMKilled。 - 调试模式:如果容器启动就死,可以尝试覆盖command,让容器保持运行状态以便调试。
实战示例:
# 查看崩溃容器的日志
kubectl logs <pod-name> -n <namespace> --previous
# 如果日志不足,进入容器调试(假设容器还在运行)
kubectl exec -it <pod-name> -n <namespace> -- /bin/bash
# 如果容器已死,用debug容器
kubectl debug node/<node-name> -it --image=busybox
3. Pending:Pod卡在调度阶段
Pod没有分配到节点,原因可能是:
- 资源不足(CPU、内存、GPU)
- 节点选择器(nodeSelector)或亲和性(affinity)不匹配
- 污点(taints)未容忍
排查思路:
kubectl describe pod中查看Events,通常会显示0/<N> nodes are available。- 检查节点资源:
kubectl top nodes。 - 检查污点和容忍度。
第二阶段:深入内核——云原生架构下的网络痛点解析
Pod能起来,但服务还是不通?这时候,网络问题就是罪魁祸首。K8s的网络模型看似简单,实则复杂得多。
痛点一:Service与Endpoint的不匹配
现象:Pod正常运行,但通过Service访问时超时或连接被拒绝。
原因分析:
- Endpoint没有更新:Pod重启后,EndpointController可能未及时更新。
- NetworkPolicy拦截:安全策略过于严格,阻断了正常流量。
- kube-proxy模式问题:iptables模式在高负载下性能下降,ipvs模式配置复杂。
实战解决方案:
# 检查Endpoint是否存在且包含Pod IP
kubectl get endpoints <service-name> -n <namespace>
# 检查NetworkPolicy是否拦截
kubectl get networkpolicy -n <namespace>
# 如果怀疑kube-proxy问题,检查其日志
kubectl logs -n kube-system <kube-proxy-pod-name>
代码级调试技巧:
在节点上使用tcpdump抓取流量,分析包是否到达Pod:
# 在节点上执行,替换为实际的Pod IP
tcpdump -i any host <pod-ip> -nn -c 10
痛点二:跨节点通信与CNI插件兼容性
现象:同一节点内的Pod通信正常,跨节点通信失败。
原因分析:
- CNI插件(如Calico、Flannel、 Cilium)配置错误。
- 节点间网络不通(防火墙、安全组)。
- MTU不匹配导致分片失败。
实战解决方案:
- 检查CNI配置:
cat /etc/cni/net.d/*.conflist。 - 测试节点间连通性:在每个节点上ping其他节点的Pod IP。
- 调整MTU:如果发现问题,尝试在CNI配置中降低MTU值(如从1500改为1450)。
Cilium的特例: 如果你使用Cilium,它可以提供更深层次的可见性:
# 使用Hubble观察流量
hubble observe
痛点三:DNS解析失败
现象:服务间无法通过域名互相访问。
原因分析:
- CoreDNS Pod未运行或配置错误。
- kube-dns服务异常。
- 客户端Pod的 resolv.conf 配置问题。
实战解决方案:
# 检查CoreDNS Pod状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
# 检查CoreDNS日志
kubectl logs -n kube-system <coredns-pod-name>
# 在客户端Pod内测试DNS解析
kubectl exec -it <pod-name> -n <namespace> -- nslookup <service-name>
第三阶段:存储的陷阱——为什么我的数据不见了?
网络问题导致服务中断,存储问题则可能导致数据丢失或应用无法启动。
痛点一:PV/PVC绑定失败
现象:Pod一直卡在ContainerCreating,因为PV无法绑定到PVC。
常见原因:
- PV的容量不足。
- PV的访问模式(ReadWriteOnce, ReadOnlyMany, ReadWriteMany)不匹配。
- StorageClass问题:PVC指定的StorageClass不存在或动态 Provisioner失败。
排查思路:
# 查看PVC状态
kubectl get pvc -n <namespace>
# 查看PV状态
kubectl get pv
# 查看绑定情况
kubectl describe pvc <pvc-name> -n <namespace>
实战案例:
某次,我们遇到PVC一直处于Pending状态,kubectl describe pvc显示:
Warning ProvisioningFailed ... storageclass.storage.k8s.io "nfs-storage" not found
原来,StorageClass被误删除了。我们重新创建了StorageClass,问题迎刃而解。
痛点二:存储插件性能瓶颈
现象:Pod正常运行,但应用读写磁盘极慢,导致超时。
原因分析:
- 后端存储性能不足(如云盘的IOPS限制)。
- 文件系统问题(如ext4在大量小文件场景下的性能下降)。
- CSI Driver配置不当。
解决方案:
- 监控存储性能:使用
iostat、df -h等工具。 - 选择合适的文件系统:对于日志类应用,考虑xfs。
- 优化CSI配置:调整CSI Driver的参数,如并发请求数。
痛点三:数据持久化与Pod重启
现象:Pod重启后,数据丢失。
原因分析:
- 使用了emptyDir卷而非PersistentVolume。
- PersistentVolume的回收策略(Retain, Delete, Recycle)设置不当。
最佳实践:
- 对于有状态应用,务必使用PVC。
- 了解不同回收策略的含义:
Delete:删除PV和底层存储。Retain:保留PV和底层存储,需手动清理。Recycle:已废弃,不推荐使用。
第四阶段:实战演练——一次完整的故障排查记录
为了让你更有体感,我分享一次真实的故障排查经历。
背景
某电商平台的下单服务,在促销活动期间,突然大量Pod出现CrashLoopBackOff,服务不可用。
排查过程
1. 初步检查
kubectl get pods -n order-service -o wide
发现大部分Pod处于CrashLoopBackOff状态,少数处于Running。
2. 查看日志
kubectl logs <crashed-pod-name> -n order-service --previous
日志显示:java.lang.OutOfMemoryError: Java heap space。
3. 分析原因
- 应用内存泄漏?
- 流量激增导致内存不足?
- JVM参数配置不合理?
4. 深入调查
- 检查Pod的资源限制:
kubectl describe pod显示limits.memory=512Mi。 - 检查应用配置:JVM启动参数
-Xmx256m,与内存限制匹配。 - 监控数据:促销期间,QPS提升了10倍。
5. 解决方案
- 短期:紧急扩容Pod副本数,分散负载。
- 中期:调整JVM参数,增加堆内存大小,如
-Xmx400m,并相应提高Pod内存限制。 - 长期:优化应用代码,修复内存泄漏;引入缓存层,减轻数据库压力。
6. 验证
# 观察Pod状态恢复情况
kubectl get pods -n order-service -w
# 监控服务错误率
# 使用监控系统查看错误率是否下降
复盘与改进
这次故障暴露了我们在容量规划和监控告警方面的不足。此后,我们建立了更完善的压力测试流程,并设置了更灵敏的内存使用告警。
第五阶段:预防优于治疗——构建 resilient 的云原生架构
故障排查是救火,预防才是防火。作为专家,我强烈建议你从以下几个方面构建更具韧性的K8s集群。
1. 完善的监控与告警体系
- 基础设施监控:Prometheus + Grafana,监控节点资源、网络流量。
- 应用监控:集成应用性能监控(APM)工具,如SkyWalking、Jaeger。
- 日志聚合:ELK Stack或Loki,集中管理日志。
- 告警规则:设置多级告警,避免告警疲劳。
2. 混沌工程
定期引入故障,检验系统的容错能力。使用Chaos Monkey或Litmus等工具,模拟Pod重启、网络延迟、节点宕机等场景。
3. 自动化运维
- 自愈能力:合理配置Health Check,让K8s自动重启异常容器。
- 弹性伸缩:使用HPA(Horizontal Pod Autoscaler)和VPA(Vertical Pod Autoscaler),根据负载自动调整资源。
- 备份与恢复:定期备份关键配置和数据,使用Velero等工具实现集群级备份。
4. 文档与演练
- 故障手册:记录常见故障的排查步骤和解决方案。
- 定期演练:组织团队进行故障应急演练,提高响应速度。
结语:在不确定性中寻找确定性
云原生架构带来了敏捷和弹性,但也引入了新的复杂性。Pod起不来、服务中断,这些故障看似随机,实则都有其内在逻辑。作为从业者,我们需要保持好奇心,不断学习,积累实战经验。
记住,每一次故障都是成长的机会。当你能够冷静地分析日志、追踪流量、定位根因,并最终解决问题时,那种成就感是无与伦比的。希望这篇文章能为你提供一些思路和帮助,让你在云原生的道路上走得更稳、更远。
如果你在具体排查过程中遇到难题,欢迎随时交流。毕竟,在这个领域,我们都是在同一条船上的人。
