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 undo tidak tersedia; di saat insiden, ini membuat segalanya lebih lambat dan berisiko.

← Sebelumnya: GitOps   Berikutnya: RBAC →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact