故障排查思路
概述#
K8s 故障排查与其他系统最大的不同在于:问题不一定是应用本身的问题,还可能是调度问题、网络问题、存储问题或节点问题。本章以决策树的方式组织排查路径,帮你从现象快速定位根因。掌握了这套排查思路,即使遇到没见过的错误也能按层次逐一排除。
一、Pod 起不来排查路径#
Pod 是 K8s 中最小的调度单位,也是问题出现最频繁的地方。排查的核心思路是:先看状态,再看事件,再看日志。
1.1 排查决策树#
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 快速诊断命令序列#
# 一条命令看所有异常 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 -201.3 退出码速查#
| 退出码 | 含义 | 排查方向 |
|---|---|---|
| 0 | 正常退出 | 程序逻辑主动退出,检查日志 |
| 1 | 应用错误 | 未捕获异常、配置错误、启动失败 |
| 137 | SIGKILL (OOM) | 内存超过 limit,调大 limits 或排查内存泄漏 |
| 139 | SIGSEGV | C/C++ 扩展段错误、空指针访问 |
| 143 | SIGTERM | K8s 发送终止信号,通常是被探针判定不健康 |
二、服务不通排查路径#
服务间的网络连通性问题是最常见的 K8s 故障类型。排查时按网络层次从下往上排查:DNS → Service → NetworkPolicy → Ingress。
2.1 排查决策树#
服务 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 网络排查命令速查#
# 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 排查决策树#
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 关键日志#
# 查看最近 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 -203.3 节点常见问题速查#
| 现象 | 可能原因 | 验证命令 | 解决方向 |
|---|---|---|---|
| NotReady + MemoryPressure | 内存不足 | free -m | 驱逐 Pod / 扩容 |
| NotReady + DiskPressure | 磁盘不足 | df -h | 清理镜像/日志 |
| NotReady + PLEG not healthy | 容器运行时响应慢 | crictl info | 重启 containerd |
| NotReady + kubelet stopped | kubelet 异常退出 | systemctl status kubelet | 查看日志后重启 |
| Ready 但 Pod 调度失败 | 节点资源已分配满 | kubectl describe node | grep Allocated | 扩容 / 调整 requests |
四、常用排查工具#
4.1 kubectl debug — 临时调试容器#
当 Pod 的基础镜像过于精简(如 distroless、scratch),缺少 curl、nslookup、tcpdump 等调试工具时,可以用 kubectl debug 创建临时调试容器:
# 方法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 镜像,它内置了 curl、nslookup、tcpdump、netstat、iperf 等常用网络工具。
4.2 ephemeral container — 注入侧车#
ephemeral container 是 K8s 的原生特性(v1.23 Beta,v1.25 GA),允许在不重启 Pod 的情况下临时注入一个调试容器:
# 向正在运行的 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 抓包
# 注入调试容器后
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:在节点上抓包
# 先找到 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 或日志里有明确提示。