High Availability #
Bagi banyak organisasi, menyebarkan aplikasi ke Kubernetes dengan parameter replicas: 2 di manifest Deployment sering kali dianggap sudah cukup untuk mengklaim bahwa aplikasi tersebut memiliki Ketersediaan Tinggi (High Availability atau HA). Namun, dalam kenyataannya, ini adalah pemahaman yang keliru. Jika scheduler meletakkan kedua pod replika tersebut pada satu Node fisik yang sama, maka kegagalan hardware pada Node tersebut akan langsung memadamkan seluruh layanan kita secara total. High Availability yang nyata membutuhkan strategi distribusi beban kerja yang disengaja (intentional distribution). Kita harus memastikan bahwa Pod tersebar di beberapa Node yang berbeda, Node tersebar di beberapa Zona Ketersediaan (Availability Zones atau AZ), dan kluster memiliki pertahanan otomatis terhadap operasi pemeliharaan terjadwal seperti peningkatan OS node. Artikel ini membahas teknik-teknik canggih untuk mengamankan ketersediaan aplikasi kita dari kegagalan infrastruktur parsial maupun total.
Mengapa Dua Replika Saja Belum Cukup #
Secara default, algoritma penjadwalan Kubernetes (kube-scheduler) memprioritaskan efisiensi penggunaan sumber daya Node. Jika sebuah Node memiliki kapasitas memori yang longgar dan letaknya dekat, scheduler cenderung meletakkan seluruh replika pod baru pada Node tersebut.
SKENARIO DEPLOYMENT DENGAN 2 REPLIKA:
1. Tanpa Aturan Distribusi (Default Scheduler):
Node Worker 1: [payment-api-pod-1] [payment-api-pod-2] <-- Keduanya menumpuk di satu node!
Node Worker 2: (Kosong / Workload lain)
Node Worker 3: (Kosong)
Akibat: Node Worker 1 mengalami mati listrik -> Kedua Pod mati secara bersamaan -> DOWNTIME.
2. Dengan Aturan Anti-Affinity:
Node Worker 1: [payment-api-pod-1]
Node Worker 2: [payment-api-pod-2] <-- Tersebar merata!
Node Worker 3: (Kosong)
Akibat: Node Worker 1 mati -> [payment-api-pod-1] hilang, namun [payment-api-pod-2] di Node 2
tetap aktif melayani transaksi pengguna -> ZERO DOWNTIME.
Untuk menjamin ketersediaan tinggi di tingkat produksi, kita harus mengarahkan scheduler secara deklaratif menggunakan fitur Pod Anti-Affinity dan Topology Spread Constraints.
Mengatur Distribusi Pod via Pod Anti-Affinity #
Pod Anti-Affinity memungkinkan kita melarang Kubernetes meletakkan beberapa Pod yang memiliki label identik pada domain topologi (seperti Node atau Zona) yang sama.
Aturan anti-affinity terbagi menjadi dua tingkat ketegasan:
- Strict Rule (
requiredDuringSchedulingIgnoredDuringExecution): Aturan keras yang wajib dipenuhi. Jika scheduler tidak dapat menemukan Node yang memenuhi syarat (misalnya kita meminta 4 replika disebar di Node berbeda, namun kluster kita hanya memiliki 3 Node), maka Pod ke-4 akan dibiarkan stuck dalam statusPendingselamanya. - Soft Rule (
preferredDuringSchedulingIgnoredDuringExecution): Aturan prioritas. Scheduler akan berusaha menyebarkan Pod jika memungkinkan, namun jika kapasitas Node tidak mencukupi, scheduler tetap akan menempatkan Pod pada Node yang sama alih-alih membiarkannya gagal berjalan.
# File: k8s/production-affinity.yaml
spec:
replicas: 3
template:
metadata:
labels:
app: payment-api
spec:
affinity:
podAntiAffinity:
# 1. ATURAN KERAS (Wajib beda Node fisik)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: payment-api
topologyKey: "kubernetes.io/hostname" # "hostname" mewakili domain Node tunggal
# 2. ATURAN PRIORITAS (Sebaiknya beda Availability Zone jika memungkinkan)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100 # Nilai prioritas tertinggi (1-100)
podAffinityTerm:
labelSelector:
matchLabels:
app: payment-api
topologyKey: "topology.kubernetes.io/zone" # mewakili domain Availability Zone (AZ)
Domain Topologi (topologyKey) Utama
#
topologyKey menentukan batas batas geografis atau infrastruktur di mana anti-affinity diterapkan:
kubernetes.io/hostname: Membatasi agar hanya boleh ada maksimal 1 Pod per Node fisik.topology.kubernetes.io/zone: Membatasi agar Pod tidak menumpuk di Zona Ketersediaan yang sama (misal di AWS:ap-southeast-1avsap-southeast-1b).topology.kubernetes.io/region: Level regional (jarang digunakan untuk kluster tunggal, biasanya untuk kluster multi-wilayah besar).
Topology Spread Constraints: Penskalaan Presisi Lintas Zona #
Meskipun Pod Anti-Affinity sangat andal untuk mencegah penumpukan Pod, ia memiliki keterbatasan: ia tidak dapat mengontrol keseimbangan jumlah Pod yang tersebar. Sebagai contoh, jika kita memiliki 10 replika Pod dan 3 Zona Ketersediaan (AZ-A, AZ-B, AZ-C), anti-affinity tingkat AZ tidak dapat mencegah scheduler meletakkan 8 Pod di AZ-A, 1 Pod di AZ-B, dan 1 Pod di AZ-C (karena di setiap AZ tetap ada Pod yang berjalan, sehingga aturan anti-affinity tidak dilanggar).
Ketidakseimbangan ini sangat berbahaya. Jika AZ-A mengalami gangguan listrik total, kita akan kehilangan 80% kapasitas komputasi aplikasi kita secara instan.
Topology Spread Constraints (TSC) menyelesaikan masalah ini secara elegan dengan memungkinkan kita menentukan batas toleransi ketidakseimbangan maksimal (disebut dengan maxSkew) antar domain topologi.
Pemahaman maxSkew #
maxSkew adalah perbedaan maksimum yang diizinkan dalam jumlah Pod aktif antara zona yang memiliki Pod paling banyak dengan zona yang memiliki Pod paling sedikit di dalam domain topologi target.
$$\text{Actual Skew} = \text{Jumlah Pod di Zona Terpadat} - \text{Jumlah Pod di Zona Terlengang}$$ $$\text{Syarat Validasi: } \text{Actual Skew} \le \text{maxSkew}$$
KASUS: 9 REPLIKA POD DI 3 AVAILABILITY ZONE (AZ-A, AZ-B, AZ-C)
1. Tanpa TSC (Skew Tinggi & Berisiko):
AZ-A: [Pod1] [Pod2] [Pod3] [Pod4] [Pod5] [Pod6] (6 Pod)
AZ-B: [Pod7] [Pod8] (2 Pod)
AZ-C: [Pod9] (1 Pod)
Actual Skew = 6 (terpadat) - 1 (terlengang) = 5.
Bahaya: Jika AZ-A down, 67% kapasitas aplikasi hilang sekaligus.
2. Dengan TSC (maxSkew: 1):
AZ-A: [Pod1] [Pod2] [Pod3] (3 Pod)
AZ-B: [Pod4] [Pod5] [Pod6] (3 Pod)
AZ-C: [Pod7] [Pod8] [Pod9] (3 Pod)
Actual Skew = 3 - 3 = 0 (memenuhi syarat <= 1).
Hasil: Distribusi sempurna. Jika salah satu AZ down, kita hanya kehilangan 33% kapasitas.
Manifest Manifest TSC di Tingkat Produksi #
# File: k8s/production-tsc.yaml
spec:
replicas: 6
template:
metadata:
labels:
app: core-api
spec:
topologySpreadConstraints:
# 1. Batasi ketidakseimbangan lintas Availability Zone (Strict)
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule # Jangan jalankan Pod baru jika membuat skew > 1
labelSelector:
matchLabels:
app: core-api
# 2. Batasi ketidakseimbangan lintas Node Host (Soft / Fleksibel)
- maxSkew: 2
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway # Tetap jalankan Pod meskipun skew > 2 jika Node penuh
labelSelector:
matchLabels:
app: core-api
Pod Disruption Budget (PDB): Proteksi Saat Pemeliharaan Node #
Ketersediaan tinggi kluster tidak hanya terancam oleh kegagalan hardware yang tidak terduga (Involuntary Disruptions seperti mati listrik atau kerusakan disk Node). Ketersediaan tinggi justru lebih sering terganggu oleh tindakan pemeliharaan kluster yang disengaja (Voluntary Disruptions) seperti:
- Pembersihan Node (draining) oleh administrator untuk melakukan peningkatan versi OS kernel host.
- Penghapusan Node otomatis oleh Cluster Autoscaler saat melakukan efisiensi biaya (scale-down).
- Proses penggusuran (eviction) Pod secara manual.
Untuk melindungi Pod aplikasi kritis dari pemadaman massal selama proses pemeliharaan ini, kita wajib mendefinisikan Pod Disruption Budget (PDB). PDB bertindak sebagai polisi lalu lintas yang melarang API Server mematikan Pod jika jumlah Pod aktif yang tersisa akan turun di bawah ambang batas minimum keselamatan.
# File: k8s/production-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-api-pdb
namespace: prod-apps
spec:
# Opsi 1: Tentukan jumlah Pod minimal yang wajib selalu sehat & aktif
minAvailable: 2
# Opsi 2 (Alternatif): Tentukan jumlah maksimal Pod yang boleh mati bersamaan
# maxUnavailable: 1 # Atau menggunakan persentase, misal "25%"
selector:
matchLabels:
app: payment-api
Simulasi Blokir Evikasi oleh PDB #
Ketika administrator menjalankan perintah pembersihan Node untuk keperluan peningkatan OS:
# Administrator mengosongkan node worker-1 untuk maintenance
$ kubectl drain node-worker-1 --ignore-daemonsets --delete-emptydir-data
Kubernetes API Server akan memeriksa seluruh PDB yang terdaftar di kluster. Jika evikasi pod payment-api-pod-1 di node-worker-1 akan menyebabkan jumlah pod aktif yang tersisa di kluster menjadi kurang dari minAvailable (2 pod), maka perintah drain akan ditangguhkan (blocked).
Terminal administrator akan menampilkan status penundaan secara visual:
evicting pod prod-apps/payment-api-pod-1
error when evicting pod "payment-api-pod-1" (retrying after 5s):
Cannot evict pod as it would violate the pod's disruption budget.
Kubelet akan menunggu hingga pod pengganti yang dijadwalkan di Node lain (misalnya node-worker-2) berhasil menyala dan lolos uji keaktifan (readinessProbe) sebelum akhirnya mengizinkan penghapusan pod lama di node-worker-1.
Graceful Shutdown & Zero-Downtime Endpoints #
Menyebarkan Pod di banyak Node dan Zona tidak menjamin bebas downtime jika aplikasi kita tidak menangani proses mematikan kontainer (graceful shutdown) dengan benar.
Ketika Kubernetes memutuskan untuk menghentikan sebuah Pod (misalnya saat proses rolling update atau scale-down), terjadi dua alur proses asinkron yang berjalan secara paralel di dalam kluster:
flowchart TD
DeleteRequest["1. API Server Menerima Perintah Delete Pod"] --> ParallelNodes{"2. Alur Asinkron Paralel"}
ParallelNodes -->|"Alur Kubelet Node"| KubeletSignal["3. Kubelet Mengirim Sinyal SIGTERM ke Kontainer"]
KubeletSignal --> RunPreStop["4. Menjalankan Siklus preStop Lifecycle Hook"]
RunPreStop --> WaitGrace["5. Menunggu terminationGracePeriodSeconds"]
WaitGrace --> ForceKill["6. Mengirim Sinyal SIGKILL (Paksa Hancurkan Kontainer)"]
ParallelNodes -->|"Alur Control Plane Jaringan"| ServiceController["7. Endpoint Controller Menghapus IP Pod dari Endpoints"]
ServiceController --> KubeProxy["8. kube-proxy Memperbarui Aturan iptables/IPVS di Semua Node"]
KubeProxy --> IngressRoute["9. Ingress Controller Berhenti Mengirim Traffic Baru ke Pod"]
Masalah Selisih Waktu Jaringan (Race Condition Jaringan) #
Karena alur di atas berjalan secara asinkron, sering kali terjadi selisih waktu (latency). Kubelet di Node tempat Pod berjalan mungkin menerima sinyal SIGTERM dan langsung menghentikan proses aplikasi kita dalam milidetik. Namun, proses pembaruan aturan iptables/IPVS oleh kube-proxy di Node lain di seberang kluster membutuhkan waktu hingga 2-3 detik untuk menyebar.
Akibatnya, selama jeda waktu 2-3 detik tersebut, Ingress Controller atau Pod lain masih mengirimkan paket lalu lintas HTTP baru ke alamat IP Pod yang sudah mati, menghasilkan kesalahan transaksi jaringan HTTP 502 Bad Gateway bagi pengguna akhir.
Solusi: Penerapan preStop Hook dan Probes Tangguh #
Untuk memitigasi selisih waktu jaringan di atas, kita wajib menyisipkan jeda tidur (sleep delay) menggunakan preStop lifecycle hook sebelum kontainer diizinkan memproses sinyal penutupan SIGTERM.
# File: k8s/production-graceful.yaml
spec:
template:
spec:
# Berikan waktu transisi yang cukup bagi aplikasi untuk menyelesaikan transaksi aktif
terminationGracePeriodSeconds: 60 # Default hanya 30 detik
containers:
- name: api-app
image: registry.company.com/apps/api:v1.0.0
lifecycle:
preStop:
exec:
command:
- "/bin/sh"
- "-c"
# Jeda tidur 10 detik menahan aplikasi agar tidak langsung memproses SIGTERM.
# Memberikan waktu bagi kube-proxy untuk menghapus IP Pod dari seluruh tabel iptables kluster.
- "sleep 10"
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2 # Cepat deteksi jika pod mengalami gangguan
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 15
Dengan konfigurasi ini, ketika perintah hapus datang:
- Kontainer diperintahkan tidur selama 10 detik via
preStop. Selama periode ini, kontainer tetap aktif dan dapat memproses transaksi lama yang tersisa. - Sementara kontainer tidur, Endpoint Controller menghapus IP Pod dari daftar
Endpointsaktif. - Ingress Controller berhenti mengarahkan lalu lintas baru ke Pod tersebut karena namanya sudah dihapus dari daftar rute aktif.
- Setelah 10 detik berlalu, kontainer bangun dan menerima sinyal
SIGTERM, lalu menyelesaikan proses internal secara bersih (graceful exit) tanpa ada risiko menjatuhkan koneksi di tengah jalan.
Control Plane High Availability (HA Multi-Master) #
Ketersediaan tinggi aplikasi kita tidak akan berarti jika Control Plane kluster kita sendiri mati. Di tingkat produksi, kita harus mengonfigurasi arsitektur HA Multi-Master:
flowchart TD
LB["EXTERNAL LOAD BALANCER (Akses API Server via Port 6443)"]
CP1["Control Plane 1 (API Server)"]
CP2["Control Plane 2 (API Server)"]
CP3["Control Plane 3 (API Server)"]
ETCD["etcd CLUSTER (3 atau 5 Node Ganjil untuk Konsensus Quorum Raft)"]
LB --> CP1
LB --> CP2
LB --> CP3
CP1 --> ETCD
CP2 --> ETCD
CP3 --> ETCD
- Load Balancer Eksternal: Menempatkan Load Balancer TCP tingkat tinggi di depan seluruh master node API Server untuk mengalihkan lalu lintas
kubectldan koneksi Kubelet dari node worker jika salah satu API Server mati. - etcd Quorum: Database konsistensi kluster
etcdmenggunakan algoritma konsensus Raft yang membutuhkan jumlah node ganjil untuk menghindari situasi Split-Brain.- 3 Node etcd: Mengizinkan maksimal 1 node mati tanpa kehilangan kuorum.
- 5 Node etcd: Mengizinkan maksimal 2 node mati sekaligus secara bersamaan.
Anti-Pattern dalam Praktik High Availability #
Hindari kesalahan fatal berikut saat merancang ketersediaan aplikasi di kluster produksi:
1. Menyetel Aturan Anti-Affinity Keras pada Kluster Kecil #
Menggunakan aturan anti-affinity requiredDuringScheduling secara ketat pada kluster yang memiliki kapasitas Node fisik terbatas.
# ANTI-PATTERN: required (strict) anti-affinity pada kluster skala kecil
spec:
replicas: 5 # Meminta 5 replika Pod disebar di node berbeda
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # KETAT
- labelSelector:
matchLabels:
app: api
topologyKey: "kubernetes.io/hostname"
Risiko Operasional:
- Jika kluster produksi kita hanya memiliki 3 Node worker aktif, Kubernetes hanya akan berhasil menjadwalkan 3 Pod pertama (1 Pod di masing-masing Node).
- Pod ke-4 dan ke-5 akan stuck dalam status 'Pending' selamanya karena tidak ada Node worker ke-4 dan ke-5 untuk memenuhi syarat strict anti-affinity.
✓ SOLUSI: Gunakan 'preferredDuringScheduling' (soft rule) atau gunakan 'topologySpreadConstraints' dengan flag 'whenUnsatisfiable: ScheduleAnyway' agar sisa pod tetap dapat dijadwalkan di node yang ada saat kapasitas node fisik habis.
2. Menjalankan Aplikasi Kritis Tanpa PodDisruptionBudget (PDB) #
Membiarkan tim mendeploy aplikasi pemrosesan transaksi utama tanpa menetapkan anggaran gangguan (PDB) di tingkat namespace produksi.
# ANTI-PATTERN: Deployment kritis tanpa objek PDB
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-engine
spec:
replicas: 3
# JANGAN: Tidak ada objek PodDisruptionBudget yang dikaitkan ke label 'app: billing-engine'!
Risiko Operasional:
- Saat administrator melakukan pembaruan OS node massal (atau saat Cluster Autoscaler melakukan perampingan kapasitas node), Kubernetes diizinkan mematikan ketiga Pod replika 'billing-engine' secara bersamaan jika mereka berada di node-node yang sedang dibersihkan.
- Aplikasi akan mengalami pemadaman transaksi total seketika bagi pengguna, meskipun kita memiliki 3 replika aktif.
✓ SOLUSI: Selalu buat berkas manifest PDB ('minAvailable: 2' atau 'maxUnavailable: 1') yang terikat pada label aplikasi kritis di setiap rilis Helm Chart produksi.
Checklist Audit High Availability #
Lakukan pengujian kepatuhan ketersediaan kluster produksi Anda menggunakan daftar checklist berikut:
DISTRIBUSI POD DAN TOPOLOGI ZONA:
□ Aplikasi produksi kritis minimal dijalankan dengan jumlah 3 replika (2 tidak cukup saat rolling update).
□ Mengonfigurasi Pod Anti-Affinity untuk mencegah penumpukan replika di satu Node fisik yang sama.
□ Menggunakan topologySpreadConstraints dengan maxSkew: 1 untuk menyebarkan Pod merata lintas Availability Zones (AZ).
□ Menghindari penggunaan requiredDuringScheduling (strict) anti-affinity pada kluster dengan jumlah node < jumlah replika.
PROTEKSI PEMELIHARAAN KLUSTER:
□ Setiap Deployment kritis produksi memiliki objek PodDisruptionBudget (PDB) terasosiasi.
□ Parameter PDB dikonfigurasi secara rasional (misal: maxUnavailable: 1 atau maxUnavailable: 25%).
□ Uji coba simulasi pembersihan Node ('kubectl drain') terbukti berhasil memindahkan Pod tanpa downtime.
□ PDB tidak disetel dengan nilai minAvailable == total replicas (yang akan memblokir total proses drain selamanya).
ZERO-DOWNTIME LIFECYCLE HOOKS:
□ Mengonfigurasi parameter 'terminationGracePeriodSeconds' dengan durasi memadai (rekomendasi 60 detik).
□ Menyisipkan perintah lifecycle preStop 'sleep 10' (atau minimal 5-10 detik) untuk mengantisipasi latency penyebaran iptables.
□ Probes keaktifan ('readinessProbe' dan 'livenessProbe') terkonfigurasi dengan port dan health-path terisolasi yang valid.
□ Aplikasi mampu menangani sinyal penutupan SIGTERM secara anggun (menyelesaikan sisa transaksi database sebelum keluar).
Ringkasan #
- Replika Banyak Bukan HA — Sadari bahwa replika banyak tanpa aturan anti-affinity tetap berisiko menumpuk di satu Node fisik yang sama; gunakan
topologyKey: kubernetes.io/hostnameuntuk memisahkan Pod.- Optimalkan Spread dengan TSC — Gunakan Topology Spread Constraints dengan
maxSkew: 1untuk menjamin pembagian kapasitas aplikasi secara seimbang melintasi Availability Zones.- PDB Wajib untuk Proteksi Drain — Terapkan PodDisruptionBudget pada seluruh layanan inti produksi untuk mencegah pemadaman massal Pod saat proses peningkatan versi OS node.
- preStop sleep 10 Detik — Wajib pasang jeda tidur preStop hook untuk mengamankan lalu lintas transaksi dari risiko HTTP 502 Bad Gateway akibat latensi penyebaran tabel iptables kluster.
- Gunakan preferred untuk Keamanan Skala — Pilih aturan anti-affinity bertipe preferred (soft rule) pada kluster kecil agar Pod tidak stuck
Pendingsaat kekurangan kapasitas Node fisik.- Konfigurasi Kuorum etcd Ganjil — Jamin ketersediaan tinggi database kluster etcd dengan mengonfigurasi jumlah Node ganjil (3 atau 5 master nodes) untuk konsensus Raft yang valid.