昨天的 Kustomize 從現成 YAML 去調整成 Kustomize 版的 YAML 內容,靠 base 和 overlay 來處理不同環境的共用或異差異設定。
今天要來說明 Helm 這另一種叢集配置管理方式。
主要會透過一個叫做「Chart 」到物件來存放 templates 與 values,在 apply 時產生對應資源,再把該次套用記成 release 存在 Chart 內。
Kustomize 與 Helm 兩者都能生成 Kubernetes 的 manifest,但管理方法與回復方式不同。
如果用開發程式的經驗來看,Kustomize 比較像是先保留原本的設定結構,再把共用部分抽出來、針對環境補差異。
Helm 則可以把一套服務整理成可重複安裝的套件,先定義哪些地方可以調整,再讓部署的人透過 values 提供對應設定。
假設有三個團隊都要安裝同一套 API,使用的 image、環境名稱與資源大小不同,就可以共用一份 Chart,各自提供自己的 values。而每次實際安裝與升級的結果,Helm 也會另外記錄下來。
先針對這幾個名詞定義進行說明:
| 名稱 | 用途 | 範例 |
|---|---|---|
| Chart | 描述一套服務如何安裝的套件,包含模板與預設設定 | api-a-chart/,打包後是 .tgz |
| templates | 要產生 Kubernetes 資源的模板 | Deployment、Service、ConfigMap、Secret |
| values | 提供模板使用的設定值 | image tag、環境名稱、資源大小 |
| release | 某個 Namespace 裡,這套 Chart 一次具名安裝的實例 | 安裝名稱 demo,Namespace 是 team-a |
| revision | 這個 release 的安裝、升級與回復紀錄序號 | revision 1、2、3 |
Chart 的版本、程式的版本與 release revision 也要分開看。比如 Chart 是 0.1.0,裡面部署的 API image 可以是 v1;後來只換 values 改用 v2,Chart 版本可能仍是 0.1.0,但 release 會多一筆新的 revision。
Chart.yaml 的 appVersion 是應用版本的資訊,不會自動把 Deployment image 改成那個版本。
Helm 使用的是 Go template 語法,所以自己撰寫 Chart 時,需要學習如何取值、判斷條件、跑迴圈、呼叫模板函式,以及處理 YAML 縮排。
但這也不代表一定要先學完整的 Go 語言,或先安裝 Go 編譯器。實際上,多數模板仍是 Kubernetes YAML,中間加入 {{ ... }} 這類模板語法,如果是使用別人已經寫好的 Chart,那通常是想辦法看懂並修改 values YAML。
當然,現在 AI 這麼強,要快速上手、調整我想成本也變很低了 😂
所以學習成本要看自己扮演的角色是什麼,如果我要維護 Chart,就要理解模板如何產生資源;如果我只是部署一套已成熟的 Chart,就先確認它提供哪些 values,以及調整後產生的 YAML 是否符合需求。
目前我還是認為該理解的操作方式、使用方式還是得親手去實際撰寫看看,而不是都交給 AI 去 vibe coding,否則如果接到一個全離線環境、無法使用 AI 的地方,總不可能一直進出限制空間然後當一個「人肉 Agent」吧?

