文章

K8s 故障排查方法论

K8s 故障排查方法论

概述

K8s 故障排查不是一条一条翻 kubectl 命令。120+ 微服务的集群,每出现一个故障就从头查一遍是不可持续的。需要一套系统化的排查框架——每一步缩小故障域、每一步排除一类问题、每一步有明确的「下一动作」而不依赖经验。

一条核心原则:永远从控制面往下查。控制面 → 节点 → Pod → 容器 → 应用日志。因为下面的问题可能是上面引发的,但上面的问题不可能是下面引发的。

排查框架

Level 1: 控制面(API Server / etcd / Controller Manager / Scheduler)
  ↓ 控制面异常 → 下面全异常
Level 2: 节点(Node Ready / 资源/ 磁盘 / kubelet / 容器运行时)
  ↓ 节点异常 → 该节点上 Pod 全异常
Level 3: Pod(调度 / 镜像 / 启动 / 探针 / 资源)
  ↓ Pod 异常 → 单个服务不可用
Level 4: 网络(Service / Ingress / DNS / NetworkPolicy)
  ↓ 网络异常 → 服务间通信用
Level 5: 存储(PV / PVC / StorageClass / CSI)
  ↓ 存储异常 → 有状态服务不可用
Level 6: 应用层(配置 / 日志 / 探针 / 依赖服务)

每层的排查命令速查:

层级第一个命令作用
控制面kubectl get componentstatuses检查 API Server/etcd/scheduler 健康
节点kubectl describe node <name>查看 Conditions(Ready/MemoryPressure/DiskPressure)和 Events
Podkubectl describe pod <name> -n <ns>查看 Events(最全的故障线索)
网络kubectl exec <pod> -n <ns> -- curl -v <svc>:<port>端到端连通性
存储kubectl describe pvc <name> -n <ns>PVC 绑定状态
应用kubectl logs <pod> -n <ns> --tail=100应用日志第一现场

11 个典型故障案例

案例 1:Pod 一直 Pending

现象kubectl get pods 显示 STATUS: Pending,超过 5 分钟未调度。

排查步骤

# Step 1: 看 Events——这是最重要的线索来源
kubectl describe pod <pending-pod> -n <ns> | grep -A 20 Events

# 根据 Events 判断根因:

# Events 显示: 0/3 nodes are available: 1 node(s) had untolerated taint {nvidia.com/gpu: }, 2 Insufficient cpu.
Events 信息根因修复
insufficient cpu/memory所有节点资源不足HPA 扩容节点 或 降低 requests
untolerated taintPod 没有匹配 Node 的 toleration添加正确的 toleration 或移除 taint
node(s) didn't match Pod's node affinity rulesnodeSelector/nodeAffinity 不匹配检查 selector 是否指向了不存在的 label
pod has unbound immediate PVCPVC 无法绑定检查 PVC 的 StorageClass 是否就绪
0/3 nodes: 3 pod has hostPort xxxhostPort 冲突(同节点已有 Pod 占用)使用 hostPort 的两个 Pod 调到不同节点
# 补充检查
kubectl describe node <node> | grep -A5 "Allocated resources"
# 看节点的资源分配率。CPU requests > 80% → 马上可能出问题
kubectl top node <node>
# 实际使用量 vs requests 的差异

案例 2:ImagePullBackOff

现象:Pod Events 中 Failed to pull image "xxx": ...

排查流程——四个最常见的原因和对应诊断:

# 1. 镜像 tag 不存在(最常见)
kubectl describe pod <pod> | grep -A5 Events
# Failed to pull image "registry.example.com/health-ack:v2.3.1": manifest for ... not found
# → 确认 tag 是否正确、CI 是否构建成功

# 2. Registry 认证失败
# Events: pull access denied / authorization failed
kubectl get secret <pull-secret> -n <ns> -o yaml
# 检查 .dockerconfigjson 中的凭据是否过期

# 3. Registry 不可达(DNS 或网络)
kubectl run test --rm -it --image=alpine -- sh -c "nslookup registry.example.com"
kubectl run test --rm -it --image=alpine -- sh -c "wget -O- https://registry.example.com/v2/"

# 4. 节点磁盘满(镜像拉取失败但表面上原因不明显)
kubectl describe node <node> | grep DiskPressure
# 如果 True → 清理节点磁盘或扩容

案例 3:CrashLoopBackOff

现象:容器启动 → 立即退出 → kubelet 重启 → 再退出 → 指数退避重启。

这不是根因,是症状。 CrashLoopBackOff 表示「容器的主进程以非 0 退出码退出」。需要查为什么退出

# Step 1: 查看上一次崩溃的日志(--previous 是关键)
kubectl logs <pod> -n <ns> --previous --tail=50

