一步一步走過真實的除錯流程,而不是背口訣。
容器啟動後立刻結束(可能是應用程式拋出例外、或直接 crash),K8S 依照重啟策略嘗試重啟,又失敗,隨重試次數增加退避間隔(backoff)——這個狀態就叫 CrashLoopBackOff。它本身不是原因,是結果;真正的原因永遠要往下挖。
與其空講排查步驟,直接製造一次真實的故障。把 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。接下來照以下三個步驟排查:

▲ CrashLoopBackOff 完整排查流程:describe 看事件、logs --previous 看死前的錯誤堆疊、exec 驗證問題範圍,最後定位到密碼設定並修復
kubectl describe podkubectl describe pod user-service-7bccf44bfb-fcx2k
看最下方的 Events:Readiness probe failed、Back-off restarting failed container。這一步能確認的是「容器起不來」這件事本身,但看不出為什麼——Events 只記錄 K8S 觀察到的現象(探針失敗、重啟次數),不包含應用程式內部的錯誤訊息。
kubectl logs --previouskubectl 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 開始看,不要被最上面那層唬住。
拿到「密碼不對」這條線索後,下一個問題是:是資料庫那端出了問題,還是這次部署帶的密碼設定錯了?直接連進資料庫本身驗證:
kubectl exec deploy/postgres -- psql -U waferbi -d waferbi_db -c "SELECT 1;"
用的是資料庫端已知正確的密碼,如果這樣都連不上,問題在資料庫本身(例如 pg_hba.conf 設定、或資料庫真的换了密碼);如果連得上,就能確定問題出在 user-service 這邊——這次剛好是我自己手動改錯的環境變數。
kubectl patch deployment user-service --type json -p '[...valueFrom.secretKeyRef...]'
改回從 Secret 讀取密碼,新 Pod 一次啟動成功,CrashLoopBackOff 解除。
這三步的順序其實對應一個邏輯:先確認現象(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 |
密碼/憑證設定錯誤(這次的案例) |
上面那套排查法有個前提:有東西在哭。Pod 重啟、探針失敗、Events 一堆紅字,你至少知道要從哪裡開始挖。
但這系列寫到最後,我在自己的叢集上撞到一個更陰險的類型——所有指標都是綠的,系統卻早就不是你以為的那個系統了。
起因很無聊:我想驗證 Day 20 的回滾行為,習慣性先看一眼環境,結果越看越不對勁。

▲ 沒有任何 Pod 在哭的故障:4 個服務全部 Running 17 天,但 ArgoCD 一個 Application 都沒有,跑的 image 也不是 Git 裡那個
一條一條看下去,證據其實很清楚:
kubectl get deploy 全部 1/1、AGE 17 天——這就是它騙人的地方,任何監控儀表板看這個環境都會給你一片綠kubectl get applications -A 回 No resources found——Day 18 裝好的 ArgoCD 那幾個 Pod 都還健康地跑著,但它手上一個 Application 都沒有。GitOps 的控制迴路根本沒有在轉,這才是根因values.yaml 指向 ghcr.io/darkschneider1024/api-gateway,叢集裡跑的卻是 wafer-api-gateway:ironman——本機手動 build 的 tag,CI 從來沒產過這個名字env: production 標籤和 deployed-at 註記(Day 20 §3 提過的那顆雷),這個 Deployment 上一個都沒有,反而有一行 kubectl.kubernetes.io/restartedAt——有人在 7/21 手動 rollout restart 過。那個人是我結論很簡單:這個環境是我當初手動 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 就是同一個思路的自動化版本),而不是等哪天要緊急回滾的時候,才發現手上這條鏈路早就斷了。
CrashLoopBackOff 的排查沒有捷徑,但有固定的路徑可以走:describe 看現象、logs --previous 找根因、exec 驗證範圍。而更難的那一種故障,是根本不會有人來通報的那種——定期去驗證「自動化還活著」,跟排查一個正在燒的 Pod 一樣重要。明天是這個系列的最後一天,回顧這 30 天實際做了些什麼。