iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Kubernetes

從看得到到看得懂:30 天在自架 K8s 上實踐可觀測性與告警系列 第 30 篇

Day 30:一鍵重建驗收:砍掉叢集重跑一次,跟 Day 1 的自己比

  • 分享至 

  • xImage
  •  

最後一天。今天只做兩件事:砍掉重建,然後回答第一天答不出來的那個問題。

驗收規則

先把標準訂死,免得事後幫自己找藉口:

  1. 砍掉整個 kind 叢集,不是移除 Helm release——昨天那個 helm uninstall 沒刪掉 PVC 已經說明了移除 release 有多不乾淨
  2. 只能用版控裡的檔案,不能翻指令歷史、不能憑記憶補指令
  3. 中途卡住就算失敗,然後照實寫出卡在哪

砍之前才發現的洞

動手前我先確認一次「要用到的東西都在 repo 裡嗎」,結果第一項就掛了:

kind-config.yaml   ✗ 沒進版控

它一直放在 repo 外面的上層目錄。我自己跑沒問題,但 clone 這個 repo 的人拿不到它,第一步就做不了。

前天盤點的時候我把它列成「叢集本身 ✓ 已經是檔案」。那句話只對了一半——是檔案,不代表在別人拿得到的地方。

這件事有點諷刺:整個 IaC 區塊在講「把東西寫成檔案」,而最底層那個檔案從頭到尾不在 repo 裡。我把它搬進去才開始。

九秒,然後一個指令

kind delete cluster --name obs
Deleting cluster "obs" ...
Deleted nodes: ["obs-worker" "obs-control-plane" "obs-worker2"]

九秒。 二十九天的東西,三行輸出。

打這行的時候比我想像中緊張——不是怕重建不出來,是因為那裡面有九點八天的指標、十二天的 log,而那些東西確實不會回來。

回來的部分靠一個 Makefile,六個步驟串起來:

up: cluster images load charts services grafana

cluster:
	kind create cluster --config kind-config.yaml

images:
	docker build --build-arg SERVICE=pricing -t pricing:$(TAG) .
	# catalog / gateway / alert-sink / loadgen 同樣四行

load:
	kind load docker-image pricing:$(TAG) catalog:$(TAG) ... --name $(CLUSTER)

charts:
	helmfile apply --skip-diff-on-install

services:
	kubectl apply -f k8s/
	kubectl apply -f grafana/

順序有兩個地方不能換:

load 一定要在 images 之後。 kind 的節點是獨立的容器,看不到你本機的 Docker 映像檔。少這步就是 ErrImagePull。

services 一定要在 charts 之後。 k8s/servicemonitors.yaml 裡的 ServiceMonitor 是 Prometheus Operator 帶進來的 CRD,Operator 還沒裝就 apply,會噴 no matches for kind。

make down && time make up

https://ithelp.ithome.com.tw/upload/images/20260930/20180570en2xWC1GtU.png

坑一:先有雞還是先有蛋

第一次跑,helmfile 那步直接掛掉:

no matches for kind "Alertmanager" in version "monitoring.coreos.com/v1"
ensure CRDs are installed first
Error: plugin "diff" exited with error

helmfile apply 的預設流程是先 diff 再 apply。而 diff 要把 chart 渲染成 YAML、再拿去跟叢集的 API 對照有沒有這些型別——但 Alertmanager、ServiceMonitor 正是這個 chart 要裝的 CRD,全新叢集上還不存在。

要裝 CRD 才能 diff,要 diff 完才會裝 CRD。

解法是 --skip-diff-on-install:第一次安裝的 release 跳過 diff 直接裝,已經存在的照樣 diff。

這個坑昨天完全遇不到,因為那時叢集已經在,五個 release 都是「已存在」。它只在從零開始時出現——這就是為什麼「應該可以重建」跟「真的重建過一次」是兩件事。

坑二和坑三:都跟「等」有關

修好之後再跑,這次走到最後一步才掛:

kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=grafana --timeout=600s
error: timed out waiting for the condition
make: *** [grafana] Error 1

https://ithelp.ithome.com.tw/upload/images/20260930/20180570iNV3G0Pkzf.png

我在 Makefile 裡寫 600 秒,自認很寬鬆了。實際上不夠——Grafana 有三個容器、Prometheus 和 Alertmanager 各有 init 容器,全部要從公開 registry 拉映像檔。

