Anti-Pattern Configuration #
Konfigurasi yang buruk di Kubernetes sering tidak terlihat masalahnya sampai terjadi insiden — secret bocor karena ditaruh di tempat yang salah, deployment gagal karena konfigurasi tidak konsisten antar environment, atau aplikasi tidak bisa diupdate dengan aman karena konfigurasi dan kode terlalu tergabung. Artikel ini mengkompilasi anti-pattern yang paling sering ditemui agar kamu bisa mengenali dan menghindarinya.
Anti-Pattern 1: Secret di Git #
# ANTI-PATTERN: file konfigurasi berisi secret di-commit ke Git
# values-production.yaml
database:
password: "SuperSecret123" # ← di Git, bisa dibaca semua contributor
# .env.production
STRIPE_API_KEY=sk_live_abc123xyz # ← di Git
JWT_SECRET=mysupersecretjwtkey # ← di Git
Konsekuensi:
→ Siapapun dengan akses repository bisa baca secret
→ Secret sudah ada di Git history meski file-nya dihapus
→ Jika repository bocor (publik tidak sengaja), semua secret bocor
→ Developer baru otomatis punya akses ke semua credential production
Solusi:
→ Gunakan external secret manager (Vault, AWS SM)
→ Atau Sealed Secrets / SOPS jika perlu store di Git
→ Scan repository secara otomatis dengan git-secrets atau truffleHog
→ Jika sudah ter-commit: rotate semua credential, lalu bersihkan history
Anti-Pattern 2: Satu ConfigMap Raksasa #
# ANTI-PATTERN: semua konfigurasi dalam satu ConfigMap besar
apiVersion: v1
kind: ConfigMap
metadata:
name: everything-config # ← nama generik, berisi semua hal
data:
# App config
APP_ENV: production
LOG_LEVEL: warn
# Database
DB_HOST: postgres
DB_PORT: "5432"
# Redis
REDIS_HOST: redis
REDIS_PORT: "6379"
# Feature flags
FEATURE_DARK_MODE: "true"
FEATURE_NEW_DASHBOARD: "false"
# Nginx config
nginx.conf: |
...200 lines of nginx config...
# App YAML
app.yaml: |
...100 lines of application config...
Konsekuensi:
→ Satu ConfigMap mendekati batas 1MB dengan cepat
→ Semua aplikasi perlu mount/inject seluruh ConfigMap meski hanya butuh sedikit
→ Update satu nilai memengaruhi semua Pod yang menggunakan ConfigMap ini
→ Sulit melacak nilai mana yang digunakan oleh service mana
→ Tidak bisa memberikan akses RBAC yang granular per bagian konfigurasi
# BENAR: pisah per concern
ConfigMap: api-app-config (konfigurasi aplikasi API)
ConfigMap: api-feature-flags (feature flags — sering berubah, terpisah)
ConfigMap: nginx-config (konfigurasi nginx)
ConfigMap: shared-infra-config (URL dan hostname infrastruktur shared)
Anti-Pattern 3: Hardcode Environment di Image #
# ANTI-PATTERN: environment hardcode di image
FROM python:3.11
ENV APP_ENV=production # ← hardcode di image
ENV LOG_LEVEL=warn # ← hardcode di image
ENV DB_HOST=prod-postgres.internal # ← hardcode di image
COPY . /app
CMD ["python", "main.py"]
Konsekuensi:
→ Image yang sama tidak bisa digunakan di staging atau development
→ Setiap perubahan konfigurasi membutuhkan build image baru
→ Image production dan staging berbeda secara teknis → "works in staging"
tidak lagi menjamin "works in production"
→ Melanggar prinsip 12-Factor App: build sekali, run di mana saja
# BENAR: image bersih tanpa konfigurasi environment
FROM python:3.11
COPY . /app
CMD ["python", "main.py"]
# Semua konfigurasi diinject via ConfigMap/Secret saat runtime
Anti-Pattern 4: Referensi ConfigMap yang Tidak Exists #
# ANTI-PATTERN: referensi ke ConfigMap yang belum tentu ada
spec:
containers:
- name: api
envFrom:
- configMapRef:
name: app-config # ← bagaimana jika ConfigMap ini belum dibuat?
Konsekuensi:
→ Pod stuck di ContainerCreating / CreateContainerConfigError
→ Error: configmap "app-config" not found
→ Terjadi saat deploy ke environment baru tanpa menyiapkan ConfigMap dulu
# BENAR: gunakan optional atau pastikan urutan deploy
envFrom:
- configMapRef:
name: app-config
optional: true # Pod tetap berjalan meski ConfigMap tidak ada
# Tapi hati-hati: nilai env var tidak ter-inject
# Atau: pastikan CI/CD selalu deploy ConfigMap sebelum Deployment
# dan test di pre-deployment check:
# kubectl get configmap app-config -n production || exit 1
Anti-Pattern 5: Tidak Ada Validasi Konfigurasi #
# ANTI-PATTERN: nilai konfigurasi tidak divalidasi sebelum digunakan
data:
MAX_CONNECTIONS: "abc" # ← bukan angka tapi dideploy ke production
TIMEOUT_SECONDS: "-1" # ← nilai negatif tidak valid
DB_PORT: "99999" # ← port di luar range valid
Konsekuensi:
→ Aplikasi crash saat startup karena gagal parse konfigurasi
→ Atau lebih buruk: aplikasi jalan tapi dengan behavior yang tidak terduga
→ Masalah baru terdeteksi saat Pod sudah di-deploy dan sedang digunakan
# BENAR: validasi konfigurasi saat startup
import os, sys
def validate_config():
errors = []
max_conn = os.getenv('MAX_CONNECTIONS')
if not max_conn or not max_conn.isdigit() or int(max_conn) <= 0:
errors.append(f"MAX_CONNECTIONS must be a positive integer, got: {max_conn}")
timeout = os.getenv('TIMEOUT_SECONDS')
if not timeout or not timeout.isdigit() or int(timeout) < 1:
errors.append(f"TIMEOUT_SECONDS must be >= 1, got: {timeout}")
if errors:
for e in errors:
print(f"CONFIG ERROR: {e}", file=sys.stderr)
sys.exit(1) # fail fast: jangan jalankan aplikasi dengan konfigurasi invalid
validate_config() # panggil sebelum inisialisasi apapun
Anti-Pattern 6: Update ConfigMap In-Place Tanpa Rolling Restart #
# ANTI-PATTERN: update ConfigMap langsung tanpa restart Deployment
kubectl edit configmap app-config -n production
# Ubah LOG_LEVEL dari warn ke debug
# Pod yang sudah running: masih pakai konfigurasi lama via env var
# Hanya Pod baru yang akan punya konfigurasi baru
# → Cluster dalam kondisi inconsistent: sebagian Pod pakai konfigurasi lama, sebagian baru
# BENAR: setelah update ConfigMap, trigger rolling restart
kubectl apply -f new-configmap.yaml -n production
kubectl rollout restart deployment/api -n production
# Atau jika menggunakan Helm dengan checksum annotation,
# rolling restart terjadi otomatis saat ConfigMap berubah
Anti-Pattern 7: Permission Secret yang Terlalu Luas #
# ANTI-PATTERN: service account dengan akses penuh ke semua Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: app-role
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# ← akses penuh ke SEMUA secret di SEMUA namespace
Konsekuensi:
→ Jika aplikasi di-compromise, attacker punya akses ke semua secret cluster
→ Credential database, API key, certificate — semuanya terbaca
→ Blast radius sangat besar dari satu service yang bermasalah
# BENAR: akses minimal, namespace-scoped, hanya resource yang diperlukan
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # Role (bukan ClusterRole) — scope namespace
metadata:
name: api-role
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-keys"] # hanya secret yang diperlukan
verbs: ["get"] # hanya get, bukan list/watch/create/update/delete
Ringkasan #
- Jangan commit Secret ke Git — meski sudah di-encode base64 atau sudah di-delete, secret masih ada di Git history; rotate semua credential jika ini pernah terjadi.
- Pisahkan ConfigMap per concern — satu ConfigMap raksasa sulit di-maintain dan di-govern; pisahkan per service, per concern (app vs feature flags vs infra), agar granular.
- Image harus environment-agnostic — tidak ada ENV production di Dockerfile; semua konfigurasi diinject saat runtime via ConfigMap/Secret; satu image untuk semua environment.
- Gunakan
optional: trueuntuk referensi yang mungkin belum ada — atau pastikan CI/CD deploy ConfigMap/Secret sebelum Deployment; Pod tidak bisa start jika resource yang direferensikan tidak ada.- Validasi konfigurasi saat startup — fail fast jika nilai konfigurasi tidak valid; lebih baik Pod gagal start dengan error jelas daripada jalan dengan konfigurasi salah.
- RBAC minimal untuk Secret — gunakan
resourceNamesuntuk batasi akses ke Secret tertentu saja; namespace-scoped Role bukan ClusterRole; hanya verbget, bukanlist.
← Sebelumnya: Multi-Environment Configuration Berikutnya: Deployment Strategy Overview →