一句話結論:umbrella chart 要多裝一個新上游 chart 時,別把新的 repo 網址直接加進 umbrella 的 dependencies。把 tgz 放進一個本地 wrapper chart 的
charts/,umbrella 只依賴file://的 wrapper,渲染環境就不需要認識那個新 repo。
遇到的問題
這個做法是用一次翻車學來的。
前提是這樣的:一個 umbrella chart 把所有元件包在一起,上游的 chart 一律「vendor」進 repo,也就是 helm dependency update 之後,把 charts/*.tgz 和 Chart.lock 一起 commit。這樣 GitOps 的渲染環境不用每次都上網抓東西。
接著有人要加一個新的上游 chart。照習慣做:在 umbrella 的 Chart.yaml 加一筆 dependency,repo 填新的網址,本機 helm dependency update,tgz 和 lock 一起 commit,推上去。
結果是所有用這個 umbrella 渲染的 Application 同時壞掉,不只新加的那個元件。
原因是渲染環境的設定。這類環境(例如 ArgoCD 的 config management plugin)通常會在 helm dependency build 之前,先替「已知的幾個 repo」跑 helm repo add。新的 repo 不在清單裡,dependency build 就停在這一步,連已經 vendor 好的 tgz 都沒機會用上。
最後只好 revert,再換一個做法重來。
先重現一次錯誤
不用整套叢集,一個本機的 chart repo 就能重現。
先做一個很小的上游 chart operator,打包後用 python3 -m http.server 當成 repo 來源,再建一個 umbrella 去依賴它:
# umbrella/Chart.yaml apiVersion: v2 name: umb version: 0.1.0 dependencies: - name: operator version: 0.1.0 repository: http://127.0.0.1:18765 在有做過 helm repo add 的環境跑 helm dependency update,會產生 Chart.lock 和 charts/operator-0.1.0.tgz。
接著把環境換成全新的(獨立的 HELM_CACHE_HOME、HELM_CONFIG_HOME、HELM_DATA_HOME,等於沒有加過任何 repo),charts/ 裡的 tgz 留著不動,直接 build:
helm dependency build umbrella Error: no repository definition for http://127.0.0.1:18765. Please add the missing repos via 'helm repo add' 就是這個錯誤。Chart.lock 在、tgz 也在,但 helm dependency build 還是要求 repo 必須先被加進本機設定。
charts/ 裡有沒有 tgz,結果都一樣(兩種情況都實測過)。build 會先照 lock 檢查 repo,不會直接拿現成的 tgz 來用。
手把手步驟
解法是多包一層。
步驟一:建一個 wrapper chart
wrapper 是一個很薄的 chart,只負責把上游 chart 的 tgz 帶進來。
components/operator-wrapper/ ├── Chart.yaml ├── values.yaml ├── charts/ │ └── operator-0.1.0.tgz # helm pull 下來的上游 tgz └── templates/ # 需要的話放自己的資源 tgz 用 helm pull 取得,直接放進 wrapper 的 charts/,這個目錄要 commit:
helm pull operator --repo https://charts.example.org/ --version 0.1.0 \ --destination components/operator-wrapper/charts 步驟二:wrapper 的 Chart.yaml 要宣告 dependencies
這一步最容易漏,下一節會用實測說明為什麼不能省。
apiVersion: v2 name: operator-wrapper version: 0.1.0 dependencies: - name: operator version: 0.1.0 repository: https://charts.example.org/ 這裡的 repo 只是記錄用途,wrapper 自己不會被 dependency build。Helm 展開 wrapper 時,直接讀 charts/ 裡的 tgz。
步驟三:umbrella 只依賴 wrapper
# umbrella/Chart.yaml dependencies: - name: operator-wrapper version: 0.1.0 repository: file://../components/operator-wrapper condition: operator-wrapper.enabled file:// 的 repo 不需要 helm repo add。
在乾淨環境跑 helm dependency build umbrella,會直接把 wrapper 打包進 umbrella,沒有任何 repo 檢查。umbrella 的依賴清單裡完全沒有新的上游 repo,渲染環境自然不用認識它。
升級上游版本時,重新 helm pull 蓋掉 wrapper 的 tgz,同步改 wrapper 的 Chart.yaml 版本即可。
踩到的坑:巢狀子 chart 的 condition 沒生效
很多上游 chart 自己還帶子 chart,並用 condition 開關,例如:
# operator 自己的 Chart.yaml dependencies: - name: sidecar version: 0.1.0 repository: file://../sidecar condition: sidecar.enabled 預設 sidecar.enabled: true。我們想關掉它,就在 wrapper 的 values 寫:
operator: sidecar: enabled: false 實測發現一個差異。同一個 tgz、同一份 values,只差 wrapper 的 Chart.yaml 有沒有宣告 operator 這個 dependency:
| wrapper 的 Chart.yaml | helm template 渲染出的資源 |
|---|---|
沒有宣告 dependencies,只是把 tgz 丟進 charts/ | operator-main、sidecar-extra(sidecar 還是被裝了) |
有宣告 operator 這個 dependency | 只有 operator-main |
沒宣告時,Helm 把 charts/ 裡的 tgz 當成「手動放進來的子 chart」,不會處理它內部的 condition,sidecar.enabled: false 被默默忽略。
這個差異很難察覺,因為不會報錯,只是多了一個不想要的資源。最初就是這樣多裝了一個不需要的元件,才發現要把 tgz 同時列在 dependencies 裡。
上面的結果是用 Helm v3.21.4 實測的。其他版本沒有逐一確認,升級 Helm 之後建議用同樣方式對一遍。
為什麼這樣選
先講代價。wrapper 多了一層,values 的路徑也多一層:想改上游的設定,得寫在 operator-wrapper.operator.… 底下,而不是直接寫 operator.…。每升級一次上游,都要手動 helm pull 並 commit 新的 tgz。
換來的好處有兩個:
- 渲染環境的 repo 清單不需要再改。只要把環境設定放在叢集外的地方(例如 ArgoCD 的 plugin 設定),每多一個上游 repo 就得動一次,而且動了之後還得重新部署那個設定。
- 新增上游 chart 的失敗範圍縮小。加進 wrapper 的 chart 有問題,頂多是 wrapper 自己渲染失敗,不會讓 umbrella 被牽連。
另一個做法是直接去改渲染環境,多加一筆 helm repo add。這能行,缺點是環境設定和 chart 內容分兩處維護,漏了其中一邊,就會再來一次整批壞掉。
延伸閱讀
- Helm 文件:Chart Dependencies
- Helm 文件:dependencies 的 condition 與 tags 欄位
