路线图

故障排查思路

星辉 2026-07-02 阅读 8 min 1,660 字 路线图
故障排查思路 封面

概述#

K8s 故障排查与其他系统最大的不同在于:问题不一定是应用本身的问题,还可能是调度问题、网络问题、存储问题或节点问题。本章以决策树的方式组织排查路径,帮你从现象快速定位根因。掌握了这套排查思路,即使遇到没见过的错误也能按层次逐一排除。


一、Pod 起不来排查路径#

Pod 是 K8s 中最小的调度单位,也是问题出现最频繁的地方。排查的核心思路是:先看状态,再看事件,再看日志

1.1 排查决策树#

text
Pod 状态异常
├─ Pending(一直调度不上)
│   └─ kubectl describe pod → Events 中的 FailedScheduling 原因
│       ├─ "Insufficient cpu/memory" → 节点资源不足
│       │   ├─ kubectl top node 确认资源使用情况
│       │   └─ 解决:扩容节点 或 降低 requests
│       ├─ "node(s) had taint" → 节点有污点,Pod 没有容忍
│       │   ├─ kubectl describe node | grep Taints
│       │   └─ 解决:添加 tolerations 或移除节点污点
│       ├─ "persistent volume not found" → PVC 无法绑定
│       │   ├─ kubectl get pvc -n <ns> 检查状态
│       │   └─ 解决:检查 StorageClass 是否存在、PV 容量是否足够
│       └─ "no nodes match pod topology spread" → 拓扑分布约束无法满足
│           └─ 解决:放宽 topologySpreadConstraints 或增加节点
├─ ImagePullBackOff / ErrImagePull(镜像拉取失败)
│   └─ kubectl describe pod → Events
│       ├─ "image not found" → 镜像名或 tag 写错了
│       │   └─ 检查 image 字段:仓库地址/tag 是否准确
│       ├─ "unauthorized" / 401 → 认证失败
│       │   ├─ 检查 imagePullSecrets 是否存在
│       │   └─ 检查密钥是否过期(ECR 默认 12 小时过期)
│       ├─ "dial tcp: i/o timeout" → 网络不通
│       │   └─ 节点能否 ping 通镜像仓库(注意 VPC/安全组)
│       └─ "toomanyrequests" → Docker Hub 限速
│           └─ 解决:使用私有镜像仓库或配置认证
├─ CrashLoopBackOff(反复崩溃重启)
│   └─ kubectl logs <pod> -n <ns> --previous(看上一次崩溃的日志)
│       ├─ 退出码 137 → OOMKilled(被内核杀掉)
│       │   └─ 见下方 OOMKilled 路径
│       ├─ 退出码 1 → 应用自身错误
│       │   └─ 看日志定位:配置缺失/数据库连不上/端口冲突/权限不足
│       ├─ 退出码 139 → SIGSEGV 段错误
│       │   └─ 一般是代码 bug(空指针、C 扩展问题)
│       └─ 退出码 143 → SIGTERM(被 K8s 终止)
│           └─ 可能是 Readiness Probe 失败 或 terminationGracePeriodSeconds 太短
├─ OOMKilled(内存溢出被杀)
│   └─ kubectl describe pod → Last State: Reason: OOMKilled
│       ├─ 确认:kubectl top pod -n <ns>,看内存是否接近 limits
│       ├─ 临时缓解:增大 resources.limits.memory
│       ├─ 根因排查:
│       │   ├─ 应用是否有内存泄漏(看时间趋势的内存曲线)
│       │   ├─ Java 应用:检查 -Xmx 设置是否合理(建议设为 limits 的 75%)
│       │   └─ 检查是否有异常流量导致内存突增
│       └─ 注意:OOMKilled 时容器被重建,需要 kubectl logs --previous 看崩溃前日志
└─ Running 但不健康(Readiness Probe 失败)
    └─ kubectl describe pod → Events: "Readiness probe failed"
        ├─ 确认探针路径是否正确:kubectl exec 进入 Pod 手动 curl 健康检查端点
        ├─ 探针超时:检查 initialDelaySeconds 是否足够(应用启动慢时需要加大)
        └─ 应用逻辑:应用可能已启动但核心功能未就绪(如等待依赖服务)

1.2 快速诊断命令序列#

bash
# 一条命令看所有异常 Pod
kubectl get pods -A --field-selector status.phase!=Running,status.phase!=Succeeded

# 针对单个 Pod 的完整诊断
POD="my-pod"; NS="my-ns"

