iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
自我挑戰組

雲端與資料平台實戰:從抽象概念到落地技術系列 第 7

Day7 Helm template 常用函數與最佳實踐

  • 分享至 

  • xImage
  •  

上一個章節中,我們介紹了如何透過減少縮排與使用 _helpers.tpl 來讓 Helm 模板更加簡潔易讀。這一章將進一步回顧並實作一些 Helm Template 常用的 Go 語法與函數,幫助我們在撰寫 Chart 時更有效率地處理各種情境。

Helm 提供了超過 60 種以上的內建函數,來源主要分為兩類:

  1. Go Template 語言

    • Helm Template 的底層語法,負責條件判斷、迴圈、變數處理等。
  2. Sprig Template Library

    • 提供各種字串、數字、清單操作與加密等進階函數,讓模板邏輯更加靈活。

你也可以透過 Helm 官方文件的 Template Function List 查閱完整清單;若想進一步理解函數的詳細運作,可以直接參考 Go Template 語言或 Sprig 函數庫 的原始文件。

本章將分享幾個我在實務專案中經常使用且特別實用的函數,這些函數能夠大幅簡化模板邏輯,讓 Helm Chart 更加精簡、易於維護。

接下來,我們將逐一介紹並提供範例:

以下是依照你提供的說明,針對 coalescejoin & splitList/splitternaryadler32sum 四個函數所做的完整示範與解說,包含實務情境、範例 YAML 片段,以及如何在 Helm Chart 中運用它們來簡化模板。


1. coalesce – 多來源參數的預設值選擇

用途
coalesce 會依序檢查傳入的參數,回傳第一個非空值
這在我們有多個可能來源的設定時非常實用,例如:

  • values.yaml 中的明確設定值
  • _helpers.tpl 中計算後的結果
  • 預設值 fallback

範例情境

某個服務的 replicas 可能來自三個來源:

  1. 使用者在 values.yaml 內設定
  2. _helpers.tpl 動態計算
  3. 預設值 1

_helpers.tpl

{{/*
根據服務類型計算預設 replicas
*/}}
{{- define "mychart.calculateReplicas" -}}
{{- if eq .Values.serviceType "high-availability" -}}
3
{{- else -}}
2
{{- end -}}
{{- end }}

deployment.yaml

spec:
  replicas: {{ coalesce .Values.replicas (include "mychart.calculateReplicas" .) 1 }}

渲染範例

values.yaml 設定 渲染結果
replicas: 5 replicas: 5
無設定,serviceType: high-availability replicas: 3
完全未設定 replicas: 1

好處
避免冗長的 if/else 判斷,且可保證值一定存在。


2. join + splitList / split – 多層清單轉字串

用途

  • splitList / split 將字串切割為清單
  • join 將清單合併回字串

搭配使用時,可以:

  1. 將複雜的設定展平成一條字串
  2. 自動控制分隔符號,不會多出尾端逗號

範例情境

假設我們的 values.yaml 定義了多個標籤與子層級設定:

tags: "env=prod,team=data,region=asia"
ports:
  - 8080
  - 9090

展平字串範例

configmap.yaml

data:
  TAGS: "{{ join ";" (splitList "," .Values.tags) }}"

渲染結果

data:
  TAGS: "env=prod;team=data;region=asia"

清單展平與自動分隔符號

在多個 port 設定下,自動生成 containerPort 並確保最後一個元素後沒有逗號

deployment.yaml

containers:
  - name: myapp
    ports:
      {{- range $index, $port := .Values.ports }}
      - containerPort: {{ $port }}
      {{- end }}

複合範例:split + join

假設我們有一個複雜的輸入,格式如下:

hosts: "kafka1:9092,kafka2:9092,kafka3:9092"

我們想要轉成 Kafka bootstrap 連線字串:

kafka1:9092;kafka2:9092;kafka3:9092

configmap.yaml

data:
  KAFKA_BOOTSTRAP: "{{ join ";" (splitList "," .Values.hosts) }}"

渲染結果

data:
  KAFKA_BOOTSTRAP: "kafka1:9092;kafka2:9092;kafka3:9092"

好處
不需要額外手動處理尾端分隔符號,模板更簡潔。


3. ternary – 三元運算簡化 if/else

用途
使用 ternary 可以在一行中完成 if/else 判斷,讓模板更加簡短。

語法

{{ ternary <真值> <假值> <條件> }}

範例情境

根據 env 判斷是否開啟 debug 模式。

values.yaml

env: prod

configmap.yaml

data:
  DEBUG_MODE: "{{ ternary "true" "false" (eq .Values.env "dev") }}"

渲染結果

env 設定 渲染結果
dev DEBUG_MODE: "true"
prod DEBUG_MODE: "false"

4. adler32sum – 生成唯一短碼

