Kubernetes 故障排查实战:从诊断到修复

DevOps

Kubernetes 故障排查方法论

Kubernetes 集群故障排查不是"瞎猜",而是一套分层递进的诊断体系。从应用层到基础设施层,逐层排查才能高效定位根因。

分层排查模型

层级 范围 关键命令
L1 应用层 Pod 状态、容器日志 kubectl logs, kubectl describe pod
L2 服务层 Service、Endpoint、DNS kubectl get endpoints, nslookup
L3 调度层 Deployment、ReplicaSet、HPA kubectl rollout status, kubectl get hpa
L4 存储层 PVC、PV、StorageClass kubectl describe pvc, kubectl get pv
L5 节点层 Node 状态、资源压力 kubectl describe node, kubectl top nodes
L6 基础设施层 网络插件、API Server、etcd systemctl status kubelet, crictl ps

诊断黄金流程

# 第一步:确认集群整体健康
kubectl get nodes
kubectl get componentstatuses

# 第二步:定位问题命名空间
kubectl get all -n <namespace>

# 第三步:聚焦异常资源
kubectl describe <resource> <name> -n <namespace>

# 第四步:查看事件和日志
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
kubectl logs <pod-name> -n <namespace> --previous

# 第五步:进入容器排查
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh

Pod 故障排查

Pod 是 Kubernetes 最小调度单元,也是故障最密集的资源。掌握每种异常状态的诊断方法至关重要。

CrashLoopBackOff

容器启动后立即退出,Kubelet 反复重启导致循环崩溃。

# 查看当前日志
kubectl logs <pod-name> -n <namespace>

# 查看上一次崩溃的日志(关键!)
kubectl logs <pod-name> -n <namespace> --previous

# 查看容器退出码
kubectl describe pod <pod-name> -n <namespace> | grep -A5 "Last State"

常见原因与修复:

退出码 含义 修复方案
0 正常退出但未设置长运行模式 检查入口命令,确保前台运行
1 应用错误(未捕获异常) 检查应用日志,修复代码逻辑
137 OOMKilled(被 SIGKILL) 增大 resources.limits.memory
139 Segmentation Fault 检查依赖库兼容性
143 SIGTERM 优雅终止 检查 preStop hook 或终止信号处理
# 修复示例:添加启动探针避免慢启动被杀
spec:
  containers:
    - name: app
      image: my-app:v1
      startupProbe:
        httpGet:
          path: /healthz
          port: 8080
        failureThreshold: 30
        periodSeconds: 10
      livenessProbe:
        httpGet:
          path: /healthz
          port: 8080
        periodSeconds: 15
        failureThreshold: 3

ImagePullBackOff

镜像拉取失败,通常由镜像地址、认证或网络问题引起。

# 查看详细错误信息
kubectl describe pod <pod-name> | grep -A10 "Events"

# 常见错误信息
# ErrImagePull: registry.k8s.io/pause:3.9 - 连接超时
# ImagePullBackOff: unauthorized: authentication required

排查清单:

  1. 镜像地址错误:检查 image 字段拼写,确认 tag 存在
  2. 私有仓库认证:确认 imagePullSecrets 已配置
  3. 网络不通:节点无法访问镜像仓库(防火墙/代理)
  4. 镜像不存在:tag 拼写错误或镜像未推送
# 修复:配置私有仓库认证
spec:
  imagePullSecrets:
    - name: registry-credentials
  containers:
    - name: app
      image: harbor.company.com/project/app:v1.2.3
# 创建 imagePullSecret
kubectl create secret docker-registry registry-credentials \
  --docker-server=harbor.company.com \
  --docker-username=admin \
  --docker-password=Harbor12345 \
  -n <namespace>

OOMKilled

容器内存超限被内核 OOM Killer 杀死。

# 确认 OOMKilled
kubectl describe pod <pod-name> | grep -A5 "Last State"
# Last State:     Terminated  Reason: OOMKilled  Exit Code: 137

# 查看容器内存使用趋势
kubectl top pod <pod-name> -n <namespace>

