Loki 持續印出 negative structured metadata bytes received。看到這種訊息,直覺會懷疑送進去的日誌壞了;當時查到的一個原因,卻是上游用來提前檢查輸入的判斷(guard clause)有問題:某些沒有附 structured metadata 的請求,也會被記成錯誤。
升級可能修正這條噪音,但另一個問題更重要:更新後的 Pod 是否仍接到原資料,其他程式是否仍能用原本的方式連進來?因此先把新舊 chart 轉成 YAML,比較名稱、儲存設定與連線方式,再決定怎麼部署。
先看三個對象:程式、儲存、連線
| 對象 | 白話意思 | 升級時想確認的事 |
|---|---|---|
| StatefulSet | 管理這組需要穩定身分與儲存的 Pod | 更新後仍是原本那組工作負載嗎? |
| PVC | Pod 提出的儲存需求 | 還能接到預期的資料嗎? |
| Service | 其他程式連進來使用的入口 | 原本的名稱、埠與選擇目標還對嗎? |
例如 Service 的名稱沒變,但 selector 改成選另一種標籤,而 Pod 沒有那個標籤,入口就可能找不到後端。反過來,PVC 範本相同也只是一個線索,還要看現場真正綁到哪個儲存空間。
遇到的問題:應用程式版本與 chart 版本是兩回事
上游修正 PR 說明了沒有 structured metadata 仍產生錯誤日誌的情況。這可以解釋一種噪音來源,不能據此把同名訊息全部判成無害;仍要核對實際版本、寫入回應與查詢結果。
當時採用的升級組合如下。這是固定歷史版本的重現範例,不是目前新安裝的版本建議。
| 升級前 | 升級後 | |
|---|---|---|
| chart version | 6.30.1 | 7.3.0 |
| chart 的 appVersion | 3.5.0 | 3.6.12 |
可以把 chart 想成安裝說明與設定模板,映像則是實際執行的 Loki 程式。例如只想修 Loki 的日誌問題,卻升了整份 chart,就可能連資源名稱與啟動參數一起改變。appVersion 是 chart 標示的程式版本;若另行覆寫 image tag,現場執行的版本還要另外看。
寫作時再次查證,官方 Helm 安裝文件 已區分社群維護的 Loki chart 與 Grafana Labs 維護的 GEL chart。下面保留原先 chart 來源重現這次比較;新部署請先確認目前選用的 chart 維護來源與升級說明,不能只看名稱都是 loki。
手把手:先做不連叢集的比較
以下只下載並渲染 YAML,不會安裝、刪除 PVC 或重啟服務。需要 Helm 3、Python 3 與 uv;Python 比較程式使用 PyYAML。uv 的安裝方式見官方文件。
1. 固定兩份 chart
在獨立工作目錄執行:
mkdir loki-upgrade-check cd loki-upgrade-check helm pull loki --repo https://grafana.github.io/helm-charts --version 6.30.1 helm pull loki --repo https://grafana.github.io/helm-charts --version 7.3.0 helm show chart ./loki-6.30.1.tgz helm show chart ./loki-7.3.0.tgz 確認輸出中的 version 與 appVersion 對上上表。固定版本並保存取得的套件,可避免兩次比較使用不同內容;若來源已不提供舊版,應從先前保存的套件重現,不要默默替換成最新版。
2. 準備兩邊共用的 values
為了單獨觀察 chart 差異,兩邊必須使用相同 release 名稱、namespace 與 values。下面的 loki-values.yaml 是空白測試環境的單副本範例:
deploymentMode: SingleBinary gateway: enabled: false chunksCache: enabled: false resultsCache: enabled: false lokiCanary: enabled: false test: enabled: false sidecar: rules: enabled: false loki: auth_enabled: false commonConfig: replication_factor: 1 schemaConfig: configs: - from: "2024-01-01" store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h storage: type: filesystem singleBinary: replicas: 1 persistence: enabled: true size: 10Gi backend: replicas: 0 read: replicas: 0 write: replicas: 0 這裡關閉認證只為本機渲染示範,沒有公開入口。套用到實際環境前,需要另外決定認證、存取限制與儲存方式。既有 Loki 的 schema 歷史也不能直接換成這份測試設定。
正式比對應換成既有部署的 values,連同實際使用的覆寫檔一起處理。如果同時改 values 與 chart,看到差異時就很難判斷是哪一邊造成的。
3. 渲染新舊資源
helm template logs ./loki-6.30.1.tgz \ --namespace observability --is-upgrade \ -f loki-values.yaml > old.yaml helm template logs ./loki-7.3.0.tgz \ --namespace observability --is-upgrade \ -f loki-values.yaml > new.yaml --is-upgrade 讓模板使用升級情境;它不會讀取現場物件,也不會讓 API server 驗證欄位。兩份檔案是同一組輸入下的靜態比較,不等於正式環境一定能升級成功。
4. 比對幾個不能忽略的欄位
先知道預期會看到兩種輸出:SAME: Service.ports 表示連線埠設定相同;CHANGED: StatefulSet.volumeClaimTemplates 表示儲存範本有差異,需要查原因。這兩行是格式示意,不是本次觀察到的新結果。
| 欄位 | 閱讀時可以問的問題 |
|---|---|
serviceName | StatefulSet 用哪個 Service 建立網路身分? |
selector | 靠哪些標籤找到要管理或轉送的 Pod? |
volumeClaimTemplates | 要建立什麼儲存需求,名稱與規格是否變了? |
podManagementPolicy | 這組 Pod 的建立、刪除順序規則是否變了? |
Service ports、type | 從哪個埠、用什麼方式連到服務? |
單純 diff 會混進映像、標籤和 ConfigMap 內容,關鍵欄位容易被淹沒。下面另存成 compare.py,先針對已知風險做一輪檢查:
import sys import yaml def read(path): with open(path, encoding="utf-8") as source: return { (item["kind"], item["metadata"]["name"]): item for item in yaml.safe_load_all(source) if item } old, new = read("old.yaml"), read("new.yaml") checks = { "StatefulSet": ( "serviceName", "selector", "volumeClaimTemplates", "podManagementPolicy", ), "Service": ("selector", "ports", "type"), } failed = False for kind, fields in checks.items(): key = (kind, "logs-loki") if key not in old or key not in new: print(f"CHANGED: {kind}/logs-loki 缺少或改名") failed = True continue for field in fields: same = old[key]["spec"].get(field) == new[key]["spec"].get(field) print(f"{'SAME' if same else 'CHANGED'}: {kind}.{field}") failed |= not same sys.exit(1 if failed else 0) 在隔離的 Python 環境安裝解析套件,再執行:
uv venv .venv uv pip install --python .venv/bin/python PyYAML==6.0.3 .venv/bin/python compare.py 本次範例兩邊各渲染 9 個物件,上述 7 個欄位都顯示 SAME。重點是資源名稱、StatefulSet selector 和 PVC 範本沒有改變;不是看到 StatefulSet 還在,就假定一定會沿用原本資料。
SAME 只代表列出的這個欄位沒變,CHANGED 則提醒先停下來閱讀差異;它不是升級成功與否的判決。程式只找範例裡指定的 logs-loki,若你的 release 名稱不同,要先調整名稱,否則「找不到」不一定真的代表資源被刪了。
踩到的坑:關鍵欄位相同,也還是要看完整差異
這次比較仍看到 memberlist Service 與 ServiceAccount 改名,以及產生出的設定有變化。它們沒有出現在前面的 7 個檢查欄位裡,所以還需要看完整 diff:
diff -u old.yaml new.yaml diff 發現差異時會回傳 1,這裡是預期行為;要閱讀內容,不能把每個差異都當成失敗,也不能因前一支程式通過就略過它。
Kubernetes StatefulSet 文件 說明了穩定儲存與身分的關係。不過,模板比較看不到現場 PVC 的綁定、既有資源預設值、Admission webhook 或儲存權限,也沒有證明舊資料仍可讀。
因此真正部署前,還要對照現場物件、可回復的資料副本,以及目標版本的升級說明;部署後檢查實際映像、Pod Ready、新日誌寫入和舊日誌查詢。若要跨版本,Loki 升級文件 也建議先在開發環境驗證。本文重跑的是模板比較,沒有執行真實叢集升級,也沒有重現這條錯誤日誌的消失率。
依序做完,再判斷能不能升級
- 固定輸入:相同 release、namespace、values,只換 chart。
- 比較重要欄位:先找工作負載名稱、儲存範本與連線方式的變動。
- 看完整差異:別漏掉程式未列入的物件。
- 到測試環境驗證:確認更新能套用、舊資料能讀。
- 部署後再確認:新日誌能寫入,舊日誌仍查得到。
本文重新實測的是前三步。比如 memberlist Service 是 Loki 成員互相聯絡的服務,ServiceAccount 則是 Pod 使用的身分;這次兩者有改名,所以即使七個欄位都相同,也還不能略過後續核對。
為什麼先做這一輪
直接換 image tag 看似影響較小,卻可能讓舊 chart 的設定與新程式不再匹配;直接升 chart 則會一起接受模板變化。兩種做法都需要驗證。
這次選擇把 chart 與應用程式版本當成一組固定輸入,先排除會改變工作負載身分與儲存範本的差異,再逐項檢查其他變更。它不能取代升級演練,卻能讓「為了消掉一條噪音而升級」多一個有根據的停損點:一旦資源身分或 PVC 範本改了,就先處理遷移方案。