iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Kubernetes

從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台系列 第 29

[Day 29] 實戰排除:當 Pod 頻繁重啟 (CrashLoopBackOff) 時該如何排查 —— 一步一步走過真實的除錯流程,而不是背口訣。

  • 分享至 

  • xImage
  •  

Day 29: 實戰排除:當 Pod 頻繁重啟 (CrashLoopBackOff) 時該如何排查

一步一步走過真實的除錯流程,而不是背口訣。

1. CrashLoopBackOff 代表什麼

容器啟動後立刻結束(可能是應用程式拋出例外、或直接 crash),K8S 依照重啟策略嘗試重啟,又失敗,隨重試次數增加退避間隔(backoff)——這個狀態就叫 CrashLoopBackOff。它本身不是原因,是結果;真正的原因永遠要往下挖。

2. 實測:故意弄壞一個服務

與其空講排查步驟,直接製造一次真實的故障。把 user-service 的資料庫密碼改成錯的:

kubectl patch deployment user-service --type json -p '[
  {"op":"replace","path":"/spec/template/spec/containers/0/env/4",
   "value":{"name":"POSTGRES_PASSWORD","value":"wrong_password_on_purpose"}}
]'

幾十秒後,Pod 真的進入 CrashLoopBackOff。接下來照以下三個步驟排查:

https://ithelp.ithome.com.tw/upload/images/20260831/201825491waZ1qqwjH.png

▲ CrashLoopBackOff 完整排查流程:describe 看事件、logs --previous 看死前的錯誤堆疊、exec 驗證問題範圍,最後定位到密碼設定並修復

3. 第一步:kubectl describe pod

kubectl describe pod user-service-7bccf44bfb-fcx2k

看最下方的 Events:Readiness probe failedBack-off restarting failed container。這一步能確認的是「容器起不來」這件事本身,但看不出為什麼——Events 只記錄 K8S 觀察到的現象(探針失敗、重啟次數),不包含應用程式內部的錯誤訊息。

4. 第二步:kubectl logs --previous

kubectl logs user-service-7bccf44bfb-fcx2k --previous

--previous 是這一步的關鍵——容器已經重啟過,kubectl logs 預設抓的是「這次」(新啟動、可能還沒印出任何東西)的日誌,要看「上一次死掉前」的內容必須加這個參數。實際看到的是一串 Spring 的 BeanCreationException 巢狀例外,順著 Caused by: 一路往下追,最底層是:

Caused by: org.postgresql.util.PSQLException: FATAL: password authentication failed for user "waferbi"

真正的根因在這裡浮現:不是程式碼問題,是資料庫密碼不對。Java 的例外堆疊習慣把最外層的、離根因最遠的錯誤放在最上面,永遠從最後一行 Caused by 開始看,不要被最上面那層唬住。

5. 第三步:縮小問題範圍

拿到「密碼不對」這條線索後,下一個問題是:是資料庫那端出了問題,還是這次部署帶的密碼設定錯了?直接連進資料庫本身驗證:

kubectl exec deploy/postgres -- psql -U waferbi -d waferbi_db -c "SELECT 1;"

用的是資料庫端已知正確的密碼,如果這樣都連不上,問題在資料庫本身(例如 pg_hba.conf 設定、或資料庫真的换了密碼);如果連得上,就能確定問題出在 user-service 這邊——這次剛好是我自己手動改錯的環境變數。

6. 修復與確認

kubectl patch deployment user-service --type json -p '[...valueFrom.secretKeyRef...]'

改回從 Secret 讀取密碼,新 Pod 一次啟動成功,CrashLoopBackOff 解除。

7. 排查心法

這三步的順序其實對應一個邏輯:先確認現象(describe)→ 找到根因(logs --previous)→ 縮小範圍驗證假設(exec 進去戳)。跳過中間這一步、只憑 Events 的表面訊息猜測,很容易把力氣花在錯的方向——例如看到 Readiness Probe 失敗就去改探針的 timeoutSeconds,而不是去看真正讓應用程式起不來的例外訊息。

其他常見的根因分類,排查時可以對照:

現象 常見根因
Liveness/Readiness probe failed 應用真的沒起來,或探測路徑/Port 設錯
OOMKilled(Day 25/28 遇到的) limits.memory 設太小,或應用本身有記憶體洩漏/尖峰
ImagePullBackOff Image tag 打錯、Registry 權限問題(Day 16 遇過)
應用日誌裡的 Connection refused 依賴的服務還沒啟動,或 Service 名稱寫錯
應用日誌裡的 Authentication failed 密碼/憑證設定錯誤(這次的案例)

8. 另一種故障:沒有任何 Pod 在哭