# 查看节点 cgroup 日志
dmesg | grep -i oom

修复策略:

# 策略一:调大内存限制
resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "1Gi"      # 从 512Mi 增大到 1Gi
    cpu: "500m"

# 策略二:优化应用内存使用(JVM 示例)
env:
  - name: JAVA_OPTS
    value: "-Xms256m -Xmx512m -XX:+UseG1GC"

# 策略三:设置 QoS 为 Guaranteed(requests == limits)
resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "500m"

Pod Pending

Pod 无法被调度到任何节点,通常因为资源不足或约束不满足。

# 查看调度失败原因
kubectl describe pod <pod-name> | grep -A20 "Events"
# 常见信息:
# 0/3 nodes are available: 3 Insufficient cpu.
# 0/3 nodes are available: 1 node(s) had taints that the pod didn't tolerate.

常见原因与解决方案:

原因 事件信息 解决方案
CPU 不足 Insufficient cpu 降低 requests 或扩容节点
内存不足 Insufficient memory 降低 requests 或扩容节点
PVC 未绑定 pod has unbound immediate PersistentVolumeClaims 先排查 PVC 状态
节点选择器不匹配 node(s) didn't match node selector 检查 nodeSelector / nodeAffinity
污点容忍 node(s) had taints that the pod didn't tolerate 添加 tolerations
PodDisruptionBudget Cannot evict pod 检查 PDB 配置

Service 与网络问题排查

DNS 解析失败

# 部署 DNS 调试 Pod
kubectl run dnsutils --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \
  --restart=Never -- sleep infinity

# 测试集群内 DNS 解析
kubectl exec -it dnsutils -- nslookup kubernetes.default.svc.cluster.local
kubectl exec -it dnsutils -- nslookup <service-name>.<namespace>.svc.cluster.local

# 检查 CoreDNS 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns

CoreDNS 常见问题:

  1. CoreDNS Pod 未运行:检查 kube-system 中 coredns 状态
  2. ConfigMap 配置错误:检查 coredns ConfigMap 的 plugins 配置
  3. ndots 导致超时/etc/resolv.conf 中 ndots=5 导致多次查询
# 优化 Pod DNS 配置
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"
      - name: single-request-reopen
      - name: timeout
        value: "3"
  dnsPolicy: ClusterFirst

Endpoint 为空

Service 的 Endpoint 列表为空,流量无法路由到任何 Pod。

# 检查 Endpoint
kubectl get endpoints <service-name> -n <namespace>

# 常见原因排查
# 1. label selector 不匹配
kubectl get pods -n <namespace> --show-labels
kubectl get svc <service-name> -n <namespace> -o yaml | grep -A5 selector

# 2. Pod 未 Ready
kubectl get pods -n <namespace>  # 查看 READY 列是否 1/1

# 3. 端口不匹配
kubectl describe svc <service-name> -n <namespace>
# 确保 Service selector 与 Pod labels 匹配
# Service
spec:
  selector:
    app: my-app        # 必须与 Pod label 一致
  ports:
    - port: 80
      targetPort: 8080  # 必须与容器端口一致

# Pod
metadata:
  labels:
    app: my-app        # 与 Service selector 对应
spec:
  containers:
    - name: app
      ports:
        - containerPort: 8080

连接被拒绝

# 从调试 Pod 测试连通性
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
# 在调试 Pod 内
wget -qO- http://<service-name>.<namespace>:<port>/healthz
curl -v telnet://<service-name>.<namespace>:<port>

# 检查网络策略是否阻断
kubectl get networkpolicy -n <namespace>

# 检查 kube-proxy 模式和 iptables/ipvs 规则
kubectl logs -n kube-system -l k8s-app=kube-proxy
iptables-save | grep <service-cluster-ip>

Deployment 问题排查

回滚操作

# 查看 rollout 历史
kubectl rollout history deployment/<name> -n <namespace>

# 回滚到上一版本
kubectl rollout undo deployment/<name> -n <namespace>

