Storage Problem di Distributed Systems #
Di dalam rekayasa perangkat lunak modern, kita sering kali terpikat oleh kemudahan yang ditawarkan oleh Kubernetes dalam mengelola aplikasi tanpa status (stateless). Kita dapat dengan mudah melakukan replikasi, autoscaling, dan rolling update tanpa perlu khawatir tentang kehilangan status internal aplikasi. Namun, ketika kita mulai melangkah ke dunia aplikasi yang memiliki status (stateful workloads)—seperti database PostgreSQL, kluster Kafka, cache Redis, atau Elasticsearch—kita akan dihadapkan pada kenyataan pahit.
Menyimpan data di dalam sistem terdistribusi (distributed systems) bukan sekadar menulis berkas ke atas disk lokal. Ketika aplikasi berjalan di atas puluhan node worker fisik yang dapat mengalami mati listrik, gangguan jaringan, atau pengusiran paksa (eviction) kapan saja oleh scheduler, penyimpanan data menjadi area paling kompleks dari seluruh operasi kluster. Artikel ini akan membedah secara mendalam tantangan-tantangan unik yang terjadi pada storage di sistem terdistribusi, mengapa split-brain adalah bencana terbesar database, alur penanganan detasemen disk cloud, hingga formula pengambilan keputusan arsitektural yang matang.
Masalah 1: Mobilitas Pod vs Imobilitas Data (Data Locality vs Portability) #
Di dalam Kubernetes, Pod dirancang untuk memiliki mobilitas tinggi. Jika node worker tempat Pod berjalan mengalami kegagalan hardware, control plane akan mendeteksi status NotReady dan scheduler akan segera memindahkan Pod tersebut ke node worker lain yang sehat.
Namun, data fisik tidak dapat berpindah secepat Pod. Jika data tersimpan di disk lokal node worker A, data tersebut secara fisik terkunci di sana. Pod yang dipindahkan ke node worker B akan menyala dengan direktori kosong dan kehilangan seluruh status historisnya.
Untuk mengatasi imobilitas data lokal ini, kita menggunakan solusi penyimpanan eksternal (seperti cloud persistent disk atau network storage). Ketika Pod dipindahkan, volume penyimpanan eksternal tersebut harus dilepas (detach) dari node lama dan dipasang kembali (attach) ke node baru.
Berikut adalah diagram visualisasi alur proses pelepasan dan pemasangan volume saat terjadi kegagalan node worker:
flowchart TD
subgraph Node1 ["Node Worker 1 (NotReady)"]
N1_Crash["Node-1 Crash / Mati Listrik"] --> N1_Stuck["Disk Cloud 'vol-xyz' Tetap Terkunci (Attached)"]
end
subgraph ControlPlane ["Control Plane (API & Controller)"]
N1_Stuck --> DetachDelay["Deteksi NotReady -. Menunggu 6 Menit (Timeout) .-> Detach Force"]
DetachDelay --> ForceDetach["Kirim Perintah Force Detach ke Cloud API"]
end
subgraph Node2 ["Node Worker 2 (Healthy)"]
ForceDetach --> AttachNew["Attach Disk 'vol-xyz' ke Node-2"]
AttachNew --> MountVol["Mount Volume ke Pod Baru"]
MountVol --> PodReady["Pod PostgreSQL Kembali Running & Data Aman"]
end
Masalah Detach/Attach Timeout #
Meskipun diagram di atas terlihat lancar, di dunia nyata proses ini sering kali terhambat oleh race condition dan latensi API cloud provider.
- Stuck Volume: Ketika
node-1mati mendadak, API Cloud Provider masih mencatat bahwa diskvol-xyzsedang menempel (attached) secara eksklusif kenode-1. - Safety Lock: Cloud provider menolak memasang disk tersebut ke
node-2karena khawatirnode-1sebenarnya masih menyala dan akan menulis data secara bersamaan (pencegahan kerusakan data). - Timeout Delay: Kubernetes harus menunggu timeout bawaan (biasanya berkisar antara 5 hingga 6 menit) sebelum memaksa melakukan perintah force detach melalui API Cloud. Selama waktu tunggu ini, Pod database kita akan stuck di status
ContainerCreatingatauPendingdengan pesan error:Volume is already exclusively attached to one node.
Masalah 2: Pola Akses Konkuren dan Bottleneck I/O Jaringan #
Ketika kita mendeploy aplikasi web dengan banyak replika (Deployment), kita sering kali menginginkan seluruh replika Pod tersebut dapat membaca dan menulis file ke folder yang sama (misalnya direktori upload gambar).
Block Storage vs File Storage #
Tantangan fisik hardware storage membatasi bagaimana data dapat diakses secara bersamaan:
flowchart TD
subgraph Block ["Block Storage (RWO / RWOP)"]
B_Pod1["Pod-1 (Node-A)"] -->|"Akses Langsung Sektor Disk<br/>(Sangat Cepat)"| B_Disk["Cloud Disk"]
B_Pod2["Pod-2 (Node-B)"] --x|"Ditolak oleh Cloud Provider!"| B_Disk
end
subgraph File ["File Storage (RWX)"]
F_Pod1["Pod-1 (Node-A)"] --> F_Server["NFS / EFS Server"]
F_Pod2["Pod-2 (Node-B)"] --> F_Server
F_Server -->|"Operasi Melalui Protokol Jaringan<br/>(Ada Latensi)"| F_Sys["File System"]
end
- Block Storage (AWS EBS, GCP PD):
- Menyediakan raw block device langsung ke sistem operasi node.
- Hanya mendukung mode
ReadWriteOnce(RWO) karena filesystem standar (seperti ext4 atau xfs) tidak didesain untuk dipasang ke beberapa mesin sekaligus tanpa koordinasi metadata tingkat rendah (distributed lock manager). Jika dipaksa, filesystem akan mengalami kerusakan (corruption) instan. - Performa: Sangat cepat karena latensi akses mendekati disk lokal.
- File Storage (NFS, Amazon EFS, Azure Files):
- Menyediakan sistem berkas siap pakai yang diakses melalui protokol jaringan (NFS atau SMB).
- Mendukung mode
ReadWriteMany(RWX), sehingga ratusan Pod lintas node worker dapat menulis file bersamaan. - Performa: Memiliki overhead latensi jaringan yang signifikan. Setiap operasi file (seperti membuat file, memindai direktori, atau menulis baris log) harus melalui jaringan TCP/IP. Jika aplikasi kita melakukan ribuan operasi baca/tulis file kecil secara konstan, performa aplikasi akan menurun drastis (I/O bottleneck).
Masalah 3: Bahaya Terbesar Database Terdistribusi: Split-Brain #
Ketika kita menjalankan database dengan topologi kluster terdistribusi (seperti PostgreSQL Master-Slave, Elasticsearch, atau MongoDB Replica Set) di Kubernetes, kita mengandalkan protokol konsensus jaringan untuk menentukan node mana yang bertindak sebagai Primary (tulis) dan node mana yang bertindak sebagai Secondary (baca).
Skenario Partisi Jaringan (Network Partition) #
Bayangkan kita memiliki kluster database 3 node yang terpisah di dua zona jaringan yang berbeda akibat kegagalan saklar jaringan (switch fail):
flowchart LR
subgraph ZoneA["Zona Jaringan A"]
db0["db-0 (Primary)<br/>(Terisolasi)<br/>Hanya bisa melihat dirinya sendiri"]
end
subgraph ZoneB["Zona Jaringan B"]
db1["db-1 (Slave)"]
db2["db-2 (Slave)"]
db1 --- db2
noteB["Dua node bisa saling melihat<br/>dan membentuk kuorum (2 dari 3)"]
end
ZoneA -. "PARTISI JARINGAN" .-> ZoneB
Jika partisi ini terjadi:
- Sisi Kanan (Zona B): Node
db-1dandb-2mendeteksi bahwa mereka tidak bisa menghubungi Masterdb-0. Karena mereka berdua membentuk kuorum mayoritas (2 dari total 3 node), mereka secara otomatis mempromosikandb-1menjadi Master baru. Sisi kanan sekarang menerima transaksi penulisan dari aplikasi. - Sisi Kiri (Zona A): Node
db-0yang terisolasi mungkin masih menyala dan berasumsi bahwa ia masih menjadi Master yang valid jika aplikasi di zona A masih bisa menghubunginya. Ia tetap menerima transaksi penulisan. - Split-Brain Terjadi: Sekarang kita memiliki dua Master aktif yang menulis data berbeda ke disk masing-masing tanpa ada sinkronisasi. Begitu partisi jaringan pulih, data di kedua sisi telah menyimpang (diverged data). Melakukan rekonsiliasi data yang tabrakan ini secara manual adalah mimpi buruk terbesar setiap tim database.
Solusi Proteksi Konsensus dan Fencing: #
Untuk menghindari split-brain, kluster database modern menggunakan algoritma konsensus seperti Raft atau Paxos:
- Node Master wajib memverifikasi secara konstan bahwa ia masih mendapatkan dukungan kuorum mayoritas. Jika
db-0mendeteksi ia terisolasi (hanya 1 node aktif), ia harus segera menurunkan status dirinya sendiri menjadi read-only secara otomatis (self-demotion). - CSI Fencing / STONITH (Shoot The Other Node In The Head): Sistem Kubernetes Controller akan memutus akses Persistent Volume secara paksa dari Pod
db-0lewat API Cloud Provider sebelum mempromosikandb-1menjadi master baru. Hal ini menjamin bahwa master lama tidak akan pernah bisa menulis satu byte data pun ke disk fisik lagi.
Masalah 4: Degradasi Performa Akibat Noisy Neighbors #
Di dalam lingkungan Kubernetes multi-tenant (di mana banyak tim developer berbagi kluster yang sama), volume penyimpanan kita sering kali diletakkan pada backend storage bersama yang sama (shared storage pool).
Jika tim A menjalankan aplikasi batch processing yang memproses data mentah 100GB secara agresif, I/O bandwidth dari jaringan storage akan terkuras penuh. Akibatnya, tim B yang menjalankan database transaksional latensi rendah pada node worker yang terhubung ke backend storage yang sama akan mengalami degradasi performa hebat (noisy neighbor effect). Latensi penulisan query SQL yang biasanya selesai dalam 3 ms akan melonjak menjadi 80 ms, memicu kegagalan transaksi dan timeout sistem.
Strategi Pencegahan Noisy Neighbor: #
- IOPS & Throughput Limits: Selalu konfigurasikan limit I/O pada tingkat
StorageClassjika cloud provider kita mendukungnya (misalnya menyetel parameteriopsdanthroughputpada disk AWS gp3). - Pemisahan Node Pool khusus Storage: Gunakan label node, taints, dan tolerations untuk mendeploy aplikasi I/O intensif pada node pool khusus yang terhubung ke jaringan storage terisolasi.
Masalah 5: Tantangan Operasi Day-2 (Resizing & Backup Konsisten) #
Menjalankan storage di sistem terdistribusi membawa tantangan pemeliharaan jangka panjang yang kompleks.
1. Online Volume Expansion #
Ketika kapasitas disk database kita mendekati 90% penuh, kita harus memperbesar ukuran volume. Kubernetes mendukung fitur Online Volume Expansion yang memungkinkan kita mengubah ukuran PVC secara deklaratif tanpa mematikan Pod.
- Bagaimana cara kerjanya? Kita mengubah properti
spec.resources.requests.storage: 100Gipada PVC menjadi200Gi. - Tantangan: CSI Driver harus mengirim instruksi resize ke cloud API, kemudian Kubelet di node worker harus menjalankan perintah Linux
resize2fsatauxfs_growfsuntuk memperluas sistem berkas kontainer yang sedang berjalan. Jika filesystem mengalami crash di tengah jalan, database kita terancam rusak.
2. Backup Konsisten (Application-Consistent Backup) #
Mengambil snapshot volume penyimpanan cloud secara mentah (crash-consistent backup) saat database sedang memproses transaksi tinggi sangat berbahaya. Snapshot tersebut berisiko menangkap data yang masih tertahan di memori cache sistem operasi atau buffer database yang belum ditulis ke disk.
- Solusi: Proses backup harus bersifat Application-Consistent. Tool backup (seperti Velero) harus mengirim instruksi untuk membekukan penulisan database (quiesce/freeze) terlebih dahulu, melakukan snapshot disk cloud, lalu mencairkan kembali (unfreeze) proses penulisan database.
Rangka Keputusan Arsitektur: Managed DB vs Self-Managed #
Menimbang seluruh kompleksitas di atas, kita harus memiliki kriteria yang jelas untuk menentukan kapan kita layak menjalankan database di dalam Kubernetes dan kapan kita harus menghindarinya.
flowchart TD
Start["Mulai Analisis Stateful Workload"] --> Q1["Apakah data ini sangat kritis untuk bisnis?"]
Q1 -->|Ya| A1["Gunakan Managed Service (AWS RDS / GCP Cloud SQL)"]
Q1 -->|Tidak| Q2["Apakah tim Anda memiliki ahli Kubernetes?"]
Q2 -->|Ya| A2["Jalankan di K8s (Gunakan StatefulSet & Official Operator SDK)"]
Q2 -->|Tidak| A3["Gunakan Managed DB atau Cloud Storage"]
Tabel Perbandingan Arsitektural: #
| Karakteristik | Managed DB Service (AWS RDS / Cloud SQL) | Self-Managed DB di Kubernetes (StatefulSet) |
|---|---|---|
| Kompleksitas Setup | Sangat Rendah (Cukup beberapa klik API) | Tinggi (Harus menyusun manifest, PVC, Headless Service) |
| Failover Multi-Zone | Otomatis & Teruji (Dikelola cloud provider) | Harus dikonfigurasi manual menggunakan Operator/Patroni |
| Overhead Operasional | Sangat Rendah | Tinggi (Pemeliharaan patch OS, resizing disk, monitor IOPS) |
| Efisiensi Biaya | Mahal (Ada markup biaya manajemen cloud) | Murah (Hanya membayar harga VM dan volume disk mentah) |
| Kedaulatan Data | Tergantung kebijakan provider cloud | Mutlak (Kontrol penuh atas penempatan enkripsi file) |
Anti-Pattern Terkait Distributed Storage & Solusinya #
Berikut adalah dua kesalahan fatal yang sering melumpuhkan kluster Kubernetes akibat salah penanganan distributed storage:
Anti-Pattern 1: Mengabaikan volumeBindingMode: WaitForFirstConsumer pada StorageClass Multi-Zone #
Membiarkan StorageClass menggunakan mode default volumeBindingMode: Immediate pada kluster Kubernetes yang tersebar di beberapa Availability Zones (AZ).
ANTI-PATTERN: volumeBindingMode: Immediate pada Kluster Multi-AZ
// KITA MELAKUKAN:
- Kita membuat StorageClass untuk cloud disk dengan mode `Immediate`.
- Kita membuat PVC baru yang meminta disk 10GB.
// KONSEKUENSI DI PRODUKSI:
- Begitu PVC dibuat, StorageClass langsung memerintahkan Cloud API untuk membuat disk fisik.
Karena Pod belum dijadwalkan, StorageClass memilih zona secara acak (misal Zona A).
- Disk fisik dibuat di Zona A.
- Selanjutnya, scheduler memproses Pod kita. Namun, karena node worker di Zona A sedang penuh
atau memiliki taint, scheduler memutuskan untuk menempatkan Pod di node worker Zona B.
- Pod di Zona B mencoba menempelkan disk fisik yang berada di Zona A.
- Pod stuck selamanya di status `ContainerCreating` dengan pesan error:
`Volume node affinity conflict: volume can only be attached to nodes in Zone A, but Pod was scheduled to Zone B`.
✓ SOLUSI YANG BENAR:
- Selalu setel `volumeBindingMode: WaitForFirstConsumer` pada semua StorageClass produksi.
- Mode ini menunda pembuatan disk fisik hingga scheduler selesai menentukan node worker mana yang akan
menjalankan Pod. Disk fisik dijamin akan dibuat di Zona yang sama dengan node worker terpilih.
Anti-Pattern 2: Menghapus Paksa (Force Delete) PV/PVC yang Stuck di Status Terminating #
Menggunakan perintah --force untuk menghapus objek PV atau PVC dari etcd tanpa memeriksa status pelepasan fisik di backend storage.
ANTI-PATTERN: kubectl delete pvc mysql-pvc --grace-period=0 --force
// KITA MELAKUKAN:
- Kita menghapus PVC mysql-pvc, namun prosesnya stuck di status `Terminating` karena masih ada Pod yang me-mount-nya.
- Karena tidak sabar, kita memaksa penghapusan dengan parameter `--force --grace-period=0`.
// KONSEKUENSI DI PRODUKSI:
- Yatim Piatu (Orphaned Storage): Kubernetes akan menghapus metadata PVC dari database etcd secara instan.
- Namun, karena komunikasi dengan Cloud API terputus atau tertunda, disk fisik di cloud provider
TIDAK IKUT TERHAPUS. Disk fisik tersebut tetap menyala secara pasif dan terus memotong tagihan cloud kita.
- Kebocoran Kouta Disk: Dalam jangka panjang, ratusan disk yatim piatu ini akan menumpuk di cloud,
membuat kita kehabisan kuota kuota pembuatan disk provider cloud saat dibutuhkan di kemudian hari.
✓ SOLUSI YANG BENAR:
- Jangan pernah memaksa menghapus PVC yang stuck di status Terminating.
- Cari tahu mengapa PVC tersebut stuck dengan memeriksa apakah masih ada Pod aktif yang me-mount PVC tersebut:
`kubectl get pods --all-namespaces -o json | jq -r '.items[] | select(.spec.volumes[].persistentVolumeClaim.claimName=="mysql-pvc") | .metadata.name'`
- Hentikan Pod tersebut terlebih dahulu, dan Kubernetes akan menghapus PVC secara bersih dan otomatis.
Ringkasan #
- Imobilitas Data Fisik — Data pada disk tidak dapat berpindah secepat Pod; gunakan cloud persistent disk yang mendukung pelepasan dan pemasangan otomatis lintas node.
- Pola Akses RWO vs RWX — Block storage (RWO) sangat cepat namun hanya untuk satu node; shared storage (RWX) mendukung multi-node namun membawa overhead latensi jaringan.
- Bahaya Split-Brain — Ini adalah risiko terbesar database terdistribusi; gunakan algoritma konsensus (Raft/Paxos) dan penguncian CSI fencing untuk mencegah double-write.
- Penundaan Detach-Attach — Pahami adanya timeout bawaan ~6 menit ketika memindahkan volume persistent dari node worker yang mati guna menghindari kerusakan filesystem.
- Noisy Neighbor Mitigation — Lindungi performa database dengan menerapkan limit IOPS di StorageClass dan memisahkan node pool untuk beban kerja I/O intensif.
- Application-Consistent Backup — Lakukan mekanisme pembekuan (freeze) penulisan database sebelum mengambil snapshot volume agar data cadangan tidak korup saat di-restore.
- Standardisasi WaitForFirstConsumer — Selalu gunakan
volumeBindingMode: WaitForFirstConsumerpada StorageClass multi-zone agar disk dibuat di zona yang sama dengan Pod.- Gunakan Managed Service — Pertimbangkan managed database cloud (RDS/Cloud SQL) untuk data bisnis kritis guna menghindari kompleksitas pengelolaan status di Kubernetes.