這不是設定寫錯,是我對環境做了一個錯的假設。 腳本裡每一個逾時值都是一個假設,而它只在你自己的網路上被驗證過。

另一個坑在後面等著。那天的網路很差,四個映像檔一直 ImagePullBackOff。換到另一個地方(實測 12.5 Mbps)之後,我以為等一下就好,結果二十五分鐘沒有任何進展。

兩個原因:

主機端那個 docker pull 還抓著換網路前的 TCP 連線,它不會知道底下的網路已經換了,只會一直等一個永遠不會回來的回應。

Kubernetes 的退避機制在拖時間。 ImagePullBackOff 每失敗一次就把下次重試的間隔拉長,最長到五分鐘。所以網路好了之後,它可能還要等好幾分鐘才會再試一次。

解法很粗暴但有效:

kubectl delete pod <那幾個> --force --grace-period=0

新的 Pod 沒有退避歷史,立刻重拉。 兩分鐘之內全部起來。

二十八分鐘

make up  13.64s user  7.74s system  1% cpu  28:03.01 total

這個數字裡最值得看的是 1% cpu。

二十八分鐘裡,我的電腦幾乎沒在工作。五個自己的映像檔全部 CACHED、五個 chart 裝起來最久的 kube-prometheus-stack 也只要 1 分 41 秒。剩下的全部是等——等從公開 registry 把別人的映像檔拉下來。

所以「一鍵重建要多久」這個問題,答案跟你的 IaC 寫得好不好幾乎無關,取決於你跟 registry 之間那條線。正式環境會用內部 registry 鏡像解決,那是我這個系列沒做、但真的要重建時第一個會遇到的問題。

驗收清單

make verify

https://ithelp.ithome.com.tw/upload/images/20260930/20180570TBCaDAoHFO.png

檢查 結果
三節點 Ready 3
監控元件 Running 16
示範服務 Running 5
自訂指標 checkout_total 有資料
我寫的告警規則 5 條
exemplar 56 個

最後兩項是我在驗收過程中改出來的。

告警規則那項原本只印「規則數:222」,看起來像我寫了 222 條。其實 217 條是 kube-prometheus-stack 自己帶的,我的只有 5 條。一個寫來確認系統沒問題的腳本,自己在誤導——跟昨天那個「diff 說沒差異但東西不在」是同一種毛病。

exemplar 那項本來不在清單裡,是看到儀表板上有綠色的點才想到要加。它其實是最該檢查的一項:exemplar 沒接起來的時候不會報錯,圖照樣畫得出來,只是少了那些點。 Day 20 就是在這裡卡了很久。

https://ithelp.ithome.com.tw/upload/images/20260930/20180570NLU48xSPmC.png

儀表板回來了,在「可觀測性」資料夾裡——那個資料夾是 OpenTofu 建的,圖是 ConfigMap 送的,兩邊靠一個名字接上。

但重點不是它出現了,是它上面的線在動。成功率 100%、P50 / P95 / P99 三條線分開、右邊那些綠色菱形是 exemplar。從指標點進 trace 那條路,重建之後也是通的。

Pod 是綠的只代表東西起來了。線在動才代表資料真的在流。

故障重現

最後一項:把故障一打開,看系統抓不抓得到。

kubectl set env deploy/pricing BUG_SILENT_DISCOUNT=true

等了二十五分鐘,Alertmanager 還是只有 Watchdog。去查規則狀態,它是 pending,但 activeAt 是 18:09:44——比我開故障的 17:46 晚了二十三分鐘。

原因在資料裡:

18:09:31  0.078  ← 資料中斷

22 個取樣點沒有任何一點是 0,pricing 也沒重啟。是 Prometheus 有一段沒抓到,時間點剛好是我換網路那段。

for: 15m 的意思不是「故障開始後 15 分鐘」,是「條件連續成立 15 分鐘」。 中間只要斷一次,計時就歸零重來。

我是去查了 activeAt 才知道它重新算過。只盯著 Alertmanager 等的話,會以為告警壞了。

18:25:18,它燒起來了:

https://ithelp.ithome.com.tw/upload/images/20260930/20180570DtHc5tCORs.png

上面那行證明故障真的開著,下面那行證明系統真的抓到了。從零重建的環境、埋回同一個故障、同一條規則同樣攔下來。

不會回來的東西

驗收通過不代表一切如初。誠實列出來:

回來了嗎
所有設定、儀表板、告警規則 ✅
九點八天的指標歷史 ❌
十二天的 log ❌
Grafana 管理員密碼 ❌ 換了一組新的
三個故障的開關狀態 ❌ 回到全部關閉

IaC 重建的是設定,不是資料。 這不是它的缺陷,是它本來就不管這件事。

密碼換掉其實是好事——它代表密碼沒有寫進 git。但它也意味著「重建完就能照舊登入」這個期待是錯的。

四句天花板

Day 7 我只用 kubectl 查那三個故障,每次都收在一句「查不到」:

天花板 被誰解決 重建後還在嗎
只有現在,沒有歷史 Prometheus ✅
只有文字,不能聚合 Loki + LogQL ✅
只有單點,無法跨服務關聯 OpenTelemetry + exemplar ✅
只能被動查,不會主動通知 Alertmanager + burn rate ✅

四句話對應四個工具,重建之後四個都還在。

這張表是這個系列真正的骨架。不是「我裝了幾個工具」,是「我原本答不出什麼,現在答得出來」。

Day 4 那五件不做的事

第四天我列了五件刻意不做的事。回頭看,其中兩件真的有後果:

不做 ingress、一律用 port-forward。 這個決定後來咬了我兩次:Collector 的接收埠預設綁在 Pod IP 上,port-forward 打不進去,第一次遇到我繞過去了,等到 agent 要從叢集外送資料才不得不改成 0.0.0.0。

不上雲。 這個影響更深——它讓「用 Terraform 管叢集外資源」那天幾乎沒有對象可管。我沒有雲,所謂的「叢集外」只剩 Grafana 裡面的東西。那天的產出因此變成一個判斷(誰該管什麼),而不是一套設定。

另外三件(不做 service mesh、不做 profiling、示範服務不寫漂亮)到最後都沒有讓我後悔。

回到那場面試

第一天的開頭是這樣寫的:

前陣子在求職的時候常常被問到 k8s 相關的問題,觀測的指標有哪些,但我總是一問三不知,只能說出查看 log 有沒有 error。

現在的答案:

先看 SLI。 成功率和延遲有沒有違反承諾。這一步告訴我有沒有事、多嚴重、從什麼時候開始。

如果是慢,看 P50、P95、P99 的形狀。 三條一起漲是全面性問題;只有 P99 漲是長尾,代表只有少數請求受影響——而那個形狀本身就是線索。

從圖上的異常點直接跳進 trace,看那棵 span 樹哪一段最耗時,再從那個 span 跳到對應的 log。三次點擊。

如果指標全部正常但使用者說有問題,那是最難的一種:系統在成功地做錯事。這時候只能翻 log,用 count_over_time 算出它多常發生、影響多少比例。

而當初那句「只能說出查看 log 有沒有 error」,現在我知道它其實不算全錯——錯的是把 log 當成唯一的工具。log 回答「發生了什麼」,指標回答「多嚴重」,trace 回答「在哪一段」。三個問題本來就需要三種東西。

第一天我還寫下一句沒把握的話,說要搞清楚監控和可觀測性的差別。三十天後我的答案是:監控是你事先決定要看什麼,可觀測性是你事後還能問出沒想過的問題。 而這兩者之間有一條路——用 log 發現未知、確認它重要、埋成指標、設成告警,它就從此變成已知。這條路不會走完,因為系統一直在變。

這三十天沒做到的

  • 沒有真實流量。 loadgen 的行為是我編的,跟真實使用者差很遠
  • 沒有真實的網路問題。 kind 的三個節點都在同一台筆電上
  • cardinality 從來沒真的爆過,除了 Day 13 故意弄的那次
  • 沒有團隊。 「告警要有主人」在一人專案裡是紙上談兵
  • 沒有半夜被叫醒過。 所以我對告警疲勞的理解仍然是二手的

這些不是謙虛,是這個系列的邊界。哪些部分我真的驗證過、哪些只是照著文件做,讀者應該分得出來。

小結

  • 二十九天的環境用一個指令重建得回來,二十八分鐘,其中 99% 的時間在等網路
  • 設定全部回來了,資料沒有——IaC 管的是前者
  • 四句天花板全部劃掉,而且重建之後還在

謝謝看到這裡的人。如果你要自己跑一遍,最有價值的部分不是那些成功的指令,是我卡住的那幾段。


上一篇
Day 29:用 Helm / Helmfile 把整套 stack 版本化
系列文
從看得到到看得懂:30 天在自架 K8s 上實踐可觀測性與告警 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言