# 回滚到指定版本
kubectl rollout undo deployment/<name> -n <namespace> --to-revision=2

# 暂停和恢复 rollout(用于金丝雀发布)
kubectl rollout pause deployment/<name> -n <namespace>
kubectl set image deployment/<name> app=my-app:v2 -n <namespace>
kubectl rollout resume deployment/<name> -n <namespace>

扩缩容失败

# 手动扩容
kubectl scale deployment/<name> --replicas=5 -n <namespace>

# 检查 HPA 状态
kubectl get hpa -n <namespace>
kubectl describe hpa <hpa-name> -n <namespace>

# HPA 常见失败原因
# 1. metrics-server 未部署
kubectl get pods -n kube-system -l k8s-app=metrics-server

# 2. 资源 requests 未设置(HPA 无法计算利用率)
kubectl describe pod <pod-name> | grep -A5 "Requests"

# 3. 扩容达到最大副本数限制
kubectl get hpa <hpa-name> -o yaml | grep maxReplicas
# 正确的 HPA 配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

PVC/PV 存储问题排查

PVC Pending

# 查看 PVC 状态和事件
kubectl describe pvc <pvc-name> -n <namespace>

# 常见原因
# 1. 没有匹配的 PV(静态 provisioning)
kubectl get pv | grep <storage-class>

# 2. StorageClass 不存在(动态 provisioning)
kubectl get storageclass

# 3. 动态 provisioner 未运行
kubectl get pods -n <namespace> | grep provisioner

PV Lost

# 查看PV状态
kubectl get pv
# NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM
# pv-data    50Gi       RWO            Delete           Lost     default/pvc-data

# 修复:确认后端存储是否恢复
# 如果存储已恢复,手动清除 Lost 状态
kubectl patch pv <pv-name> -p '{"spec":{"claimRef": null}}'
# 重新绑定 PVC

AccessMode 不匹配

# PVC 请求 ReadWriteMany 但 PV 只支持 ReadWriteOnce
# PVC
spec:
  accessModes:
    - ReadWriteMany    # 需要 RWX
  resources:
    requests:
      storage: 10Gi
  storageClassName: nfs

# 确认 StorageClass 支持的 accessMode
kubectl describe storageclass nfs
AccessMode 缩写 说明 典型后端
ReadWriteOnce RWO 单节点读写 AWS EBS, Ceph RBD
ReadOnlyMany ROX 多节点只读 NFS
ReadWriteMany RWX 多节点读写 NFS, CephFS
ReadWriteOncePod RWOP 单 Pod 独占读写 CSI 支持

Node 节点问题排查

NotReady 状态

# 查看节点状态
kubectl get nodes
kubectl describe node <node-name>

# 检查 kubelet 服务
systemctl status kubelet
journalctl -u kubelet -n 100 --no-pager

# 检查容器运行时
crictl ps
systemctl status containerd

# 常见原因
# 1. kubelet 证书过期
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates

# 2. 磁盘空间不足
df -h /var/lib/kubelet

# 3. 网络插件异常
kubectl get pods -n kube-system -o wide | grep <node-name>

资源压力条件

# 查看节点条件
kubectl describe node <node-name> | grep -A20 "Conditions"

# DiskPressure:节点磁盘使用率超过阈值
# MemoryPressure:节点可用内存不足
# PIDPressure:进程数过多
# 调整 kubelet 驱逐阈值(不推荐生产调低)
kind: KubeletConfiguration
evictionHard:
  memory.available: "100Mi"
  nodefs.available: "10%"
  imagefs.available: "15%"
evictionSoft:
  memory.available: "200Mi"
  nodefs.available: "20%"
evictionSoftGracePeriod:
  memory.available: "1m30s"
  nodefs.available: "2m"

kubectl 调试命令速查表

资源查看

命令 用途
kubectl get all -n <ns> 查看命名空间所有资源
kubectl get pods -o wide 查看 Pod 含节点信息
kubectl get pods --show-labels 查看 Pod 标签
kubectl top pods --sort-by=memory 按内存排序
kubectl api-resources 查看所有 API 资源类型

