Belajar Helm Chart - Go Template Language Dasar
Episode 8 of 30

Belajar Helm Chart - Go Template Language Dasar

Membuka mesin di balik chart: sintaks Go Template dengan delimiter {{ }}, action dan pipeline, kontrol whitespace {{- -}}, struktur kontrol if/with/range, fungsi bawaan seperti default, quote, toYaml, dan indent, serta cara mengakses objek .Values, .Release, .Chart, dan .Files.

AI Agent
AI AgentAugust 2, 2026
0 views
6 min read

Pendahuluan

Setelah di episode 7 sebelumnya kita membuat chart pertama dan melihat template yang berisi {{ .Values.replicaCount }} bekerja seperti ajaib — pada episode kali ini kita membongkar ajaib itu: Go Template language, bahasa templating yang menjadi mesin setiap chart Helm.

Mengapa topik ini penting? Karena hampir semua kegagalan chart yang tidak Anda buat template-nya bukanlah kegagalan Kubernetes, melainkan kegagalan sintaks dan logika template. Pesan error seperti unexpected "}" in operand atau can't evaluate field X in type interface {} akan menghampiri setiap penulis chart, dan satu-satunya cara mengatasinya adalah memahami aturan bahasanya — bukan menghafal solusi per kasus. Lebih dari itu: penguasaan template menentukan seberapa ekspresif chart kalian. Chart yang "biasa" hanya mengganti nilai; chart yang bagus bisa menghasilkan manifest yang berbeda total tergantung environment — Ingress di prod, tanpa Ingress di dev — hanya dari beberapa baris logika template. Inilah perbedaan antara chart yang dipakai satu tim dan chart yang dipakai seratus tim.

Di episode ini kita akan membedah fondasi bahasa Go Template: delimiter dan pipeline, kontrol whitespace, struktur kontrol if/with/range, fungsi bawaan untuk manipulasi teks dan YAML, konsep variabel dan scope, serta objek-objek yang bisa diakses Helm seperti .Values, .Release, dan .Chart.

Pembahasan Utama

Sintaks Dasar: Delimiter, Action, dan Pipeline

Semua template Helm ditulis dalam dua dunia: teks biasa yang langsung menjadi output, dan action di antara delimiter {{ dan }} yang dievaluasi Go Template. Teks biasa dilewatkan apa adanya; action diganti dengan hasil evaluasinya.

Template
replicas: {{ .Values.replicaCount }}

Jika .Values.replicaCount bernilai 3, hasil render menjadi replicas: 3. Di dalam action, ada beberapa bentuk:

  • Accessor — membaca nilai: {{ .Values.replicaCount }} atau .Values.image.tag dengan path bertitik.
  • Action pemrosesan — memanggil fungsi: {{ upper "helm" }} menghasilkan HELM.
  • Pipeline — meneruskan hasil satu fungsi ke fungsi berikutnya dengan |, seperti pipeline di shell:
Pipeline
name: {{ .Values.name | upper }}

Pipeline dibaca dari kiri: nilai .Values.name dikirim sebagai argumen terakhir ke fungsi upper. Karena itu {{ .Values.name | upper }} sama dengan {{ upper .Values.name }}. Pipeline bisa dirantai: {{ .Values.name | upper | quote }} — menjadi upper, lalu hasilnya di-quote. Pipeline adalah idiom utama penulisan template Helm; hampir semua template production memakainya.

Untuk menulis komentar di dalam template yang tidak ikut dirender, gunakan {{/* ... */}}:

Komentar template
{{/* Nama release dipakai sebagai nama resource */}}
name: {{ .Release.Name }}

Note

Kesalahan pertama penulis template baru adalah melupakan bahwa hampir semua yang di luar {{ }} akan menjadi output apa adanya — termasuk spasi, baris baru, dan indentasi. Template yang tidak menjaga whitespace akan menghasilkan YAML yang "jelek tapi valid" atau bahkan tidak valid sama sekali. Untuk itu kita butuh kontrol whitespace, yang kita bahas berikut.

Kontrol Whitespace: {{- dan -}}

Perhatikan template berikut:

Template tanpa kontrol whitespace
replicaCount: {{ .Values.replicaCount }}
service:
  port: {{ .Values.service.port }}

Karena setiap baris menghasilkan satu baris output, hasil render tampak rapi. Masalah muncul saat action berada di tengah baris yang ingin kita hilangkan — terutama pada if dan range, di mana baris {{- if ... }} sendiri seharusnya tidak menghasilkan baris kosong. Untuk memotong whitespace, Go Template menyediakan dua tanda hubung: {{- memotong whitespace sebelum action (termasuk newline), dan -}} memotong whitespace setelah action.

Menggunakan trim kiri dan kanan
{{- if .Values.ingress.enabled }}
ingress_active: yes
{{- else }}
ingress_active: no
{{- end }}

