Tampilan
Kenapa penting
Layanan e-commerce jarang menerima beban rata: flash sale jam 12 siang bisa sepuluh kali lipat dari jam 3 pagi. Kubernetes memudahkan menambah replika, tetapi menambah pod tidak otomatis menambah kapasitas — bila aplikasi menyimpan state di memori, database kehabisan koneksi, atau partisi Kafka terlalu sedikit, replika baru hanya memindahkan bottleneck. Ketersediaan juga bukan sekadar "pod hidup": rolling update, upgrade node, dan gangguan zona harus terjadi tanpa pelanggan melihat error. Bab ini membahas cara merancang dan mengonfigurasi keduanya — dan cara tidak membuang uang karenanya.
Konsep inti
Stateless dulu, baru scale
Scale horizontal berarti request yang sama bisa dilayani pod mana pun. Syaratnya, pod tidak menyimpan state yang harus bertahan antar request:
| State | Jangan disimpan di | Simpan di |
|---|---|---|
| Sesi login | HttpSession di memori pod | token (JWT) atau Redis |
| Keranjang belanja | Map statis | database / Redis |
| File upload | disk lokal container | object storage (S3-compatible) |
| Cache | boleh lokal, asal boleh hilang | Redis bila harus konsisten antar pod |
Job terjadwal (@Scheduled) | berjalan di setiap replika | lock terdistribusi (ShedLock) atau Job |
Pod bisa dibunuh, dipindah, atau digandakan kapan saja. Bila aplikasi tetap benar dalam kondisi itu, ia siap di-scale.
HorizontalPodAutoscaler
HPA menambah/mengurangi replicas Deployment berdasarkan metrik. Bentuk paling umum memakai CPU dari Metrics Server:
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # persen dari requests.cpu
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # jangan buru-buru turunRumus intinya: desiredReplicas = ceil(currentReplicas × currentMetric / targetMetric). Contoh: 3 pod dengan rata-rata CPU 140% dari target 70% → ceil(3 × 140 / 70) = 6 pod. Utilisasi dihitung terhadap resources.requests.cpu, jadi requests wajib diisi (lihat bab konfigurasi).
CPU tidak selalu mencerminkan beban. Layanan yang banyak menunggu I/O lebih cocok di-scale dengan metrik custom — request per detik atau latency dari Prometheus — lewat adapter (mis. Prometheus Adapter) dengan type: Pods atau type: External. Ingat juga JVM: startup dan JIT warm-up memakan CPU tinggi sesaat, sehingga scaleUp yang terlalu agresif bisa memicu lonjakan semu.
PodDisruptionBudget
PDB membatasi berapa pod boleh hilang karena gangguan sukarela — kubectl drain, upgrade node, autoscaler cluster yang mengecilkan node.
yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: order-service
spec:
minAvailable: 2 # atau maxUnavailable: 1
selector:
matchLabels:
app: order-servicePDB tidak melindungi dari node yang mati mendadak; untuk itu kamu butuh replika yang tersebar.
Anti-affinity dan multi-zone
Tiga replika di satu node sama saja dengan satu replika saat node itu mati. Sebarkan pod antar node dan zona dengan topologySpreadConstraints (atau podAntiAffinity):
yaml
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: order-service
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: order-serviceScheduleAnyway adalah preferensi; DoNotSchedule adalah aturan keras yang bisa membuat pod Pending bila zona tidak cukup. Di kind semua node ada di satu mesin, jadi konsep zona hanya bisa disimulasikan lewat label.
Graceful shutdown dan readiness saat rolling update
Saat pod dihentikan, Kubernetes secara paralel mencabut pod dari endpoint Service dan mengirim SIGTERM. Bila aplikasi langsung berhenti, request yang masih diarahkan ke pod itu gagal. Polanya:
preStopmenundaSIGTERMbeberapa detik agar pencabutan endpoint menyebar ke kube-proxy, Ingress, dan gateway.- Spring Boot menerima
SIGTERM, readiness berubah menjadiREFUSING_TRAFFIC, menolak request baru, dan menyelesaikan request yang sedang berjalan. - Semua ini harus selesai sebelum
terminationGracePeriodSeconds(default 30 detik); lewat dari itu container di-SIGKILL.
yaml
# application.yaml (potongan)
server:
shutdown: graceful # default sejak Spring Boot 3.4, tulis eksplisit agar jelas
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
management:
endpoint:
health:
probes:
enabled: true # /actuator/health/liveness dan /readinessyaml
# Deployment (potongan)
spec:
template:
spec:
terminationGracePeriodSeconds: 45 # > preStop 10s + shutdown 20s + margin
containers:
- name: app
lifecycle:
preStop:
sleep:
seconds: 10 # aksi sleep bawaan; image distroless tak punya /bin/sleep
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080Di sisi pod baru, readiness probe memastikan trafik baru masuk hanya setelah aplikasi siap (koneksi database terbentuk, cache hangat).
Connection pool vs jumlah replika
Setiap pod punya pool sendiri. HikariCP default maximumPoolSize = 10, PostgreSQL default max_connections = 100.
| Replika | Pool per pod | Total koneksi | Status (batas 100) |
|---|---|---|---|
| 3 | 10 | 30 | aman |
| 8 | 10 | 80 | mepet (sisakan untuk admin) |
| 12 | 10 | 120 | gagal: too many clients |
| 12 | 5 | 60 | aman |
Hitung dari maxReplicas HPA, bukan replika saat ini, dan tambahkan pod lama selama rolling update (maxSurge). Pool besar jarang membuat aplikasi lebih cepat; bila jumlah pod memang banyak, pasang PgBouncer di depan database.
Autoscaling consumer Kafka
Dalam satu consumer group, satu partisi dibaca oleh tepat satu consumer. Jumlah partisi adalah batas atas paralelisme: topic 6 partisi tidak akan diproses lebih cepat oleh 10 consumer. Rencanakan partisi untuk puncak beban di depan (menambah partisi nanti mengubah pemetaan key → partisi), dan batasi maxReplicas consumer ≤ jumlah partisi.
Metrik yang tepat untuk consumer adalah consumer lag (pesan yang belum diproses), bukan CPU. KEDA (Kubernetes Event-driven Autoscaling) menyediakan scaler Kafka yang membaca lag dan mengatur HPA untukmu, bahkan bisa scale ke nol:
yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: invoice-consumer
spec:
scaleTargetRef:
name: invoice-consumer # Deployment
minReplicaCount: 1
maxReplicaCount: 6 # = jumlah partisi
triggers:
- type: kafka
metadata:
bootstrapServers: kafka:9092
consumerGroup: invoice-service
topic: order-created
lagThreshold: "100" # target lag per replikaCost awareness
Autoscaling juga soal biaya. Hal yang perlu diperhatikan:
requestsyang terlalu besar memboroskan node; terlalu kecil membuat node over-commit. Ukur dari metrik nyata.minReplicas2 per service × 20 service sudah 40 pod sebelum ada trafik — sesuaikan per kritikalitas.- Scale-down yang terlalu cepat menimbulkan flapping; terlalu lambat membayar kapasitas menganggur.
- Node spot/preemptible murah untuk workload yang tahan gangguan (consumer, batch), bukan untuk database.
Contoh di dunia backend
Persiapan flash sale untuk checkout-service dan invoice-consumer:
text
checkout-service (REST, stateless, sesi di JWT)
HPA: CPU 70%, minReplicas 3, maxReplicas 10, scaleDown stabilization 300s
PDB: maxUnavailable 1
Spread: zona + hostname (ScheduleAnyway)
Shutdown: preStop sleep 10s, graceful 20s, terminationGracePeriodSeconds 45
Pool: maxReplicas 10 + surge 1 = 11 pod x pool 6 = 66 koneksi (PostgreSQL max 150, sisa untuk service lain via PgBouncer)
invoice-consumer (Kafka, topic order-created 12 partisi)
KEDA: lagThreshold 200, minReplicaCount 1, maxReplicaCount 12
Idempoten: invoice dibuat dengan unique key order_id, aman saat rebalance memproses ulang pesanUji dengan load test sebelum hari H: pantau apakah HPA naik sebelum latency melewati SLO, apakah koneksi database stabil, dan apakah lag Kafka turun setelah KEDA menambah replika. Jalankan rolling update di tengah load test — jumlah error harus nol.
Pertanyaan interview
1. Kenapa menambah replika tidak selalu menambah throughput?
Karena bottleneck bisa berada di luar pod: database yang kehabisan koneksi atau CPU, lock baris yang diperebutkan, API pihak ketiga dengan rate limit, atau partisi Kafka yang lebih sedikit dari consumer. Replika baru juga menambah total koneksi pool. Sebelum menaikkan maxReplicas, cari dulu sumber daya yang benar-benar jenuh lewat metrik.
2. Bagaimana memastikan rolling update tidak menghasilkan error untuk pengguna?
Readiness probe yang jujur untuk pod baru, maxUnavailable: 0 agar kapasitas tidak turun, graceful shutdown di aplikasi, jeda preStop agar endpoint dicabut sebelum aplikasi berhenti menerima koneksi, dan terminationGracePeriodSeconds yang lebih panjang dari total waktu shutdown. Tambahkan PDB agar maintenance node tidak menghabisi banyak pod sekaligus, dan buktikan dengan rolling update di bawah load test.
3. Apa beda HPA berbasis CPU dengan KEDA untuk consumer Kafka?
HPA CPU bereaksi pada beban komputasi, padahal consumer bisa menumpuk lag sambil CPU rendah (misalnya menunggu database). KEDA membaca metrik sumber event seperti consumer lag, lalu menggerakkan HPA berdasarkan antrean yang sebenarnya, dan bisa scale ke nol saat topic sepi. Apa pun alatnya, batas atas tetap jumlah partisi.
Jebakan umum
- Menyimpan sesi atau keranjang di memori pod — pengguna "logout sendiri" saat request jatuh ke pod lain atau pod di-restart.
- HPA tanpa
resources.requests.cpuatau tanpa Metrics Server — target<unknown>dan tidak pernah scale. - Lupa menghitung
maxReplicas × maximumPoolSizeterhadapmax_connectionsdatabase. - Men-scale consumer melebihi jumlah partisi dan heran throughput tidak naik.
- Tidak ada
preStop/graceful shutdown, sehingga setiap deploy menghasilkan lonjakan error 502/connection refused. - PDB
minAvailablesama dengan jumlah replika — drain node tidak pernah bisa selesai.
Ringkasan
- Aplikasi harus stateless (state di database/Redis/object storage) sebelum di-scale horizontal.
- HPA menghitung replika dari metrik relatif terhadap
requests; metrik custom dan KEDA untuk beban yang tidak tercermin di CPU. - PDB membatasi gangguan sukarela;
topologySpreadConstraintsmenyebar pod antar node dan zona. - Rolling update aman = readiness jujur +
preStop+ graceful shutdown + grace period yang cukup. - Total koneksi = replika maksimum × pool size; jumlah partisi Kafka = batas paralelisme consumer.
- Autoscaling harus diimbangi kesadaran biaya: requests yang terukur,
minReplicaswajar, scale-down stabil.