StatefulSet #
Di dalam arsitektur sistem terdistribusi, kita mengenal dua kategori beban kerja (workloads): Stateless (tanpa status) dan Stateful (memiliki status). Aplikasi stateless—seperti API Gateway, frontend, atau microservices biasa—dapat dengan mudah dimatikan, dipindahkan, atau diduplikasi kapan saja karena mereka tidak menyimpan data lokal yang berharga. Namun, aplikasi stateful—seperti database PostgreSQL, MySQL, cache Redis, Kafka message queue, atau kluster Elasticsearch—membutuhkan penanganan yang jauh lebih rumit. Mereka harus selalu mengingat identitas mereka, di mana data mereka disimpan, dan dalam urutan apa mereka harus berkomunikasi.
Untuk mengelola aplikasi stateful ini secara aman, Kubernetes menyediakan pengontrol khusus bernama StatefulSet. StatefulSet bertindak sebagai pengawal yang memberikan identitas unik, jaringan yang stabil, dan penyimpanan persisten yang terikat secara permanen pada masing-masing replika Pod. Memahami cara kerja StatefulSet secara mendalam membantu kita menghindari bencana kehilangan data (data loss) serta memudahkan pemeliharaan database di atas Kubernetes.
Perbedaan Fundamental dengan Deployment #
Banyak tim DevOps pemula mencoba mendeploy database menggunakan objek Deployment karena dianggap lebih familiar. Ini adalah kesalahan fatal.
Berikut adalah tabel perbandingan arsitektural yang membedakan penanganan Pod antara Deployment dan StatefulSet:
| Karakteristik | Deployment (Stateless) | StatefulSet (Stateful) |
|---|---|---|
| Penamaan Pod | Acak & Tidak Stabil (misal app-7f4d9b-abcde) |
Ordinal & Stabil (misal db-0, db-1, db-2) |
| Identitas DNS | Berbagi DNS tunggal (Load Balanced) | Memiliki DNS unik per Pod (Headless Service) |
| Penyimpanan (Storage) | Berbagi satu volume bersama (atau ephemeral) | Memiliki PersistentVolume khusus per Pod |
| Urutan Operasi | Acak dan Paralel (semua Pod start bersamaan) | Berurutan (Ordered) dan Bertahap |
| Penanganan Crash | Pod mati digantikan Pod baru dengan nama baru | Pod mati digantikan Pod baru dengan nama yang sama |
StatefulSet memandang setiap Pod sebagai individu yang unik dan tidak dapat saling menggantikan (pet model), sedangkan Deployment memandang Pod sebagai kawanan ternak yang identik dan dapat dibuang kapan saja (cattle model).
Struktur Manifes StatefulSet Secara Mendalam #
Untuk menyusun kluster database yang tangguh, kita harus menulis manifest StatefulSet dengan menyertakan blok deklarasi volumeClaimTemplates dan mengaitkannya ke sebuah Headless Service.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb
namespace: database
spec:
serviceName: "mariadb-headless" # Nama Headless Service wajib didefinisikan
replicas: 3
podManagementPolicy: OrderedReady # OrderedReady (Default) | Parallel
selector:
matchLabels:
app: mariadb
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb:10.11
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: db-data
mountPath: /var/lib/mysql
# Setiap Pod otomatis mendapatkan klaim penyimpanan tersendiri
volumeClaimTemplates:
- metadata:
name: db-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "premium-rwo-ssd"
resources:
requests:
storage: 20Gi
Penjelasan Parameter Kunci: #
serviceName: Kunci penghubung yang menunjuk ke nama objek Headless Service. Tanpa adanya kecocokan field ini, resolusi DNS individu Pod tidak akan berfungsi.volumeClaimTemplates: Templat pembuatan PersistentVolumeClaim (PVC) otomatis. Jika kita meminta 3 replika, StatefulSet Controller secara dinamis akan membuat 3 PVC independen di API Server:db-data-mariadb-0,db-data-mariadb-1, dandb-data-mariadb-2.
Identitas Stabil: Skema Penamaan Ordinal dan Resolusi DNS #
Setiap Pod yang diciptakan oleh StatefulSet akan mendapatkan nomor indeks ordinal dari 0 hingga N-1 (di mana N adalah jumlah replika). Nomor ini menempel secara permanen pada identitas fisik maupun jaringan Pod.
Kestabilan Nama dan Hostname #
Jika Pod mariadb-1 mengalami crash atau Worker Node-nya mati, Scheduler akan menjadwalkan ulang Pod tersebut ke node lain yang sehat.
- Nama Tetap Sama: Pod baru yang menyala akan tetap memiliki nama
mariadb-1. - Penyimpanan Tetap Terikat: Kubelet akan melacak PVC
db-data-mariadb-1dan memasangnya kembali ke Podmariadb-1yang baru. Data database kita tidak akan tertukar dengan data milikmariadb-0ataumariadb-2.
Format FQDN DNS Unik #
Dengan bantuan Headless Service, setiap Pod memiliki Fully Qualified Domain Name (FQDN) yang stabil di dalam jaringan internal Kubernetes CoreDNS:
$$\text{Format DNS} = \text{{nama-pod}}.\text{{nama-service}}.\text{{namespace}}.svc.cluster.local$$
Kluster MariaDB dengan 3 replika memiliki alamat DNS internal:
- mariadb-0.mariadb-headless.database.svc.cluster.local
- mariadb-1.mariadb-headless.database.svc.cluster.local
- mariadb-2.mariadb-headless.database.svc.cluster.local
Nama domain ini tidak pernah berubah meskipun IP Pod dinamis di belakangnya berubah-ubah akibat restart. Hal ini mempermudah konfigurasi sinkronisasi kluster database (seperti penentuan master node di indeks 0 dan slave node di indeks 1 dan 2).
Peran Vital Headless Service #
Sebuah StatefulSet wajib dipasangkan dengan Headless Service. Headless Service adalah Service Kubernetes biasa, namun didefinisikan dengan parameter clusterIP: None.
apiVersion: v1
kind: Service
metadata:
name: mariadb-headless # Harus COCOK dengan spec.serviceName di StatefulSet
namespace: database
spec:
clusterIP: None # Kunci utama yang menjadikannya Headless
selector:
app: mariadb
ports:
- port: 3306
targetPort: 3306
Mengapa Harus clusterIP: None? #
- Tanpa Load Balancer Virtual: Service biasa memiliki IP virtual tunggal (ClusterIP) yang melakukan load balancing acak (round-robin) ke seluruh Pod di belakangnya. Pola ini tidak cocok untuk database karena klien harus tahu secara spesifik apakah mereka sedang menembak Master Node (untuk menulis data) atau Slave Node (untuk membaca data).
- Direct IP Resolution: Headless Service tidak memiliki ClusterIP. Ketika sebuah aplikasi melakukan query DNS ke
mariadb-headless, CoreDNS tidak mengembalikan satu IP virtual, melainkan mengembalikan daftar IP langsung dari seluruh Pod sehat yang terhubung. Klien aplikasi dapat memilih IP Pod mana yang ingin ditembak secara langsung.
Urutan Operasi (Ordered Operations) Startup dan Shutdown #
Untuk mencegah ketimpangan data saat proses pembentukan kluster terdistribusi, StatefulSet Controller menerapkan aturan urutan eksekusi yang ketat.
Berikut adalah visualisasi alur proses pengurangan replika (scale-down) berurutan pada StatefulSet:
flowchart TD
StartScaleDown["Mulai Perintah Scale-Down (3 Replicas ➔ 1 Replica)"] --> SelectHigh["1. Pilih Pod indeks tertinggi: mariadb-2"]
SelectHigh --> TerminateHigh["2. Kirim sinyal terminasi (SIGTERM) ke mariadb-2"]
TerminateHigh --> WaitHigh{"3. Apakah mariadb-2\nsudah mati total?"}
WaitHigh -- "Belum" --> WaitHigh
WaitHigh -- "Ya" --> SelectNext["4. Pilih Pod indeks tertinggi berikutnya: mariadb-1"]
SelectNext --> TerminateNext["5. Kirim sinyal terminasi ke mariadb-1"]
TerminateNext --> WaitNext{"6. Apakah mariadb-1\nsudah mati?"}
WaitNext -- "Belum" --> WaitNext
WaitNext -- "Ya" --> Stop["7. Scale-down selesai. Hanya mariadb-0 yang berjalan."]
OrderedReady vs Parallel Management #
Secara default, StatefulSet menerapkan kebijakan podManagementPolicy: OrderedReady:
- OrderedReady: Pod berikutnya tidak akan dibuat sebelum Pod sebelumnya dinyatakan benar-benar siap (Ready). Ini sangat penting untuk kluster seperti Apache ZooKeeper yang membutuhkan kuorum bootstrap bertahap.
- Parallel: Menghapus seluruh batasan urutan. Semua Pod akan dinyalakan atau dimatikan secara paralel dan bersamaan. Kebijakan ini sangat tepat digunakan jika kita hanya membutuhkan kestabilan nama dan PVC, namun menginginkan proses provisioning kluster selesai secepat mungkin (misal pada kluster Apache Cassandra).
Kebijakan Update dan Fitur Partition Canary #
StatefulSet mendukung strategi RollingUpdate yang memproses pembaruan dari indeks tertinggi ke terendah secara berurutan. Di samping itu, StatefulSet memiliki fitur canggih yang disebut Partition.
Properti partition memungkinkan kita melakukan rilis tipe Canary di tingkat database. Bayangkan kita memiliki kluster database dengan 5 replika (indeks 0 s.d. 4) dan ingin menguji versi baru hanya pada indeks 3 dan 4:
spec:
replicas: 5
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 3 # Hanya lakukan update versi baru pada Pod dengan indeks >= 3
Dengan konfigurasi di atas:
- Jika kita mengupdate image StatefulSet ke versi baru, hanya Pod
mariadb-3danmariadb-4yang akan di-restart dan diperbarui ke versi baru. - Pod
mariadb-0,mariadb-1, danmariadb-2dijamin tetap berjalan menggunakan versi lama. - Hal ini memberikan ruang bagi tim DevOps untuk menguji stabilitas performa versi baru pada satu atau dua replika slave terlebih dahulu sebelum menerapkan rilis secara menyeluruh ke database utama (master).
Kebijakan Retensi Volume (PVC Retention Policy) #
Sebuah perilaku penting dari StatefulSet yang wajib dipahami oleh setiap administrator kluster: Ketika StatefulSet dihapus atau di-scale down, objek PVC tidak akan pernah ikut terhapus.
- Mencegah Bencana: Ini adalah keputusan desain sengaja dari Kubernetes untuk melindungi data berharga kita. Jika kita secara tidak sengaja menghapus StatefulSet, data di disk tetap aman. Saat kita membuat kembali StatefulSet dengan nama yang sama, data lama akan langsung tersambung kembali.
- Pembersihan Manual: Untuk benar-benar membuang data, kita harus menghapus PVC secara manual setelah StatefulSet mati:
kubectl delete pvc data-mariadb-0 data-mariadb-1
Fitur Modern Retensi Otomatis #
Pada Kubernetes versi modern, kita dapat mengendalikan perilaku retensi PVC ini secara deklaratif menggunakan blok persistentVolumeClaimRetentionPolicy:
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete # Hapus PVC secara otomatis jika objek StatefulSet dihapus
whenScaled: Retain # Tetap simpan PVC jika hanya melakukan scale-down
Penanganan Skenario Kegagalan Node dan Aturan “At-Most-One” #
Salah satu aspek perilaku StatefulSet yang paling sering disalahpahami adalah cara penanganan kegagalan Node. Pada objek Deployment (stateless), jika sebuah Worker Node mati (berstatus NotReady), Kubernetes akan mendeteksi hilangnya komunikasi (setelah durasi tertentu, biasanya dikendalikan oleh parameter kube-controller-manager) dan secara otomatis membuat ulang Pod tersebut di node lain yang sehat.
Namun, perilaku ini sangat berbeda pada StatefulSet. Demi menjaga konsistensi data yang ketat dan mencegah kerusakan data akibat penulisan ganda, StatefulSet memberlakukan aturan At-Most-One Pod Semantics (Semantik Paling Banyak Satu Pod).
Mengapa At-Most-One Sangat Penting? #
Bayangkan kita menjalankan kluster database PostgreSQL dengan topologi replikasi aktif. Pod db-0 bertindak sebagai Master Node yang menulis data ke disk eksternal (cloud storage). Tiba-tiba, node tempat db-0 berjalan mengalami gangguan jaringan ringan (network partition). Node tersebut tidak bisa dihubungi oleh Control Plane, sehingga berstatus NotReady di mata API Server. Namun, node itu sebenarnya masih menyala dan proses database di dalamnya masih aktif menulis data.
Jika Kubernetes secara naif langsung membuat Pod db-0 baru di node lain dan menyambungkannya ke volume penyimpanan yang sama, kita akan memiliki dua proses database berbeda yang menulis ke disk yang sama secara bersamaan. Ini adalah bencana besar yang disebut split-brain, yang pasti akan merusak tabel database dan menghancurkan seluruh data kita.
Siklus Hidup Pod Saat Node Mengalami Kegagalan #
Ketika sebuah Node mati, Pod StatefulSet di dalamnya akan ditandai dengan status Terminating atau Unknown oleh control plane. Namun:
- Pod Tidak Akan Dihapus Secara Otomatis: Kubelet di node yang mati tidak dapat mengonfirmasi ke API Server bahwa kontainer di dalamnya telah benar-benar berhenti berjalan.
- Pod Baru Tidak Akan Dibuat: StatefulSet Controller menolak membuat Pod pengganti dengan nama ordinal yang sama (misal
db-0) di node lain sampai Pod lama benar-benar mati. Pod kita akan stuck di statusTerminatingselamanya.
Langkah Penanganan yang Aman oleh Administrator #
Untuk memulihkan keadaan ini di lingkungan produksi, kita tidak boleh langsung memaksa penghapusan Pod (force delete) tanpa melakukan verifikasi fisik terlebih dahulu. Berikut adalah langkah yang aman yang harus kita ikuti:
- Verifikasi Kondisi Node Fisik: Pastikan node lama benar-benar mati total (mengalami kegagalan hardware permanen) atau telah diputus koneksinya dari storage network secara fisik.
- Lakukan Evacuation / Force Delete: Jika dan hanya jika kita sudah yakin node lama tidak akan pernah menulis ke volume penyimpanan lagi, kita dapat menghapus Pod secara paksa menggunakan perintah berikut:
Setelah perintah ini dijalankan, API Server akan menghapus entri Pod lama dari etcd, dan StatefulSet Controller akan langsung menjadwalkan Pod
kubectl delete pod db-0 --grace-period=0 --force -n databasedb-0baru di node lain dengan aman.
Anti-Pattern dalam Manajemen StatefulSet #
Berikut adalah dua kesalahan fatal yang sering kali merusak data atau melumpuhkan kluster database:
Anti-Pattern 1: Mengabaikan Headless Service atau Salah Memetakan serviceName #
Kesalahan konfigurasi nama service yang memutuskan komunikasi resolusi DNS kluster.
ANTI-PATTERN: Menulis serviceName: "mariadb-service" Namun Membuat Service Tanpa clusterIP: None
// KITA MELAKUKAN:
- Menuliskan `serviceName: "mariadb-headless"` di manifest StatefulSet.
- Namun kita lupa membuat objek Service bernama `mariadb-headless`,
atau membuat Service tersebut sebagai Service standar (memiliki ClusterIP virtual).
// KONSEKUENSI DI PRODUKSI:
- DNS Breakage: Alamat individual Pod seperti `mariadb-0.mariadb-headless` gagal di-resolve oleh CoreDNS.
- Kegagalan Replikasi: Kluster database gagal melakukan sinkronisasi data master-slave
karena node-node database tidak dapat menemukan satu sama lain secara internal, memicu status database split-brain.
✓ SOLUSI YANG BENAR:
- Pastikan selalu ada objek Service pendamping yang memiliki `clusterIP: None` dengan nama yang
sama persis dengan properti `spec.serviceName` yang tertulis di dalam manifes StatefulSet.
- Verifikasi resolusi nama DNS dari dalam kluster menggunakan perintah diagnostik:
`nslookup mariadb-0.mariadb-headless.database.svc.cluster.local`
Anti-Pattern 2: Menggunakan Storage Kelas Lambat/Non-SSD untuk Database Stateful #
Memilih StorageClass standar murahan demi menekan biaya infrastruktur.
ANTI-PATTERN: Menggunakan storageClassName: "standard-hdd" untuk Database PostgreSQL
// KITA MELAKUKAN:
- Mendeploy kluster database produksi dengan tipe PVC harddisk mekanis biasa (HDD).
// KONSEKUENSI DI PRODUKSI:
- I/O Bottleneck: Database stateful melakukan transaksi penulisan I/O tinggi. HDD lambat memicu penumpukan antrean penulisan.
- Crash Loop: Proses database akan sering kali gagal lolos liveness probe akibat hang I/O,
membuat Kubelet merestart Pod secara berulang, merusak integritas tabel database.
✓ SOLUSI YANG BENAR:
- Selalu gunakan kelas penyimpanan SSD dengan kinerja terjamin (misalnya `premium-rwo-ssd` di GCP atau `gp3/io2` di AWS).
- Konfigurasikan parameter `volumeClaimTemplates` untuk selalu menunjuk ke nama `storageClassName` SSD tersebut.
Ringkasan #
- Identitas Stabil Unik — StatefulSet memberikan nama ordinal permanen (
db-0,db-1) dan alokasi PersistentVolume tersendiri untuk setiap replika Pod, menjaga kestabilan data saat restart.- Wajib Headless Service — Tanpa pendefinisian Headless Service (
clusterIP: None), resolusi nama domain DNS individual per Pod tidak akan berfungsi di kluster.- Daur Hidup Ordered Operations — Pembentukan Pod berjalan teratur dari indeks terkecil (
0 ➔ 1 ➔ 2), sedangkan pemadaman berjalan sebaliknya dari indeks terbesar (2 ➔ 1 ➔ 0) guna menjaga replikasi data aman.- Retensi PVC Terproteksi — Secara default, Kubernetes tidak menghapus PVC saat StatefulSet mati demi melindungi data; gunakan kebijakan retensi modern atau hapus PVC manual jika data ingin dibuang.
- Rilis Canary via Partition — Manfaatkan fitur
rollingUpdate.partitionpada StatefulSet untuk menguji versi image baru pada satu Pod indeks tertinggi saja sebelum merilis ke seluruh database.- Hindari Deployment untuk DB — Jangan gunakan Deployment biasa untuk database replikasi guna mencegah tabrakan penulisan volume penyimpanan dan hilangnya status identitas jaringan.
- Gunakan Penyimpanan SSD — Selalu kaitkan
volumeClaimTemplateske StorageClass berbasis SSD guna menjamin kecepatan transaksi I/O database tetap optimal.