# Step 2: 查看退出原因
kubectl describe pod <pod> | grep -A5 "Last State"
# Exit Code: 1   → 应用内部错误(代码 panic、配置错误)
# Exit Code: 137  → OOMKilled(被内核 kill)
# Exit Code: 143  → SIGTERM(正常终止信号,可能是资源不足被驱逐或 kubelet 要求退出)
# Exit Code: 139  → SIGSEGV(段错误,内存访问越界)
# Reason: OOMKilled → 内存超限

# Step 3: 如果是 OOMKilled
kubectl top pod <pod> -n <ns>          # 看实际内存使用
kubectl describe pod <pod> | grep -A2 "Limits\|Requests"
# limits.memory 是否太小?
# requests.memory 是否至少等于应用正常运行所需的最小值?

# 容器内没有 logs 文件(/dev/termination-log 为空)→ 容器启动过程中就崩溃了

案例 4:Readiness Probe 失败

现象:Pod Running 但 READY: 0/1,Service 不转发流量。

# 查看探针配置和失败历史
kubectl describe pod <pod> -n <ns> | grep -A10 "Readiness\|Liveness"
# 关键信息:
#   Readiness:  http-get http://:8080/api/health delay=10s period=5s timeout=3s failure=3
#   Warning  Unhealthy  10s (x15 over 2m)  Readiness probe failed: Get "http://x.x.x.x:8080/api/health": dial tcp: connection refused

# 诊断:
# 1. "connection refused" → 应用还没监听 8080 端口(启动太慢)
#    → 调大 initialDelaySeconds
# 2. "HTTP 503" → 应用在运行但 /api/health 返回 503
#    → 检查依赖(数据库、Nacos 等)是否就绪
# 3. 超时 (timeout after 3s) → 健康检查端点本身执行太慢
#    → 优化 /api/health 的性能 或 调大 timeoutSeconds

# 直接在 Pod 内验证探针
kubectl exec <pod> -n <ns> -- curl -v http://localhost:8080/api/health

案例 5:Service 不通

现象:Pod A 通过 Service ClusterIP 调 Pod B,返回 connection refused 或超时。

# 诊断清单(顺序执行,每一步都排除一类可能性)

# 1. Pod 本身是否正常?
kubectl get pods -n <ns> -l app=<svc-selector>
kubectl describe pod <pod> -n <ns> | grep -E "Ready|Conditions"

# 2. Endpoints 是否正确?
kubectl get endpoints <svc> -n <ns>
# 如果 ENDPOINTS 列为空 → Service selector 没有匹配到 Pod
kubectl get pods -n <ns> -l app=health-ack --show-labels
# 对比 Service 的 spec.selector 和 Pod 的 labels 是否一致

# 3. 网络连通性(从源 Pod 内测试)
kubectl exec <source-pod> -n <src-ns> -- sh -c "
  echo '=== DNS ==='
  nslookup <svc>.<ns>.svc.cluster.local        # DNS 解析是否正常?
  echo '=== TCP ==='
  timeout 5 bash -c 'cat < /dev/tcp/<svc>/<port>' 2>&1   # TCP 能通吗?
  echo '=== HTTP ==='
  curl -v --max-time 5 http://<svc>.<ns>:<port>/health
"

# 4. NetworkPolicy 拦截?
kubectl get networkpolicies -n <ns>
# 如果存在 deny-all → 逐一检查是否有 allow 白名单规则

案例 6:Ingress 返回 502 Bad Gateway

对应你之前遇到的 api-tpa Ingress 问题。

# Step 1: 检查后端 Service 和 Endpoint
kubectl get ingress <name> -n <ns> -o yaml | grep -A5 backend
kubectl get endpoints <backend-svc> -n <ns>
# 如果 ENDPOINTS 列为空 → 确认 Pod Ready

# Step 2: 检查 Ingress Controller 日志
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller --tail=200 | grep <host>
# 或者 Istio Gateway:
kubectl logs -n istio-system deployment/istio-ingressgateway --tail=200

# Step 3: 检查 Ingress Controller 是否正确 reload 了配置
kubectl exec -n ingress-nginx deployment/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep <host>
# 如果 reload 失败 → Events 中会有 "configmap reload failure" 或类似信息

# Step 4: 验证 SSL 证书
openssl s_client -connect <host>:443 -servername <host> 2>&1 | grep -A2 "Verify return code"
# 不匹配 → 检查 tls.secretName 是否存在且证书域名匹配

案例 7:Node NotReady

现象kubectl get nodes 中某节点 STATUS: NotReady

# Step 1: 看节点 Events
kubectl describe node <node> | tail -30
# 关键 Conditions:
#   MemoryPressure: True  → 内存不够,kubelet 开始驱逐 Pod
#   DiskPressure: True    → 磁盘不够
#   PIDPressure: True     → 进程数超限
#   NetworkUnavailable: True → CNI 未正确初始化

# Step 2: SSH 到节点,检查 kubelet 状态
ssh <node> "systemctl status kubelet"
# 如果 kubelet down → 查看 journal
ssh <node> "journalctl -u kubelet -n 100 --no-pager"

