iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Kubernetes

從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作系列 第 22

Day 22|Helm 進階模板與 `_helpers.tpl` — 讓 Chart 更靈活、更好維護

  • 分享至 

  • xImage
  •  

前言

昨天我們從零手寫了一個精簡的 Helm Chart,成功部署了 Nginx。

但仔細看 deployment.yamlservice.yaml,會發現同樣的名稱與 Label 重複出現在很多地方,例如:

{{ .Release.Name }}-nginx

以及:

app: {{ .Release.Name }}-nginx

如果哪天想把命名規則從:

<Release.Name>-nginx

改成:

<Release.Name>-<Chart.Name>

就必須到不同模板裡逐一修改。

當 Chart 越來越大、模板越來越多,這種重複不只難維護,也很容易發生漏改。

今天我們要學的,就是用 _helpers.tpl 與進階 Helm Template 技巧,把重複邏輯集中管理,讓 Chart 更有彈性、也更容易維護。

今天內容包含:

  1. _helpers.tpl —— 建立可以重複使用的共用模板
  2. define / include —— 定義與引用模板
  3. include vs template —— 兩種引用方式有什麼差別
  4. with —— 縮小作用域,讓模板更簡潔
  5. toYaml / nindent —— 將結構化資料輸出成正確縮排的 YAML
  6. 多環境管理 —— 使用 values-dev.yaml / values-prod.yaml
  7. 功能開關 —— 使用 if 控制是否建立 Ingress

以下操作延續 Day 21 的 my-chart,皆在 master 節點執行。


一、_helpers.tpl 是什麼?

_helpers.tpl 是 Helm Chart 中常用來放置**共用模板片段(helper templates)**的檔案,通常位於 templates/ 目錄下。

它可以把重複使用的名稱、Label 或其他模板邏輯集中定義,再讓 deployment.yamlservice.yaml 等其他模板重複引用。

💡 為什麼檔名用底線 _ 開頭?

templates/ 目錄中的大多數檔案都會參與 Helm Template 渲染,並可能產生 Kubernetes Manifest。

但檔名以 _ 開頭的檔案,Helm 會把它視為 partial / helper template,不會直接渲染成 Kubernetes Object。

因此 _helpers.tpl 很適合當成 Chart 裡的「共用工具箱」,專門放給其他模板引用的共用內容。

核心概念

可以用程式語言的角度來理解:

  • _helpers.tpl:像是一個共用函式庫
  • define:用來定義可重複使用的模板片段
  • include:在其他模板中引用這些共用片段
  • 好處:集中管理,改一處就能讓所有引用的位置一起更新

二、實戰:建立 _helpers.tpl

Step 1:建立檔案

vim my-chart/templates/_helpers.tpl

輸入以下內容:

{{- define "my-chart.fullname" -}}
{{- printf "%s-nginx" .Release.Name -}}
{{- end -}}

{{- define "my-chart.labels" -}}
app: {{ include "my-chart.fullname" . }}
chart: {{ .Chart.Name }}
release: {{ .Release.Name }}
{{- end -}}

這段做了什麼?

my-chart.fullname —— 集中管理資源名稱的產生邏輯。

例如 Release 名稱是:

my-release

經過:

{{ printf "%s-nginx" .Release.Name }}

會產生:

my-release-nginx

之後如果想修改命名規則,只需要修改 _helpers.tpl 裡這一處。


my-chart.labels —— 集中管理多個資源共用的 Labels。

渲染後會類似:

app: my-release-nginx
chart: my-chart
release: my-release

Deployment、Service 等資源都可以引用同一份 Labels,避免每個模板重複撰寫。

💡 為什麼名稱寫成 my-chart.fullname

Helm 的 Named Template 名稱是全域的,因此通常會加上 Chart 名稱作為前綴,例如:

my-chart.fullname
my-chart.labels

這樣可以降低不同 Chart 或 Subchart 之間發生命名衝突的機會。


三、改寫模板:引用 _helpers.tpl

現在來改寫 Day 21 的 deployment.yamlservice.yaml,把重複的名稱與 Labels 改成引用 _helpers.tpl

Step 2:改寫 deployment.yaml

vim my-chart/templates/deployment.yaml

改寫後:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-chart.fullname" . }}
  labels:
    {{- include "my-chart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ include "my-chart.fullname" . }}
  template:
    metadata:
      labels:
        {{- include "my-chart.labels" . | nindent 8 }}
    spec:
      containers:
        - name: nginx
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.containerPort }}
              protocol: TCP

💡 為什麼這裡使用 nindent

include 會回傳一段文字,而 YAML 對縮排非常敏感。

nindent 4 會:

  1. 先加入一個換行
  2. 再把每一行往右縮排 4 格

這樣很適合把 _helpers.tpl 產生的多行內容放進 YAML 結構中。

例如:

{{ include "my-chart.labels" . | nindent 4 }}

就能把共用 Labels 正確放進 metadata.labels

Step 3:改寫 service.yaml