用途
當需要根據服務名稱或命名空間生成唯一 ID 時,adler32sum 提供一個快速的哈希演算法,適合用於:

  • Kafka Broker ID
  • StatefulSet Pod 唯一識別
  • Job 名稱避免重複

範例情境

我們想要針對 Release.Name 生成一個唯一數字 ID。

statefulset.yaml

metadata:
  name: {{ .Release.Name }}-{{ adler32sum .Release.Name }}

渲染結果

如果 Release 名稱是 kafka-prod

metadata:
  name: kafka-prod-2874896061

實務延伸

Kafka Broker 需要唯一的 broker.id,我們可以直接使用 adler32sum 確保不同 Release 有不同 ID:

env:
  - name: BROKER_ID
    value: "{{ adler32sum (printf "%s-%s" .Release.Name .Values.kafka.brokerName) }}"

5. 綜合範例

以下是一個結合上述所有函數的完整範例,展示如何在實務中同時運用。

values.yaml

replicas: ""
serviceType: high-availability
tags: "env=prod,team=data,region=asia"
hosts: "kafka1:9092,kafka2:9092,kafka3:9092"
env: dev
kafka:
  brokerName: brokerA

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-{{ adler32sum .Release.Name }}
spec:
  replicas: {{ coalesce .Values.replicas (include "mychart.calculateReplicas" .) 1 }}
  template:
    spec:
      containers:
        - name: myapp
          env:
            - name: DEBUG_MODE
              value: "{{ ternary "true" "false" (eq .Values.env "dev") }}"
            - name: TAGS
              value: "{{ join ";" (splitList "," .Values.tags) }}"
            - name: KAFKA_BOOTSTRAP
              value: "{{ join ";" (splitList "," .Values.hosts) }}"
            - name: BROKER_ID
              value: "{{ adler32sum (printf "%s-%s" .Release.Name .Values.kafka.brokerName) }}"

渲染結果

metadata:
  name: myrelease-2874896061
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: myapp
          env:
            - name: DEBUG_MODE
              value: "true"
            - name: TAGS
              value: "env=prod;team=data;region=asia"
            - name: KAFKA_BOOTSTRAP
              value: "kafka1:9092;kafka2:9092;kafka3:9092"
            - name: BROKER_ID
              value: "1770260294"

函數 核心價值
coalesce 依序檢查並回傳第一個非空值,簡化多來源 fallback 邏輯
join + splitList 將複雜清單或字串展平成乾淨格式,避免手動處理尾端分隔符號
ternary 簡化 if/else,讓模板更簡潔
adler32sum 生成唯一短碼,常用於 ID 或名稱避免衝突

透過這些函數,可以讓 Helm Template 更加模組化、可讀性更高,同時降低錯誤發生的機率,適合應用在大型服務部署與自動化流程中。


以上分享了一些我在日常工作中時常使用的函數,這些函數能夠讓模板更簡潔、可讀性更高,也能減少在專案中處理複雜邏輯時的心智負擔。接下來,我想分享一些我自己在工作中遵循的最佳實踐

⚠️ 注意:這些方法不一定完全適合每個團隊,但可以作為參考依據,讓你思考如何優化自己的 Helm Chart。


官方最佳實踐文件

Helm 官方文件提供了完整的 Chart 撰寫建議與原則,主要分為三個章節:

  1. General Conventions

    • 命名規範、檔案結構建議
  2. Values

    • 如何設計與管理 values.yaml 中的參數
  3. Templates

    • 模板邏輯與結構設計技巧

如果你的團隊剛開始接觸 Helm,這三個章節是必讀的內容,能幫助建立一致且可維護的 Chart 基礎。


我在實務上的模板撰寫習慣

除了官方的原則,我也整理了一些個人經驗,特別是針對 模板結構與可讀性 的最佳實踐。
在實際專案中,模板經過不斷迭代與功能擴充後,常會遇到以下問題:

  • 模板中充斥大量的 if/else 判斷式
  • 不同資源的變數散落在各處,難以追蹤
  • 後期維護時,渲染結果與 YAML 原始樣貌差距太大,增加 Debug 難度

以下是我常用的兩項策略。


1. 利用 _helpers.tpl 收斂判斷邏輯

Helm 提供 _helpers.tpl 作為自訂函數集中管理的檔案。
我建議將複雜或重複的判斷邏輯全部收納到 _helpers.tpl 中,模板本身只負責呼叫結果。

範例:不好的寫法

判斷條件直接寫在模板內,導致程式碼冗長、可讀性差:

metadata:
  labels:
    {{- if eq .Values.env "prod" }}
    environment: production
    {{- else if eq .Values.env "dev" }}
    environment: development
    {{- else }}
    environment: staging
    {{- end }}

優化後寫法