# 1. 看状态和事件
kubectl describe pod $POD -n $NS

# 2. 看退出码(CrashLoopBackOff 时)
kubectl get pod $POD -n $NS \
  -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'

# 3. 看崩溃前日志
kubectl logs $POD -n $NS --previous --tail=100

# 4. 看该 Namespace 近 30 分钟的事件
kubectl get events -n $NS --sort-by='.lastTimestamp' | tail -20

1.3 退出码速查#

退出码含义排查方向
0正常退出程序逻辑主动退出,检查日志
1应用错误未捕获异常、配置错误、启动失败
137SIGKILL (OOM)内存超过 limit,调大 limits 或排查内存泄漏
139SIGSEGVC/C++ 扩展段错误、空指针访问
143SIGTERMK8s 发送终止信号,通常是被探针判定不健康

二、服务不通排查路径#

服务间的网络连通性问题是最常见的 K8s 故障类型。排查时按网络层次从下往上排查:DNS → Service → NetworkPolicy → Ingress

2.1 排查决策树#

text
服务 A 访问服务 B 不通
├─ Layer 1: DNS 解析是否正常?
│   └─ 在 Pod 内测试 DNS 解析
│       kubectl exec -it <pod-a> -n <ns> -- nslookup <service-b>
│       ├─ 解析失败 → DNS 问题
│       │   ├─ 检查 CoreDNS Pod 是否 Running
│       │   │   kubectl get pods -n kube-system -l k8s-app=kube-dns
│       │   ├─ 检查 CoreDNS 日志
│       │   │   kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
│       │   ├─ 是否使用了完整域名?
│       │   │   短名:service-b → 长名:service-b.<ns>.svc.cluster.local
│       │   └─ 检查 Pod 的 dnsPolicy 配置(默认为 ClusterFirst)
│       │
│       └─ 解析成功 → DNS 正常,进入下一层
├─ Layer 2: Service Endpoints 是否有后端?
│   └─ 检查 Service 的 Endpoints
│       kubectl get endpoints <service-b> -n <ns>
│       ├─ Endpoints 为空(ENDPOINTS 列显示 <none>)
│       │   ├─ 原因:Service 的 selector 没有匹配到任何 Pod
│       │   ├─ 检查 Service 的 selector 字段
│       │   │   kubectl get svc <service-b> -n <ns> -o jsonpath='{.spec.selector}'
│       │   ├─ 检查目标 Pod 的 labels
│       │   │   kubectl get pods -n <ns> --show-labels | grep <app-name>
│       │   └─ 确认 Service 的 targetPort 与 Pod 的 containerPort 一致
│       │
│       └─ Endpoints 有值 → Service 配置正常,进入下一层
├─ Layer 3: 直接访问 Pod IP 是否通?
│   └─ 绕过 Service,直接访问后端 Pod
│       # 获取 Pod IP
│       POD_IP=$(kubectl get pod <pod-b> -n <ns> \
│         -o jsonpath='{.status.podIP}')
│       # 从 Pod A 直接 curl
│       kubectl exec -it <pod-a> -n <ns> -- curl -v --connect-timeout 3 http://$POD_IP:<port>
│       ├─ 不通 → 网络层面问题
│       │   ├─ 检查 NetworkPolicy 是否拦截
│       │   │   kubectl get networkpolicy -n <ns>
│       │   │   如果有 policy,检查是否放行了相应端口和来源
│       │   ├─ 检查 CNI 插件状态(Calico/Cilium/Flannel)
│       │   │   kubectl get pods -n kube-system | grep -E "calico|cilium|flannel"
│       │   └─ 检查网络命名空间(在节点上用 crictl 或 tcpdump 排查)
│       │
│       └─ 通 → Service 代理有问题(大概率是 kube-proxy)
│           ├─ 检查 kube-proxy 状态
│           │   kubectl get pods -n kube-system -l k8s-app=kube-proxy
│           └─ 检查 kube-proxy 模式(iptables/IPVS)
├─ Layer 4: 通过 Service ClusterIP 访问是否通?
│   └─ kubectl exec -it <pod-a> -n <ns> -- curl -v http://<service-b>:<port>
│       ├─ 不通但直接 Pod IP 通 → kube-proxy 规则有问题
│       └─ 通 → Service 层正常,进入下一层
└─ Layer 5: Ingress 层(外部访问)
    └─ 外部请求 → Ingress → Service → Pod
        ├─ 检查 Ingress 资源配置
        │   kubectl describe ingress <name> -n <ns>
        ├─ 检查 Ingress Controller 日志
        │   kubectl logs -n ingress-nginx <ingress-controller-pod>
        └─ 常见问题:
            ├─ host 不匹配 → 检查 Ingress 的 host 字段
            ├─ TLS 证书问题 → kubectl describe certificate
            ├─ backend serviceName 写错 → 检查与 Service 名称是否一致
            └─ 路径不匹配 → 检查 path 和 pathType 配置

