Memanfaatkan siklus hidup release: konsep pre/post-install, upgrade, rollback, dan delete hooks; annotation helm.sh/hook, hook-weight, dan hook-delete-policy; implementasi migration Job, backup, smoke test, cleanup, hingga praktik terbaik idempotency dan timeout.

Setelah di episode 11 sebelumnya kita membangun chart dari komponen-komponen lewat dependencies dan subcharts — dan di episode-episode 3 sampai 5 kita mempelajari instalasi, upgrade, dan rollback — pada episode kali ini kita membahas apa yang membuat release menjadi lebih dari sekadar kumpulan manifest: hooks, mekanisme Helm untuk menjalankan tindakan di titik-titik kritis siklus hidup sebuah release.
Kenapa ini penting? Pikirkan aplikasi database yang nyata. Sebelum versi baru aplikasi di-deploy, skema database harus di-migrasi lebih dulu — jika tidak, versi baru yang butuh kolom baru akan error begitu berjalan. Setelah aplikasi ter-deploy, tim ingin memastikan smoke test berjalan sebelum trafik dialihkan. Sebelum release dihapus, mungkin data sementara perlu dibersihkan. Semua tindakan ini tidak bisa diwakili oleh Deployment atau Service biasa — ia butuh urutan eksekusi yang tepat relatif terhadap resource lain, dan berjalan hanya pada titik tertentu. Tanpa hooks, tim harus mengelola migrasi, backup, dan smoke test secara manual di luar Helm — rapuh dan mudah terlewat. Dengan hooks, seluruh urutan itu menjadi bagian dari definisi chart itu sendiri, deterministik dan terdokumentasi. Inilah yang membuat Helm benar-benar menjadi package manager, bukan sekadar templating engine.
Hook adalah resource Kubernetes biasa (biasanya Job atau Pod) yang ditandai dengan annotation khusus. Saat Helm melakukan sebuah event dalam siklus hidup release, ia mencari template yang memiliki annotation helm.sh/hook yang cocok dengan event tersebut, merender-nya, dan mengeksekusinya pada waktu yang tepat.
Helm mendefinisikan sepuluh jenis hook:
| Hook | Kapan Dieksekusi |
|---|---|
pre-install | Setelah template dirender, sebelum resource apapun dibuat |
post-install | Setelah semua resource release berhasil dibuat |
pre-delete | Sebelum resource apapun dihapus |
post-delete | Setelah semua resource release dihapus |
pre-upgrade | Sebelum resource release di-upgrade |
post-upgrade | Setelah semua resource release berhasil di-upgrade |
pre-rollback | Sebelum resource release di-rollback |
post-rollback | Setelah semua resource release berhasil di-rollback |
pre-crd-install | Sebelum CRD (Custom Resource Definition) di-install |
test | Saat helm test dijalankan (dibahas di episode 14) |
Bayangkan hooks sebagai middleware atau lifecycle callbacks dalam framework web: aplikasi utama tidak peduli detailnya, tapi setiap fase punya titik penyisipan yang bisa dimanfaatkan. Bedanya, titik penyisipan ini dijalankan oleh Helm dengan semantik yang ketat — gagal satu hook berarti operasi gagal.
Sebuah template menjadi hook cukup dengan menambahkan annotation helm.sh/hook. Tiga annotation yang mengendalikan perilakunya:
helm.sh/hook — daftar event yang memicu resource ini, dipisahkan koma. Contoh: helm.sh/hook: pre-upgrade,post-install.helm.sh/hook-weight — bilangan bulat yang menentukan urutan eksekusi antar hook pada event yang sama. Semakin kecil angka, semakin dulu dijalankan. Defaultnya 0, bisa negatif.helm.sh/hook-delete-policy — menentukan kapan resource hook dihapus setelah dieksekusi. Nilai yang tersedia: before-hook-creation (hapus hook lama sebelum membuat yang baru — berguna untuk upgrade berulang), hook-succeeded (hapus setelah sukses), hook-failed (hapus setelah gagal). Bisa digabungkan dengan koma.Resource hook tidak dikelola sebagai bagian dari release seperti resource biasa. Ia dibuat, dieksekusi, dan dihapus sesuai delete-policy; saat helm rollback atau helm uninstall, hook lama tidak ikut di-rollback. Inilah mengapa helm get manifest tidak menampilkan hook — untuk melihatnya gunakan helm get hooks.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
"helm.sh/hook-ttl": "0"
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["/bin/sh", "-c", "npm run migrate -- --force"]Perhatikan dua annotation tambahan di atas: helm.sh/hook-ttl memberi tahu Helm untuk membersihkan Job yang sudah selesai setelah TTL tertentu (fitur Kubernetes), berguna jika delete-policy tidak menangani kasus kegagalan. Dan restartPolicy: Never — Job tidak boleh menggunakan Always, karena Job yang selesai dengan sukses tidak boleh restart.
Helm menjalankan hook dengan cara yang berbeda tergantung jenis resource:
Succeeded) sebelum melanjutkan operasi utama. Jika Job Failed, operasi dianggap gagal dan (tergantung flag) bisa di-rollback otomatis.Output hook disimpan oleh Helm dalam ConfigMap/Secret bernama <release>-<hook-name>.hooks — ini yang memungkinkan kalian melihat log hook setelah selesai dengan helm get hooks. Dan kegagalan hook tercatat di status release: release akan berstatus failed, dan helm list --failed akan menampilkannya.
Warning
Jika hook gagal saat install, Helm tidak meng-uninstall hook-nya secara otomatis jika delete-policy tidak mengaturnya. Job yang menggantung akan tetap ada dan memakan resource. Kombinasi restartPolicy: Never, backoffLimit yang wajar, helm.sh/hook-delete-policy yang tepat, dan (jika perlu) helm.sh/hook-ttl adalah pertahanan standar terhadap job zombie. Selalu periksa dengan kubectl get jobs --all-namespaces setelah kegagalan.
Hooks menjadi alat yang benar-benar berguna ketika diterapkan pada kebutuhan nyata. Lima pola berikut adalah yang paling sering ditemui di production:
Pola paling klasik — dan contoh yang sudah kita lihat. Sebelum versi aplikasi baru di-deploy, jalankan migrasi skema. Penting: migrasi sebaiknya idempotent (aman dijalankan ulang), karena pre-upgrade akan berjalan setiap kali chart di-upgrade, dan jika hook gagal lalu retry, migrasi bisa dijalankan dua kali. Strategi umum: tambahkan flag seperti --force untuk migrasi yang dirancang idempotent, atau pisahkan script migrasi dalam container yang sama.
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: url
command: ["/bin/sh", "-c", "npx prisma migrate deploy"]"Better safe than sorry" dalam bentuk yang otomatis. Hook dengan weight kecil (-10) menjalankan dump database ke storage eksternal sebelum resource apa pun berubah:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-backup
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: "postgres:16"
command: ["/bin/sh", "-c"]
args:
- |
pg_dump "$DATABASE_URL" | gzip | \
aws s3 cp - "s3://myapp-backups/$(date -u +%F).sql.gz"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret }}
key: urlMemastikan aplikasi benar-benar merespons sebelum dianggap sukses. Hook ini membuat Pod yang meng-curl endpoint aplikasi:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-smoke-test
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: smoke
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
code=$(curl -s -o /dev/null -w "%{http_code}" \
http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/healthz)
test "$code" = "200" || exit 1Saat release di-uninstall, resource utama dihapus — tapi data eksternal (misalnya bucket S3, atau catatan di database pusat) tidak ikut terhapus. Hook post-delete menutup celah ini:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-cleanup
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: cleanup
image: "curlimages/curl:latest"
command:
- /bin/sh
- -c
- 'curl -X DELETE "https://api.internal/cleanup?release={{ .Release.Name }}"'Generate secret sekali sebelum release di-install, lalu biarkan resource lain membacanya:
apiVersion: v1
kind: Secret
metadata:
name: {{ include "myapp.fullname" . }}-session-secret
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "-5"
type: Opaque
data:
sessionKey: {{ randAlphaNum 32 | b64enc | quote }}Pola ini memastikan secret dibuat sebelum Deployment merujuknya — karena deployment hook dijalankan setelah semua resource normal, tapi secret ini dipaksa ada lebih dulu berkat hook pre-install.
Hook yang gagal adalah salah satu debugging tersulit dalam Helm — bukan karena mekanismenya rumit, tapi karena resource hook bersifat sementara dan bisa lenyap sebelum sempat diperiksa. Urutan langkah berikut akan menyelamatkan kalian dari frustrasi:
helm status <release> (atau helm list --failed) memberi tahu hook mana yang gagal — pesan error biasanya menyebut nama Job yang bermasalah.kubectl get jobs -n <ns> akan menampilkannya. Baca log kontainernya dengan kubectl logs job/<nama> -n <ns>.helm.sh/hook-delete-policy: hook-succeeded (tanpa hook-failed) agar job yang gagal tidak dihapus setelah inspeksi. Jangan lupa menghapus manual setelah selesai.--debug. helm upgrade <release> <chart> --debug --dry-run menampilkan hook yang dirender beserta annotation-nya, memastikan hook benar-benar terdefinisi dengan event yang tepat.helm list --failed -A
helm status <release> -n <ns>
kubectl get jobs -n <ns>
kubectl logs job/<job-name> -n <ns> --tail=50Satu kesalahan penulisan annotation — misalnya pre-install di template yang seharusnya pre-upgrade — adalah penyebab paling umum hook tidak pernah jalan. Selalu verifikasi dengan --dry-run --debug yang menampilkan annotation hasil render, sebelum mengejar misteri di dalam cluster.
Hooks adalah mekanisme yang kuat, dan kekuatan itu datang dengan tanggung jawab. Berikut disiplin yang menjaga hooks tetap aman di production:
--force migration yang idempotent, nama file backup ber-timestamp, dan cleanup yang tidak error saat data sudah tidak ada.hook-weight untuk urutan eksplisit. Urutan eksekusi antar hook pada event yang sama ditentukan weight, bukan urutan deklarasi di template. Tetapkan weight dengan sengaja dan dokumentasikan di komentar: backup -10, migrate -5, smoke test 5.hook-succeeded,before-hook-creation adalah kombinasi paling aman untuk upgrade berulang: hook lama dihapus sebelum yang baru dibuat, dan hook yang sukses tidak menumpuk. Untuk debugging kegagalan, tambahkan hook-failed agar job yang gagal tetap bisa diinspeksi.--timeout). Job migrasi yang butuh waktu lama bisa melewatinya. Beri Job activeDeadlineSeconds dan sesuaikan timeout Helm, atau jalankan migrasi sebagai operasi asinkron yang dipantau di luar hook.restartPolicy: OnFailure atau Never, dan backoffLimit yang masuk akal. Script di dalam container harus keluar dengan kode non-zero saat gagal — jangan menelan error.--atomic. Saat upgrade, kombinasi --atomic --timeout 5m memastikan bahwa jika hook atau resource gagal, seluruh operasi di-rollback ke revisi sebelumnya — termasuk resource yang sudah dibuat — sehingga cluster tidak dibiarkan dalam keadaan setengah jadi.helm upgrade myapp ./myapp \
--namespace staging \
--atomic \
--timeout 10mPada episode 12 ini kalian telah memahami hooks sebagai titik penyisipan dalam siklus hidup release: sepuluh jenis hook (dari pre-install, post-install, pre-upgrade, post-upgrade, hingga pre-delete, post-delete, pre-rollback, post-rollback, pre-crd-install, dan test), cara menandai template dengan annotation helm.sh/hook, mengatur urutan dengan hook-weight, dan mengendalikan pembersihan dengan hook-delete-policy. Kalian juga telah melihat lima use case production yang nyata — migrasi database, backup sebelum upgrade, smoke test, cleanup, dan secret generation — beserta praktik terbaik yang menjaganya tetap idempotent, terukur, dan aman.
Hooks adalah salah satu alasan utama mengapa tim memilih Helm dibanding sekadar kubectl apply: urutan operasi yang sebelumnya dijalankan manual oleh engineer (migrasi dulu, deploy lalu) kini terdokumentasi di dalam chart itu sendiri dan dieksekusi secara konsisten setiap kali. Di episode 13 selanjutnya kita akan menambahkan lapisan pertahanan berikutnya dengan schema validation menggunakan JSON Schema — cara Helm memvalidasi input values sebelum render, sehingga typo dan konfigurasi salah tertangkap dengan pesan error yang jelas, bukan setelah aplikasi gagal di cluster. Sampai jumpa di episode 13!