# Membangun Pipeline CI/CD Zero-Downtime Menggunakan Kubernetes dan ArgoCD

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/membangun-pipeline-cicd-zero-downtime-menggunakan-kubernetes-dan-argocd

---

# Membangun Pipeline CI/CD Zero-Downtime Menggunakan Kubernetes dan ArgoCD

## 1. Titik Awal Masalah: Mengapa Arsitektur Lama Sering Ambruk di Produksi

Ingatkah Anda malam-malam panjang ketika tombol 'deploy' terasa seperti tombol pemicu bom waktu? Di era arsitektur monolitik lama, atau bahkan di masa awal migrasi ke mesin virtual dengan Nginx, PHP-FPM, dan skrip bash manual, merilis pembaruan sering kali berarti berdoa. Bayangkan skenario klasik: cron job berjalan setengah mati, antrian Redis menumpuk, koneksi WebSocket klien terputus secara brutal, dan tiba-tiba pagerduty berbunyi nyaring karena *database deadlock* atau layanan mengalami *502 Bad Gateway* selama sepuluh detik penuh. Sepuluh detik itu mungkin terdengar singkat, namun bagi jutaan pengguna yang sedang melakukan transaksi *checkout* atau *streaming* real-time, itu adalah bencana bisnis yang nyata.

Masalah utamanya bukan pada kodenya, melainkan pada cara kita mengirimkannya. Pendekatan *stop-the-world* yang dipaksakan oleh skrip deployment naif membuat infrastruktur web kita rentan terhadap human error. Ketika backend Node.js atau Go Anda dimatikan secara paksa untuk diganti versi baru, load balancer upstream masih mengirimkan paket data ke port yang sudah mati. Kita butuh cara yang lebih elegan, di mana proses pembaruan berjalan mulus bagaikan mengganti ban mobil sport saat ia sedang melaju di lintasan tol. Di sinilah Kubernetes dan ArgoCD masuk sebagai penyelamat, mengubah mimpi buruk *deployment* menjadi rutinitas harian yang membosankan—dalam arti kata yang paling membahagiakan.

Mari kita bedah bagaimana arsitektur modern meredam ketakutan ini secara permanen, menggeser paradigma dari manual SSH ke otomatisasi GitOps yang presisi.

## 2. Di Balik Layar: Bagaimana Sistem Ini Bekerja Tanpa Sihir

Sebelum kita menyentuh baris kode konfigurasi, mari kita ratakan mental model kita tentang apa yang sebenarnya terjadi di balik layar kluster Kubernetes saat proses *rolling update* terjadi. Anggap Kubernetes sebagai sebuah orkestra simfoni, dan ArgoCD sebagai konduktor yang sangat disiplin. Ketika Anda melakukan *commit* perubahan pada repositori Git—misalnya memperbarui tag citra Docker dari aplikasi Go atau Node.js Anda—ArgoCD tidak langsung membabi buta menimpa lingkungan produksi.

Sebaliknya, ArgoCD melakukan sinkronisasi terus-menerus (*continuous reconciliation*) dengan mendeteksi perbedaan antara *Desired State* di Git dan *Actual State* di kluster. Begitu perbedaan ditemukan, Kubernetes *Deployment Controller* mengambil alih. Ia tidak langsung mematikan pod lama. Alih-alih, ia menciptakan pod baru dengan versi yang diperbarui, lalu menunggu sampai pod baru tersebut benar-benar siap menerima beban.

Di sinilah keajaiban *liveness* dan *readiness probe* bekerja sebagai sensor gerak yang sangat sensitif. Pod baru tidak akan dimasukkan ke dalam daftar endpoint Service (yang terhubung ke Nginx Ingress atau Cloudflare Load Balancer) sebelum ia lulus ujian *readiness*. Begitu pod baru siap, Kubernetes baru secara perlahan memangkas jumlah pod lama (*graceful termination*), memastikan tidak ada satu pun *request* HTTP, koneksi database MySQL, atau pesan antrian Redis yang terputus di tengah jalan. Transisi ini terjadi begitu natural hingga pengguna akhir di ujung sana tidak merasakan kedipan latensi sedikit pun.

> [!TIP]
> Jangan pernah mengandalkan asumsi bahwa aplikasi Anda langsung siap melayani trafik begitu prosesor selesai melakukan *booting*. Selalu pisahkan antara proses menyala (*startup*) dan proses siap melayani (*readiness*).