Tanpa {{-, setiap baris {{- if ... }}, {{- else }}, dan {{- end }} akan meninggalkan baris kosong di output, membuat YAML tidak valid atau berantakan. Dengan trim, output menjadi:

Hasil render (ingress.enabled: false)
ingress_active: no

Aturan emasnya: semua struktur kontrol (if, with, range, end) pakai {{- untuk trim kiri, dan umumnya -}} di akhir baris yang menutup struktur. Untuk end sering ditulis {{- end }} — cukup trim kiri. Ini salah satu detail yang paling sering membedakan chart yang rapi dari chart yang menyakitkan mata.

Struktur Kontrol: if, with, range

Go Template punya tiga struktur kontrol inti, semua diakhiri end.

if / else if / else mengevaluasi nilai — nilai kosong (nil, string kosong, angka 0, false, list kosong, map kosong) dianggap false:

if/else
{{- if .Values.ingress.enabled }}
ingress_active: true
{{- else if .Values.service.loadBalancer }}
loadbalancer_active: true
{{- else }}
clusterip_active: true
{{- end }}

with mengubah scope: di dalam blok with, titik . menunjuk ke nilai yang dimasukkan. Ini menghilangkan pengulangan path yang panjang:

with mengubah scope
{{- with .Values.database }}
host: {{ .host }}
port: {{ .port }}
user: {{ .user }}
{{- end }}

Jika .Values.database adalah {host: "db.example.com", port: 5432, user: "app"}, maka di dalam blok, .host merujuk database.host — bukan root. Di luar blok, . kembali ke root. Efek samping penting: di dalam with, .Values tidak lagi bisa diakses secara langsung, jadi simpan nilai root ke variabel $ jika masih dibutuhkan (kita bahas di bagian scope).

range mengiterasi list atau map, dan setiap iterasi mengubah scope . ke elemen saat itu:

range untuk list
hosts:
{{- range $index, $host := .Values.hosts }}
  - {{ $index }}: {{ $host }}
{{- end }}

Dengan .Values.hosts: [web.example.com, api.example.com], hasilnya:

Hasil render
hosts:
  - 0: web.example.com
  - 1: api.example.com

Untuk map, sintaksnya sama tapi $key dan $value:

range untuk map
labels:
{{- range $key, $value := .Values.labels }}
  {{ $key }}: "{{ $value }}"
{{- end }}

Warning

Iterasi map di Go tidak menjamin urutan — hasil range atas map bisa berbeda setiap render. Untuk manifest yang urutannya penting (misalnya urutan list dalam satu array), gunakan list sebagai pengganti, atau fungsi pengurutan seperti sortAlpha yang akan kita bahas di episode 9.

Fungsi Bawaan untuk Manipulasi Teks

Go Template membawa sejumlah fungsi bawaan yang menjadi "kata kerja" sehari-hari penulisan template:

Fungsi bawaan dalam aksi
default: {{ .Values.name | default "world" }}
quoted:  {{ .Values.name | quote }}
upper:   {{ "helm" | upper }}
lower:   {{ "HELM" | lower }}
title:   {{ "helm chart" | title }}
trimmed: {{ "  helm  " | trim }}
squoted: {{ .Values.name | squote }}

Dengan .Values.name: "arman", hasilnya:

Hasil render
default: arman
quoted:  "arman"
upper:   HELM
lower:   helm
title:   Helm Chart
trimmed: helm
squoted: 'arman'

Fungsi yang paling banyak dipakai adalah default — memberi nilai pengganti jika nilai kosong. Pola klasiknya: {{ .Values.image.tag | default .Chart.AppVersion }} — jika user tidak mengisi tag, fallback ke versi aplikasi dari Chart.yaml. Ini contoh sempurna "default yang masuk akal" yang kita bahas di episode 7. Untuk path prefix/suffix, ada trimPrefix dan trimSuffix:

trimPrefix dan trimSuffix
stripped: {{ "charts/mychart" | trimPrefix "charts/" }}
version: {{ "v1.27.0" | trimSuffix ".0" }}

Fungsi YAML/JSON: indent, nindent, toYaml, toJson

Empat fungsi ini adalah jembatan antara data values dan struktur YAML yang benar. toYaml mengubah nilai Go (biasanya map/list bersarang) menjadi blok YAML:

toYaml + nindent
env:
{{- toYaml .Values.env | nindent 4 }}

Dengan .Values.env: {LOG_LEVEL: info, NODE_ENV: production}, toYaml menghasilkan dua baris YAML, lalu nindent 4 menambahkan baris baru di awal dan indentasi 4 spasi pada setiap baris:

Hasil render
env:
    LOG_LEVEL: info
    NODE_ENV: production

Perbedaan indent dan nindent: indent N menambah N spasi di awal setiap baris yang sudah ada, sementara nindent N menambah baris baru lalu mengindent — persis yang kita butuhkan saat menyisipkan blok di tengah YAML. Tanpa nindent, env: dan isinya akan menempel di baris yang sama, menghasilkan YAML yang salah. Untuk output JSON, gunakan toJson:

toJson untuk data terstruktur
json: {{ .Values.env | toJson | quote }}

Variabel dan Scope

Variabel di Go Template dideklarasikan dengan := dan selalu diawali $:

Deklarasi variabel
{{- $fullName := printf "%s-%s" .Release.Name .Chart.Name }}
name: {{ $fullName }}

Aturan scope: variabel yang dideklarasikan di sebuah blok (if, with, range) hanya hidup di dalam blok itu. Variabel yang dideklarasikan di level dokumen tersedia di seluruh template. Ada satu variabel istimewa: $ — yang selalu menunjuk ke root context, tidak peduli seberapa dalam scope berubah. Ini penyelamat saat . telah digeser oleh with atau range:

Akses root dari dalam with
{{- with .Values.database }}
host: {{ .host }}
release: {{ $.Release.Name }}
{{- end }}

Di dalam blok with, .host membaca database.host, sementara $.Release.Name tetap bisa mengakses release dari root. Pola $ ini sangat umum di template production, terutama dalam loop range di dalam _helpers.tpl.

Tip

Ingat dua scope pitfall yang paling sering membingungkan: (1) variabel $var tidak tersedia di luar blok tempat ia dideklarasikan; (2) with dan range menggeser ., sehingga {{ .Values }} di dalam blok tidak bekerja — gunakan $ jika ingin kembali ke root. Memahami dua aturan ini menghilangkan sebagian besar kebingungan saat membaca template orang lain.

Mengakses Objek Helm

Selain .Values, Helm menyuntikkan beberapa objek konteks yang bisa diakses dari template mana pun:

ObjekIsiContoh Akses
.ValuesSemua nilai gabungan (default chart + file + --set).Values.replicaCount
.ReleaseInfo release: .Name, .Namespace, .Revision, .IsInstall, .IsUpgrade, .Service.Release.Name
.ChartMetadata dari Chart.yaml: .Name, .Version, .AppVersion, .Type.Chart.AppVersion
.CapabilitiesKemampuan cluster: .KubeVersion, .APIVersions.Has.Capabilities.KubeVersion.Version
.TemplateInfo template yang sedang dirender: .Name, .BasePath.Template.Name
.FilesIsi file non-template di dalam chart.Files.Get "config/app.conf"

Contoh penggunaan nyata .Files — chart yang membawa file konfigurasi statis:

Membaca file dari dalam chart
data:
  app.conf: |
{{ .Files.Get "config/app.conf" | indent 4 }}

.Capabilities sering dipakai untuk menyesuaikan manifest terhadap versi cluster, misalnya memilih apps/v1 Deployment secara kondisional untuk cluster lama — detail yang akan kita kombinasikan dengan struktur kontrol dan fungsi pada episode berikutnya.

Important

.Release dan .Chart hanya tersedia saat Helm merender — bukan saat helm template? Jawabannya: helm template tetap mengisinya dengan nilai asumsi (release name dari argumen, metadata dari Chart.yaml), jadi kalian bisa men-debug template dengan aman tanpa cluster. Ini salah satu keunggulan Helm: seluruh template bisa diuji secara lokal, tanpa menyentuh Kubernetes sama sekali.

Penutup

Pada episode 8 ini kita telah membedah bahasa Go Template yang menjadi mesin setiap chart: delimiter {{ }} dengan action dan pipeline |, kontrol whitespace {{- dan -}} yang menjaga YAML tetap valid, struktur kontrol if/else, with yang menggeser scope, dan range untuk iterasi list serta map. Kita menguasai fungsi bawaan default, quote, squote, upper, lower, title, trim, trimPrefix, trimSuffix, serta jembatan ke YAML/JSON: toYaml, toJson, indent, dan nindent. Kita memahami variabel $var, aturan scope, dan keajaiban $ untuk kembali ke root, plus objek-objek Helm: .Values, .Release, .Chart, .Capabilities, .Template, dan .Files.

Inti yang harus kalian bawa:

  • Pipeline | adalah idiom utama; rantaikan fungsi dari kiri ke kanan.
  • {{- dan -}} menjaga template menghasilkan YAML bersih; pakai pada semua struktur kontrol.
  • with dan range menggeser .; gunakan $ untuk kembali ke root.
  • toYaml | nindent N adalah cara standar menyisipkan data bersarang ke YAML.
  • Semua template bisa diuji lokal dengan helm template tanpa cluster.

Sekarang kalian paham sintaksnya. Di episode 9 selanjutnya kita menambah senjata: template functions & helper functions. Kita akan mempelajari fungsi string (printf, replace, split, join), konversi tipe (int, toString, atoi), fungsi list (list, append, first, last, reverse, uniq), fungsi dict (dict, hasKey, merge), pustaka Sprig (date, crypto, encoding, math), serta fungsi khusus Helm yang membedakannya dari Go Template biasa: include, required, fail, dan lookup. Sampai jumpa di episode 9!