iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Kubernetes

從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台系列 第 10

[Day 10] Helm 管理:如何優雅地管理多服務的部署文件 —— 使用變數與 Template,告別 YAML 地獄。

  • 分享至 

  • xImage
  •  

Day 10: Helm 管理:如何優雅地管理多服務的部署文件

使用變數與 Template,告別 YAML 地獄。

1. 什麼是 Helm?K8s 的套件管理神器 🚀

在深入實作之前,先認識一個 Kubernetes 界的超級好幫手——Helm

簡單來說,Helm 就是 Kubernetes 的「套件管理工具」(Package Manager)。如果你用過 Linux,可以想像成 aptyum;寫 Python 的話就像 pip;寫 Node.js 就像 npm。只不過 Helm 管的是 Kubernetes 應用程式。

為什麼需要它?

在 K8S 部署一個完整應用,要寫一大堆 YAML(DeploymentServiceIngressConfigMap...)。每次部署都手動改版本號、改密碼、改 Port,不僅眼花容易出錯,長期下來根本沒辦法維護。

Helm 怎麼解決?

  • 打包成 Chart(模板化):把整包 YAML 打包成模板,稱為 Chart,就是應用程式的「安裝包」。
  • 參數化配置 (values.yaml):會變動的值(映像檔版本、副本數、域名)抽出來統一放 values.yaml,部署時 Helm 自動填入所有模板。
  • 版本控制與一鍵回滾:新版部署炸了?一行 helm rollback 無痛退回上一版。

沒有 Helm 的話,裝個 MySQL 要自己找一堆 YAML 複製貼上慢慢套用(光想就心累);有了 Helm 就是一行:

helm install my-database bitnami/mysql

2. Wafer BI 的 Chart 結構

回到我們的專案。helm/wafer-bi/ 的結構長這樣:

helm/wafer-bi/
├── Chart.yaml              # Chart 的身分證(名稱、版本)
├── values.yaml             # 預設參數(開發環境)
├── values-production.yaml  # 生產環境覆寫
└── templates/
    ├── namespace.yaml
    ├── wafer-backend.yaml
    ├── wafer-frontend.yaml
    ├── user-service.yaml
    ├── api-gateway.yaml
    ├── postgres.yaml
    ├── ingress.yaml
    ├── network-policy.yaml
    └── ...(共 19 個模板)

values.yaml 把所有會變的東西集中管理,例如:

waferBackend:
  replicas: 1
  image:
    repository: ghcr.io/darkschneider1024/wafer-bi-backend
    tag: a840fafd1ce53d38268e0f67ef001b4c2394cf09   # CI 會自動更新這行
    pullPolicy: Always
  resources:
    requests:
      cpu: 100m
      memory: 256Mi

那模板長什麼樣?直接把 templates/wafer-backend.yaml 整份貼出來——它就是 Day 8 那份 Deployment,只是把會變的地方全部挖成變數:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wafer-backend
  namespace: {{ .Values.global.namespace }}
  labels:
    {{- include "wafer-bi.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.waferBackend.replicas }}
  selector:
    matchLabels:
      app: wafer-backend
  template:
    metadata:
      labels:
        app: wafer-backend
        env: {{ .Values.global.environment }}
      annotations:
        deployed-at: {{ now | date "2006-01-02 15:04:05" | quote }}
    spec:
      {{- if .Values.global.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml .Values.global.imagePullSecrets | nindent 8 }}
      {{- end }}
      containers:
      - name: backend
        image: "{{ .Values.waferBackend.image.repository }}:{{ .Values.waferBackend.image.tag }}"
        imagePullPolicy: {{ .Values.waferBackend.image.pullPolicy }}
        ports:
        - containerPort: 8000
        resources:
          {{- toYaml .Values.waferBackend.resources | nindent 10 }}
---
apiVersion: v1
kind: Service
metadata:
  name: wafer-backend-svc
  namespace: {{ .Values.global.namespace }}
  labels:
    {{- include "wafer-bi.labels" . | nindent 4 }}
spec:
  selector:
    app: wafer-backend
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  type: ClusterIP

短短四十幾行,其實把 Helm 模板語法該會的幾乎都用過一輪了。逐個拆:

① 最基本的取值:{{ .Values.xxx }}

{{ .Values.waferBackend.replicas }} 就是去 values.yamlwaferBackend.replicas 的值填進來。image 那行則是把 repository 和 tag 兩個變數用冒號拼起來——這一行就是 Day 19 GitOps 閉環的關鍵零件:CI 只要改掉 values.yaml 裡的 tag,這裡渲染出來的 image 就變了,ArgoCD 比對到差異就會自動部署。

② 減號 {{- 是在修剪空白

{{- if ... }} 的減號代表「把左邊的空白和換行吃掉」。這不是龜毛,是 YAML 的生存問題——YAML 靠縮排決定結構,模板留下的空行和多餘縮排會直接讓渲染結果變成無效的 YAML。這也是寫 Helm 最容易卡住的地方,helm template 渲染出來一堆縮排錯誤,八成是減號沒放對。

include + nindent:共用片段與縮排控制

{{- include "wafer-bi.labels" . | nindent 4 }} 引用的是 templates/_helpers.tpl 裡定義的具名模板:

{{- define "wafer-bi.labels" -}}
app.kubernetes.io/part-of: wafer-bi
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
{{- end -}}

底線開頭的檔案(_helpers.tpl)不會被當成 K8S 資源渲染,專門放這種可重複使用的片段。19 個模板全都 include 同一段 labels,要改標籤策略只要改這裡一次

nindent 4 的意思是「先換行,再把整段內容縮排 4 格」。因為 include 出來的是一段多行文字,直接塞進 YAML 會全部貼在同一層,縮排必須自己算好。

toYaml:整個區塊原封不動搬過去

        resources:
          {{- toYaml .Values.waferBackend.resources | nindent 10 }}

resources 底下有 requests / limits、各有 cpu / memory,共四個值。與其寫四行 {{ .Values... }},不如用 toYaml 把 values 裡那整個結構轉成 YAML 直接塞進來。好處是之後想在 values 加 ephemeral-storage,模板一個字都不用改。

{{- if }}:條件區塊

imagePullSecrets 只有在 values 有設定時才會出現。本機開發拉的是公開 image 不需要它,雲端拉私有 registry 才要——同一份模板同時服務兩種環境,這正是 Chart 的價值。

⑥ 函式與管線 |

{{ now | date "2006-01-02 15:04:05" | quote }} 讀作:取現在時間 → 格式化 → 加上引號。跟 shell 的 pipe 是同一個直覺,Helm 內建了幾十個這種函式(defaultupperb64encrequired…)。

順帶一提,now 這個看起來很貼心的設計後來變成一顆雷:每次渲染出來的值都不一樣,導致 ArgoCD 永遠覺得有差異。Day 17 會講怎麼用 ignoreDifferences 跟它和解。

--- 分隔多個資源

Deployment 和 Service 寫在同一個檔案,用 --- 隔開。習慣上會把「一個服務相關的資源」放在一起,比拆成 wafer-backend-deployment.yaml + wafer-backend-service.yaml 好維護。

渲染出來到底長怎樣?

講再多不如直接看結果。helm template 可以只渲染單一檔案(-s 參數),不用連叢集:

helm template wafer-bi helm/wafer-bi -s templates/wafer-backend.yaml

上面那份模板,套進 values.yaml 之後吐出來的 Deployment 是這樣(Service 部分省略):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wafer-backend
  namespace: k8sdemo
  labels:
    app.kubernetes.io/part-of: wafer-bi
    app.kubernetes.io/managed-by: Helm
    helm.sh/chart: wafer-bi-1.0.0
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wafer-backend
  template:
    metadata:
      labels:
        app: wafer-backend
        env: development
      annotations:
        deployed-at: "2026-08-12 10:32:27"
    spec:
      imagePullSecrets:
        - name: ocirsecret
      containers:
      - name: backend
        image: "ghcr.io/darkschneider1024/wafer-bi-backend:a840fafd1ce53d38268e0f67ef001b4c2394cf09"
        imagePullPolicy: Always
        ports:
        - containerPort: 8000
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 256Mi

所有 {{ }} 都不見了,剩下的是純粹的 K8S YAML——這就是 ArgoCD 實際拿去 apply 的東西(Day 17 會提到 ArgoCD 底下沒有 Helm release,就是因為它做的正是這件事)。對照幾個點:include 的 labels 攤成三行、toYaml 的 resources 完整長出四個值、{{- if }} 判定為真所以 imagePullSecrets 出現了、now 被算成一個具體時間。

一份模板,兩種環境

最後看 Chart 真正的價值。同一份模板疊上 values-production.yaml 再渲染一次,然後跟開發環境版本 diff:

$ diff <(helm template wafer-bi helm/wafer-bi -s templates/wafer-backend.yaml) \
       <(helm template wafer-bi helm/wafer-bi -f helm/wafer-bi/values.yaml \
         -f helm/wafer-bi/values-production.yaml -s templates/wafer-backend.yaml)

32c32
<   replicas: 1
---
>   replicas: 2
40c40
<         env: development
---
>         env: production

48 行的模板一個字都沒改,換一個 values 檔就得到另一套環境的配置。 這就是 Day 10 開頭說的「告別 YAML 地獄」——地獄的定義不是 YAML 很多,是同一份 YAML 被複製成好幾份、然後開始各自演化

3. 實測:lint 與 template

Chart 寫完先體檢。helm lint 檢查語法與結構,helm template 在本地渲染出最終 YAML(不用連叢集,CI 裡跑超方便):

https://ithelp.ithome.com.tw/upload/images/20260812/20182549L7cfpRyUXb.png

▲ helm lint 通過 + helm template 渲染出 34 個 K8S 資源

一個 Chart 渲染出 34 個 K8S 資源——想像一下沒有 Helm 的話,這 34 份 YAML 要手動維護,版本號要一個一個改……不敢想,哭啊。

幾個實用指令整理:

helm lint helm/wafer-bi                          # 語法體檢
helm template wafer-bi helm/wafer-bi             # 本地渲染(乾看結果)
helm upgrade --install wafer-bi helm/wafer-bi    # 安裝或升級(冪等,CI 最愛)
helm upgrade --install wafer-bi helm/wafer-bi \
  -f helm/wafer-bi/values-production.yaml        # 疊加生產環境參數
helm rollback wafer-bi 1                         # 回滾到 revision 1

最後那行 helm rollback 先打個預防針:它的前提是「這個 release 是你自己用 helm upgrade --install 裝的」。等 Day 17 把部署交給 ArgoCD 之後,Helm 會退化成純粹的模板引擎,叢集裡不再有 release 歷史,這行指令會直接回你 release: not found——Day 20 講回滾時會把這個坑挖開來看。

4. 一個小提醒:CRD 依賴

我們的 Chart 裡有一張 ClusterIssuer(cert-manager 的資源,Day 11 SSL 憑證會用到)。如果目標叢集還沒裝 cert-manager,helm install 會直接報 no matches for kind "ClusterIssuer"——Helm 只認叢集裡已存在的 CRD。所以部署順序是:先裝 cert-manager,再裝我們的 Chart。這種「Chart 之間的隱形依賴」在拆模板時要特別留意。

5. 小結

從今天起,Wafer BI 的部署文件正式從「一堆散裝 YAML」升級成「一個版本化的 Chart」。明天來處理流量的門面:Nginx Ingress Controller 與 SSL 憑證,讓服務用漂亮的 HTTPS 域名見人。


上一篇
[Day 9] 雲端評估:Always Free 額度的真實限制,以及我們決定先留在本機的理由 —— 免費不等於沒有代價,這篇記錄一次評估雲端部署、最後決定暫緩的完整過程。
下一篇
[Day 11] 流量控制:Nginx Ingress Controller 與 SSL 憑證設定 —— 讓外部流量安全、精準地導向正確的微服務。
系列文
從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言