Optimalkan penggunaan Vault Agent dengan in-memory caching untuk memangkas beban API dan latensi, lalu kuasai template Consul Template tingkat lanjut dengan loop, kondisional, dan validasi render sebelum dipakai di produksi.

Setelah di episode 14 sebelumnya kita membahas konsep dasar Vault Agent — Auto-Auth, Template & Sink yang merender file konfigurasi berisi secret dari Vault — pada episode kali ini kita akan menaikkan level penggunaannya ke dua fitur yang paling sering dipakai di produksi: Vault Agent Caching dan Advanced Templates.
Mengapa dua hal ini penting? Bayangkan satu node menjalankan sepuluh microservices, dan masing-masing service membaca secret yang sama setiap kali melakukan connection pooling atau healthcheck. Tanpa caching, setiap pembacaan akan memicu HTTP round-trip ke Vault — memboroskan API request, menambah latensi, dan menambah beban di sisi server Vault. Sementara itu, template sederhana dengan satu secret memang mudah, tapi di dunia nyata kebutuhan lebih kompleks: kalian ingin merender daftar kredensial dalam satu file, membuat konfigurasi yang kondisional, atau menggabungkan beberapa secret sekaligus. Di episode ini kita bedah keduanya sampai tuntas.
Vault Agent Caching bekerja seperti cache di lapisan proxy: agent menyimpan hasil pembacaan secret di memori, lalu melayani request yang sama tanpa perlu meneruskan ke Vault. Semua request yang masuk ke agent melalui listener lokal akan dijawab dari cache selama masih valid.
Alur kerjanya:
Aplikasi 1 ─┐
Aplikasi 2 ─┼─> listener lokal 127.0.0.1:8200 ──> Vault Agent Cache ──> Vault Server
Aplikasi 3 ─┘ │
└─> jawaban dari cache (jika masih TTL valid)Aplikasi tidak perlu diubah sama sekali — mereka tetap membaca dari Vault API, hanya saja alamat yang mereka tuju kini adalah alamat agent, bukan alamat Vault asli. Inilah yang membuat caching ini transparent.
Untuk mengaktifkan caching, tambahkan blok cache dan satu listener yang menjadi "pintu masuk" bagi aplikasi:
pid_file = "/var/run/vault-agent.pid"
vault {
address = "http://127.0.0.1:8200"
}
listener "tcp" {
address = "127.0.0.1:8201"
tls_disable = true
}
cache {
use_auto_auth_token = true
}
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault-agent/role-id"
secret_id_file_path = "/etc/vault-agent/secret-id"
}
}
sink "file" {
config = {
path = "/etc/vault-agent/token"
}
}
}Poin penting dari konfigurasi di atas:
listener "tcp" — listener ini adalah API surface yang dilihat aplikasi. Aplikasi akan mengarahkan VAULT_ADDR ke http://127.0.0.1:8201.cache {} — mengaktifkan penyimpanan hasil read di memori.use_auto_auth_token = true — membuat agent otomatis menginjeksi token hasil Auto-Auth ke setiap request yang diteruskan ke Vault. Ini sangat berguna karena aplikasi tidak perlu menyediakan token sama sekali — token dikelola dan disuntikkan oleh agent. Tanpa opsi ini, aplikasi harus menyuplai token valid yang dikirim agent ke Vault.Warning
Saat mengaktifkan caching, jangan gunakan port yang sama antara listener agent dan address Vault asli. Kebingungan paling umum di tim baru: aplikasi mengarahkan VAULT_ADDR ke listener, lupa bahwa request sebenarnya sudah diteruskan ke upstream — lalu heran kenapa muncul connection refused. Periksa vault.status untuk memastikan koneksi ke agent berjalan benar.
Apa yang membuat cache ini aman dan tidak menyajikan data basi? Jawabannya ada pada lease dan TTL:
Tip
Aturan praktis: gunakan cache untuk secret yang jarang berubah (config, credential statis). Untuk secret yang sering berubah dan kritis (dynamic database credentials), pertimbangkan membaca langsung dari Vault atau menonaktifkan cache pada path tertentu — cache yang menyajikan kredensial lama justru berbahaya.
Di episode 14 kita sudah melihat bentuk template paling sederhana: {{ with secret "secret/data/myapp" }}. Sekarang kita naik level dengan fitur-fitur yang membuat template benar-benar powerful.
withSecret)Sintaks {{ with secret "path" }} menetapkan konteks dot ke hasil secret. Ini berguna saat hanya perlu sebagian data. Ada juga fungsi withSecret yang memungkinkan kalian menangkap hasil sekaligus mendeteksi apakah secret ada:
{{- $app := withSecret "secret/data/myapp" }}
{{- if $app }}
DB_HOST={{ $app.Data.data.DB_HOST }}
DB_PORT={{ $app.Data.data.DB_PORT }}
{{- end }}Perhatikan penggunaan $app — hasil secret ditangkap ke variabel sehingga bisa dipakai berkali-kali dalam satu template. Ini jauh lebih efisien daripada memanggil secret berkali-kali.
rangeSalah satu kekuatan Consul Template adalah iterasi. Misalkan secret secret/data/redis-clusters menyimpan daftar cluster dalam bentuk list:
{{- with secret "secret/data/redis-clusters" }}
{{- range $cluster := .Data.data.clusters }}
redis-server cluster={{ $cluster.name }} host={{ $cluster.host }} port={{ $cluster.port }}
{{- end }}
{{- end }}Template di atas merender satu baris konfigurasi untuk setiap elemen dalam list clusters — tanpa perlu tahu berapa jumlah elemennya. Ini sangat berguna untuk service discovery dan konfigurasi berbasis daftar.
if / else)Kadang sebuah baris konfigurasi hanya boleh muncul dalam kondisi tertentu. Misalnya flag debug yang hanya aktif di environment development:
{{- with secret "secret/data/myapp" }}
{{- if eq .Data.data.ENVIRONMENT "dev" }}
DEBUG_MODE=true
{{- else }}
DEBUG_MODE=false
{{- end }}
{{- end }}Fungsi eq, ne, contains, hasPrefix dari Go template siap dipakai untuk logika yang lebih kompleks.
toJSON, parseJSON & envConsul Template juga menyediakan fungsi utilitas untuk mengubah bentuk data. Kombinasi yang paling sering dipakai untuk aplikasi modern adalah toJSON (mengubah data secret menjadi JSON) dan parseJSON (mem-parsing string JSON menjadi objek):
{{- with secret "secret/data/myapp" }}
FEATURE_FLAGS={{ toJSON .Data.data.feature_flags }}
{{- end }}
# Nilai dari environment tempat agent berjalan (bukan dari Vault)
LOG_LEVEL={{ env "LOG_LEVEL" }}{{- with secret "secret/data/backends" }}
{{- range $item := parseJSON .Data.data.list }}
upstream {{ $item.name }} { server {{ $item.host }}; }
{{- end }}
{{- end }}Tip
Fungsi env memungkinkan template memakai environment variable proses agent — sangat berguna untuk nilai non-rahasia seperti environment name, hostname, atau region. Bedakan dengan jelas: secret datang dari Vault, konfigurasi non-rahasia bisa datang dari env.
error_on_missing_keySalah satu perlindungan paling penting yang sering diabaikan adalah error_on_missing_key. Tanpa opsi ini, template dengan key yang salah ketik akan menghasilkan nilai kosong diam-diam — berbahaya karena aplikasi bisa berjalan dengan konfigurasi kosong tanpa peringatan. Aktifkan agar agent error dan berhenti merender saat key tidak ditemukan:
template {
source = "/etc/vault-agent/templates/app.env.tpl"
destination = "/var/lib/myapp/.env"
perms = 0600
error_on_missing_key = true
}Warning
Dengan error_on_missing_key = true, template yang mereferensikan key yang tidak ada di Vault akan gagal total — agent mencatat error dan tidak menulis file. Ini adalah perilaku fail-closed yang benar: lebih baik aplikasi tidak jalan karena konfigurasi jelas salah, daripada jalan diam-diam dengan nilai kosong. Pasangkan dengan -render di CI untuk menangkap error lebih awal.
Kekuatan konsul template adalah fleksibilitasnya untuk format file apa pun — karena pada dasarnya ia hanya teks. Contoh merender config.json untuk aplikasi Node.js:
{{- with secret "secret/data/myapp" }}
{
"app": {
"env": "{{ .Data.data.ENVIRONMENT }}",
"port": {{ .Data.data.PORT }}
},
"db": {
"host": "{{ .Data.data.DB_HOST }}",
"password": "{{ .Data.data.DB_PASSWORD }}"
}
}
{{- end }}Begitu juga untuk konfigurasi nginx yang memerlukan upstream credential, atau file application.yml ala Spring Boot — selama kalian paham sintaksnya, semua format teks bisa di-render.
Important
Perhatikan whitespace control {{- dan -}}. Tanda minus tersebut memotong spasi/baris kosong di sekitar blok template sehingga output tetap rapi dan valid (misalnya JSON tidak boleh ada koma menggantung atau baris kosong di tengah). Kesalahan paling umum di template JSON: output tidak valid karena spasi tak terduga.
vault agent -renderBayangkan kalian menulis template dengan sintaks yang salah dan baru menyadarinya saat produksi — itu mimpi buruk. Vault Agent menyediakan mode validasi tanpa menjalankan daemon: flag -render merender semua template satu kali lalu keluar.
vault agent -config=/etc/vault-agent/config.hcl -renderOutput yang dihasilkan:
=== rendered: /var/lib/myapp/.env ===
APP_ENV=production
APP_PORT=8080
DB_HOST=postgres.internal.local
DB_PASSWORD=********
=== rendered: /var/lib/myapp/config.json ===
{
"app": {
"env": "production",
"port": 8080
},
"db": {
"host": "postgres.internal.local",
"password": "********"
}
}Tip
Jadikan vault agent -render bagian dari pipeline CI/CD kalian. Setiap perubahan template wajib lolos render sebelum di-deploy, sehingga kesalahan sintaks Consul Template tertangkap lebih awal — bukan saat pod crash di produksi.
Setelah memahami caching dan template, mari kita peta-kan kapan memakai agent dan kapan memakai SDK secara langsung di aplikasi:
| Skenario | Vault Agent | SDK Langsung |
|---|---|---|
| Secret statis (KV, config) | Sangat cocok | Bisa, tapi menambah boilerplate |
| Dynamic DB credentials per request | Tidak ideal (lease di cache) | Pilihan utama |
| Secret sangat sering dirotasi | Rentan stale (butuh tuning TTL) | Lebih akurat |
| Banyak aplikasi / bahasa berbeda | Satu pola, semua bahasa | SDK berbeda per bahasa |
| Aplikasi tanpa akses API Vault | Sempurna (tidak perlu akses) | Tidak mungkin |
| Butuh enkripsi/dekripsi per-field (Transit) | Tidak didukung | Cocok (SDK panggil transit) |
| Startup cepat tanpa Vault | Rendah (file sudah ada) | Tergantung koneksi Vault |
Warning
Aturan paling penting: jangan mencampur keduanya tanpa alasan. Ada tim yang mengaktifkan cache agent, lalu di dalam aplikasi mereka memakai SDK untuk membaca secret lagi — hasilnya duplikasi path akses dan kebingungan soal lease siapa yang merenew. Pilih satu pola dominan per aplikasi.
| Kesalahan | Gejala | Solusi |
|---|---|---|
| Cache menyajikan secret lama setelah rotasi | Aplikasi memakai kredensial basi | Tuning TTL cache; untuk secret dinamis hindari cache |
use_auto_auth_token tidak diaktifkan | Aplikasi dapat 403 permission denied | Set use_auto_auth_token = true di blok cache |
Whitespace tak terkontrol ({{ tanpa -) | JSON/YAML invalid saat render | Gunakan {{- dan -}} |
Memanggil secret berulang dalam satu template | Banyak round-trip ke Vault | Tangkap ke variabel sekali via withSecret |
Lupa -render sebelum deploy | Template rusak ketahuan di produksi | Validasi di CI dengan vault agent -render |
| Cache diaktifkan untuk dynamic creds | Kredensial kadaluarsa tak terdeteksi | Baca langsung / sesuaikan TTL dengan default_ttl |
Blok listener tidak ada padahal cache aktif | Agent error: listener not found | Pastikan listener "tcp" terdefinisi |
Pada episode 15 ini kita telah membahas Vault Agent Caching — bagaimana hasil read secret disimpan di memori lewat blok cache, bagaimana lease & TTL mengatur eviction, dan bagaimana use_auto_auth_token menghilangkan kebutuhan aplikasi memegang token. Kita juga menguasai Advanced Templates: loop dengan range, kondisional if, alias withSecret, render untuk format JSON/YAML, serta praktik validasi dengan vault agent -render.
Kunci episode ini: caching membuat aplikasi ringan dan responsif, sementara template lanjutan membuat konfigurasi dinamis menjadi ekspresif — dan keduanya harus diuji sebelum produksi.
Di episode 16 selanjutnya kita akan berpindah sisi: dari pendekatan tidak langsung (agent) menuju integrasi langsung dengan aplikasi web menggunakan SDK resmi di Python, Node.js, Go, dan Laravel, lengkap dengan pola retry, renewal, dan fallback. Pastikan tetap semangat!