iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0

昨天處理了 Grafana 裡面的設定,今天處理叢集裡面的東西。

前天盤點出三個洞:Loki 的設定只存在叢集裡、Tempo 全吃預設、五個 chart 的版本號沒有一個地方記著。今天要做的就是把它們補起來,順便把五條散在終端機歷史裡的 helm install 收成一個檔案。

這是最後一天的準備工作。收完之後,砍掉叢集重建才有東西可跑。

補洞:Loki 和 Tempo

Loki 的設定當初是用 --set 打在指令列上的,但它其實一直好好地存在叢集裡——Helm 會記住你給過什麼:

helm get values loki -n monitoring | tail -n +2 > helm/loki-values.yaml

一行就搬完了。tail -n +2 是為了拿掉第一行的 USER-SUPPLIED VALUES:,那是標題不是設定。

Tempo 就沒這麼簡單,因為 helm get values tempo 回的是 null——它從頭到尾吃預設值,沒有東西可以匯出。

這裡有個查法值得記:同一個指令加上 -a,看到的是完整生效的設定,也就是 chart 預設值疊上你給的值之後的結果。Tempo 那份有 199 行。

$ helm get values tempo -n monitoring        # 只有我給過的
null

$ helm get values tempo -n monitoring -a     # 實際生效的全部
...199 行...
persistence:
  enabled: false        ← 在這裡

兩個指令的用途不一樣:匯出成 values 檔要用不加 -a 的版本,因為 -a 會把 chart 當下的預設值全部寫死進你的檔案,等於凍結在這個版本,以後升級反而變成阻礙。但要查「現在到底是什麼設定在生效」,一定要用 -a。

我當初如果用 -a 看過一眼,那行 persistence: enabled: false 就在眼前。我沒看,因為我以為「沒有自訂設定」等於「沒什麼好看的」。

實際去查才發現不是:

kubectl -n monitoring get sts tempo -o jsonpath='{.spec.template.spec.volumes[*].name}'
# tempo-conf

只有一個設定檔的 volume,沒有任何儲存。 Tempo 的設定裡寫著 trace 寫到 /var/tempo/traces,而那個路徑在 Pod 的暫存檔案系統上。Pod 重啟,trace 就沒了——它已經重啟過三次。

這是同一個教訓學第二次。Grafana 被 helm upgrade 洗掉之後,我補了 Grafana 和 Prometheus 的持久化,也回頭查了 Loki,唯獨沒查 Tempo。因為 Tempo 從來沒出過事,所以我從來沒看它一眼。

# helm/tempo-values.yaml
persistence:
  enabled: true
  size: 5Gi

Helmfile:把五個 release 收成一個檔案

到這裡為止,五個元件各自有一份 values 檔了。但還缺三件事:chart 從哪個 repo 來、要裝哪個版本、誰先誰後。這三件事現在只存在我的終端機歷史裡。

一個做法是寫一支 shell script,把五條 helm upgrade --install 排好順序。可以,但那是命令式的——它描述「怎麼做」,不是「要什麼」。跑到一半失敗了,你得自己想清楚重跑會不會出事。

Helmfile 是管這件事的工具。一個 YAML 描述我要裝哪些 chart、什麼版本、用哪份 values、誰先誰後,然後它自己算出該做什麼。

repositories:
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts
  - name: grafana
    url: https://grafana.github.io/helm-charts
  - name: open-telemetry
    url: https://open-telemetry.github.io/opentelemetry-helm-charts

releases:
  - name: kps
    namespace: monitoring
    createNamespace: true
    chart: prometheus-community/kube-prometheus-stack
    version: 90.0.0
    values:
      - helm/kube-prometheus-stack-values.yaml

  - name: tempo
    namespace: monitoring
    chart: grafana/tempo
    version: 1.24.4
    values:
      - helm/tempo-values.yaml

  - name: otel-collector
    namespace: monitoring
    chart: open-telemetry/opentelemetry-collector
    version: 0.173.1
    values:
      - helm/otel-collector-values.yaml
    needs:
      - monitoring/tempo

三個指令,跟昨天的 OpenTofu 是同一個節奏:

helmfile deps     # 拉 chart repo
helmfile diff     # 看「如果執行會改什麼」
helmfile apply    # 執行

