【架构实战】Kubernetes故障排查全景图:从Pod异常到集群雪崩的诊断手册

【架构实战】Kubernetes故障排查全景图:从Pod异常到集群雪崩的诊断手册

Kubernetes出问题的时候,往往不是单个症状。Pod起不来可能只是冰山一角,底下可能是资源耗尽、网络分区、etcd抖动、OOMKiller连环杀节点......这一篇把故障排查的思维框架和实操命令整理成册,遇到问题照着走,少踩坑。

一、故障排查的正确姿势:分层定位

遇到K8s故障,很多人第一反应是kubectl get pods------然后对着CrashLoopBackOff发呆。正确思路是从下往上逐层定位,先确认基础设施正常,再看编排层,最后看应用层:

复制代码
第一层:节点层(Node)
  ↓ 节点是否Ready?资源是否耗尽?
第二层:网络层(Network)
  ↓ DNS解析正常吗?Pod间能互通吗?
第三层:控制平面层(Control Plane)
  ↓ API Server响应吗?etcd健康吗?Controller正常吗?
第四层:调度层(Scheduling)
  ↓ Pod卡在Pending?调度失败原因是什么?
第五层:运行时层(Runtime)
  ↓ 容器启动了吗?健康检查过了吗?
第六层:应用层(Application)
  ↓ 进程正常运行吗?配置对吗?

每层都有对应的检查命令,定位清楚了再动手,别上来就删Pod重启。

二、节点层:先确认机器还活着

节点是所有Pod的宿主机,节点出问题上层必遭殃。

2.1 节点状态速查

bash 复制代码
# 快速看所有节点状态
kubectl get nodes -o wide

# 看节点详情(含事件、污点、条件)
kubectl describe node <node-name>

# 看节点资源使用(需Metrics Server)
kubectl top nodes

正常节点应该满足:Ready=True,没有MemoryPressureDiskPressurePIDPressureNetworkUnavailable

2.2 常见节点异常

症状 可能原因 排查命令
NotReady kubelet停了/网络问题/etcd失联 systemctl status kubelet
MemoryPressure 内存不足,OOMKiller正在杀进程 `dmesg
DiskPressure 磁盘空间不足 df -h / docker system df
PIDPressure 进程数超限 `ps aux

2.3 OOMKiller连环杀人事件

最恶心的场景:节点内存紧张 → 内核OOMKiller杀Pod → Pod重启 → 内存继续紧张 → 继续杀。循环往复。

排查步骤:

bash 复制代码
# 看dmesg里的OOM日志(实时)
dmesg -w | grep -i "oom"

# 看哪个容器被杀了
dmesg | grep -i "killed process" | tail -20

# 看节点内存分配
free -h
kubectl top nodes --sort-by=memory

根因解决

  • Pod设合理的resources.limits.memory,防止贪婪容器耗尽节点;
  • 节点加内存或扩容;
  • 调低Pod的OOMScoreAdj让不重要的先被杀:kubectl annotate pod <pod> scheduler.alpha.kubernetes.io/oom-score-adj=-999

2.4 节点网络排查

bash 复制代码
# 看节点能通API Server吗
curl -k https://<API-SERVER>:6443/healthz

# 看节点的Pod子网是否正确路由
ip route show

# 看kube-proxy是否正常(iptables规则)
iptables -L -n -t nat | grep KUBE-SERVICES | wc -l

三、控制平面层:API Server还活着吗

API Server是整个集群的大脑。它一倒,所有kubectl命令都废了。

3.1 控制平面健康检查

bash 复制代码
# 最直接的健康检查(所有组件)
kubectl get --raw='/healthz?verbose'

# 分开检查各组件
kubectl get --raw='/healthz/apiserver'   # API Server
kubectl get --raw='/healthz/etcd-0'       # etcd(如果直连)
kubectl get --raw='/healthz/scheduler'    # Scheduler
kubectl get --raw='/healthz/controller-manager'  # Controller Manager

3.2 etcd是根本

etcd存着整个集群的状态,etcd挂了API Server也活不了。

bash 复制代码
# etcd健康检查(需要etcdctl)
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  endpoint health

# 看etcd日志是否有写入延迟或leader切换
journalctl -u etcd -n 100 --no-pager

常见etc d问题

  • 磁盘慢 :etcd对磁盘IO极为敏感,强烈建议用SSD。iostat -x 1看磁盘utilization;
  • Leader频繁切换:网络抖动或节点负载过高导致。查网络延迟和节点负载;
  • 空间不足:etcd DB超过默认8GB限制。清理或扩磁盘,紧急时做defrag。

3.3 Controller Manager和Scheduler

这两个组件跑在Pod里(static pod或Deployment),如果它们不工作:

  • Scheduler挂了:新Pod全部卡在Pending,集群停止调度新工作;
  • Controller Manager挂了:Deployment/ReplicaSet不再维护期望副本数,Service不更新Endpoints。