這裡用同一個 api-a 做示範。
最小 Chart 可先有 Chart.yaml、values.yaml,以及 templates/deployment.yaml、templates/service.yaml。
例如 values.yaml 放 image.repository、image.tag、service.port;Deployment template 讀這些值,讓不同環境不必複製整份 Deployment。
這次再加上 ConfigMap 與 Secret,方便後續觀察 服務 rollback 範圍。
整個目錄如下:
api-a-chart/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
configmap.yaml
secret.yaml
values-dev.yaml
values-test.yaml
環境 values 放在 Chart 目錄外,讓共用套件與環境設定分開維護。
先建立 api-a-chart/Chart.yaml:
# api-a-chart/Chart.yaml
apiVersion: v2
name: api-a
description: API A teaching chart
type: application
version: 0.1.0
appVersion: "v1"
version: 0.1.0 是 Chart 套件版本,打包時會用在檔名裡。這裡的 apiVersion: v2 是 Chart 格式版本,Helm 3 與 Helm 4 都可以使用。
預設設定則放在 api-a-chart/values.yaml:
# api-a-chart/values.yaml
image:
repository: registry.example.internal/demo/api-a
tag: "v1"
service:
port: 80
app:
environment: Production
secret:
demoToken: demo-only-default
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
接著建立 Deployment 模板:
# api-a-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-api-a
namespace: {{ .Release.Namespace }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: api-a
app.kubernetes.io/instance: {{ .Release.Name | quote }}
template:
metadata:
labels:
app.kubernetes.io/name: api-a
app.kubernetes.io/instance: {{ .Release.Name | quote }}
annotations:
checksum/config: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print .Template.BasePath "/secret.yaml") . | sha256sum }}
spec:
containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- name: http
containerPort: 8080
envFrom:
- configMapRef:
name: {{ .Release.Name }}-api-a-settings
env:
- name: DEMO_TOKEN
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-api-a-auth
key: demoToken
resources:
{{- toYaml .Values.resources | nindent 12 }}
這份模板還是可以看到原本 Deployment 的結構,只是部分欄位改成由 Helm 取值:
.Values.image.tag:讀取 values 裡的 image tag。.Release.Name:讀取安裝名稱。以 demo 安裝時,Deployment 就叫 demo-api-a。.Release.Namespace:讀取這次安裝的 Namespace。quote:將值輸出成加上引號的字串。toYaml、nindent:把 resources 設定轉成 YAML,並補上正確縮排。selector 與 Pod label 都包含 release 名稱,是為了讓不同 release 的 Service 找到各自的 Pod。
Service 模板如下:
# api-a-chart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-api-a
namespace: {{ .Release.Namespace }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: api-a
app.kubernetes.io/instance: {{ .Release.Name | quote }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
ConfigMap 與 Secret 也由同一份 Chart 產生:
# api-a-chart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-api-a-settings
namespace: {{ .Release.Namespace }}
data:
ASPNETCORE_ENVIRONMENT: {{ .Values.app.environment | quote }}
# api-a-chart/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ .Release.Name }}-api-a-auth
namespace: {{ .Release.Namespace }}
type: Opaque
data:
demoToken: {{ .Values.secret.demoToken | b64enc | quote }}
b64enc 是把字串轉成 Kubernetes Secret data 使用的 base64 格式。
Deployment 裡另外加了兩個 checksum annotation。這是讓 ConfigMap 或 Secret 模板內容改變時,Pod template 也跟著改變,進而觸發 Deployment rollout。
如果只有 ConfigMap 物件更新,原本透過環境變數讀設定的 Pod 不會自動取得新值,所以不能只看 ConfigMap 已經套用成功就結束。
像是 .NET 的 appsettings.json 也通常是需要讓 .NET 程式服務重啟後去套用的。
dev 的設定放在 values-dev.yaml:
# values-dev.yaml
image:
tag: "v2"
app:
environment: Development
secret:
demoToken: demo-only-dev
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
test 的設定放在 values-test.yaml:
# values-test.yaml
image:
tag: "v1"
app:
environment: Staging
secret:
demoToken: demo-only-test
test 沒有另外設定 resources,所以會使用 Chart 預設的資源大小。這裡和昨天的 overlay 有相似的目的,都是讓共用設定與環境差異分開,但 Helm 的 values 必須由模板實際讀取才會生效。
例如我在 values 加上 service.nodePort,但模板根本沒有使用這個欄位,最後也不會憑空多出 NodePort。
若想限制輸入欄位的型別或必填欄位,Chart 也可以加入 values.schema.json 做驗證。
透過 -f 指定環境設定檔時,會覆蓋 Chart 的預設值;多份 -f 對到相同欄位時,以最右邊的檔案優先,--set 則可以再覆蓋指定欄位。
真正部署、套用之前,可以先 dry-run 看輸出結果:
helm lint ./api-a-chart
helm template demo ./api-a-chart -f values-dev.yaml
helm upgrade --install demo ./api-a-chart -n team-a --create-namespace -f values-dev.yaml
helm history demo -n team-a
helm template 的輸出要檢查資源名稱、selector、image、port 與 Secret 引用。
若後續套用新的配置後,又突然需要回到先前版本,可用 helm rollback demo <revision> -n team-a,再驗證 Deployment rollout 和實際呼叫 API 來確認。
要套用 helm 時,與 kustomize 一樣,要特別注意當前 terminal 介面中正位於哪個目錄。
Helm 會按 release 紀錄處理它管理的 Kubernetes 資源,非被 Chart 紀錄或是人工改過的環境設定不會被跟著 rollback。
Kustomize 常用 Git revert 搭配重新 apply 來達成 rollback ;kubectl rollout undo 則偏向 Deployment 的 Pod template 歷史,不能順手回復其他 ConfigMap 或 Service。
Helm 可以回復同一個 release 管理的 ConfigMap、Secret 等資源。 條件是它們確實由 Chart 產生並納入這個 release;單純被 Pod 引用,並不代表它們就由 Helm 管理。
| 資源或內容 | Helm rollback 的範圍 |
|---|---|
| Chart 普通模板產生的 Deployment、Service、ConfigMap、Secret | 可依目標 release revision 回復,實際執行仍須成功 |
| Pod 只引用的外部既有 Secret | 不會回復該 Secret 內容,需要自己的維護與回復流程 |
| PVC 裡已經寫入的資料、資料庫 migration | 不會把資料一起回到舊版 |
Chart hooks 的資源、crds/ 裡的 CRD |
有另外的生命週期規則,不能視為一般模板資源一併回復 |
rollback 本身也可能失敗,像是 RBAC 不足、資源 immutable 欄位無法改回,或叢集已不再支援舊 API。即使舊 YAML 還在,舊 image 也要能拉得到;如果舊 tag 被覆寫或 image 被清掉,也無法保證重新啟動的是原來那個版本。
Kustomize 沒有 Helm 這種 release history 與原生 rollback 指令,所以通常會搭配使用 Git 的 revert 還原版本再 apply,但這會需要整合 git 的服務,例如 gitlab。
當實際環境沒有整合 gitlab 這種服務時,可能就得每次都在可以連線到 gitlab 的地方手動切換到指定版本並輸出檔案,然後再傳輸到 K8s 環境內來做後續處理。
Harbor 可以保存 OCI 格式的 Helm Chart,讓 Chart 像其他 artifact 一樣,以版本形式分發到內網部署環境。
如果原本已經有內部 Harbor,可以把 api-a Chart 打包成 api-a-0.1.0.tgz,再推送到 Harbor 的 charts 專案。
例如:
helm registry login harbor.example.internal
helm package ./api-a-chart
helm push ./api-a-0.1.0.tgz oci://harbor.example.internal/charts
helm pull oci://harbor.example.internal/charts/api-a --version 0.1.0
登入時依提示輸入帳號與密碼。push 的目的地只寫到 Harbor 專案,Chart 名稱與版本會從套件內的 Chart.yaml 取得。
下載或安裝時,才指定包含 Chart 名稱的完整路徑與版本。例如改從 Harbor 安裝:
helm upgrade --install demo oci://harbor.example.internal/charts/api-a --version 0.1.0 --kube-context k3d-ironman -n team-a --create-namespace --reset-values -f values-dev.yaml --wait --timeout 5m
| 要保存的東西 | 保存方式與用途 |
|---|---|
| Chart 原始碼的修改差異、審查紀錄 | Git 或企業另外規定的原始碼管理流程 |
| 已發布的 Chart 套件版本 | Harbor 的 OCI artifact,負責儲存與分發 |
| 每次實際安裝使用的設定與 release revision | Helm 的 release history,預設保存於叢集該 Namespace 的 Secret |
所以「把 Chart 放 Harbor 做發布版本管理」是可行的。
這種情況我覺得很值得討論,因為企業現場有可能連不到 GitLab,也不允許接 CI/CD 或 GitOps 工具,最後只能透過部署主機手動執行指令。
Helm 與 Kustomize 都可以在這種環境使用。 Helm 可以安裝本機 Chart 或 .tgz,Kustomize 可以使用完整的本機 base、overlay 與其他引用檔案。兩者都不要求先接上 GitLab 或 DevOps 平台。
我在實際整理隔離環境的部署設定時,就需要把引用的檔案一起帶進去,避免 build 時還要從外面的 repository 下載。
沒有自動化平台,影響的是交付、審查與部署流程要如何管理。
但離線準備要帶齊內容,只有 Chart 或 YAML 還不夠:
charts/ 再打包。Chart 的 .tgz 包含模板與 Chart 依賴,不會把 container image 一起包進去。所以 Helm 已經成功從 Harbor 取得 Chart,Pod 還是可能因為拉不到 image 而啟動失敗。
工具版本、Chart 版本、image tag/digest,以及部署時使用的 values 或 overlay 都要留存。這樣回復時,才能確認取到的是原本那套設定與程式版本。
下面是依照前面的能力與維護成本整理的選擇方式,實際還是要配合團隊分工與操作習慣:
| 現場情境 | 我會優先考慮 | 原因與要承擔的工作 |
|---|---|---|
| 自家 API,已經有跑通的 YAML,環境差異不多 | Kustomize | 保留原本 YAML,整理 base/overlay;需自行保存設定版本與回復流程 |
| 多個團隊要重複安裝同一套應用,希望有標準安裝包 | Helm | Chart/values 提供安裝介面,可發布套件版本並記錄 release;維護者要掌握模板 |
| 安裝已經有成熟官方 Chart 的平台產品 | Helm | 使用現有 Chart 比自己重寫整套資源省事;仍要確認 values、依賴與離線 image |
| 維運人員習慣直接檢查完整 YAML | Kustomize,或先 render 的 Helm | Kustomize 可直接查看來源資源;Helm 要確認模板產生的最終內容 |
| 已有 Harbor,但沒有 GitLab/CI/CD | 兩者都可 | Helm 可用 Harbor 分發 Chart;Kustomize 可交付完整資料夾,仍要建立交付紀錄 |
| 重視同一組受管理資源的 upgrade/rollback 歷史 | Helm | release revision 提供回復入口,但資料與外部資源仍要另外處理 |
就像昨天提到的企業維護權責,除了工具能做什麼,還要確認誰修改共用模板、誰提供環境設定、誰核准升級,以及誰負責回復。
如果 Chart 把多個團隊共同使用的入口也包進去,回復某個應用時會不會一起改到別人的路由?這些管理邊界要先決定,不能只因為 Helm 能 rollback,就把所有資源全部放進同一個 release。
假設某個平台產品已經有官方 Chart,但企業想替 Deployment 加上自己的 label 或排程設定,Chart 又沒有提供對應 values,這時就可以讓 Helm 先產生 YAML,再由 Kustomize 補上企業需要的差異。
兩者確實可以結合,但最後由誰安裝與管理資源,會影響回復方式:
| 結合方式 | 流程 | 誰管理安裝與回復 |
|---|---|---|
| Helm post-renderer | Helm render → Kustomize 修改 → Helm install/upgrade | Helm 保存修改後的 manifest,保留 release history 與 rollback |
helm template 後交給 Kustomize |
產生 YAML → 加入 Kustomize → kubectl apply |
使用 apply 的流程管理,沒有因此建立 Helm release |
Kustomize 的 helmCharts |
Kustomize 呼叫 Helm 產生 YAML,再組合與套用 | 產生 YAML 的流程,不能直接視為 Helm install |
官方的 Helm post-renderer 說明 就有介紹使用 Kustomize 修改 Chart 輸出的方式。Helm 3 接受 renderer 執行檔路徑,Helm 4 則改用 post-renderer plugin,所以舊文章裡的 shell 腳本指令需要先核對版本與作業系統。
使用 post-renderer 的人,每次預覽、安裝與升級,都要使用一致的 renderer、工具版本與 patches;Chart 升級後也要重新驗證 patch 是否仍能對到資源。rollback 則使用 release 已保存的歷史 manifest,不會重跑目前版本的 renderer。
Kustomize 的 helmCharts 需要另外安裝 Helm,並開啟 --enable-helm 才能使用。
如果再接上 GitOps,Flux 的 HelmRelease 可以使用 Kustomize post-renderer,並由 helm-controller 執行 Helm 的安裝與升級;Argo CD 使用 Helm Chart 時則主要用 Helm 產生 YAML,資源生命週期由 Argo CD 管理。兩者不能都直接理解成日後由操作者執行 helm rollback。
我的判斷會是,Chart 已經有提供需要的 values,就先使用 values;確實有補丁需求,再考慮增加 Kustomize。多一層工具,也會多一份要維護與驗證的設定。尤其要先決定同一個資源的管理入口,避免一邊 Helm upgrade、一邊又直接 apply 不同的 YAML,最後誰改了什麼都說不清楚。