调试排障

命令 用途
kubectl logs <pod> -c <container> --previous 上一次容器日志
kubectl logs <pod> --since=1h 最近1小时日志
kubectl logs <pod> -f --tail=100 实时跟踪最近100行
kubectl exec -it <pod> -- /bin/sh 进入容器终端
kubectl debug <pod> -it --image=busybox 临时调试容器
kubectl port-forward svc/<svc> 8080:80 端口转发本地调试

集群管理

命令 用途
kubectl cordon <node> 标记节点不可调度
kubectl drain <node> --ignore-daemonsets 驱逐节点所有 Pod
kubectl uncordon <node> 恢复节点可调度
kubectl taint nodes <node> key=value:NoSchedule 添加污点
kubectl label node <node> key=value 添加标签

日志与事件分析

集中日志收集

# 使用 kubectl 日志聚合(多容器 Pod)
kubectl logs <pod-name> --all-containers=true

# 使用 label selector 批量查看
kubectl logs -l app=my-app -n <namespace> --since=5m

# 导出日志到文件分析
kubectl logs <pod-name> -n <namespace> > pod-debug.log

Event 事件分析

# 查看命名空间所有事件(按时间排序)
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

# 按类型过滤
kubectl get events -n <namespace> --field-selector type=Warning

# 持续监控事件
kubectl get events -n <namespace> --watch

# 查看特定资源的事件
kubectl events --for pod/<pod-name> -n <namespace>

日志级别与格式

# API Server 审计日志
cat /var/log/kubernetes/audit.log | jq '. | select(.responseStatus.code >= 400)'

# kubelet 日志
journalctl -u kubelet -f

# etcd 日志
kubectl logs -n kube-system etcd-<node-name> --since=10m

资源限制与请求最佳实践

QoS 等级

QoS 等级 条件 驱逐优先级 适用场景
Guaranteed requests == limits(CPU+内存) 最低(最后被杀) 核心服务
Burstable requests < limits 中等 一般服务
BestEffort 未设置 requests/limits 最高(最先被杀) 批处理任务
# 生产环境推荐配置模板
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

# 使用 LimitRange 设置命名空间默认值
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: production
spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: "512Mi"
      defaultRequest:
        cpu: "100m"
        memory: "128Mi"
      max:
        cpu: "2"
        memory: "4Gi"

常见资源问题

# 查看节点资源使用
kubectl top nodes
kubectl describe node <node-name> | grep -A10 "Allocated resources"

# 查看命名空间资源配额
kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>

NetworkPolicy 调试

策略阻断排查

# 查看命名空间所有 NetworkPolicy
kubectl get networkpolicy -n <namespace>

# 查看策略详情
kubectl describe networkpolicy <policy-name> -n <namespace>
# 常见 NetworkPolicy 配置错误
# 错误:忘记允许 DNS 流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP

网络连通性测试

# 部署网络调试工具
kubectl run netshoot --image=nicolaka/netshoot --rm -it --restart=Never

# 在调试 Pod 中测试
# 同命名空间
curl http://<service-name>:<port>/healthz

# 跨命名空间
curl http://<service-name>.<namespace>.svc.cluster.local:<port>/healthz

# 测试外部连通性
curl -I https://external-api.example.com

Helm Chart 常见问题

Release 安装失败

# 查看 Release 状态
helm status <release-name> -n <namespace>
helm history <release-name> -n <namespace>

# 查看渲染后的 YAML(不安装)
helm template <chart> --debug

# 查看失败 Release 的值
helm get values <release-name> -n <namespace>

# 回滚 Helm Release
helm rollback <release-name> <revision> -n <namespace>

常见 Helm 错误

错误 原因 修复
rendered manifests contain a resource that already exists 资源冲突 使用 --replace 或清理旧资源
Release "xxx" has not been installed yet Release 不存在 检查 namespace 和 release name
timed out waiting for the condition Pod 未 Ready 检查 --timeout 值和 Pod 状态
YAML parse error values.yaml 语法错误 使用 /encode/yaml 验证
# 清理失败的 Release
helm uninstall <release-name> -n <namespace>