bash 复制代码
# 看kube-controller-manager日志
kubectl logs -n kube-system kube-controller-manager-<node-name> --tail=100

# 看scheduler日志
kubectl logs -n kube-system kube-scheduler-<node-name> --tail=100

# 确认leader是谁(多实例HA时)
kubectl get endpoints kube-controller-manager -n kube-system -o yaml
kubectl get endpoints kube-scheduler -n kube-system -o yaml

四、调度层:Pod为什么卡在Pending

Pod一直是Pending,说明调度失败。常见原因:

4.1 资源不足

bash 复制代码
# 看看哪些资源紧张
kubectl describe node | grep -A 5 "Allocated resources"

# 看节点可分配资源
kubectl top nodes

# 看具体Pod的资源请求
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources}'

解决思路:扩容节点、减少Pod资源请求、或用Pod优先级/抢占(PriorityClass)。

4.2 亲和性/反亲和性冲突

bash 复制代码
# 看Pod的调度决策详情
kubectl describe pod <pod-name> | grep -A 20 "Events:"

# 典型输出
# 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, 2 Insufficient memory.

亲和性/反亲和性规则太严格时,很容易无节点满足。检查:

bash 复制代码
kubectl get pod <pod-name> -o jsonpath='{.spec.affinity}' | jq .

4.3 PVC绑定超时

有StatefulSet挂载PVC时,PVC如果因为存储类问题无法绑定,Pod也会卡住:

bash 复制代码
kubectl get pvc  # 看哪些PVC是Pending
kubectl describe pvc <pvc-name>

五、运行时层:容器启动失败

Pod不再是Pending,但容器有问题。

5.1 容器状态速查

bash 复制代码
# 按状态筛选
kubectl get pods --all-namespaces --field-selector=status.phase=Failed
kubectl get pods --all-namespaces --field-selector=status.phase=CrashLoopBackOff

# 看容器详情
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous  # 上一次崩溃的日志

5.2 CrashLoopBackOff详解

这个状态是"容器启动→退出→重试→再退"的循环。原因是Exit Code非0。

常见原因排查路径:

1. 应用启动命令/参数错误

bash 复制代码
# 看镜像启动命令
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].command}'

# 在本地测试镜像
docker run --rm <image> <command-from-above>

2. 依赖服务不可达

应用启动需要连数据库/Redis/配置中心,但依赖还没Ready。解决方案:用initContainer做健康检查或用depends-on语义(通过AdmissionWebhook实现)。

3. 配置文件缺失或权限问题

bash 复制代码
# 看容器文件系统
kubectl exec -it <pod-name> -- ls -la /app/

# 看环境变量
kubectl exec -it <pod-name> -- env | sort

4. 健康检查失败导致OOM

Liveness探针连续失败3次会重启容器。如果应用启动慢,initialDelaySeconds设小了就会误杀:

yaml 复制代码
livenessProbe:
  initialDelaySeconds: 60   # 给够启动时间
  periodSeconds: 10
  failureThreshold: 3

5.3 ImagePullBackOff:拉不到镜像

bash 复制代码
# 看拉取错误详情
kubectl describe pod <pod-name> | grep -A 10 "Events:"

# 常见原因:
# 1. 镜像名拼写错误
# 2. 私有镜像仓库未配置imagePullSecrets
# 3. 仓库认证过期(docker login过期)
# 4. 节点没有该仓库的访问权限

解决 :检查imagePullSecrets有效性,确认节点能访问镜像仓库(docker pull <image>在节点上跑一次)。

5.4 OOMKilled

容器内存超限被杀,不一定是节点内存不足,也可能是Pod的resources.limits.memory设小了:

bash 复制代码
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# 如果是137,说明被SIGKILL(OOM)

排查 :看容器启动时的内存峰值,kubectl top pod结合Prometheus看内存曲线,适当调高limit。

六、网络层:服务之间通不了

网络问题最复杂,但有章可循。

6.1 DNS是万恶之源

Pod间通信失败,80%是DNS问题。先查:

bash 复制代码
# 进入问题Pod
kubectl exec -it <pod-name> -- sh

# 在Pod内测试DNS
nslookup kubernetes.default
nslookup <service-name>.<namespace>.svc.cluster.local
cat /etc/resolv.conf   # 看ndots配置

# 测试网络连通性
curl -v telnet://<service-name>:<port>

常见DNS坑

  • ndots太多 :Pod里/etc/resolv.confndots默认是5,意味着任何少于5个点的域名都会被追加搜索域,导致内部服务解析变成外部查询。解决方案:Pod启动时加- DNSDOT=1或直接用全限定域名。
  • CoreDNS不健康:看CoreDNS日志和Pod状态:
bash 复制代码
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl get pods -n kube-system -l k8s-app=kube-dns

6.2 Service连接失败

bash 复制代码
# 看Endpoints是否有人
kubectl get endpoints <service-name> -n <namespace>

# 如果Endpoints为空,说明selector匹配的Pod有问题(标签不对/全部宕机)