先看再做是 IaC 的通用習慣,不是某個工具的特色。昨天是 tofu plan,今天是 helmfile diff,同一件事。

依賴鏈

needs 那幾行宣告誰要先裝好:

kps(Prometheus + Grafana + Alertmanager + Operator 的 CRD)
loki  ────────────►  alloy            Alloy 把 log 送給 Loki
tempo ────────────►  otel-collector   Collector 把 trace 送給 Tempo

寫這幾行的時候我才發現:這條鏈我從來沒完整寫下來過。

前面二十八天是一次裝一個,每次只知道「這個要先裝好」,但整張圖長什麼樣、哪兩個其實可以並行,是被這個檔案逼出來的。IaC 除了可重現之外的另一個價值就是這個——它逼你把架構寫成一句能執行的話。

第一次 diff

版本全部釘死之後,第一次跑 helmfile diff 對著現在的叢集比:

release 差異
kps 一致
loki 一致
tempo 新增 16 行
otel-collector 一致
alloy 一致

https://ithelp.ithome.com.tw/upload/images/20260929/20180570JfmSjMibW9.png

只有 Tempo 有差,而且差的正好是我剛加的持久化。其他四個完全一致,代表這份檔案真的描述了現在跑著的叢集,不是我照記憶重寫的另一份東西。

這個比對本身就是驗收。如果有哪個 release 冒出我沒預期的差異,那就代表叢集裡有東西不在檔案裡。

不可變欄位

helmfile apply,然後爆了:

level=WARN msg="this chart is deprecated"
Error: UPGRADE FAILED: StatefulSet.apps "tempo" is invalid:
spec.volumeClaimTemplates: Invalid value: [...]: field is immutable

https://ithelp.ithome.com.tw/upload/images/20260929/20180570bF28GoIt9v.png

StatefulSet 的儲存宣告建立之後改不了。 不能替一個已經在跑的 StatefulSet 加上持久化,只能把它砍掉重建。

這件事很符合這幾天的主題:有些改動本質上就是砍掉重來,不是修改。 而如果你不知道這件事,看到這個錯誤會以為是自己 YAML 寫錯。

diff 比的不是叢集,是上一次的紀錄

接下來這段是今天最重要的發現,而且我是不小心撞到的。

我照著錯誤訊息的意思,把 StatefulSet 刪掉重跑:

kubectl -n monitoring delete sts tempo
helmfile apply

回傳 exit=0,看起來成功了。結果去看叢集:

$ kubectl -n monitoring get pod -l app.kubernetes.io/name=tempo
No resources found in monitoring namespace.

Tempo 不見了,而 apply 說一切正常。

順著查下去,鏈條是這樣的:

發生什麼
1 升級因為不可變欄位失敗
2 但 Helm 還是把新的 manifest 記成 revision 2,狀態 failed
3 我手動刪掉 StatefulSet
4 helmfile diff 說沒有差異
5 helmfile apply 什麼都沒做

https://ithelp.ithome.com.tw/upload/images/20260929/20180570c4erwGBsS2.png

確認第 2 步:

helm get manifest tempo -n monitoring | grep -c volumeClaimTemplates
# 1

Helm 記的那份 manifest 已經有持久化了,而我的檔案也有持久化,兩邊一樣,所以 diff 是空的。

helm diff 比對的是「你的檔案」跟「Helm 資料庫裡記的上次部署內容」,不是叢集的實際狀態。 我在叢集上刪掉的東西,它完全看不見。

這跟昨天那個工具剛好相反。tofu plan 的第一行是:

grafana_folder.observability: Refreshing state... [id=1:observability]

它會真的打一次 API,去確認那個資料夾還在不在。所以昨天我把資料夾從 state 移掉之後,plan 馬上就發現「線上已經有一個了」。

兩個工具的 plan 和 diff 長得很像,可信度不一樣。 用 helmfile 的時候,「沒有差異」的意思是「跟上次部署的內容一樣」,不是「叢集就是這樣」。

修法是把那個失敗的 release 整個移掉重裝:

helm uninstall tempo -n monitoring
helmfile apply

這次 Tempo 帶著 5Gi 的 PVC 回來了。五個 release 全部收斂:

https://ithelp.ithome.com.tw/upload/images/20260929/20180570AcGcuA0dLP.png

