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#故障排查#运维#教程