# Step 3: 检查容器运行时
ssh <node> "crictl ps"      # containerd/CRI-O 是否正常
ssh <node> "df -h /"        # 根分区是否写满
ssh <node> "df -h /var/lib/containerd"

# Step 4: 如果节点无响应甚至 SSH 不上
# 进入到云平台控制台查看节点状态
# 如果无法恢复 → cordon + drain + 替换节点
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

案例 8:Pod Pending(反亲和规则冲突)

对应你遇到的 Deployment 反亲和导致的 Pending 问题。

kubectl describe pod <pod> -n <ns> | grep -A10 Events
# Events: 0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules

# 检查 Deployment 的反亲和配置
kubectl get deployment <name> -n <ns> -o yaml | grep -A20 affinity
# spec.template.spec.affinity.podAntiAffinity:
#   requiredDuringSchedulingIgnoredDuringExecution:
#     - topologyKey: kubernetes.io/hostname  → 每个节点只能有一个 Pod
#       labelSelector: { matchLabels: { app: xxx } }

# 问题:replicas=3,但只有 2 个节点 → 至少一个 Pod 永远无法调度
# 解决:
#   1. 增加节点数 ≥ replicas
#   2. 改为 preferredDuringScheduling(软反亲和,尽量但不强制)
#   3. 改用 podAffinity(同节点)而非 podAntiAffinity

案例 9:DNS 解析异常(间歇性超时)

K8s DNS 的经典问题:部分 Pod 的 DNS 解析间歇性失败或延迟 5 秒。

# Step 1: 验证 CoreDNS Pod 健康
kubectl get pods -n kube-system -l k8s-app=kube-dns

# Step 2: 检查 CoreDNS 日志
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=100

# Step 3: 从 Pod 内测试 DNS 解析
kubectl exec <pod> -n <ns> -- nslookup kubernetes.default

# 常见根因:
# 1. CoreDNS OOM → 日志中有 "OOMKilled"
#    → 增大 CoreDNS 的 memory limits
# 2. conntrack 表满(UDP DNS 查询用 conntrack,高并发下溢出)
#    → 增大 nf_conntrack_max
# 3. ndots 配置不当(默认 ndots:5,导致多级 DNS 查询链)
#    → Pod spec: dnsConfig.options: [{name: ndots, value: "2"}]
# 4. CoreDNS cache 未命中率太高
#    → 启用 CoreDNS cache plugin: cache 30

案例 10:etcd 写空间满

# 症状:
# kubectl apply 任何资源返回: etcdserver: mvcc: database space exceeded
# 集群所有写操作被拒绝(只读)

# 紧急修复:
# 1. 碎片整理
ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379

# 2. 若 defrag 后仍告警→ compact(压缩历史版本)
rev=$(etcdctl endpoint status --write-out=json | jq -r '.[0].Status.header.revision')
etcdctl compact $rev
etcdctl defrag

# 3. 根本解决:配 auto-compaction
# etcd 启动参数: --auto-compaction-mode=periodic --auto-compaction-retention=1h

案例 11:证书过期

kubeadm 部署的集群,所有证书 1 年有效期。

# 检查所有证书过期时间
kubeadm certs check-expiration

# 如果即将过期:
kubeadm certs renew all
# 重启控制面组件:
crictl ps | grep -E "kube-apiserver|kube-controller|kube-scheduler|etcd" | awk '{print $1}' | xargs -I {} crictl stop {}

# 如果不 renew 的后果:
# API Server 拒绝所有 kubectl 请求 → 看上去像集群挂了
# 检查 apiserver 日志: x509: certificate has expired

通用排查工具包

# 1. 一次性看所有 namespace 的异常 Pod
kubectl get pods -A --field-selector=status.phase!=Running \
  -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName

# 2. 最近 1 小时的所有 Events(最全的问题线索)
kubectl get events -A --sort-by='.lastTimestamp' | tail -50

# 3. 查看资源 top(CPU/内存实际使用)
kubectl top nodes
kubectl top pods -A --sort-by=memory | tail -20

# 4. 所有 namespace 的 NotReady 节点
kubectl get nodes --field-selector=spec.unschedulable=true

# 5. 查看某个 Pod 的资源使用详情(cgroup 层面)
kubectl exec <pod> -n <ns> -- cat /sys/fs/cgroup/memory.current
# 或 kubectl top pod <pod> -n <ns> --containers

# 6. Debug 容器(K8s v1.31+)
kubectl debug <pod> -n <ns> -it --image=alpine --target=<container>
# 进入一个临时容器(共享同一个 PID namespace),不影响原容器

关联知识

参考资源

学习时间

阶段时间备注
方法论2026-07-086 层排查框架 + 11 个典型案例(你遇到的场景全覆盖)

状态: 🌱 学习中 下次复习日期: 2026-07-15