[零基礎入門] 使用最小 Helm chart,一步一步玩 `with` / `omit` / `toYaml` / `nindent` === ###### tags: `K8s / Helm / template` ###### tags: `Kubernetes`, `k8s`, `Helm Chart`, `HelmChart`, `chart`, `template`, `Go template`, `with`, `omit`, `toYaml`, `indent`, `nindent`, `YAML` <br> [TOC] <br> :::info ## 0. 目標與前置需求 ### 目標: * 建一個**最小 Helm chart**,專門當「Template 實驗室」。 * 不用 K8s cluster,只靠 `helm template` 就能觀察: * `with` vs `{{- with`(空白處理差異) * `omit` 怎麼從 map 裡排除欄位 * `toYaml` + `nindent` 怎麼影響輸出 YAML 結構 ### 前置需求: * 已安裝 Helm CLI(`helm version` 能跑起來) * 有一個工作目錄(例如:`~/work/tpl-lab`) ::: --- ## 1. 建立「超迷你 chart 實驗室」骨架 ### Step 1-1. 建立目錄結構 ```bash mkdir -p tpl-lab/templates cd tpl-lab ``` ### Step 1-2. 建立最小 `Chart.yaml` ```bash= cat > Chart.yaml << 'EOF' apiVersion: v2 name: tpl-lab description: Small lab chart to test Helm templates type: application version: 0.1.0 appVersion: "1.0.0" EOF ``` > 這個 chart 不會真的部署,只是給 `helm template` 有東西可以渲染。 <br> --- <br> ## 2. 準備第一份 `values.yaml`(persistence 測試資料) 建立 `values.yaml`: ```bash= cat > values.yaml << 'EOF' controller: persistence: existingClaim: claimName: my-pvc storageClassName: nfs-client size: 10Gi accessModes: - ReadWriteOnce EOF ``` 這裡我們有: * `existingClaim`:一個 map(裡面有 `claimName`) * `storageClassName`:一個單純字串 * `size` / `accessModes`:一般欄位,待會會用 `omit` 測試 <br> --- <br> ## 3. 建立 persistence 測試模板 ### Step 3-1. 在 `templates/` 裡建 YAML 模板 ```bash= cat > templates/persistence.yaml << 'EOF' apiVersion: v1 kind: ConfigMap metadata: name: tpl-lab-persistence-test data: config: | persistence: {{- with .Values.controller.persistence.existingClaim }} # existingClaim block {{- toYaml . | nindent 6 }} {{- end }}{{- /* with existingClaim */}} {{- with .Values.controller.persistence.storageClassName }} # storageClassName (示範:直接對字串 toYaml) {{- toYaml . | nindent 6 }} {{- end }}{{- /* with storageClassName */}} {{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }} # omit 之後剩下的欄位 {{- toYaml . | nindent 6 }} {{- end }}{{- /* with omit ... */}} EOF ``` > 注意: > > * `config: |` 底下多一層縮排,所以我用 `nindent 6`(2 個空白 + 再 4 格)。 > * `{{- ... }}` 前面的 `-` 是為了「吃掉左邊空白/換行」,避免多出空白行。 <br> --- <br> ## 4. 第一次渲染:觀察整體輸出 在 `tpl-lab` 目錄內執行: ```bash helm template demo . ``` 你會看到其中一段像這樣(局部示意): ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: tpl-lab-persistence-test data: config: | persistence: # existingClaim block claimName: my-pvc # storageClassName (示範:直接對字串 toYaml) nfs-client # omit 之後剩下的欄位 size: 10Gi accessModes: - ReadWriteOnce ``` ### 4-1. 「套用前 / 套用後」局部對照(existingClaim) **Template(套用前)** ```gotemplate {{- with .Values.controller.persistence.existingClaim }} # existingClaim block {{- toYaml . | nindent 6 }} {{- end }} ``` **Values(輸入資料)** ```yaml existingClaim: claimName: my-pvc ``` **渲染後(套用後)** ```yaml persistence: # existingClaim block claimName: my-pvc ``` * `with`:如果存在 → 進 block,把 `.` 換成 `existingClaim` 這個 map。 * `toYaml .`:把 `existingClaim` 轉成 YAML。 * `nindent 6`:前面加一個換行,再縮排 6 格,對齊到 `persistence:` 下面。 * `nindent` vs `indent` * `nindent N`:控制「多一個換行+縮排 N 格」,讓輸出對齊到正確層級 * `indent N`:控制「縮排 N 格」,讓輸出對齊到正確層級 * 情境用途: * 想要 **緊貼下一行** → 多半用 `indent`。 * 想要 **多一個空行再開始內容** → 用 `nindent`。 <br> --- <br> ## 5. 深入理解 `{{ with }}` vs `{{- with }}`(吃不吃空白) 這裡玩一下空白差異。 ### Step 5-1. 改 template:先拿掉 `-`(實驗版) 把 `templates/persistence.yaml` 中的其中一段改成**沒有 `-`** 的版本(先示範一段就好): ```gotemplate {{ with .Values.controller.persistence.existingClaim }} # existingClaim block {{ toYaml . | nindent 6 }} {{ end }} ``` 再跑一次: ```bash helm template demo . ``` 你可能會看到類似(局部示意): ```yaml persistence: # existingClaim block claimName: my-pvc ``` 差異: * 因為 `with` 那一行本身被渲染成「空白行」,沒有被 `-` 吃掉。 * 所以 `persistence:` 和 `# existingClaim block` 中間,多了一行空白。 ### Step 5-2. 用 `{{- with` / `{{- end` 清空多餘空白 把它改回: ```gotemplate {{- with .Values.controller.persistence.existingClaim }} # existingClaim block {{- toYaml . | nindent 6 }} {{- end }} ``` 再跑一次 `helm template demo .`,就回到比較緊實的版本: ```yaml persistence: # existingClaim block claimName: my-pvc ``` **重點小抄:** * `with`:**邏輯**一樣,不影響內容輸出。 * `{{- with` / `{{- end`:多了 `-` → 把左邊的空白/換行吃掉 → 避免多出空白行。 <br> --- <br> ## 6. 深入理解 `omit`:把特定 key 排除掉 看這段: ```gotemplate {{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }} # omit 之後剩下的欄位 {{- toYaml . | nindent 6 }} {{- end }} ``` ### 6-1. 套用前:原始 map 原始的 `controller.persistence` 是: ```yaml existingClaim: claimName: my-pvc storageClassName: nfs-client size: 10Gi accessModes: - ReadWriteOnce ``` ### 6-2. `omit` 做了什麼? ```gotemplate omit .Values.controller.persistence "existingClaim" "storageClassName" ``` 概念上就是: > 從這個 map 裡,**排除** `existingClaim` 和 `storageClassName` 這兩個 key。 所以 `omit` 後得到的 map 概念上變成: ```yaml size: 10Gi accessModes: - ReadWriteOnce ``` ### 6-3. 套用後:搭配 `with + toYaml + nindent` 整段: ```gotemplate {{- with omit .Values.controller.persistence "existingClaim" "storageClassName" }} # omit 之後剩下的欄位 {{- toYaml . | nindent 6 }} {{- end }} ``` * `omit ...` → 得到只含 `size` / `accessModes` 的 map。 * `with` → 如果 map 非空 → 進 block,`.` 變成「那個 map」。 * `toYaml .` → 轉成 YAML: ```yaml size: 10Gi accessModes: - ReadWriteOnce ``` * `nindent 6` → 對齊到 `persistence:` 下面。 **渲染結果(局部)** ```yaml persistence: # existingClaim block claimName: my-pvc # storageClassName (示範:直接對字串 toYaml) nfs-client # omit 之後剩下的欄位 size: 10Gi accessModes: - ReadWriteOnce ``` > 這招很適合:「某幾個欄位要特別處理,其餘全部交給 `toYaml` 一次吐出」。 <br> --- <br> ## 7. 額外練習:values 不同情境(nil / 少欄位) ### Step 7-1. 建立「沒有 existingClaim」的 values ```bash cat > values-no-claim.yaml << 'EOF' controller: persistence: storageClassName: nfs-client size: 5Gi EOF ``` 執行: ```bash helm template demo . -f values-no-claim.yaml ``` 你會看到: * `with .Values.controller.persistence.existingClaim` → 因為 `existingClaim` 不存在 / 為空 → **整個 block 不輸出**。 * `omit .Values.controller.persistence "existingClaim" "storageClassName"` → 只會輸出剩下的欄位(這裡只剩 `size`)。 **渲染結果(局部示意):** ```yaml persistence: # storageClassName (示範:直接對字串 toYaml) nfs-client # omit 之後剩下的欄位 size: 5Gi ``` <br> --- <br> ## 8. 額外實驗:`indent` vs `nindent` ### Step 8-1. 新增 `nindent-test.yaml` ```bash= cat > templates/nindent-test.yaml << 'EOF' apiVersion: v1 kind: ConfigMap metadata: name: nindent-test data: with-indent: | {{ toYaml .Values | indent 4 }} with-nindent: | {{ toYaml .Values | nindent 4 }} EOF ``` 再跑: ```bash helm template demo . ``` 你會看到(局部示意): ```yaml= data: with-indent: | controller: persistence: existingClaim: claimName: my-pvc storageClassName: nfs-client size: 10Gi accessModes: - ReadWriteOnce with-nindent: | controller: persistence: existingClaim: claimName: my-pvc storageClassName: nfs-client size: 10Gi accessModes: - ReadWriteOnce ``` **差異:** * `indent 4`:只幫你縮排 4 個空白,不會自動加前置換行。 * `nindent 4`:會先加一個「換行」,再縮排 4 個空白。 → 你會看到 `with-nindent` 底下多一行空白。 **小抄:** * 用在 `key: |` 後面時: * 想要 **緊貼下一行** → 多半用 `indent`。 * 想要 **多一個空行再開始內容** → 用 `nindent`。 <br> --- <br> ## 9. 建議的練習路線(把這個實驗室當 REPL 用) 之後你要玩其他 Helm 函式(`include` / `tpl` / `default` / `required`…)時,可以照這套路: 1. **固定使用 `tpl-lab` 這個 chart** 當實驗場。 2. 新增或修改 `templates/xxx.yaml`,專門測某一個語法。 3. 用不同 `values-*.yaml` 搭配 `-f` 切換情境: ```bash helm template demo . -f values.yaml helm template demo . -f values-no-claim.yaml ``` 4. 如果 chart 以後變大,可以用: ```bash helm template demo . --show-only templates/persistence.yaml ``` 只看單一模板的輸出。 --- 如果你接下來想把 `storageClassName` 那段改成「更語意正確」(輸出 `storageClassName: nfs-client` 而不是孤零零 `nfs-client`),我也可以幫你再寫一個「原始寫法 vs 改良寫法」的對照版實驗章節。 <br> --- <br> ## 討論 ### `{{ with` 前面的數個空白會被吃掉? - ### Question ```gotemplate {{ with .Values.controller.persistence.existingClaim }}#這裡是註解 ``` {{ 前面的空白會保留?還是整行是空白行? - ### 結論:渲染結果 ``` #這裡是註解 ``` - ### gpt-5.1 (2025/11/28) 會保留,而且**會真的輸出那些空白**,只是因為 `with` 這行本身不產生任何文字,所以最後看起來像「只有空白的空行」。 我們拆開來看這一行: ```gotemplate {{ with .Values.controller.persistence.existingClaim }} ``` * ` ` ← 這 6 個空白是「純文字」,Go template / Helm 不會幫你吃掉 * `{{ with ... }}` ← 這個動作本身不輸出任何內容(只是改變 `.` 和控制之後的區塊要不要執行) ### 實際效果示意 - 假設 template 是: ```gotemplate persistence: ${{ with .Values.controller.persistence.existingClaim }} {{ toYaml . | nindent 6 }} #comment {{ end }}& next-key: xxx ``` - `{{with}}` 和 `{{end}}` 前後用字元 `$`、`&` 來當作分隔符號,易於判斷空白部份 - 而 `.Values.controller.persistence.existingClaim` **不存在 / 為空** 時,整個 `with` block 都不輸出內容,結果會變成: ```yaml persistence: $& next-key: xxx ``` - 中間那一行實際上是: ```text <六個空白>$&<換行> ``` - `<換行>`: `&` 到下一行`next-key:`中間有 `\n` - `#comment` 位於 `{{with}}` 和 `{{end}}` 區間,因 key 不存在而不輸出 - 也就是你問的那句: > `{{` 前面的空白會保留?還是整行是空白行? 答案是: * **空白會保留**,所以這一行會變成「只包含空白的行」。 * 在 YAML 看起來就像「空白行」,但技術上裡面是有空白字元的。 <br> --- ### 如果改成 `{{- with ... }}` 會怎樣? 若寫成: ```gotemplate {{- with .Values.controller.persistence.existingClaim }} ``` 那就不一樣了: * `{{-` 會把「在 `{{` 左邊的空白+換行」全部吃掉 * 實際上會連同**上一行結尾的換行 + 這一行開頭的空白**一起清掉 常見 pattern 是讓這種「只有控制語法的行」完全不留下視覺上的空行,因此你會看到很多 Helm 寫法長這樣: ```gotemplate persistence: {{- with .Values.controller.persistence.existingClaim }} {{- toYaml . | nindent 2 }} {{- end }} next-key: xxx ``` 這樣如果 `with` 沒進去,就 **不會多出任何空白行**。 --- 如果你願意,下一步我可以幫你把你現在那一段 `persistence` 全部改寫成一個「不會產生孤零零 value、也不會亂多空行」的版本,順便標註哪一行在「輸出前 / 輸出後」長什麼樣子。 <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}