iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

昨天的 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、values、release 與 revision 的關係

先針對這幾個名詞定義進行說明:

名稱 用途 範例
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 嗎?

Helm 使用的是 Go template 語法,所以自己撰寫 Chart 時,需要學習如何取值、判斷條件、跑迴圈、呼叫模板函式,以及處理 YAML 縮排。

但這也不代表一定要先學完整的 Go 語言,或先安裝 Go 編譯器。實際上,多數模板仍是 Kubernetes YAML,中間加入 {{ ... }} 這類模板語法,如果是使用別人已經寫好的 Chart,那通常是想辦法看懂並修改 values YAML。

當然,現在 AI 這麼強,要快速上手、調整我想成本也變很低了 😂

所以學習成本要看自己扮演的角色是什麼,如果我要維護 Chart,就要理解模板如何產生資源;如果我只是部署一套已成熟的 Chart,就先確認它提供哪些 values,以及調整後產生的 YAML 是否符合需求。

目前我還是認為該理解的操作方式、使用方式還是得親手去實際撰寫看看,而不是都交給 AI 去 vibe coding,否則如果接到一個全離線環境、無法使用 AI 的地方,總不可能一直進出限制空間然後當一個「人肉 Agent」吧?


Helm Chart 到 Release 與回復範圍

這裡用同一個 api-a 做示範。

最小 Chart 可先有 Chart.yaml、values.yaml,以及 templates/deployment.yaml、templates/service.yaml。

例如 values.yaml 放 image.repository、image.tag、service.port;Deployment template 讀這些值,讓不同環境不必複製整份 Deployment。

把 API 整理成一份 Chart

這次再加上 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 程式服務重啟後去套用的。

同一份 Chart,如何設定 dev 與 test?

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 則可以再覆蓋指定欄位。

先檢查產生的 YAML,再安裝 release

真正部署、套用之前,可以先 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 介面中正位於哪個目錄。

回到先前版本時,會包含 ConfigMap 與 Secret 嗎?

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 也能回復 ConfigMap、Secret,只是流程不同

Kustomize 沒有 Helm 這種 release history 與原生 rollback 指令,所以通常會搭配使用 Git 的 revert 還原版本再 apply,但這會需要整合 git 的服務,例如 gitlab。

當實際環境沒有整合 gitlab 這種服務時,可能就得每次都在可以連線到 gitlab 的地方手動切換到指定版本並輸出檔案,然後再傳輸到 K8s 環境內來做後續處理。

Helm Chart 可以存到 Harbor

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 或 DevOps 平台,該怎麼選?

這種情況我覺得很值得討論,因為企業現場有可能連不到 GitLab,也不允許接 CI/CD 或 GitOps 工具,最後只能透過部署主機手動執行指令。

Helm 與 Kustomize 都可以在這種環境使用。 Helm 可以安裝本機 Chart 或 .tgz,Kustomize 可以使用完整的本機 base、overlay 與其他引用檔案。兩者都不要求先接上 GitLab 或 DevOps 平台。

我在實際整理隔離環境的部署設定時,就需要把引用的檔案一起帶進去,避免 build 時還要從外面的 repository 下載。

沒有自動化平台,影響的是交付、審查與部署流程要如何管理。

但離線準備要帶齊內容,只有 Chart 或 YAML 還不夠:

  • Helm 要準備 CLI、Chart、環境 values,以及 Chart 使用的子 Chart。依賴要在可取得來源的準備區先下載到 charts/ 再打包。
  • Kustomize 要準備 kubectl 或固定版本的獨立工具,以及所有本機引用的 base、overlay、Component、patch 和 generator 輸入。不要留下仍指向隔離區外 GitHub 的引用。
  • 兩者都要準備應用、initContainer、hook 等使用的 container image,以及部署端與節點需要的 registry 憑證信任和認證設定。

Chart 的 .tgz 包含模板與 Chart 依賴,不會把 container image 一起包進去。所以 Helm 已經成功從 Harbor 取得 Chart,Pod 還是可能因為拉不到 image 而啟動失敗。

工具版本、Chart 版本、image tag/digest,以及部署時使用的 values 或 overlay 都要留存。這樣回復時,才能確認取到的是原本那套設定與程式版本。

哪些場景我會考慮 Helm,哪些會考慮 Kustomize?

下面是依照前面的能力與維護成本整理的選擇方式,實際還是要配合團隊分工與操作習慣:

現場情境 我會優先考慮 原因與要承擔的工作
自家 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。

Helm 與 Kustomize 也可以一起使用

假設某個平台產品已經有官方 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,最後誰改了什麼都說不清楚。

參考資料


上一篇
Day 23 - 用 Kustomize 達到通用化、差異化、可維護性高的 YAML
系列文
初見 Kubernetes - 純網站後端開發踏入 K8s 世界的經驗分享 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言