2.2 网络排查命令速查#

bash
# DNS 测试
kubectl exec -it <pod> -n <ns> -- nslookup kubernetes.default

# Service Endpoints 检查
kubectl get endpoints -n <ns>

# 在 Pod 内测试连接
kubectl exec -it <pod> -n <ns> -- curl -v --connect-timeout 3 http://<svc>:<port>

# NetworkPolicy 审计
kubectl get networkpolicy -A

# 检查 kube-proxy
kubectl get pods -n kube-system -l k8s-app=kube-proxy

# Ingress 状态
kubectl describe ingress -n <ns>

三、节点异常排查路径#

节点问题是 K8s 集群中最基础也可能最严重的问题。一个节点变为 NotReady 会影响调度在该节点上的所有 Pod。

3.1 排查决策树#

text
kubectl get nodes → 某个节点 NotReady
├─ Step 1: 看 Conditions
│   └─ kubectl describe node <node-name>
│       └─ 重点关注 Conditions 区域:
│           ├─ MemoryPressure = True → 节点内存不足
│           │   ├─ on node: free -m 看可用内存
│           │   ├─ kubectl top node <node> 看资源分配比例
│           │   └─ 解决:驱逐部分 Pod 或扩容节点
│           │
│           ├─ DiskPressure = True → 节点磁盘不足
│           │   ├─ on node: df -h 看磁盘使用率
│           │   ├─ 特别检查 /var/lib/docker 或 /var/lib/kubelet
│           │   ├─ 检查是否有大量容器镜像未清理
│           │   │   docker system prune -a  # Docker
│           │   │   crictl rmi --prune       # containerd
│           │   └─ 检查 /var/log 是否过大
│           │
│           ├─ PIDPressure = True → 进程数超限
│           │   ├─ on node: ps aux | wc -l 看进程数
│           │   └─ 检查是否有僵尸进程或 fork 炸弹
│           │
│           ├─ NetworkUnavailable = True → 网络插件异常
│           │   ├─ 检查 CNI 插件 Pod 状态
│           │   │   kubectl get pods -n kube-system -o wide | grep <node>
│           │   └─ on node: 检查 CNI 配置目录 /etc/cni/net.d/
│           │
│           └─ KubeletHasSufficientMemory/Disk/PID = False
│               └─ 资源被系统预留耗尽,需要调整 kubelet 预留参数
├─ Step 2: 检查 kubelet 状态
│   └─ SSH 到问题节点
│       ├─ systemctl status kubelet
│       │   └─ 如果 kubelet 已停止:systemctl restart kubelet
│       ├─ journalctl -u kubelet -f(实时看 kubelet 日志)
│       │   └─ 常见错误:
│       │       ├─ "failed to get cgroup stats" → cgroup 驱动不匹配
│       │       ├─ "failed to connect to apiserver" → 网络问题
│       │       ├─ "PLEG is not healthy" → 容器运行时异常
│       │       └─ "container runtime is down" → Docker/containerd 挂了
│       └─ 检查容器运行时
│           └─ systemctl status containerd(或 docker)
├─ Step 3: 检查容器运行时
│   └─ on node:
│       ├─ crictl ps(看容器是否正常运行)
│       ├─ crictl info(看运行时状态)
│       └─ 如果 containerd 异常:
│           └─ systemctl restart containerd
└─ Step 4: 资源快速诊断(on node)
    ├─ df -h                        # 磁盘空间
    ├─ free -m                      # 内存
    ├─ top -bn1 | head -5           # CPU 负载
    └─ netstat -an | wc -l          # 连接数

3.2 kubelet 关键日志#

bash
# 查看最近 100 条 kubelet 日志
journalctl -u kubelet -n 100 --no-pager

# 过滤错误
journalctl -u kubelet | grep -i error | tail -20

# 查看 PLEG 相关(Pod Lifecycle Event Generator,常见 NotReady 根因)
journalctl -u kubelet | grep PLEG | tail -20

3.3 节点常见问题速查#

