Kubernetes 故障排查實戰:從診斷到修復
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
排查清單:
- 映像檔位址錯誤:檢查 image 欄位拼寫,確認 tag 存在
- 私有倉庫認證:確認 imagePullSecrets 已設定
- 網路不通:節點無法存取映像檔倉庫(防火牆/代理)
- 映像檔不存在: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 常見問題:
- CoreDNS Pod 未執行:檢查 kube-system 中 coredns 狀態
- ConfigMap 設定錯誤:檢查
corednsConfigMap 的 plugins 設定 - 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> 恢復。
本站提供瀏覽器本地工具,免註冊即可試用 →