## 3. Pilihan Pendekatan & Kompromi yang Harus Diambil

Dalam merancang jalur pipa CI/CD skala besar, kita selalu dihadapkan pada kompromi abadi: kecepatan versus keamanan, kesederhanaan versus ketahanan. Di satu sisi, Anda memiliki strategi *Recreate* yang sangat cepat—mematikan semua pod lama sekaligus dan menyalakan yang baru—tetapi ini adalah resep instan untuk *downtime* total. Di sisi lain, ada *Blue/Green Deployment* dan *Canary Deployment* yang menawarkan keamanan tingkat tinggi namun menuntut kompleksitas infrastruktur tambahan, seperti manajemen rute DNS yang rumit, pencetakan header cookie khusus di Nginx, atau penggunaan *service mesh* seperti Istio.

Untuk mayoritas tim web modern yang mengelola aplikasi backend berbasis Go, Node.js, atau PHP-FPM di balik Ingress Controller, *Rolling Update* yang dikonfigurasi secara presisi melalui Kubernetes sudah lebih dari cukup untuk mencapai standar *Zero-Downtime*. Namun, jika Anda mengelola sistem finansial berisiko tinggi di mana kesalahan kecil pada satu fungsi API dapat berakibat fatal, kombinasi ArgoCD dengan *Canary Release* menggunakan Argo Rollouts adalah jalan keluar yang wajib ditempuh.

Mari kita ambil kompromi yang paling rasional dan terbukti andal di medan perang produksi: *Rolling Update* cerdas yang dikawal oleh ArgoCD dengan parameter *maxSurge* dan *maxUnavailable* yang disetel secara hati-hati.

## 4. Meracik Lingkungan: Persiapan & Prasyarat Lapangan

Sebelum kita merangkai file manifes, pastikan fondasi di bawahnya sudah kokoh. Anda membutuhkan kluster Kubernetes yang sehat (versi 1.24 ke atas sangat disarankan), ArgoCD yang terinstal di namespace terdedikasi (`argocd`), serta infrastruktur penunjang seperti Ingress Nginx, penyimpanan persisten S3/MinIO untuk aset statis jika diperlukan, dan konektivitas ke database eksternal seperti MariaDB atau kluster Redis terdistribusi.

Pastikan juga CLI alat bantu Anda sudah siap di mesin lokal atau *runner* CI (seperti GitHub Actions atau GitLab CI):

```bash
# Verifikasi koneksi kubectl ke kluster produksi Anda
kubectl cluster-info

# Pastikan namespace argocd berjalan normal
kubectl get pods -n argocd

# Verifikasi ArgoCD CLI
argocd version --client
```

Jika semua lampu indikator menyala hijau, saatnya kita masuk ke inti dapur pacu penulisan manifes Kubernetes.

## 5. Langkah Implementasi: Membangun Solusi yang Tangguh

Jantung dari *zero-downtime deployment* terletak pada bagaimana kita mendefinisikan strategi pembaruan di dalam file `Deployment` Kubernetes. Kita tidak ingin menyerahkan angka-angka ini pada *default* sistem yang sering kali terlalu agresif dalam mematikan pod lama.

Berikut adalah contoh manifes produksi nyata yang mengimplementasikan strategi *Rolling Update* yang aman, lengkap dengan konfigurasi *resource limits*, *probes*, dan *lifecycle hooks* agar aplikasi backend Anda (misalnya layanan berbasis Go atau Node.js) dapat menutup koneksi yang sedang berjalan dengan anggun (*graceful shutdown*).

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: core-api-service
  namespace: production
  labels:
    app.kubernetes.io/name: core-api
    app.kubernetes.io/part-of: e-commerce-backend
spec:
  # Menentukan jumlah replika minimal yang harus selalu ada
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      # Berapa banyak pod tambahan yang boleh dibuat di atas jumlah replika selama proses update
      maxSurge: 1
      # Berapa banyak pod yang boleh dimatikan secara bersamaan di bawah jumlah replika
      maxUnavailable: 0
  selector:
    matchLabels:
      app.kubernetes.io/name: core-api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: core-api
    spec:
      containers:
      - name: api
        image: registry.company.internal/backend/core-api:v1.4.2
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
          limits:
            cpu: "2000m"
            memory: "2Gi"
        # Liveness Probe untuk memastikan aplikasi tidak mengalami deadlock
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 10
          failureThreshold: 3
        # Readiness Probe untuk memastikan pod siap menerima trafik dari Ingress
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          failureThreshold: 2
        # Lifecycle hook untuk graceful shutdown aplikasi
        lifecycle:
          preStop:
            exec:
              command: ["sh", "-c", "sleep 10"]