vim my-chart/templates/service.yaml

改寫後:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-chart.fullname" . }}
  labels:
    {{- include "my-chart.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  selector:
    app: {{ include "my-chart.fullname" . }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.containerPort }}
      protocol: TCP

改寫前 vs 改寫後

項目 Day 21(精簡版) Day 22(進階版)
資源名稱 每個模板各自寫 {{ .Release.Name }}-nginx 統一引用 {{ include "my-chart.fullname" . }}
Labels 每個位置各自撰寫 統一引用 {{ include "my-chart.labels" . }}
修改命名規則 需要修改多個模板 只需要修改 _helpers.tpl
維護方式 重複邏輯分散在不同檔案 共用邏輯集中管理

透過 _helpers.tpl,我們把原本散落在不同模板中的重複邏輯集中到同一個地方。

之後如果想修改資源名稱或共用 Labels,只需要調整 _helpers.tpl,其他引用它的模板就會一起套用新的規則。


四、include vs template — 差在哪?

Helm 有兩種常見方式可以引用 Named Template:

include template
類型 Function Action
可以接 Pipeline ✅ 可以 ❌ 不行
可以搭配 indent / nindent ✅ 可以 ❌ 無法直接接 Pipeline
實務使用 ✅ 通常優先使用 適合不需要額外處理輸出的簡單場景

例如:

{{ include "my-chart.labels" . | nindent 4 }}

include 會先取得 Named Template 的輸出,再把結果交給 nindent 4 處理。

template

{{ template "my-chart.labels" . }}

會直接把內容插入目前的位置,無法再接:

| nindent 4

💡 實務上優先使用 include

YAML 對縮排非常敏感,而 include 可以搭配 Pipeline 使用,例如 nindentindentquote 等函式。

因此在 Helm Chart 中,尤其是需要控制 YAML 縮排時,通常會優先使用 include


五、with — 縮小作用域

當模板裡頻繁存取同一組巢狀資料時,例如:

.Values.image.repository
.Values.image.tag
.Values.image.pullPolicy

可以使用 with 縮小作用域,讓模板更簡潔。

改寫前:

image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}

改寫後:

{{- with .Values.image }}
image: "{{ .repository }}:{{ .tag }}"
imagePullPolicy: {{ .pullPolicy }}
{{- end }}

進入 with .Values.image 之後,. 會改成指向 .Values.image,所以原本:

.Values.image.repository

就可以簡化成:

.repository

⚠️ 注意 with 會改變 . 的作用域

with 區塊中,. 不再代表原本的 root context。

如果這時候需要存取最上層的物件,例如 Release 名稱,可以使用 $

{{- with .Values.image }}
release: {{ $.Release.Name }}
image: "{{ .repository }}:{{ .tag }}"
{{- end }}

可以把 $ 理解成回到模板最上層 context 的入口。


六、toYaml — 輸出結構化資料

當你想把 values.yaml 裡的一整段結構直接輸出成 YAML 時,可以使用 toYaml

常見例子就是 resources

values.yaml 加入:

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

在模板中引用:

resources:
  {{- toYaml .Values.resources | nindent 2 }}

toYaml 會把 .Values.resources 轉成 YAML 格式,再搭配 nindent 2 處理換行與縮排。

渲染後會變成:

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

這樣就不用在模板裡一個欄位一個欄位手動對應,values.yaml 裡的結構也能比較完整地保留下來。

💡 toYaml 常和 nindent 一起使用

toYaml 負責把資料轉成 YAML,nindent 則負責把輸出的內容放到正確的縮排層級。


七、多環境管理:切換不同的 values 檔案

實務上通常會有 dev、staging、prod 等不同環境,而每個環境的參數可能不一樣。

Helm 可以透過 -f 指定額外的 values 檔案,覆寫 Chart 內建的 values.yaml

Step 4:建立環境專屬 values

# 開發環境
vim my-chart/values-dev.yaml
replicaCount: 1

image:
  tag: "latest"
# 正式環境
vim my-chart/values-prod.yaml
replicaCount: 5

image:
  tag: "1.27"

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 200m
    memory: 256Mi

使用方式

# 先移除 Day 21 的 Release(如果還存在)
helm uninstall my-release

# 開發環境
helm install my-release-dev ./my-chart \
  -f my-chart/values-dev.yaml

# 正式環境
helm install my-release-prod ./my-chart \
  -f my-chart/values-prod.yaml

https://ithelp.ithome.com.tw/upload/images/20260820/20181928kb9lTJ0w4k.png

這樣同一份 Chart,就可以搭配不同的 values 檔案部署到不同環境。

⚠️ Release 名稱

在同一個 Namespace 中,Release Name 必須唯一。

如果同一個 Release 還存在,又再次執行:

helm install my-release ./my-chart

就會因名稱已被使用而安裝失敗。

實務上不同環境通常會使用不同的 Release Name,也常會部署到不同的 Namespace,例如 devstagingprod

💡 Values 覆寫優先順序