现象可能原因验证命令解决方向
NotReady + MemoryPressure内存不足free -m驱逐 Pod / 扩容
NotReady + DiskPressure磁盘不足df -h清理镜像/日志
NotReady + PLEG not healthy容器运行时响应慢crictl info重启 containerd
NotReady + kubelet stoppedkubelet 异常退出systemctl status kubelet查看日志后重启
Ready 但 Pod 调度失败节点资源已分配满kubectl describe node | grep Allocated扩容 / 调整 requests

四、常用排查工具#

4.1 kubectl debug — 临时调试容器#

当 Pod 的基础镜像过于精简(如 distrolessscratch),缺少 curlnslookuptcpdump 等调试工具时,可以用 kubectl debug 创建临时调试容器:

bash
# 方法1:创建临时副本并替换镜像为调试镜像
kubectl debug my-pod -n my-ns \
  --image=nicolaka/netshoot \
  --copy-to=my-pod-debug \
  -- sh

# 进入调试容器后:
# nslookup target-service
# curl -v http://target-service:8080
# tcpdump -i eth0 port 8080

# 方法2:ephemeral container 方式(K8s v1.23+)
# 不创建新 Pod,直接在原 Pod 中注入调试容器
kubectl debug -it my-pod -n my-ns \
  --image=nicolaka/netshoot \
  --target=my-container \
  -- sh

推荐使用 nicolaka/netshoot 镜像,它内置了 curlnslookuptcpdumpnetstatiperf 等常用网络工具。

4.2 ephemeral container — 注入侧车#

ephemeral container 是 K8s 的原生特性(v1.23 Beta,v1.25 GA),允许在不重启 Pod 的情况下临时注入一个调试容器:

bash
# 向正在运行的 Pod 注入调试容器
kubectl debug -it my-pod -n my-ns \
  --image=busybox:1.36 \
  --target=my-container \
  -- sh

# 在注入的容器中:
# 1. 共享 PID 空间,查看主容器进程
#    ps aux
# 2. 检查网络连通性
#    nslookup target-service
# 3. 检查文件系统(通过 /proc/<pid>/root)
#    ls /proc/1/root/app/

ephemeral container 的优势:不会重启 Pod,不影响运行中的服务,用完即销毁。

4.3 抓包分析#

在某些复杂网络问题中,抓包是最直接的排查手段:

方法1:在 Pod 内用 tcpdump 抓包

bash
# 注入调试容器后
kubectl debug -it my-pod -n my-ns --image=nicolaka/netshoot --target=my-container -- sh

# 在调试容器内抓包
tcpdump -i eth0 -w /tmp/capture.pcap port 8080

# 下载抓包文件到本地分析
kubectl cp my-ns/my-pod:/tmp/capture.pcap ./capture.pcap -c debugger-xxx

方法2:在节点上抓包

bash
# 先找到 Pod 所在的节点
kubectl get pod my-pod -n my-ns -o wide
# 找到 NODE 列

# SSH 到该节点
# 找到 Pod 的网络接口(需要容器运行时支持)
crictl pods --name my-pod  # 获取 Pod ID
# 进入容器网络命名空间抓包

4.4 其他实用工具#

工具/命令用途示例
kubectl get events --sort-by='.lastTimestamp'查看命名空间内最近事件快速了解集群"发生了什么"
kubectl describe <resource>查看资源详情(含 Events)排查 Pod/Node/Service 问题第一步
kubectl logs --previous查看崩溃容器上一次日志CrashLoopBackOff 必用
kubectl top pods/nodes查看资源使用(需 metrics-server)排查资源不足
kubectl exec在容器内执行命令测试网络、检查进程、查看文件
kubectl auth can-i检查权限排查 RBAC Permission Denied

小结#

K8s 故障排查的核心是分层排查,按从外到内、从上到下的顺序逐步缩小范围:

  • Pod 起不来:先看状态(Pending/CrashLoopBackOff/ImagePullBackOff)→ 再看 describe events → 最后看 logs --previous
  • 服务不通:DNS → Endpoints → Pod IP → ClusterIP → Ingress,逐层测试连通性
  • 节点异常:Conditions → kubelet 日志 → 容器运行时 → 系统资源,逐一排除

最重要的两个命令:kubectl describe(事件和状态)和 kubectl logs --previous(崩溃原因),这俩能解决 80% 的 Pod 层面问题。对于复杂的网络问题,kubectl debug 注入调试容器配合 tcpdump 是最强力的组合拳。

记住一个原则:不要猜,要看日志和事件。K8s 的 Events 机制就是为排障设计的,99% 的问题都在 Events 或日志里有明确提示。