順帶一提,helm uninstall 不會刪掉 StatefulSet 建立的 PVC。我重裝之前那個 storage-tempo-0 還好好地留在叢集裡,重裝之後新的 Pod 直接接手用它。這是刻意的設計——資料比工作負載值錢,不該被一個 uninstall 帶走。但它也意味著「移除 release」離「清乾淨」還有距離。

已棄用的 chart

那次失敗的 apply 還順手告訴我另一件事,夾在一堆錯誤訊息中間:

level=WARN msg="this chart is deprecated"

查一下:

helm show chart grafana/tempo --version 1.24.4 | grep deprecated
# deprecated: true

我釘的是一個已經棄用的 chart 的版本。

這件事把「版本鎖定」的意義切得更清楚了:

  • 鎖版本解決的是「同一份檔案,今天和三個月後會不會裝出不同的東西」
  • 它不解決「這個東西三個月後還在不在」

而且這個警告只在 upgrade 的時候跳出來。helm search repo tempo 的輸出裡完全看不出來,helm install 當初也沒攔我。要查得自己去讀 Chart.yaml。

我這次不換,因為換成 tempo-distributed 是把單機模式改成微服務模式,對一個筆電上的 kind 叢集是反方向。但我把這件事寫進 values 檔的註解裡——已知的技術債要寫下來,不然它就只是忘記。

工具鏈比檔案難搞

今天真正花時間的不是寫 YAML,那個檔案四十行、半小時就好了。花掉一整個晚上的是把工具裝起來:

卡在哪 怎麼過
brew install 失敗 連不到 ghcr.io,改抓官方二進位檔
二進位檔下載中斷兩次 curl -C - 續傳
helm plugin install 被拒 新版 Helm 預設要驗證外掛簽章,加 --verify=false
外掛裝好了卻不能執行 helm plugin list 有它,但 bin/diff 沒下載到
helmfile diff 說 repo 連不上 不是連不上,是索引檔太大逾時,先單獨 helm repo update

第四個最值得講。helm plugin list 明明列出 diff 3.15.13,執行卻是 no such file or directory——登記了,但檔案不在。安裝腳本要去抓一個 34 MB 的執行檔,抓不完就結束了,而且沒有回報失敗。

這個系列一路在收集的那種錯誤,又一個:它告訴你成功了,實際上沒有。

一個不做的決定

Helmfile 只管 Helm release,管不到 k8s/ 底下那些用 kubectl apply 部署的東西——三個示範服務、ServiceMonitor、告警規則。

要收進來的話,正規做法是把它們包成一個自己的 chart,變成 Helmfile 的第六個 release。這樣做有一個好處:故障開關可以變成 values,改檔案就能切換,而且 git 上看得到誰在什麼時候開了哪個。

我不做,兩個理由。

那些檔案已經在版控裡了。 包成 chart 不會讓它們「更在版控裡」,只是多一層樣板語法。這跟昨天決定不把儀表板搬到 Terraform 是同一個判斷。

它們沒有需要參數化的地方。 Helm 的價值在於「同一份樣板,不同環境給不同的值」。我只有一個環境,套上樣板之後每個值都只會有一種可能,那層抽象就是純成本。

代價是最後一天的重建會有兩個步驟:helmfile apply 之後還要 kubectl apply -f k8s/。兩行指令換掉一層抽象,我覺得划算。

現在版控裡有什麼

清點一下:

路徑 管什麼
kind-config.yaml 叢集本身
helmfile.yaml + helm/*.yaml 叢集裡的五個元件,含版本
k8s/*.yaml 三個示範服務、ServiceMonitor、告警規則
grafana/*.yaml 兩張儀表板
tofu/main.tf Grafana 的資料夾
agent/ 那隻 agent、它的埋點與評測

這六個就是前面二十九天的全部。 明天要驗證的就是這句話對不對。

小結

  • helm get values 可以把只存在叢集裡的設定倒出來變檔案
  • 鎖版本保證「同一份檔案裝出同樣的東西」,不保證「那個東西還在」
  • helmfile diff 比的是上次部署的紀錄,不是叢集實際狀態——這點跟 tofu plan 不一樣

明天砍掉整個叢集,用這六個目錄重建一次。


上一篇
Day 28:用 Terraform 管叢集外資源,並把 Grafana 設定納入版控
系列文
從看得到到看得懂:30 天在自架 K8s 上實踐可觀測性與告警 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言