```

> [!IMPORTANT]
> Perhatikan baris `maxUnavailable: 0` dan `maxSurge: 1`. Konfigurasi ini memerintahkan Kubernetes untuk tidak pernah menurunkan kapasitas layanan di bawah jumlah minimum, melainkan membuat satu pod baru terlebih dahulu, memastikannya sehat, baru kemudian mematikan pod lama satu per satu.

## 6. Membedah Baris Kode Krusial yang Kerap Menjebak

Mari kita zoom-in pada bagian paling krusial yang sering diabaikan oleh para engineer pemula: blok `lifecycle.preStop` dan hubungannya dengan penanganan sinyal di dalam kode aplikasi backend Anda.

Ketika Kubernetes memutuskan untuk menghentikan sebuah pod, ia mengirimkan sinyal `SIGTERM` ke proses utama aplikasi. Secara bersamaan, Kubernetes menghapus pod tersebut dari endpoint Service sehingga Ingress (seperti Nginx Ingress Controller) berhenti mengirimkan trafik baru ke pod itu. Namun, pembaruan aturan routing di Ingress membutuhkan waktu sepersekian detik untuk merambat (*propagation delay*).

Jika aplikasi Anda langsung mati begitu menerima `SIGTERM`, maka permintaan HTTP yang sedang dalam perjalanan (*in-flight requests*) akan mendadak gagal dengan *connection reset by peer*. Perintah `sleep 10` pada `preStop` memberikan jeda waktu krusial bagi Ingress untuk memperbarui tabel routing-nya, sementara aplikasi Anda menyelesaikan sisa permintaan yang sedang diproses.

Berikut adalah cuplikan kode penanganan sinyal di sisi aplikasi (menggunakan Go sebagai contoh standar industri performa tinggi):

```go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // Menjalankan server di goroutine terpisah
    go func() {
        log.Println("Memulai server HTTP pada port 8080...")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Gagal menjalankan server: %v", err)
        }
    }()

    // Kanal untuk menangkap sinyal sistem operasi
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Menerima sinyal shutdown, mematikan server secara anggun...")

    // Berikan waktu maksimal 15 detik untuk menyelesaikan request yang sedang berjalan
    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server terpaksa dimatikan secara paksa: %v", err);
    }

    log.Println("Server berhasil dimatikan tanpa kehilangan data.")
}
```

> [!TIP]
> Pastikan koneksi ke database (seperti MySQL/MariaDB) dan pool Redis juga diatur dengan *timeout* yang selaras agar tidak menggantung proses *shutdown* aplikasi.

## 7. Bertahan di Bawah Tekanan: Skenario Traffic Nyata

Bayangkan situasi ini: pukul 20.00 malam, kampanye diskon besar-besaran dimulai. Trafik melonjak 500% dalam waktu tiga menit. Pada saat yang sama, tim produk mendesak Anda untuk merilis *hotfix* kritis untuk memperbaiki bug pembayaran. Di arsitektur lama, kombinasi lonjakan trafik dan *deployment* adalah resep bencana yang menjamin server kolaps.

Namun, dengan arsitektur Kubernetes dan ArgoCD yang kita bangun, sistem bernapas dengan tenang. ArgoCD mendeteksi perubahan tag citra di Git. Kubernetes mulai meluncurkan pod baru satu per satu dengan strategi `maxSurge: 1`. Karena batas sumber daya (*resource limits*) dan permintaan (*requests*) terdefinisi dengan jelas di manifes, kluster secara otomatis melakukan penjadwalan ulang (*scheduling*) ke node yang memiliki kapasitas kosong atau memicu *Cluster Autoscaler* jika diperlukan.

Endpoint `/ready` kita bertindak sebagai penjaga gerbang yang bijak. Jika pod baru kebanjiran koneksi dan latensi basis data meningkat, *readiness probe* akan menunda penggabungan pod tersebut ke dalam kolam trafik aktif hingga ia benar-benar stabil. Pengguna tidak merasakan gangguan, transaksi tetap berjalan lancar, dan Anda bisa menikmati secangkir kopi hangat sambil memantau dasbor ArgoCD yang berubah warna menjadi hijau terang.

## 8. Jebakan Klasik & Cara Menghindarinya Sebelum Meledak

Berdasarkan pengalaman bertahun-tahun menangani insiden produksi di berbagai perusahaan teknologi, ada beberapa kesalahan fatal yang sering dilakukan bahkan oleh engineer berpengalaman saat menerapkan pola ini:

1. **Mengabaikan `initialDelaySeconds` pada Probe**: Seringkali pengembang menyetel waktu tunggu probe terlalu singkat (misalnya 1 detik). Akibatnya, pod baru langsung dianggap gagal oleh Kubernetes karena aplikasi butuh waktu 5 detik untuk melakukan koneksi awal ke database MariaDB atau memuat cache Redis. Kubernetes akan terjebak dalam siklus *CrashLoopBackOff* tanpa henti.
2. **Ketiadaan Resource Limits yang Tegas**: Membiarkan pod berjalan tanpa batasan CPU dan Memory adalah undangan terbuka bagi *noisy neighbor effect*. Satu pod yang mengalami kebocoran memori (*memory leak*) dapat menghabiskan seluruh sumber daya node, memicu *Eviction* pada pod lain yang tidak bersalah.
3. **Ketergantungan Skema Database yang Destruktif**: Melakukan *deployment* kode baru yang mengubah struktur tabel SQL secara destruktif (misalnya menghapus kolom yang masih dipakai oleh versi pod lama yang sedang berjalan bersamaan) akan memecah aplikasi. Selalu gunakan pola *Expand and Contract* untuk migrasi database pada arsitektur *rolling update*.

## 9. Uji Nyali: Mengukur Performa di Titik Batas

Jangan pernah mempercayai bahwa sistem Anda sudah *zero-downtime* sampai Anda sendiri yang menyiksanya di lingkungan *staging* atau kluster pengujian. Kita perlu melakukan uji beban (*load testing*) secara bersamaan saat proses *deployment* sedang berlangsung menggunakan alat seperti `k6` atau `hey`.

Berikut adalah skrip pengujian sederhana menggunakan `hey` untuk mengirimkan 10.000 permintaan HTTP secara simultan ke endpoint API sambil Anda memicu *sync* manual di ArgoCD:

```bash
# Jalankan pengujian beban konstan 50 koneksi selama proses deployment berjalan
hey -z 60s -c 50 -m GET https://api.company.internal/healthz
```

Sambil skrip di atas berjalan, jalankan perintah pembaruan atau picu sinkronisasi di ArgoCD:

```bash
argocd app sync core-api-production
```

Amati keluarannya. Jika metrik *error rate* tetap berada di angka 0.00% dan tidak ada satupun respons berstatus `502` atau `504` selama durasi pengujian, selamat! Pipeline *zero-downtime* Anda telah teruji di medan perang.

## 10. Catatan Lapangan & Checklist Sebelum Rilis

Sebagai penutup dari panduan komprehensif ini, simpan dan cetak *checklist* operasional berikut di dinding ruang kerja Anda sebelum menekan tombol rilis ke produksi:

- [ ] Pastikan strategi `RollingUpdate` memiliki `maxUnavailable: 0` dan `maxSurge` yang proporsional dengan kapasitas node.
- [ ] Verifikasi bahwa *Liveness* dan *Readiness Probe* mengarah ke endpoint kesehatan yang benar dan independen.
- [ ] Pastikan kode aplikasi menangani sinyal `SIGTERM` dengan benar melalui implementasi *Graceful Shutdown*.
- [ ] Periksa kembali bahwa variabel lingkungan (*Environment Variables*) dan *ConfigMap/Secret* sudah disinkronkan dengan benar di ArgoCD.
- [ ] Lakukan uji beban simulasi *zero-downtime* menggunakan `k6` atau `hey` sebelum merilis fitur kritikal ke publik.

Dengan fondasi arsitektur ini, Anda tidak lagi sekadar mengirimkan kode; Anda sedang membangun sistem yang tahan banting, mandiri, dan siap melayani pertumbuhan bisnis tanpa rasa cemas.