將判斷式集中在 _helpers.tpl 中:

_helpers.tpl

{{- define "mychart.environmentLabel" -}}
{{- if eq .Values.env "prod" }}production
{{- else if eq .Values.env "dev" }}development
{{- else }}staging
{{- end }}
{{- end }}

deployment.yaml

metadata:
  labels:
    environment: {{ include "mychart.environmentLabel" . }}

好處:

  • 模板內只保留最終渲染的 YAML 結構
  • 判斷邏輯統一管理,便於日後修改

2. 在模板最上方集中定義變數

在 Helm Template 中,你可以使用 {{- $var := ... -}} 來定義變數。
我的習慣是在每個模板的最上方區塊統一準備好渲染過程中會使用的變數,並確保變數命名清楚、語意明確。

範例

{{- $fullName := include "mychart.fullname" . -}}
{{- $replicas := coalesce .Values.replicas (include "mychart.calculateReplicas" .) 1 -}}
{{- $envLabel := include "mychart.environmentLabel" . -}}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ $fullName }}
  labels:
    environment: {{ $envLabel }}
spec:
  replicas: {{ $replicas }}

好處:

  1. 變數集中,一眼即可看出此模板需要哪些輸入
  2. 在下方核心 YAML 區塊,看到的內容與最終渲染結果幾乎一致
  3. Debug 或修改時,降低錯誤風險

撰寫 Helm Template 時,應兼顧功能完整性可維護性

  • 善用 _helpers.tpl 收斂複雜邏輯,避免模板本體過於混亂
  • 將所有需要的變數集中在模板最上方,讓 YAML 主體結構簡潔、接近最終渲染結果
  • 遵循官方文件的命名規則與結構建議,確保團隊協作時的一致性

除了函數與模板結構的設計外,我認為理解 Helm 的運作本質,也能夠很好的幫助各位進行Helm Chart的設計,Helm template的本質上是文字整理與渲染流程

  1. 接收 values.yaml 作為輸入
  2. 經由模板進行轉換與組裝
  3. 輸出最終的 Kubernetes YAML

但重點在於:值的結構與型別會深刻影響模板邏輯。特別要注意 values.yaml 中輸入的是 map、list 還是 slice——這會直接左右你如何迭代、取值與判斷:

map:適合具命名的鍵值對,便於條件判斷與鍵名存取

list:適合順序資料,例如 ports、env 等

slice:常見於由 splitList 等轉換產生的集合,需要確認型別使用方式

若在設計階段沒有把資料結構設計好,後續模板會充斥型別判斷與轉換邏輯,降低可讀性。這也是為何把型別處理與資料準備封裝到 _helpers.tpl 非常重要——它能讓主模板更貼近結果,專注於「要輸出的 YAML」而非處理細節。


小結

在本章中,我們深入探討了 Helm Template 中常見且實用的函數,並透過實務案例展示如何在複雜的專案中保持模板簡潔、乾淨,提升維護效率。

  1. 函數應用回顧

    • coalesce:簡化多來源參數選擇,避免冗長的 if/else
    • join + splitList:輕鬆進行清單與字串轉換,減少尾端符號錯誤
    • ternary:一行完成三元判斷,讓模板邏輯更直觀
    • adler32sum:快速生成唯一短碼,避免名稱或 ID 衝突
  2. 模板最佳實踐

    • 將複雜邏輯收斂於 _helpers.tpl:保持主模板純淨,專注於 YAML 結構
    • 統一變數於模板頂部:變數集中定義,方便維護與除錯
    • 遵循官方規範:命名、結構及管理建議,確保團隊標準一致
  3. 理解 Helm 的本質

    • Helm 是文字整理與渲染流程的工具,將 values.yaml 輸入轉成 Kubernetes YAML。
    • 同時,它也需要型別控制與邏輯設計maplistslice 的選擇會影響模板迭代、判斷與結構設計。
    • 掌握這兩面思維,你的 Helm Chart 才能兼具簡潔性與可維護性

總結一句話
你可以簡單地把 Helm 看作是文字整理工具,也可以嚴謹地將它當作需要型別控制與邏輯設計的程式工具;兩種思維的取捨,決定了 Chart 的整潔度與維護成本。

在掌握了 Helm 函數應用與模板設計的基本功後,下一步,我們將 瀏覽 Bitnami Helm Charts
這些 Chart 以結構清晰、命名一致、可擴展性高聞名,能為我們在實務專案中的 template 結構設計values.yaml 規劃 提供寶貴參考。

感謝各位閱讀,我們明天見!


上一篇
Day6 設計 Helm values.yaml:為什麼減少階層可以更優雅
下一篇
Day 8 瀏覽 Bitnami Library ,學習建構Helm Chart
系列文
雲端與資料平台實戰:從抽象概念到落地技術9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言