簡化來看,可以記成:

  1. values.yaml —— Chart 內建的預設值
  2. -f values-dev.yaml —— 額外指定的 values 檔案
  3. --set replicaCount=10 —— 命令列直接覆寫

後面的設定會覆蓋前面相同的 Key。

因此通常會把:

  • values.yaml:放通用的預設值
  • values-dev.yaml / values-prod.yaml:只放各環境不同的設定

這樣就不用為每個環境複製一整份 Chart。


八、功能開關:用 if 做可選資源

不是每個環境都需要 Ingress 或 HPA。

透過 if 判斷 values 裡的開關,就能決定某個 Kubernetes 資源是否要被產生。

Step 5:在 values.yaml 加入 Ingress 開關

vim my-chart/values.yaml

在最後加入:

ingress:
  enabled: false
  host: myapp.example.com

https://ithelp.ithome.com.tw/upload/images/20260820/201819282XjHqg6ISD.png

Step 6:建立 Ingress 模板

vim my-chart/templates/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "my-chart.fullname" . }}
  labels:
    {{- include "my-chart.labels" . | nindent 4 }}
spec:
  rules:
    - host: {{ .Values.ingress.host }}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: {{ include "my-chart.fullname" . }}
                port:
                  number: {{ .Values.service.port }}
{{- end }}

當:

ingress:
  enabled: false

這份 Ingress Template 就不會產生任何 Kubernetes Manifest。

效果

預設:

helm template my-release ./my-chart

因為:

ingress.enabled: false

所以不會看到 Ingress 資源。

如果想開啟 Ingress:

helm template my-release ./my-chart \
  --set ingress.enabled=true

這次渲染結果就會多出一個 Ingress。

⚠️ 注意

ingress.enabled=true 只代表 Helm 會建立 Ingress Resource。

要讓 Ingress 實際處理外部流量,叢集裡還需要有對應的 Ingress Controller


九、驗證

完成所有模板修改後,再跑一次驗證流程:

# 1. 檢查 Chart
helm lint ./my-chart

# 2. 本地渲染,確認最終 YAML
helm template my-release ./my-chart

# 3. 實際部署
helm upgrade --install my-release ./my-chart

其中:

helm upgrade --install

代表:

  • Release 已存在 → 執行 upgrade
  • Release 不存在 → 執行 install

很適合在反覆修改 Chart 時使用。

https://ithelp.ithome.com.tw/upload/images/20260820/20181928YDyefMrtbQ.png
https://ithelp.ithome.com.tw/upload/images/20260820/20181928SxPi5vm5J6.png

檢查重點

  • 資源名稱是否正確,例如 my-release-nginx
  • Labels 是否包含 appchartrelease
  • Deployment 與 Service 是否引用相同的 fullname / labels
  • ingress.enabled: false 時,是否不會產生 Ingress
  • 開啟 Ingress 後,是否能在 helm template 的輸出中看到 Ingress Resource

例如:

helm template my-release ./my-chart \
  --set ingress.enabled=true

這時就應該會多出 Ingress Manifest。


十、常用 Helm 模板函式速查

函式 用途 範例
default 值為空時提供預設值 default 80 .Values.port
printf 格式化字串 printf "%s-%s" .Release.Name .Chart.Name
toYaml 將資料轉成 YAML 格式 toYaml .Values.resources
indent 為每一行加上縮排 include "tpl" . | indent 4
nindent 先換行,再為每一行加上縮排 include "tpl" . | nindent 4
quote 將值包成雙引號字串 quote .Values.image.tag
upper / lower 字串大小寫轉換 upper .Values.env
trimSuffix 移除字串尾端指定內容 trimSuffix "-" $name
required 值為空時中止渲染並顯示錯誤 required "需要 image.tag" .Values.image.tag

小結

今天我們把 Day 21 的精簡模板進一步整理成更容易維護的版本,回顧重點:

學到的東西 一句話總結
_helpers.tpl define 定義共用模板,再透過 include 重複引用,集中管理命名與 Labels
include vs template 實務上通常優先使用 include,因為可以搭配 Pipeline、indentnindent 等函式
with 縮小作用域,簡化巢狀資料存取,但要注意 . 的指向會改變
toYaml 將結構化資料轉成 YAML,再搭配 nindent 放進正確的縮排層級
多環境 values 使用 -f values-prod.yaml 等檔案覆寫預設值,讓同一份 Chart 套用到不同環境
功能開關(if 透過 enabled: true/false 控制某些 Kubernetes 資源是否產生

到這裡,我們已經從「會建立 Helm Chart」,進一步學會如何讓 Chart 減少重複、提高彈性,也更容易維護

下一篇,我們會進一步學習 Helm Repository 與 Chart 依賴管理,了解如何讓自己的 Chart 引用其他 Chart,並管理更複雜的應用組合!


參考資源


上一篇
Day 21|打造你的第一個 Helm Chart
下一篇
Day 23|Helm Repository 與 Chart 依賴管理
系列文
從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言