ConfigMap vs Secret #
Saat merancang arsitektur aplikasi di Kubernetes, salah satu keputusan paling mendasar yang harus kita ambil adalah menentukan tempat penyimpanan data konfigurasi aplikasi. Kubernetes menyediakan dua resource bawaan untuk memisahkan konfigurasi dari kode aplikasi (sesuai prinsip 12-Factor App): ConfigMap dan Secret. Meskipun keduanya memiliki antarmuka API yang serupa dan dapat dikonsumsi oleh Pod dengan cara yang hampir sama, keduanya dirancang untuk tujuan keamanan, siklus hidup, dan mekanisme penyimpanan yang sangat berbeda.
Memilih resource yang salah bukan sekadar masalah estetika kode manifest, melainkan dapat membuka celah keamanan yang serius, menyulitkan proses audit, hingga mengganggu kestabilan sistem di lingkungan produksi. Artikel ini menyajikan panduan mendalam untuk membantu kita memilih antara ConfigMap dan Secret secara tepat melalui framework keputusan yang teruji, perbandingan teknis tingkat rendah, analisis kasus-kasus abu-abu, diagram alur pengambilan keputusan, serta deretan anti-pattern vs solusi nyata di produksi.
Filosofi Pemisahan Konfigurasi #
Sebelum kita masuk ke perbandingan teknis, penting untuk memahami mengapa Kubernetes membedakan kedua objek ini. Filosofi dasarnya adalah pemisahan tanggung jawab (separation of concerns) dan pembatasan hak akses (principle of least privilege).
ConfigMap dirancang untuk menjadi representasi dari apa yang membuat aplikasi berjalan, seperti file konfigurasi, flag fitur, port, URL, dan parameter tuning performa. Data di dalam ConfigMap bersifat transparan dan tidak perlu disembunyikan dari pengembang, sistem pemantauan, atau audit log umum.
Sebaliknya, Secret dirancang untuk menjadi representasi dari kunci akses atau identitas aplikasi, seperti password database, API key layanan pihak ketiga, sertifikat TLS, token autentikasi, dan kunci enkripsi. Data ini harus dilindungi secara ketat, baik saat disimpan di database cluster (etcd), saat ditransmisikan melalui jaringan, maupun saat dipasang ke dalam kontainer aplikasi.
Framework Keputusan #
Untuk mempermudah pemilihan secara konsisten di seluruh tim engineering, kita dapat menggunakan framework keputusan terstruktur. Framework ini didasarkan pada tingkat sensitivitas data, kebutuhan audit, dan cara aplikasi mengonsumsi data tersebut.
flowchart TD
Q["Apakah data ini sensitif jika terekspos ke publik atau pihak yang tidak memiliki otoritas penuh?"]
Q -->|YA| Secret["Gunakan SECRET"]
Q -->|TIDAK| ConfigMap["Gunakan CONFIGMAP"]
Berikut adalah daftar periksa cepat untuk mengelompokkan variabel aplikasi kita:
Kapan Kita Menggunakan ConfigMap? #
- Aplikasi Settings: Pengaturan internal aplikasi seperti
LOG_LEVEL(debug, info, warn, error),MAX_CONNECTION_POOL,REQUEST_TIMEOUT_SECONDS, atauTHREAD_POOL_SIZE. - URL dan Hostname: Alamat endpoint internal atau eksternal yang tidak mengandung token otorisasi, misalnya
DB_HOST=postgres-service.prod.svc.cluster.local,REDIS_PORT=6379, atauPAYMENT_GATEWAY_URL=https://api.sandbox.midtrans.com. - Flag Fitur (Feature Flags): Konfigurasi dinamis untuk mengaktifkan atau menonaktifkan fitur tertentu tanpa melakukan deployment ulang, seperti
ENABLE_NEW_DASHBOARD=trueatauMAINTENANCE_MODE=false. - File Konfigurasi Sistem: File teks utuh yang dibaca oleh server web atau proxy, seperti
nginx.conf,prometheus.yml,redis.conf, atau konfigurasi logging XML/JSON. - Metadata Environment: Identitas penanda lingkungan kerja aplikasi, seperti
APP_ENV=productionatauCLUSTER_REGION=ap-southeast-3.
Kapan Kita Menggunakan Secret? #
- Kredensial Autentikasi: Kombinasi nama pengguna dan kata sandi untuk database, Redis, broker pesan, atau repositori privat. Contoh:
DB_PASSWORD,RABBITMQ_DEFAULT_PASS. - API Token dan Private Keys: Kunci akses untuk layanan eksternal sensitif. Contoh:
STRIPE_SECRET_KEY,SENDGRID_API_KEY,AWS_SECRET_ACCESS_KEY. - Kunci Kriptografi: Kunci enkripsi simetris maupun asimetris yang digunakan aplikasi untuk mengenkripsi data pengguna, menandatangani JWT (JSON Web Token), atau melakukan hashing. Contoh:
JWT_SIGNING_KEY,AES_256_ENCRYPTION_KEY. - Sertifikat TLS/SSL: Sertifikat publik dan kunci privat terkait yang digunakan untuk mengamankan komunikasi HTTPS atau TLS pada Ingress controller atau antar-pod (mTLS).
- Docker Registry Credentials: Informasi otentikasi (username, password, registry URL) dalam format JSON yang digunakan oleh Kubelet untuk menarik image kontainer dari registry privat (imagePullSecrets).
Perbandingan Teknis Tingkat Rendah #
Meskipun di level manifes Kubernetes keduanya tampak mirip, cara Kubernetes mengelola kedua objek ini di balik layar sangat berbeda. Tabel berikut merangkum perbedaan arsitektural dan operasional antara ConfigMap dan Secret:
| Aspek Perbandingan | ConfigMap | Secret |
|---|---|---|
| Representasi Data di API | Plaintext (Teks biasa yang langsung terbaca) | Base64 Encoded (Disandikan untuk mendukung data biner) |
| Penyimpanan di etcd | Plaintext secara default | Plaintext secara default (Wajib enkripsi at-rest secara manual) |
| Media Mount (Volume) | Disimpan pada filesystem disk node | Menggunakan tmpfs (RAM-backed filesystem) untuk meminimalkan jejak di disk |
| Batas Ukuran Maksimum | 1 MB per objek | 1 MB per objek |
| Kontrol Akses (RBAC) | Terpisah, tapi sering kali diberikan akses baca luas | Sangat ketat, biasanya dibatasi hanya untuk operator dan service account |
| Kebijakan Audit | Dicatat di audit log sebagai akses data biasa | Dicatat khusus; isi payload dapat disembunyikan dari log aktivitas |
| Tipe Objek Khusus | Tidak ada (Generic) | Memiliki sub-tipe khusus (Opaque, TLS, ServiceAccountToken, dll.) |
| Mekanisme Rotasi | Update otomatis (melalui symlink) dalam beberapa menit | Update otomatis (melalui symlink) dalam beberapa menit |
Mengapa Base64 Bukan Enkripsi? #
Sering kali terjadi kesalahpahaman bahwa data di dalam Secret aman karena nilainya tidak langsung terbaca di dalam manifes YAML melainkan berupa string acak seperti dGVzdHBhc3N3b3Jk. String ini sebenarnya hanyalah encoding Base64 standar yang bertujuan agar Secret dapat menampung data biner (seperti sertifikat SSL atau file ZIP kecil) tanpa merusak parser YAML. Siapa pun yang memiliki akses ke manifes Secret tersebut dapat dengan mudah mendekodifikasi string tersebut menggunakan perintah sederhana:
echo "dGVzdHBhc3N3b3Jk" | base64 --decode
# Output: testpassword
Oleh karena itu, pengamanan Secret di Kubernetes tidak bergantung pada Base64, melainkan pada pembatasan RBAC (Role-Based Access Control) yang ketat dan konfigurasi KMS (Key Management Service) untuk mengenkripsi data tersebut sebelum ditulis ke database etcd.
Alur Pengambilan Keputusan (Decision Tree) #
Guna memastikan standarisasi pemilihan resource ini, kita dapat merujuk pada diagram alur di bawah ini saat meninjau konfigurasi aplikasi:
flowchart TD
Start["Identifikasi Variabel / File Konfigurasi"] --> C1{"Apakah data sensitif?<br>'(Password, API Key, Cert, Token)'"}
C1 -- "Ya (Sensitif)" --> SecretObj["PILIH: SECRET"]
C1 -- "Tidak (Non-Sensitif)" --> C2{"Apakah data berbeda<br>antar-environment?"}
C2 -- "Ya (Dinamis)" --> C3{"Apakah ukuran data<br>lebih besar dari 1 MB?"}
C2 -- "Tidak (Statis)" --> Hardcode["Hardcode di Code/Dockerfile<br>atau gunakan default value"]
C3 -- "Ya" --> ExternalStorage["Gunakan Storage Luar<br/>'(Persistent Volume, S3, dll)'"]
C3 -- "Tidak" --> ConfigMapObj["PILIH: CONFIGMAP"]
style SecretObj stroke:#d32f2f,stroke-width:2px
style ConfigMapObj stroke:#388e3c,stroke-width:2px
style ExternalStorage stroke:#f57c00,stroke-width:2px
Kasus Abu-Abu di Dunia Nyata #
Dalam praktik di lapangan, kita sering menemui variabel-variabel yang berada di area abu-abu. Parameter ini tidak sepenuhnya berupa rahasia negara, tetapi juga tidak pantas dibagikan secara bebas. Berikut cara kita menganalisis dan memutuskan penempatannya:
1. Connection String Database Lengkap #
Sebuah connection string biasanya ditulis seperti ini: postgresql://app_user:[email protected]:5432/main_db.
- Analisis: Meskipun berisi nama host dan port yang tidak sensitif, string ini juga memuat username dan password secara eksplisit.
- Keputusan: Wajib masuk SECRET. Memisahkan string ini menjadi bagian non-sensitif (ConfigMap) dan sensitif (Secret) lalu merangkainya kembali di level kode aplikasi adalah praktik terbaik yang sangat direkomendasikan.
2. URL Layanan Internal dengan Token Otentikasi #
Misalnya, endpoint webhook: https://slack.com/services/hooks/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX.
- Analisis: Domain Slack bersifat publik, namun token webhook di bagian akhir URL memberikan akses langsung untuk mengirim pesan ke channel internal perusahaan kita.
- Keputusan: Wajib masuk SECRET. Eksposur URL webhook Slack ke log atau git repositori dapat berujung pada eksploitasi spamming atau kebocoran informasi internal.
3. Sertifikat Otoritas Sertifikasi (CA Certificate) #
Sertifikat CA Root (ca.crt) digunakan untuk memverifikasi identitas server eksternal atau internal.
- Analisis: Berbeda dengan private key, sertifikat publik tidak dapat digunakan untuk menyamar sebagai server. Sertifikat ini didistribusikan secara luas untuk membangun rantai kepercayaan (trust chain).
- Keputusan: Gunakan CONFIGMAP. Namun, jika sertifikat tersebut dirangkai bersama dengan private key dalam satu kesatuan TLS handshake (seperti pada sertifikat server web), seluruh bundel tersebut (publik + privat) harus disimpan di dalam SECRET.
4. Admin Username (Tanpa Password) #
Konfigurasi seperti ADMIN_USERNAME=admin atau DB_USER=root.
- Analisis: Username bukanlah rahasia autentikasi utama, namun informasi ini memperkecil ruang pencarian bagi penyerang yang ingin melakukan serangan brute-force.
- Keputusan: Pada cluster dengan tingkat keamanan standar, data ini cukup diletakkan di CONFIGMAP. Namun, pada lingkungan dengan standar kepatuhan tinggi (seperti PCI-DSS atau HIPAA), menaruh username ke dalam SECRET bersama password-nya merupakan standar operasional.
Anti-Pattern vs Solusi Terbaik #
Mari kita pelajari kesalahan-kesalahan umum yang sering terjadi di dunia nyata dan bagaimana kita dapat mengatasinya dengan solusi yang aman dan efisien.
Anti-Pattern 1: Menggabungkan Kredensial Sensitif ke dalam ConfigMap #
Tindakan menyimpan password atau token langsung di dalam data ConfigMap dengan dalih kepraktisan. Hal ini membatalkan semua kebijakan pengamanan RBAC yang biasanya kita terapkan pada Secret.
# ✗ ANTI-PATTERN: Menyimpan password database di dalam ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-production
namespace: payment
data:
DB_HOST: "mysql-db-prod.internal"
DB_PORT: "3306"
DB_NAME: "payment_db"
DB_USER: "pay_admin"
DB_PASSWORD: "SuperSecretPassword123" # ← KRITIS: Kredensial bocor ke siapa pun yang memiliki akses baca ConfigMap
Solusi Terbaik #
Pisahkan variabel non-sensitif ke ConfigMap, dan simpan variabel sensitif di dalam Secret. Gabungkan keduanya saat mendefinisikan Pod menggunakan envFrom atau referensi variabel individu.
# ✓ SOLUSI: Pisahkan data non-sensitif
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-production
namespace: payment
data:
DB_HOST: "mysql-db-prod.internal"
DB_PORT: "3306"
DB_NAME: "payment_db"
DB_USER: "pay_admin"
---
# ✓ SOLUSI: Simpan kredensial di Secret Opaque
apiVersion: v1
kind: Secret
metadata:
name: app-credentials-production
namespace: payment
type: Opaque
stringData:
DB_PASSWORD: "SuperSecretPassword123" # ← Aman, dikelola oleh API Secret dengan enkripsi etcd
Anti-Pattern 2: Menaruh Seluruh File .env ke dalam Satu ConfigMap
#
Banyak tim developer yang memindahkan file .env lokal mereka secara mentah-mentah ke dalam satu entri ConfigMap. File .env tersebut biasanya berisi campuran antara konfigurasi framework, URL, dan API key produksi.
# ✗ ANTI-PATTERN: Membungkus seluruh file .env (campuran data) ke dalam ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: monolith-dotenv
namespace: core
data:
.env: |
NODE_ENV=production
PORT=8080
DEBUG=false
DATABASE_URL=postgresql://monolith:SuperPassword@db-host:5432/core
STRIPE_KEY=sk_live_51Nz8xxxxxxxxxxxxxxxxxx # ← KRITIS: Token Stripe live terekspos di ConfigMap
Solusi Terbaik #
Kita harus memisahkan file .env menjadi dua file terpisah sebelum di-deploy ke Kubernetes, atau memanfaatkan mekanisme pemetaan variabel lingkungan individual di level manifes Deployment.
# ✓ SOLUSI: Definisikan ConfigMap untuk variabel lingkungan non-sensitif
apiVersion: v1
kind: ConfigMap
metadata:
name: monolith-config
namespace: core
data:
NODE_ENV: "production"
PORT: "8080"
DEBUG: "false"
DATABASE_USER: "monolith"
DATABASE_HOST: "db-host"
DATABASE_PORT: "5432"
DATABASE_NAME: "core"
---
# ✓ SOLUSI: Definisikan Secret untuk data sensitif
apiVersion: v1
kind: Secret
metadata:
name: monolith-secret
namespace: core
type: Opaque
stringData:
DATABASE_PASSWORD: "SuperPassword"
STRIPE_KEY: "sk_live_51Nz8xxxxxxxxxxxxxxxxxx"
Aplikasi kita kemudian dapat mengonsumsi konfigurasi ini secara mulus melalui Deployment dengan menyusun ulang variabel yang dibutuhkan:
# ✓ SOLUSI: Konsumsi kedua resource di level Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: monolith-app
namespace: core
spec:
replicas: 3
selector:
matchLabels:
app: monolith
template:
metadata:
labels:
app: monolith
spec:
containers:
- name: app
image: company/monolith:v1.2.0
envFrom:
- configMapRef:
name: monolith-config
env:
- name: DATABASE_URL
value: "postgresql://$(DATABASE_USER):$(DATABASE_PASSWORD)@$(DATABASE_HOST):$(DATABASE_PORT)/$(DATABASE_NAME)"
- name: STRIPE_KEY
valueFrom:
secretKeyRef:
name: monolith-secret
key: STRIPE_KEY
Anti-Pattern 3: Hardcode Secret di dalam Docker Image (Build-Time Secret) #
Tindakan menuliskan kata sandi atau API key secara langsung di dalam Dockerfile menggunakan instruksi ENV atau menyalin file .env lokal ke dalam image saat proses build.
# ✗ ANTI-PATTERN: Menyimpan kredensial permanen di dalam image layer
FROM node:18-alpine
ENV DATABASE_URL="postgresql://user:pass@host:5432/db"
COPY . /app
WORKDIR /app
RUN npm install
CMD ["node", "server.js"]
Solusi Terbaik #
Jadikan image kontainer kita bersifat stateless dan agnostik terhadap lingkungan kerja (env-agnostic). Baca semua konfigurasi dinamis dan rahasia dari variabel lingkungan yang di-inject oleh Kubernetes pada saat runtime (bukan build-time).
# ✓ SOLUSI: Dockerfile bersih tanpa menyimpan konfigurasi sensitif
FROM node:18-alpine
COPY . /app
WORKDIR /app
RUN npm install --only=production
# Pastikan kode aplikasi membaca process.env.DATABASE_URL
CMD ["node", "server.js"]
Anti-Pattern 4: Tidak Mengaktifkan Enkripsi at-Rest untuk Secret di etcd #
Secara default, data di dalam database etcd Kubernetes disimpan dalam bentuk plaintext. Jika seseorang berhasil membobol akses ke mesin etcd atau mencuri snapshot backup etcd, seluruh Secret cluster akan langsung terekspos.
flowchart LR
APIServer["kube-apiserver"] -->|"Plaintext Secret"| ETCD["etcd (DB_PASSWORD)"]
Solusi Terbaik #
Aktifkan enkripsi pada database etcd menggunakan objek EncryptionConfiguration dengan penyedia (provider) enkripsi seperti AES-GCM atau KMS (Key Management Service) cloud (AWS KMS, GCP Cloud KMS, Azure Key Vault).
# ✓ SOLUSI: File konfigurasi EncryptionConfiguration untuk API Server
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms: # Menggunakan KMS eksternal untuk pengamanan tingkat tinggi
name: aws-kms-provider
endpoint: unix:///var/run/kmsprovider/kms.sock
cachesize: 1000
timeout: 3s
- aescbc: # Fallback menggunakan kunci lokal AES-CBC
keys:
- name: key1
secret: c3VwZXJzZWNyZXRrZXk= # Base64 dari 32-byte key
Mekanisme Pemasangan (Mounting Mechanism) #
Kita dapat mengonsumsi ConfigMap dan Secret ke dalam Pod menggunakan dua metode utama: Variabel Lingkungan (Environment Variables) atau Volume Mount (Berkas pada Filesystem). Masing-masing metode memiliki implikasi teknis yang signifikan terhadap keamanan dan pembaruan data secara dinamis.
1. Menggunakan Environment Variables #
Data langsung dipetakan ke dalam variabel lingkungan kontainer.
- Kelebihan: Sangat mudah dibaca oleh hampir semua framework aplikasi (Node.js
process.env, Pythonos.environ). - Kekurangan:
- Tidak dapat diperbarui secara dinamis (Static). Jika nilai ConfigMap atau Secret di cluster diubah, kita harus melakukan restart (rolling restart) pada Pod agar aplikasi dapat membaca nilai baru.
- Risiko Kebocoran Informasi (Leakage). Variabel lingkungan sering kali tercatat di crash dump, log debugging kontainer, atau terekspos ketika kita menjalankan perintah debugging seperti
kubectl describe podatauenvdi dalam kontainer.
2. Menggunakan Volume Mounts #
Data dipetakan sebagai file di dalam direktori khusus kontainer (misalnya /etc/config atau /run/secrets).
- Kelebihan:
- Mendukung Pembaruan Dinamis (Hot Reload / Symlink Swap). Kubelet secara periodik memeriksa perubahan pada ConfigMap/Secret. Ketika ada perubahan di cluster, Kubelet akan memperbarui file di dalam kontainer secara otomatis menggunakan mekanisme pemindahan symlink secara atomik tanpa perlu merestart Pod.
- Keamanan Lebih Baik (Khusus Secret). Secret yang di-mount sebagai volume dipasang menggunakan sistem file
tmpfs(RAM). Data tidak pernah ditulis ke disk fisik node worker, sehingga tidak ada jejak forensik yang tertinggal jika media penyimpanan node tersebut disita atau diretas.
- Kekurangan: Aplikasi harus diprogram secara khusus untuk mendengarkan perubahan file (menggunakan pustaka watcher seperti
fsnotifyatau mendeteksi event perubahan inotify) agar dapat melakukan reload konfigurasi secara langsung di memori.
sequenceDiagram
participant Dev as Developer
participant API as Kubernetes API
participant Kube as Kubelet Daemon
participant Pod as Pod Container (Volume Mount)
Dev->>API: Ubah nilai ConfigMap di Cluster
API-->>Kube: Sinkronisasi objek (Watch Event)
Kube->>Pod: Perbarui file via symlink swap atomik
Note over Pod: File terupdate tanpa restart!
Checklist Ulasan Arsitektur Konfigurasi Pod #
Sebelum meluncurkan aplikasi ke lingkungan produksi, pastikan tim engineering kita melakukan audit konfigurasi menggunakan checklist berikut:
PENGELOLAAN DATA SENSITIF:
□ Seluruh kredensial (API key, password, token) telah dipindahkan dari ConfigMap ke Secret.
□ Tidak ada konfigurasi sensitif yang tercantum di file Dockerfile (ENV) atau hardcoded di kode sumber.
□ String koneksi database dipecah menjadi hostname (ConfigMap) dan kredensial (Secret).
KONTROL AKSES & KEAMANAN INFRASTRUKTUR:
□ Akses RBAC ke API Secret dibatasi ketat hanya untuk service account aplikasi yang membutuhkan.
□ Enkripsi at-rest untuk Secret di etcd telah diaktifkan di tingkat control plane cluster.
□ Kontainer aplikasi berjalan sebagai non-root user untuk membatasi akses ke file Secret yang di-mount.
OPERASIONAL & PEMELIHARAAN:
□ Ukuran total ConfigMap atau Secret tidak mendekati batas maksimal etcd (1 MB).
□ Untuk konfigurasi dinamis yang sering berubah, telah dipastikan metode konsumsi (Volume vs Env) mendukung siklus hidup reload aplikasi.
□ File konfigurasi berukuran besar (seperti konfigurasi proxy Nginx) di-mount sebagai Volume untuk menghindari limitasi ukuran etcd.
Ringkasan #
- Bedakan peran fundamental — ConfigMap digunakan untuk pengaturan operasional aplikasi yang tidak sensitif; Secret digunakan untuk melindungi kredensial, sertifikat, dan token autentikasi.
- Base64 bukanlah enkripsi — Nilai Secret yang di-encode Base64 hanya bertujuan untuk kompatibilitas data biner; perlindungan sejati diperoleh melalui pembatasan RBAC dan aktivasi KMS Encryption at-rest di etcd.
- Gunakan Volume Mount untuk pembaruan dinamis — Mounting ConfigMap/Secret sebagai volume memungkinkan pembaruan data secara langsung (hot reload) di dalam kontainer tanpa perlu me-restart Pod, berbeda dengan Environment Variables yang bersifat statis.
- Pisahkan string koneksi — Hindari penulisan URI lengkap yang mengandung kredensial di dalam ConfigMap; pisahkan host ke ConfigMap dan password ke Secret, lalu gabungkan di level Pod manifest atau kode aplikasi.
- Hormati batas ukuran 1 MB — Baik ConfigMap maupun Secret memiliki batas ukuran keras sebesar 1 MB per objek karena keterbatasan arsitektur etcd; gunakan volume eksternal atau object storage untuk file konfigurasi raksasa.
- Gunakan tmpfs untuk Secret — Mount Secret sebagai volume memanfaatkan RAM filesystem (
tmpfs) pada host worker node untuk menjamin kredensial tidak pernah tertulis ke media penyimpanan fisik.
← Sebelumnya: External Secret Manager Berikutnya: Configuration Hot Reload →