Topik ini membahas penulisan chart Helm untuk aplikasi catatan yang terdiri atas lebih dari satu objek, lengkap dengan values per lingkungan dan fungsi templating untuk membentuk manifest. Pembahasan dimulai dari anatomi chart dan berkas Chart.yaml, dilanjutkan lima fungsi templating yang paling sering muncul pada chart nyata, kemudian berkas values untuk staging dan produksi, dan ditutup perenderan chart dengan tiga kombinasi values. Struktur chart pada topik ini sudah lengkap, sehingga bidang version pada Chart.yaml naik menjadi 1.0.0.
Anatomi chart aplikasi nyata#
Chart nyata jarang berhenti pada satu template. Aplikasi catatan membutuhkan Deployment dan Service, dan keduanya berbagi nama serta label yang sama, sehingga nilai-nilai itu perlu dinyatakan satu kali lalu disisipkan ke setiap template. Helm menyediakan folder templates untuk menampung semua template, ditambah berkas khusus bernama _helpers.tpl untuk definisi yang digunakan ulang di seluruh chart.
catatan-api/
├── Chart.yaml
├── values.yaml
└── templates/
├── _helpers.tpl
├── deployment.yaml
└── service.yamlChart.yaml pada tahap ini menambahkan deskripsi yang lebih jelas dan menaikkan version karena struktur chart sudah lengkap. Bidang appVersion tetap mengikuti versi aplikasi yang dibungkus, sedangkan version menyatakan versi chart itu sendiri. Pada bundle topik ini, seluruh berkas chart ditampilkan datar di samping artikel sesuai nama yang disesuaikan, misalnya template-deployment.yaml yang dalam chart sesungguhnya tersimpan sebagai templates/deployment.yaml.
apiVersion: v2
name: catatan-api
description: Chart Deployment dan Service untuk API aplikasi catatan
version: 1.0.0
appVersion: "1.2.0"values.yaml pada chart ini lebih kaya daripada draf awal. Kunci containerPorts berbentuk daftar karena aplikasi membuka dua port, yaitu 8000 untuk API dan 9090 untuk metrik, sedangkan resources menyimpan permintaan dan batas sumber daya yang diteruskan ke container.
replicaCount: 1
image:
repository: registry.local/catatan-api
tag: ""
containerPorts:
- 8000
- 9090
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256MiFungsi templating yang digunakan#
Lima fungsi berikut menutupi hampir seluruh kebutuhan penulisan chart sehari-hari, yaitu .Values untuk mengambil nilai, include untuk menyisipkan template bernama, toYaml untuk mengubah struktur menjadi teks YAML, range untuk mengulang butir daftar, dan default untuk mengganti nilai kosong.
| Fungsi | Peran | Contoh pada chart |
|---|---|---|
.Values | mengambil nilai dari values.yaml | {{ .Values.replicaCount }} |
include | menyisipkan template bernama dari _helpers.tpl | {{ include "catatan.nama" . }} |
toYaml | mengubah struktur menjadi teks YAML | {{ toYaml .Values.resources }} |
range | mengulang setiap butir daftar | {{ range .Values.containerPorts }} |
default | mengganti nilai yang kosong | `{{ .Values.image.tag |
Definisi template bernama ditulis pada _helpers.tpl melalui kata kunci define, kemudian disisipkan dengan include. Tanda strip pada pembuka dan penutup aksi mengendalikan spasi di sekitar keluaran, sehingga baris kosong yang tidak diinginkan tidak muncul pada manifest hasil perenderan.
{{- define "catatan.nama" -}}
{{ .Chart.Name }}
{{- end -}}
{{- define "catatan.label" -}}
app: {{ .Chart.Name }}
komponen: api
{{- end -}}Berkas deployment.yaml berikut menempatkan kelima fungsi tersebut pada posisi yang menyusun manifest utuh. Placeholder .Chart.Name dan .Chart.AppVersion mengambil nilai dari Chart.yaml, sehingga label dan tag image ikut berganti apabila versi berubah.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "catatan.nama" . }}
labels:
{{- include "catatan.label" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
{{- include "catatan.label" . | nindent 8 }}
spec:
containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
{{- range .Values.containerPorts }}
- containerPort: {{ . }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}Tanda pipa meneruskan keluaran satu fungsi ke fungsi berikutnya. Contoh paling terlihat adalah {{ toYaml .Values.resources | nindent 12 }}. Struktur resources diubah menjadi teks YAML, lalu setiap barisnya diberi indentasi sepanjang dua belas spasi agar sesuai kedudukannya di dalam manifest.
values untuk tiap lingkungan#
Perbedaan antara lingkungan ditampung pada berkas values terpisah yang menimpa nilai default. Berkas staging menaikkan jumlah replika menjadi dua, menyematkan tag kandidat rilis, dan memperbesar batas sumber daya, sedangkan berkas produksi menetapkan tiga replika dengan tag rilis stabil serta permintaan sumber daya yang lebih besar.
replicaCount: 2
image:
tag: "1.3.0-rc.2"
resources:
limits:
cpu: 500m
memory: 512MireplicaCount: 3
image:
tag: "1.2.0"
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512MiBerkas lingkungan hanya menuliskan kunci yang berbeda dari values.yaml. Saat instalasi atau peningkatan berjalan, Helm menggabungkan keduanya secara bertingkat. Kunci pada berkas lingkungan menimpa nilai default, sedangkan kunci yang tidak disebut mengikuti nilai default. Perintah pemanggilannya berbentuk helm upgrade -f values-produksi.yaml, dan apabila lebih dari satu berkas disebutkan, berkas yang dituliskan belakangan menimpa berkas sebelumnya.
Merender chart dengan tiga kombinasi values#
Perenderan chart pada sandbox artikel ini disimulasikan dengan Python. Skrip berikut membaca template-deployment.yaml dan Chart.yaml dari bundle, kemudian merendernya tiga kali, yaitu dengan nilai default, dengan gabungan values staging, dan dengan gabungan values produksi. Fungsi include, toYaml, nindent, range, dan default ditiru secara sederhana agar hasilnya setia pada perilaku Helm.
# Perender sederhana templat Helm: meniru penggantian .Values, include,
# toYaml, range, dan default pada tiga kombinasi values.
import copy
import re
import yaml
with open("Chart.yaml", encoding="utf-8") as berkas:
chart = yaml.safe_load(berkas)
with open("template-deployment.yaml", encoding="utf-8") as berkas:
templat = berkas.read()
with open("values.yaml", encoding="utf-8") as berkas:
values_default = yaml.safe_load(berkas)
with open("values-staging.yaml", encoding="utf-8") as berkas:
values_staging = yaml.safe_load(berkas)
with open("values-produksi.yaml", encoding="utf-8") as berkas:
values_produksi = yaml.safe_load(berkas)
def gabung(asal, tambahan):
# penggabungan bertingkat seperti helm upgrade -f
hasil = copy.deepcopy(asal)
for kunci, nilai in tambahan.items():
if isinstance(nilai, dict) and isinstance(hasil.get(kunci), dict):
hasil[kunci] = gabung(hasil[kunci], nilai)
else:
hasil[kunci] = copy.deepcopy(nilai)
return hasil
def helper_nama(konteks):
# meniru define "catatan.nama" pada _helpers.tpl
return konteks["Chart"]["Name"]
def helper_label(konteks):
# meniru define "catatan.label" pada _helpers.tpl
return "app: " + konteks["Chart"]["Name"] + "\nkomponen: api"
HELPERS = {"catatan.nama": helper_nama, "catatan.label": helper_label}
def ambil_nilai(jalur, konteks):
if jalur == ".":
return konteks["."] if "." in konteks else konteks
objek = konteks
for kunci in jalur.lstrip(".").split("."):
objek = objek[kunci]
return objek
def ke_yaml(objek):
return yaml.dump(objek, sort_keys=True, default_flow_style=False).rstrip("\n")
def terapkan(nama, argumen, nilai_masuk, konteks):
if nama == "include":
return HELPERS[argumen[0]](konteks)
if nama == "toYaml":
return ke_yaml(argumen[0] if argumen else nilai_masuk)
if nama == "nindent":
jumlah = int(argumen[0])
return "\n" + "\n".join(" " * jumlah + b for b in str(nilai_masuk).split("\n"))
if nama == "default":
return nilai_masuk if nilai_masuk not in (None, "") else argumen[0]
raise SystemExit("fungsi tidak dikenal: " + nama)
def evaluasi(ekspresi, konteks):
tahap = [t.strip() for t in ekspresi.split("|")]
nilai = None
if tahap[0].startswith("."):
nilai = ambil_nilai(tahap[0], konteks)
tahap = tahap[1:]
for teks_tahap in tahap:
m = re.match(r"(\w+)\s*(.*)$", teks_tahap)
nama, sisa = m.group(1), m.group(2).strip()
argumen = [b.strip('"') for b in re.findall(r'"[^"]*"|[^\s]+', sisa)] if sisa else []
# argumen yang berupa jalur objek dievaluasi terlebih dahulu
argumen = [ambil_nilai(a, konteks) if a.startswith(".") else a for a in argumen]
nilai = terapkan(nama, argumen, nilai, konteks)
return nilai
POLA_AKSI = re.compile(r"(\{\{-?)\s*(.*?)\s*(-?\}\})", re.DOTALL)
def tokenisasi(templat):
mentah, posisi = [], 0
for m in POLA_AKSI.finditer(templat):
teks = templat[posisi:m.start()]
if m.group(1) == "{{-":
teks = teks.rstrip()
mentah.append(["teks", teks, False])
mentah.append(["aksi", m.group(2).strip(), m.group(3) == "-}}"])
posisi = m.end()
mentah.append(["teks", templat[posisi:], False])
token = []
for i, t in enumerate(mentah):
if t[0] == "teks" and i > 0 and mentah[i - 1][2]:
t = ["teks", t[1].lstrip(), False]
token.append(t)
return token
def render(token, konteks):
keluaran, i = [], 0
while i < len(token):
t = token[i]
if t[0] == "teks":
keluaran.append(t[1])
elif t[1].startswith("range "):
dalam, i = 0, i + 1
isi = []
while not (token[i][0] == "aksi" and token[i][1] == "end" and dalam == 0):
if token[i][0] == "aksi" and token[i][1].startswith("range "):
dalam += 1
if token[i][0] == "aksi" and token[i][1] == "end":
dalam -= 1
isi.append(token[i])
i += 1
for butir in ambil_nilai(t[1].split(" ", 1)[1], konteks):
keluaran.append(render(isi, {**konteks, ".": butir}))
elif t[1] == "end":
pass
else:
keluaran.append(str(evaluasi(t[1], konteks)))
i += 1
return "".join(keluaran)
for nama, nilai in (("default", values_default),
("staging", gabung(values_default, values_staging)),
("produksi", gabung(values_default, values_produksi))):
# objek Chart pada Helm berbentuk kamus dengan kunci kapital
konteks = {"Values": nilai,
"Chart": {"Name": chart["name"], "AppVersion": chart["appVersion"]}}
print(f"=== values {nama} ===")
print(render(tokenisasi(templat), konteks))=== values default ===
apiVersion: apps/v1
kind: Deployment
metadata:
name: catatan-api
labels:
app: catatan-api
komponen: api
spec:
replicas: 1
selector:
matchLabels:
app: catatan-api
template:
metadata:
labels:
app: catatan-api
komponen: api
spec:
containers:
- name: api
image: "registry.local/catatan-api:1.2.0"
ports:
- containerPort: 8000
- containerPort: 9090
resources:
limits:
cpu: 250m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
=== values staging ===
apiVersion: apps/v1
kind: Deployment
metadata:
name: catatan-api
labels:
app: catatan-api
komponen: api
spec:
replicas: 2
selector:
matchLabels:
app: catatan-api
template:
metadata:
labels:
app: catatan-api
komponen: api
spec:
containers:
- name: api
image: "registry.local/catatan-api:1.3.0-rc.2"
ports:
- containerPort: 8000
- containerPort: 9090
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
=== values produksi ===
apiVersion: apps/v1
kind: Deployment
metadata:
name: catatan-api
labels:
app: catatan-api
komponen: api
spec:
replicas: 3
selector:
matchLabels:
app: catatan-api
template:
metadata:
labels:
app: catatan-api
komponen: api
spec:
containers:
- name: api
image: "registry.local/catatan-api:1.2.0"
ports:
- containerPort: 8000
- containerPort: 9090
resources:
limits:
cpu: '1'
memory: 512Mi
requests:
cpu: 250m
memory: 256MiBagian default memperlihatkan cara kerja fungsi default. Tag image yang kosong pada values.yaml digantikan oleh appVersion 1.2.0. Bagian staging memperlihatkan penggabungan bertingkat. Permintaan sumber daya tetap dipertahankan dari nilai default, sedangkan batasnya mengikuti berkas staging. Bagian produksi memperlihatkan tiga replika dengan permintaan yang lebih besar, dan ketiga keluaran hanya berbeda pada nilai yang disebut oleh berkas lingkungannya.
Pada kluster sungguhan, hasil yang sama dapat dilihat tanpa instalasi melalui helm template.
# tidak-dijalankan
# merender seluruh templat chart ke terminal menggunakan values staging
helm template catatan ./catatan-api -f values-staging.yaml---
# Source: catatan-api/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: catatan-api
labels:
app: catatan-api
komponen: apiRingkasan#
- Chart nyata menampung beberapa template sekaligus dan memanfaatkan _helpers.tpl untuk definisi nama serta label yang digunakan ulang.
- Fungsi .Values, include, toYaml, range, dan default menutupi kebutuhan templating yang paling sering muncul pada chart sehari-hari.
- values per lingkungan dituliskan secara parsial dan digabung secara bertingkat di atas nilai default saat instalasi atau peningkatan.
- helm template merender chart tanpa instalasi, sehingga hasil setiap kombinasi values dapat diperiksa sebelum dikirim ke kluster.
Referensi#
- Charts — Helm Documentation — Anatomi chart, aturan Chart.yaml, serta perbedaan bidang version dan appVersion.
- Getting Started with the Chart Template Guide — Helm Documentation — Pengantar mesin template Helm beserta fungsi yang tersedia di dalamnya.