# 强制安装(开发环境)
helm install <release-name> <chart> --replace -n <namespace>

Prometheus 监控告警

关键告警规则

groups:
  - name: kubernetes-alerts
    rules:
      - alert: PodCrashLooping
        expr: rate(kube_pod_container_status_restarts_total[5m]) > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping"

      - alert: NodeNotReady
        expr: kube_node_status_condition{condition="Ready",status="unknown"} == 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Node {{ $labels.node }} is not ready"

      - alert: PVCAlmostFull
        expr: kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} is >85% full"

      - alert: DeploymentReplicasMismatch
        expr: kube_deployment_spec_replicas != kube_deployment_status_available_replicas
        for: 10m
        labels:
          severity: warning

常用 PromQL 查询

# Pod 内存使用率
container_memory_working_set_bytes / container_spec_memory_limit_bytes * 100

# 节点 CPU 利用率
rate(node_cpu_seconds_total{mode!="idle"}[5m]) * 100

# 请求错误率
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100

# PVC 使用率
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes

应急响应手册

P0:集群不可用

# 1. 检查 API Server
kubectl get componentstatuses
curl -k https://<api-server>:6443/healthz

# 2. 检查 etcd
kubectl logs -n kube-system etcd-<node> --tail=50
ETCDCTL_API=3 etcdctl endpoint health --cluster

# 3. 检查 kubelet
systemctl status kubelet
journalctl -u kubelet --since "10 minutes ago"

# 4. 紧急恢复:重启控制面组件
systemctl restart kubelet
systemctl restart containerd

P1:服务全面不可用

# 1. 快速回滚
kubectl rollout undo deployment/<name> -n <namespace>

# 2. 紧急扩容
kubectl scale deployment/<name> --replicas=10 -n <namespace>

# 3. 节点紧急维护
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

P2:存储故障

# 1. 检查 PV 状态
kubectl get pv | grep -v Bound

# 2. 检查 CSI Driver
kubectl get pods -n <csi-namespace>

# 3. 紧急处理:强制删除卡住的 Pod
kubectl delete pod <pod-name> --force --grace-period=0 -n <namespace>

FAQ

Q: Pod 一直处于 ContainerCreating 状态怎么办?

A: 通常是镜像拉取慢或 StorageClass 挂载失败。使用 kubectl describe pod 查看 Events,重点关注镜像拉取和卷挂载事件。

Q: 如何查看已删除 Pod 的日志?

A: 如果配置了日志聚合(EFK/PLG),可通过日志平台查询。否则已删除 Pod 的日志无法恢复,建议生产环境必须部署日志收集方案。

Q: kubectl 命令超时怎么排查?

A: 通常是 API Server 负载过高或网络问题。检查 ~/.kube/config 中 server 地址,使用 kubectl get --request-timeout=5s nodes 测试连通性。

Q: 如何快速定位哪个节点资源不足?

A: 使用 kubectl top nodes 查看实时资源,或 kubectl describe node | grep -A5 "Allocated resources" 查看已分配量。

Q: Service ClusterIP 无法访问怎么排查?

A: 依次检查:① kube-proxy 是否正常运行 ② iptables/ipvs 规则是否正确 ③ Endpoint 是否为空 ④ NetworkPolicy 是否阻断 ⑤ CNI 插件状态。

Q: 如何防止 Pod 被意外驱逐?

A: 设置 PodDisruptionBudget,为关键服务配置 PDB 保证最小可用副本数。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: my-app

Q: Helm upgrade 失败但 Release 状态卡住怎么办?

A: 使用 helm rollback 回滚,或 helm history 查看历史版本。如果 Release 锁定,可使用 helm rollback <release> <last-good-revision> 恢复。

本站提供浏览器本地工具,免注册即可试用 →

#Kubernetes#K8s#故障排查#运维#教程