Anti-Pattern Deployment #
Deployment yang terlihat berjalan lancar bisa menyimpan masalah yang baru muncul saat traffic tinggi, saat engineer sedang libur, atau saat rollback paling dibutuhkan. Kesalahan deployment jarang terlihat di development environment — mereka muncul di production, di saat yang paling tidak tepat. Artikel ini mengkompilasi anti-pattern yang paling sering menyebabkan insiden deployment.
Anti-Pattern 1: Image Tag latest
#
# ANTI-PATTERN: menggunakan tag latest di production
spec:
containers:
- name: api
image: my-api:latest # ← tidak pernah tahu versi mana yang jalan
imagePullPolicy: Always # ← selalu pull, performa buruk dan tidak deterministik
Konsekuensi:
→ Tidak bisa rollback ke "versi sebelumnya" karena tidak tahu versi mana
→ Node yang berbeda mungkin pull image berbeda di waktu berbeda
→ Deploy ulang tanpa perubahan kode bisa mendapat versi berbeda
(jika ada push baru ke latest di antara deployment)
→ Tidak bisa reproduce masalah production karena image sudah berubah
→ Audit trail tidak ada: "kita deploy versi apa kemarin?"
# BENAR: pin dengan commit hash atau semantic version
spec:
containers:
- name: api
image: my-api:a1b2c3d # commit hash — immutable
# atau:
image: my-api:2.1.3 # semantic version
imagePullPolicy: IfNotPresent # hanya pull jika belum ada di node
Anti-Pattern 2: Tidak Ada readinessProbe #
# ANTI-PATTERN: tidak ada readinessProbe
spec:
containers:
- name: api
image: my-api:v2
livenessProbe: # hanya liveness, tidak ada readiness
httpGet:
path: /health
port: 8080
# Tidak ada readinessProbe
Konsekuensi:
Rolling update: Pod v2 dianggap Ready segera setelah container started
→ Traffic dikirim ke Pod yang sedang inisialisasi database connection
→ 503 errors atau connection refused selama beberapa detik di setiap Pod baru
→ Semakin banyak replicas yang di-update bersamaan, semakin banyak error
→ Tidak terlihat di staging tapi sangat terasa di production
dengan traffic real
# BENAR: readinessProbe wajib untuk zero-downtime rolling update
spec:
containers:
- name: api
readinessProbe:
httpGet:
path: /health/ready # endpoint terpisah dari liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
Anti-Pattern 3: Tidak Ada Resource Requests/Limits #
# ANTI-PATTERN: Pod tanpa resource requests dan limits
spec:
containers:
- name: api
image: my-api:v2
# Tidak ada resources block
Konsekuensi pada deployment:
→ Scheduler tidak tahu berapa resource yang dibutuhkan Pod
→ Pod bisa di-schedule ke node yang sudah penuh
→ Pod baru gagal start karena node kehabisan memory
→ OOMKilled ditengah rolling update → Pod crash berulang
→ Deployment stuck: Pod baru tidak pernah Ready
→ maxUnavailable Pod lama belum dihapus → traffic ke v1 yang mungkin sudah
tidak kompatibel dengan state terbaru
Anti-Pattern 4: Deployment dan Database Migration dalam Satu Step #
# ANTI-PATTERN: migration dijalankan sebagai init container di Deployment
spec:
initContainers:
- name: migrate
image: my-api:v2
command: ["python", "manage.py", "migrate"]
# Migration dijalankan SETIAP KALI Pod baru dibuat
Konsekuensi:
Deployment dengan 5 replicas → 5 Pod baru → migration dijalankan 5 kali
sekaligus secara paralel!
Race condition: dua migration instance mencoba apply migration yang sama
→ Bisa menyebabkan lock pada database
→ Atau duplicate migration failure yang membuat semua Pod crash
→ Rollback deployment tidak rollback migration yang sudah dijalankan
Lebih fatal: jika migration destructive (hapus kolom), semua instance
mencoba hapus kolom yang sama → failure cascade
# BENAR: migration sebagai Job tersendiri, bukan init container
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration-v2-001
spec:
completions: 1 # hanya 1 instance yang jalan
parallelism: 1
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: my-api:v2
command: ["python", "manage.py", "migrate"]
Anti-Pattern 5: Lupa preStop Hook untuk Graceful Shutdown #
# ANTI-PATTERN: tidak ada preStop dan terminationGracePeriodSeconds kecil
spec:
terminationGracePeriodSeconds: 5 # terlalu singkat
containers:
- name: api
# Tidak ada lifecycle.preStop
Yang terjadi saat Pod dihapus dalam rolling update:
1. Kubernetes kirim SIGTERM ke container
2. Kubernetes SEGERA hapus Pod dari Endpoints Service
(kira-kira bersamaan dengan SIGTERM)
3. Tapi: kube-proxy perlu waktu ~1-2 detik untuk update iptables rules
di semua node
4. Selama jeda itu, ada traffic yang masih dikirim ke Pod yang
sedang di-shutdown
5. terminationGracePeriodSeconds=5 → SIGKILL dikirim setelah 5 detik
meski masih ada request yang diproses
Hasilnya: connection reset, 502/503 errors selama rolling update
# BENAR: preStop + terminationGracePeriodSeconds yang cukup
spec:
terminationGracePeriodSeconds: 60
containers:
- name: api
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
# sleep 5 memberi waktu iptables rules ter-update sebelum Pod
# benar-benar berhenti menerima koneksi baru
Anti-Pattern 6: Deploy Manual ke Production (Bypass GitOps) #
# ANTI-PATTERN: kubectl apply langsung ke production
kubectl apply -f deployment.yaml -n production
# atau:
kubectl set image deployment/api api=my-api:v2 -n production
Konsekuensi:
→ Tidak ada review — satu orang bisa deploy tanpa approval
→ Tidak ada audit trail yang konsisten
(siapa deploy apa kapan, dengan perubahan apa)
→ Configuration drift: cluster tidak sama dengan Git
→ ArgoCD/Flux menandai OutOfSync dan mungkin rollback perubahan tersebut
→ Tidak bisa reproduce dari scratch: "state cluster" tidak terdefinisi
Untuk hotfix yang butuh cepat:
→ Tetap buat commit ke Git (branch hotfix atau langsung ke main)
→ Trigger sync manual di ArgoCD: argocd app sync api-production
→ Atau tambahkan commit ke repository GitOps dan biarkan
operator sync dalam ~1 menit
Anti-Pattern 7: revisionHistoryLimit Terlalu Kecil atau Nol #
# ANTI-PATTERN: hapus semua history
spec:
revisionHistoryLimit: 0 # ← tidak ada history yang tersimpan
Konsekuensi:
kubectl rollout undo deployment/api
→ Error: no rollout history available
Saat insiden terjadi dan butuh rollback instan:
→ Tidak bisa rollback via kubectl rollout undo
→ Harus push image lama lagi lewat CI/CD → butuh waktu lebih lama
→ Atau kubectl set image secara manual → bypass GitOps
# BENAR: simpan history yang cukup
spec:
revisionHistoryLimit: 5 # minimal 3-5 revision
Anti-Pattern 8: maxSurge dan maxUnavailable Keduanya Nol #
# ANTI-PATTERN: konfigurasi yang tidak valid
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 0 # ← Kubernetes menolak ini: tidak mungkin update
Konsekuensi:
Kubernetes validation akan menolak konfigurasi ini.
Jika entah bagaimana bisa masuk, update tidak akan pernah berjalan:
- maxSurge: 0 → tidak bisa buat Pod baru ekstra
- maxUnavailable: 0 → tidak bisa hapus Pod lama
→ Deadlock: tidak ada yang bisa dilakukan
Konfigurasi umum yang benar:
maxSurge: 1, maxUnavailable: 0 (zero-downtime, lebih lambat)
maxSurge: 25%, maxUnavailable: 0 (zero-downtime, lebih cepat)
maxSurge: 1, maxUnavailable: 1 (default Kubernetes)
Ringkasan #
- Jangan gunakan tag
latest— pin image dengan commit hash atau semantic version; tanpa ini rollback tidak mungkin dan audit trail tidak ada.- readinessProbe wajib untuk rolling update — tanpa readiness probe, traffic dikirim ke Pod sebelum siap; error rate naik selama setiap deployment.
- Migration sebagai Job terpisah, bukan init container — init container di-run setiap Pod dibuat; untuk Deployment multi-replica ini berarti migration paralel yang bisa menyebabkan race condition.
- preStop sleep untuk graceful shutdown — tanpa preStop, ada jeda antara Pod dihapus dari Endpoints dan iptables rules ter-update; request yang masuk selama jeda itu akan gagal.
- Jangan bypass GitOps dengan kubectl langsung — semua perubahan harus melalui Git; hotfix sekalipun harus lewat commit dan trigger sync manual.
- revisionHistoryLimit minimal 3-5 — tanpa history, rollback via
kubectl rollout undotidak tersedia; di saat insiden, ini membuat segalanya lebih lambat dan berisiko.