上面那套排查法有個前提:有東西在哭。Pod 重啟、探針失敗、Events 一堆紅字,你至少知道要從哪裡開始挖。

但這系列寫到最後,我在自己的叢集上撞到一個更陰險的類型——所有指標都是綠的,系統卻早就不是你以為的那個系統了

起因很無聊:我想驗證 Day 20 的回滾行為,習慣性先看一眼環境,結果越看越不對勁。

https://ithelp.ithome.com.tw/upload/images/20260831/20182549vfgXmITnHs.png

▲ 沒有任何 Pod 在哭的故障:4 個服務全部 Running 17 天,但 ArgoCD 一個 Application 都沒有,跑的 image 也不是 Git 裡那個

一條一條看下去,證據其實很清楚:

  1. kubectl get deploy 全部 1/1、AGE 17 天——這就是它騙人的地方,任何監控儀表板看這個環境都會給你一片綠
  2. kubectl get applications -ANo resources found——Day 18 裝好的 ArgoCD 那幾個 Pod 都還健康地跑著,但它手上一個 Application 都沒有。GitOps 的控制迴路根本沒有在轉,這才是根因
  3. image 對不上:Git 的 values.yaml 指向 ghcr.io/darkschneider1024/api-gateway,叢集裡跑的卻是 wafer-api-gateway:ironman——本機手動 build 的 tag,CI 從來沒產過這個名字
  4. Pod template 的指紋也對不上:Chart 會蓋上 env: production 標籤和 deployed-at 註記(Day 20 §3 提過的那顆雷),這個 Deployment 上一個都沒有,反而有一行 kubectl.kubernetes.io/restartedAt——有人在 7/21 手動 rollout restart。那個人是我
  5. 時間軸對不上:Git 最後一筆 deploy commit 是 8/04,叢集裡的 Deployment 停在 7/20。Git 自己往前走了兩週,叢集完全不知情

結論很簡單:這個環境是我當初手動 kubectl apply 起來的,之後 ArgoCD 的 Application 不知道在哪次重裝/清理時消失了,而沒有任何人、任何工具、任何告警發現這件事。我以為我在跑 GitOps,實際上我在跑一個 17 天前的手工部署。

這跟 Day 19 那個「CI 改的目錄不是 ArgoCD 看的目錄」是同一種病的兩個變體:

Day 19 的版本 這次的版本
斷點 CI 更新 k8s/base/,ArgoCD 讀 helm/wafer-bi/ ArgoCD 連 Application 都不在了
表面症狀 CI 綠燈、ArgoCD 顯示 Synced Pod 全部 Running、健康檢查全過
實際後果 production 版本凍結在某一天 同上
誰會發現 沒有人 沒有人

自動化最危險的失敗模式不是「壞掉」,是「停止運作但看起來像在運作」。 壞掉會有人喊,停止運作只是很安靜。

所以這類故障的排查方向跟前面七節完全相反——不是「從症狀往下挖根因」,而是主動去驗證那些你預設為真的事。給未來的自己一份對帳清單:

# 1. 控制迴路還在嗎?
kubectl get applications -A

# 2. Git 說的 image 和叢集跑的 image 是同一個嗎?
kubectl get deploy -n k8sdemo \
  -o custom-columns=NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image

# 3. 最後一次真的部署,是什麼時候?
git log -1 --format='%h %ad %s' --date=iso
kubectl get deploy -n k8sdemo \
  -o custom-columns=NAME:.metadata.name,CREATED:.metadata.creationTimestamp

# 4. 這個資源身上有沒有「被自動化管過」的指紋?
kubectl get deploy api-gateway -n k8sdemo -o jsonpath='{.spec.template.metadata}'

四個指令,一分鐘不到,但它們能回答一個平常沒人問的問題:「我的自動化,今天還活著嗎?」 這件事最好是排進固定的檢查(Day 19 那支 check-config-sync.py 就是同一個思路的自動化版本),而不是等哪天要緊急回滾的時候,才發現手上這條鏈路早就斷了。

9. 小結

CrashLoopBackOff 的排查沒有捷徑,但有固定的路徑可以走:describe 看現象、logs --previous 找根因、exec 驗證範圍。而更難的那一種故障,是根本不會有人來通報的那種——定期去驗證「自動化還活著」,跟排查一個正在燒的 Pod 一樣重要。明天是這個系列的最後一天,回顧這 30 天實際做了些什麼。


上一篇
[Day 28] 效能調優:JVM 與 Python 容器的資源限制與優化 —— 針對異構語言特性,調整 K8S 資源分配以達到最佳效能。
下一篇
[Day 30] 完賽感言:技術成長、架構反思與未來展望 —— 回顧這 30 天做了什麼、學到什麼。
系列文
從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言