Skip to main content

helm diff?我裝個 plugin 連環踩了 4 個坑

· 5 min read
Charles Wang
DevOps / Backend Engineer

今天的學習任務本來很單純:裝 helm-diff plugin,體驗一次 helm diff upgrade,理解為什麼 production 上要用它。

結果從 helm plugin install 那一刻起,連環踩了 4 個坑——而最後一個讓我真的見識到「diff 顯示無變化,但實際部署會改東西」是怎麼發生的,把過程完整記下來。

Pitfall 1:plugin 裝不起來,要 --verify=false

第一步就卡:

helm plugin install https://github.com/databus23/helm-diff
# Error: plugin source does not support verification. Use --verify=false to skip verification

原因是 Helm v3.18+ 開始預設要求 plugin 有 GPG provenance 簽章驗證,但大多數社群 plugin(包含 helm-diff)的 release 根本沒附簽章檔,驗證就一定失敗。照提示加上 flag 即可:

helm plugin install https://github.com/databus23/helm-diff --verify=false

--verify 在做的是供應鏈防護——確認你下載的 plugin 沒被竄改、確實出自作者。在本機 lab、來源又是知名官方 repo 的情況下,--verify=false 是合理的。但要建立的正確直覺是:production / CI 自動裝 plugin 時,「跳過驗證」不該是隨手帶過的習慣。較穩的做法是先 clone、檢視內容、鎖定特定 tag/commit,再從本機路徑安裝。供應鏈攻擊很多就是從「反正只是裝個工具」開始的。

Pitfall 2:schema 把 image tag 擋下來

裝好後第一次 diff,吐出這個:

Error: at '/image/tag': got number, want string

我前一天的練習幫 chart 加了 values.schema.json,裡面規定 image.tag 必須是 string。問題出在 values.yaml

image:
tag: 1.27

沒加引號的 1.27,YAML 會解析成數字,schema 一比對就擋下來。加上引號就過了:

image:
tag: "1.27"

這其實是 schema validation 替我抓到的隱性 bug:image tag 永遠該加引號。像 1.201.0 這種值若被當數字,1.20 會被截成 1.21.01,部署時就抓錯 image。一個看起來無害的引號,避免的是 production 抓錯版本。

Pitfall 3:helm diff 沒有任何輸出

過了 schema 之後,diff 的輸出讓我愣住——有時印出 -/+,有時完全空白。釐清後記下兩個關鍵:

- 是 cluster 現存 release 的狀態(live),+ 是套用後會變成的狀態。 不是「檔案 vs 檔案」,是「現況 vs 套用後」。

值的優先序(高到低)--set-f/--values 檔案 > chart 的 values.yaml > 子 chart values。我一度在 values.yaml 改半天 diff 沒反應,其實是被命令列的 --set 蓋掉了——這是最常見的「改了沒生效」陷阱。

排查時最有用的手法,是手動把 helm-diff 底層做的事拆開來看:

# live:cluster 上 release 實際存的 manifest
helm get manifest demo -n demo | grep -E "^kind:|replicas:"

# new:用 values.yaml 重新渲染的結果
helm template demo ~/helm-lab/myfirst-chart | grep -E "^kind:|replicas:"

helm get manifest 是「現況」,helm template 是「用檔案重算」,兩邊一比就知道哪個物件不一樣。理解這一層之後,helm-diff 對我就不再是黑盒子。

Pitfall 4:diff 顯示「無變化」,但 helm upgrade 其實會改東西

這是今天最重要、也最該寫進筆記的一個。

當時的狀態是這樣:values.yamlreplicaCount: 1,但 cluster 上的 release 是當初用 replicaCount: 2 部署存進去的。我跑:

helm diff upgrade demo ~/helm-lab/myfirst-chart -n demo
# (沒有任何輸出)

diff 顯示「無變化」。但我手動比對 live 和 template,明明 Deployment 的 replicas 一個是 2、一個是 1,不應該會沒有差別?

加上 --reset-values 再跑一次,真相就出來了:

helm diff upgrade demo ~/helm-lab/myfirst-chart -n demo --reset-values
demo, demo-myfirst-chart, Deployment (apps) has changed:
spec:
- replicas: 2
+ replicas: 1

問題的根源是兩個指令的預設行為不一致

  • helm diff upgrade 預設會沿用 release 裡存的舊值(reuse),所以它拿 replicaCount: 2 去算,跟 live 一比當然「無變化」。
  • 但 Helm 3 的 helm upgrade 預設是 reset——改用 chart 的 values.yaml(也就是 1)加上你給的 override。

換句話說:diff 騙了我。它說「無變化」,但我若真的去跑 helm upgrade demo .,replicas 會從 2 被改成 1。diff 與實際部署不符,這正是真實事故的來源——你看 diff 沒變化就安心 merge,實際 apply 卻動了資源。

由此得到一條鐵則:在 CI 用 helm-diff,它的 value 解析模式必須跟你真正部署的指令一致。 三個相關 flag 要記住:

  • --reuse-values:沿用舊 release 的值,只 merge 這次的 override
  • --reset-values:丟掉舊值,完全用 chart 的 values.yaml
  • --reset-then-reuse-values(v3.14+):先 reset 再把舊 override 疊回來,通常最安全

附帶一個小發現:hook(像我 Day 7 加的 pre-install Job、helm create 預設的 test-connection Pod)預設不會進 diff,因為它們不是常駐的 desired state,拿來比對沒有意義。所以 helm template 看得到、diff 卻不提它們,是正常的。

小結

一個 plugin 的安裝,意外變成今天收穫最多的一課。四個坑串起來其實是同一個主題:工具的預設值,不一定等於你真正想要的行為。

  • helm plugin install 預設要驗章 → 不一定裝得起來
  • YAML 預設把 1.27 當數字 → schema 擋下來
  • --set 預設優先序最高 → 蓋掉你改的檔案
  • helm diff 預設 reuse、helm upgrade 預設 reset → diff 會騙你

DevOps 工具用久了會發現,真正會咬人的往往不是「不會用」,而是「以為它會 A,其實它預設做 B」。下一篇針對 Helm chart 收尾,我會把 Helm 跟 Kustomize 的取捨整理成一篇深度文,順便附上我這兩週寫的 chart。