# 看Service配置
kubectl get svc <service-name> -n <namespace> -o yaml

典型问题:改Deployment的Pod标签忘了更新Service的selector,导致流量找不到后端。

6.3 NetworkPolicy误伤

配了NetworkPolicy但只开放了部分流量,默认是"全部拒绝"。确认策略是否覆盖了所有必要的流量:

bash 复制代码
kubectl get networkpolicy -A
kubectl describe networkpolicy <name> -n <namespace>

七、集群雪崩:连锁故障怎么破

最危险的场景:单个服务故障 → 请求堆积 → 资源耗尽 → 大量Pod重启 → 更大规模故障。

7.1 雪崩的触发链条

常见触发路径:

  1. 超时设置缺失:A服务调用B服务无超时,B慢→A线程池耗尽→A不可用→调用A的服务也崩;
  2. 重试风暴:没有退避策略的重试,一倒全倒;
  3. 健康检查不完善:不健康的服务未被及时摘除,持续接收请求;
  4. 资源无隔离:所有Pod跑在同一节点,资源竞争互相影响。

7.2 快速止血

第一步:切断入口流量

bash 复制代码
# 紧急下线Service(改副本数为0)
kubectl scale deployment <svc-name> --replicas=0 -n <namespace>

# 或者加NoSchedule污点强制摘除
kubectl cordon <node-name>

# 或者删掉有问题的Pod让它不再重建
kubectl delete pod <pod-name> --grace-period=0 --force

第二步:限流保护上游

yaml 复制代码
# Istio/Envoy限流示例
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: myapp
spec:
  host: myapp
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: UPGRADE
        http1MaxPendingRequests: 100
        maxRequestsPerConnection: 100

第三步:加超时+重试退避

yaml 复制代码
# Hystrix/Resilience4j超时配置示例
@HystrixCommand(
    commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
    }
)
public String callService() { ... }

7.3 根治方案

  • 服务网格:用Istio/Linkerd做流量管理,熔断、重试超时、限流全在Sidecar处理,应用零改动;
  • HPA自动扩缩容:根据CPU/内存自动扩容,防止单点过载;
  • PodDisruptionBudget:保证故障时仍有最少数量的Pod存活;
  • 资源LimitRange:在namespace级别设默认limit,防止贪婪Pod吃光资源。

八、故障排查黄金命令速查

场景 命令
看所有Pod状态 kubectl get pods -A -o wide
看Pod事件 kubectl describe pod <name> -n <ns>
看容器日志 kubectl logs <name> -n <ns> --tail=200 -f
看上一次崩溃日志 kubectl logs <name> -n <ns> --previous
看节点资源 kubectl top nodes
看Pod资源 kubectl top pods -A
看Endpoints kubectl get endpoints -A
看所有事件 kubectl get events -A --sort-by='.lastTimestamp'
看Service关联的Pod kubectl get endpoints <svc> -n <ns>
快速进入Pod kubectl debug <pod> -it --image=busybox -- sh
看CoreDNS状态 kubectl get pods -n kube-system -l k8s-app=kube-dns

九、小结

K8s故障排查的核心是分层定位

  1. 节点层:先确认机器还活着,资源没耗尽;
  2. 控制平面层:API Server和etcd是根本,它们健康才有后面的一切;
  3. 调度层:Pending的Pod重点看资源和亲和性;
  4. 运行时层:容器退出看Exit Code,拉不到镜像查认证;
  5. 网络层:80%的网络问题查DNS,服务不通先查Endpoints;
  6. 应用层:超时、重试、依赖是连锁故障的三大元凶。

记住:kubectl describekubectl get更有价值,事件(Events)才是真相。多看日志,少删Pod重启,大多数问题在事件和日志里已经有答案了。


下篇预告:Kubernetes存储实战------从EmptyDir到Ceph CSI的持久化存储选型指南。

相关推荐
AAA@峥1 小时前
K8s 资源管控:ResourceQuota 与 LimitRange 机制详解与实战
云原生·容器·kubernetes
晴天162 小时前
Cordis框架: 为可逆软件系统而生的元框架-Day18
ai·架构
daoihfoaf2 小时前
太原助听器品质甄选
容器
代码方舟2 小时前
零信任架构实战:基于天远车辆vin码查车辆信息详版构建自动化汽车金融审批网关
人工智能·架构·自动化·汽车
暖核2 小时前
Keepalived + LVS(DR)+ MariaDB 主主高可用架构实战指南
架构·mariadb·lvs
小生凡一2 小时前
【图解】DeepSeek Harness 架构解析
架构
Dr.kangder2 小时前
嵌入式面试总结(十六)——AMBA总线
面试·职场和发展·架构·嵌入式·虚拟化·总线
秦渝兴3 小时前
Ansible 自动化部署 K8s 三节点集群(完整教程)
kubernetes·自动化·ansible
Dr.kangder3 小时前
嵌入式面试总结(十九)——内存泄露
单片机·算法·面试·职场和发展·架构·硬件架构