Database di Kubernetes #
Menjalankan database di dalam Kubernetes merupakan salah satu topik yang paling sering memicu perdebatan sengit di kalangan insinyur sistem (system engineers). Di satu sisi, ada kelompok yang secara agresif berpendapat bahwa Kubernetes didesain murni untuk beban kerja tanpa status (stateless) dan menjalankan database di atasnya merupakan resep instan menuju bencana kehilangan data. Di sisi lain, banyak perusahaan teknologi skala besar berhasil menjalankan ribuan kluster database secara andal di atas Kubernetes.
Kenyataannya, kedua argumen tersebut tidak salah—konteks operasional dan tingkat keahlian timlah yang membedakannya. Kubernetes telah berevolusi jauh dari sekadar orkestrator kontainer stateless menjadi platform orkestrasi cloud-native yang sangat kuat untuk beban kerja stateful, berkat adanya API StatefulSet dan Operator Pattern. Artikel ini akan membedah secara objektif karakteristik database dibanding aplikasi stateless, panduan kelayakan deploy database in-cluster, penulisan manifes StatefulSet tunggal produksi, pentingnya Headless Service, hingga otomasi operasional tingkat lanjut menggunakan Operator.
Karakteristik Database vs Aplikasi Stateless #
Sebelum kita memutuskan untuk memasukkan database ke dalam kluster Kubernetes, kita harus memahami perbedaan fundamental perilaku runtime antara aplikasi stateless dengan mesin database:
| Karakteristik | Aplikasi Stateless (Web API / Frontend) | Mesin Database (PostgreSQL / Kafka / MySQL) |
|---|---|---|
| Identitas Pod | Acak dan identik (bisa diganti kapan saja) | Unik dan stabil (indeks ordinal seperti db-0, db-1) |
| Pola Skalabilitas | Horisontal (tinggal tambah replika instan) | Kompleks (replika baru harus sinkronisasi data dahulu) |
| Daur Hidup Penyimpanan | Ephemeral (boleh hilang saat restart) | Persisten (data harus dikunci ke disk fisik eksklusif) |
| Prosedur Restart | Instan (kontainer langsung melayani request) | Butuh pemulihan (recovery/WAL replay sebelum siap) |
| Skema Upgrade Versi | Paralel (langsung timpa seluruh Pod lama) | Berurutan (Rolling Upgrade koordinasi master-slave) |
Mengingat karakteristik database yang sangat sensitif terhadap perubahan identitas dan konektivitas, Kube-scheduler dan Controller Manager menerapkan aturan pengamanan yang jauh lebih ketat pada StatefulSet dibanding Deployment.
Rangka Pengambilan Keputusan: Kapan Database di K8s Worth It? #
Menjalankan database di Kubernetes membawa konsekuensi perawatan operasional jangka panjang yang tinggi (day-2 operations). Gunakan matriks keputusan berikut sebelum kita melangkah lebih jauh:
1. Kriteria Layak Menjalankan Database di Kubernetes: #
- Kebutuhan Portabilitas Tinggi: Kita membutuhkan sistem yang berjalan sama persis di lingkungan on-premise, AWS, GCP, maupun Azure tanpa terikat pada API cloud provider tertentu.
- Tersedianya Operator Resmi yang Mature: Database yang kita gunakan memiliki Operator aktif yang teruji (seperti Strimzi untuk Apache Kafka atau CloudNativePG untuk Postgres).
- Lingkungan Development dan Staging: Sangat masuk akal menjalankan database di dalam Kubernetes untuk lingkungan non-produksi guna menghemat biaya sewa cloud database managed yang mahal.
- Microservices Skala Kecil: Database mikro yang didedikasikan untuk satu service dengan trafik rendah-menengah.
2. Kriteria Lebih Baik Menggunakan Managed DB Service (RDS/Cloud SQL/Atlas): #
- Downtime Sangat Mahal: Bisnis kita tidak dapat mentoleransi downtime database lebih dari beberapa menit per tahun (SLA 99.99%).
- Kelangkaan Keahlian Tim: Tim DevOps/SRE kita tidak memiliki keahlian mendalam mengenai internal replikasi database terdistribusi, quorum election, dan penanganan bencana (disaster recovery) filesystem.
- Keterbatasan Bandwidth Operasional: Kita ingin menyerahkan tanggung jawab backup rutin, patching keamanan OS, dan failover multi-zone secara penuh kepada cloud provider.
Pola Dasar: Manifes StatefulSet PostgreSQL Tunggal Produksi #
Untuk database non-cluster sederhana (single-instance) yang digunakan untuk kebutuhan internal, kita dapat menyusun manifest StatefulSet yang andal.
Satu hal yang paling kritis adalah konfigurasi Health Probes (Liveness & Readiness). Database yang sedang melakukan pemulihan data (WAL replay) setelah crash tidak boleh dianggap gagal oleh Liveness Probe; jika Kubelet membunuhnya di tengah proses recovery, database kita akan masuk ke dalam loop kerusakan data (corruption loop).
Berikut adalah manifes StatefulSet PostgreSQL tunggal dengan konfigurasi probe yang aman:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-db
namespace: database
spec:
serviceName: "postgres-headless" # Menunjuk ke Headless Service
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
securityContext:
fsGroup: 999 # User ID postgres untuk permission volume
containers:
- name: postgres
image: postgres:15.4-alpine
env:
- name: POSTGRES_DB
value: "production_db"
- name: POSTGRES_USER
value: "pg_admin"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
name: dbport
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
volumeMounts:
- name: db-data-vol
mountPath: /var/lib/postgresql/data
# Liveness probe menggunakan pg_isready dengan buffer waktu startup longgar:
livenessProbe:
exec:
command:
- pg_isready
- -U
- pg_admin
initialDelaySeconds: 60 # Memberi waktu 1 menit untuk WAL replay/startup
periodSeconds: 10
timeoutSeconds: 5
# Readiness probe memastikan db siap menerima traffic SQL:
readinessProbe:
exec:
command:
- pg_isready
- -U
- pg_admin
initialDelaySeconds: 15
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: db-data-vol
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "premium-ssd-sc" # StorageClass SSD IOPS tinggi
resources:
requests:
storage: 50Gi
Pentingnya Headless Service untuk Jaringan Database #
StatefulSet wajib dipasangkan dengan sebuah Headless Service. Headless Service adalah Service Kubernetes biasa yang didefinisikan dengan parameter clusterIP: None.
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: database
spec:
clusterIP: None # Kunci utama yang menjadikannya Headless
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
Mengapa Headless Service Sangat Vital? #
- Direct IP Resolution: Service biasa memiliki ClusterIP virtual yang bertindak sebagai Load Balancer acak. Ini tidak cocok untuk database. Klien (aplikasi kita) harus dapat menembak IP pod database secara spesifik (misalnya menembak master node untuk menulis data, dan replica node untuk membaca data).
- DNS Stabil: Dengan Headless Service, CoreDNS di Kubernetes akan mendaftarkan alamat FQDN unik untuk masing-masing pod StatefulSet:
$$\text{FQDN} = \text{postgres-db-0.postgres-headless.database.svc.cluster.local}$$
Meskipun Pod
postgres-db-0mengalami restart dan mendapatkan IP internal baru, alamat DNS di atas dijamin akan tetap sama, mencegah kegagalan koneksi pada konfigurasi aplikasi kita.
[!CAUTION] Jangan pernah meng-expose database internal kita ke internet menggunakan LoadBalancer Service atau NodePort. Ini adalah celah keamanan yang sangat berbahaya. Akses database dari luar kluster untuk kebutuhan debugging harus selalu menggunakan jalur aman seperti VPN kluster atau perintah secure port-forwarding:
kubectl port-forward statefulset/postgres-db 5432:5432 -n database.
Operator Pattern: Otomatisasi Operasi Kompleks (Day-2) #
Mendeploy database single-instance menggunakan StatefulSet biasa sangatlah mudah. Namun, ketika kita butuh menjalankan kluster database multi-node dengan replikasi aktif, backup otomatis terjadwal ke S3, pemulihan data titik waktu tertentu (Point-in-Time Recovery / PITR), dan failover otomatis saat Master mati, StatefulSet standar saja tidak cukup. Kita butuh Operator.
Operator adalah perpanjangan tangan dari kecerdasan manusia yang ditulis menjadi kode program. Operator mendengarkan Custom Resource Definition (CRD) kustom database dan menjalankan loop rekonsiliasi untuk memastikan kluster berjalan sesuai spesifikasi.
Berikut adalah diagram alur kerja rekonsiliasi otomatis yang dilakukan oleh Database Operator:
flowchart TD
CRD_Deploy["Developer Mendeploy CRD: apiVersion: cloudnative-pg.io/v1\nkind: Cluster (instances: 3)"] --> OperatorLoop["Operator Reconciliation Loop Aktif"]
subgraph OperatorLogic ["Logika Otomatisasi Operator"]
OperatorLoop --> DetectState["Deteksi Status Saat Ini: 0 Instance Berjalan"]
DetectState --> CreateMaster["1. Buat Pod master-0 & PVC master-0"]
CreateMaster --> WaitMasterReady["Tunggu master-0 Siap (Ready)"]
WaitMasterReady --> CreateSlaves["2. Buat Pod replica-1 & replica-2"]
CreateSlaves --> BootstrapReplication["3. Jalankan rsync/pg_basebackup dari master-0 ke replica-1 & 2"]
BootstrapReplication --> StartSync["4. Aktifkan Replikasi Streaming (Sync)"]
end
StartSync --> MonitorHA["Monitor Kesehatan Kluster Terus-Menerus"]
MonitorHA --> DetectMasterCrash{"Apakah Master\nPod master-0 Crash?"}
DetectMasterCrash -- "Tidak" --> MonitorHA
DetectMasterCrash -- "Ya" --> FailoverAction["Pilih replica-1 sebagai Master Baru\nUbah Konfigurasi DNS & Promosikan ke Primary"]
FailoverAction --> MonitorHA
Operator Database Populer di Industri: #
- CloudNativePG (PostgreSQL): Salah satu operator PostgreSQL terbaik yang didesain secara native untuk Kubernetes tanpa wrapper kompleks seperti Patroni. Sangat efisien dalam manajemen backup ke Object Storage (S3/GCS) dan replikasi streaming.
- Zalando Postgres Operator: Teruji di skala produksi raksasa oleh tim Zalando. Menggunakan Spilo (PostgreSQL + Patroni) untuk konsensus ketersediaan tinggi (HA).
- Strimzi (Apache Kafka): Mengotomatisasikan manajemen kluster Kafka dan ZooKeeper, pengelolaan user, serta pembuatan topic secara deklaratif via Kubernetes API.
- Percona Operator for MySQL / MongoDB: Sangat andal untuk setup kluster multi-master dan replikasi terdistribusi dengan fitur backup terintegrasi.
Checklist Produksi Sebelum Meluncurkan Database di Kubernetes #
Sebelum kita menekan tombol deploy database ke lingkungan produksi kluster Kubernetes, pastikan kita telah mencentang seluruh daftar pengaman berikut:
1. Hardening Storage #
- Reclaim Policy Retain: Pastikan
StorageClassatau PV manual menggunakan policyRetainagar data tidak hilang jika PVC terhapus tidak sengaja. - WaitForFirstConsumer: Meminimalisir kesalahan penjadwalan akibat mismatch zona cloud.
- IOPS Tinggi: Gunakan StorageClass yang mendukung disk tipe SSD (gp3/pd-ssd) dengan performa minimum 3000 IOPS terdedikasi.
- Volume Expansion: Pastikan SC memiliki parameter
allowVolumeExpansion: true.
2. Keamanan Jaringan dan Rahasia #
- Secret Injection: Kredensial database wajib disembunyikan di dalam Secret, bukan ConfigMap atau env var statis.
- NetworkPolicy: Batasi akses masuk ke database. Hanya Pod backend dengan label tertentu yang boleh berkomunikasi dengan port database.
- Non-Root Execution: Pastikan kontainer database berjalan sebagai user non-root (misalnya user ID
999milik postgres) dengan konfigurasirunAsNonRoot: truedi SecurityContext.
3. Ketahanan Layanan (High Availability) #
- Anti-Affinity Rules: Terapkan rules
podAntiAffinityagar Pod master dan slave database dijamin dijadwalkan pada node worker fisik yang berbeda guna mengantisipasi kematian node tunggal. - Connection Pooler: Gunakan tool connection pooler (seperti PgBouncer) untuk mencegah habisnya slot koneksi database akibat autoscaling Pod backend.
4. Observabilitas & Alerting #
- Exporter Metrics: Pasang exporter database (seperti
postgres_exporter) untuk membuang metrik ke Prometheus. - Alerting Disk & Connection: Setel alert kritis jika pemakaian disk volume > 80% atau persentase pemakaian koneksi > 90%.
Anti-Pattern dalam Manajemen Database di Kubernetes #
Berikut adalah tiga kesalahan konfigurasi fatal yang sering melumpuhkan kluster produksi saat menjalankan database:
Anti-Pattern 1: Menggunakan Deployment Biasa (Stateless) untuk Database Replikasi #
Mendeploy database relasional Master-Slave menggunakan objek Deployment standar karena dianggap lebih mudah dikonfigurasi.
ANTI-PATTERN: Deployment dengan replicas: 3 untuk Kluster PostgreSQL
// KITA MELAKUKAN:
- Kita menulis manifest Deployment dengan replicas: 3 dan memasangnya ke satu PVC bersama.
// KONSEKUENSI DI PRODUKSI:
- Tabrakan Penulisan (Data Corruption): Tiga Pod akan mencoba menulis ke folder data yang sama secara bersamaan.
Karena database relasional tidak didesain untuk multi-writer tanpa lock manager eksternal,
sistem berkas database akan langsung rusak total (*corrupted filesystem*).
- Identitas Acak: Pod mendapatkan nama acak (seperti `db-app-8fbc10-xyz`). Klien aplikasi tidak dapat
membedakan mana master dan mana slave karena IP dan nama pod berubah-ubah setiap kali restart.
✓ SOLUSI YANG BENAR:
- Selalu gunakan objek **StatefulSet** untuk database. StatefulSet menjamin penomoran ordinal
dan alokasi PersistentVolume eksklusif yang tidak saling menimpa antar replika Pod.
Anti-Pattern 2: Menyetel Liveness Probe Terlalu Agresif Tanpa Buffer Startup #
Mengonfigurasi Liveness Probe database dengan parameter timeout ketat demi mendeteksi kegagalan layanan secepat mungkin.
ANTI-PATTERN: livenessProbe dengan initialDelaySeconds: 5 dan failureThreshold: 2
// KITA MELAKUKAN:
- database kita mengalami mati mendadak karena node restart. Saat menyala kembali, database harus membaca
file transaksi terakhir (WAL Replay) untuk memperbaiki integritas tabel. Proses ini memakan waktu 30 detik.
- Namun, kita menyetel `initialDelaySeconds: 5`.
// KONSEKUENSI DI PRODUKSI:
- Loop Kematian (Crash Loop): Lima detik setelah menyala, Kubelet mulai melakukan ping probe.
Karena database masih sibuk memproses recovery WAL, ia tidak menjawab ping probe.
- Kubelet menganggap database mati dan langsung membunuh kontainer secara paksa (restart).
- Kontainer database menyala kembali, mengulang proses recovery dari awal, dan dibunuh lagi setelah 5 detik.
database kita masuk ke dalam loop kematian tanpa pernah bisa menyala sukses.
✓ SOLUSI YANG BENAR:
- Berikan nilai `initialDelaySeconds` yang realistis (misalnya 60 atau 90 detik) khusus untuk database.
- Sebagai alternatif, gunakan **Startup Probe** terlebih dahulu untuk mengunci Liveness Probe agar tidak berjalan
sebelum database benar-benar menyelesaikan seluruh proses pemulihan internalnya.
Anti-Pattern 3: Menghubungkan Database ke Default StorageClass Berkinerja Rendah #
Mengabaikan penentuan StorageClass pada volumeClaimTemplates database sehingga otomatis menggunakan default StorageClass kluster.
ANTI-PATTERN: volumeClaimTemplates Tanpa Spesifikasi storageClassName
// KITA MELAKUKAN:
- Kita membiarkan properti `storageClassName` kosong pada template klaim database.
// KONSEKUENSI DI PRODUKSI:
- Latensi I/O Tinggi: Kubernetes akan membuat disk otomatis menggunakan default StorageClass kluster
(yang biasanya menggunakan disk HDD mekanis standar demi menghemat biaya).
- Transaksi database yang membutuhkan kecepatan tulis disk tinggi akan mengalami antrean I/O parah.
Aplikasi backend kita mulai menghasilkan error HTTP 504 Gateway Timeout saat trafik puncak.
✓ SOLUSI YANG BENAR:
- Selalu tentukan StorageClass khusus berkinerja SSD terdedikasi (seperti `premium-ssd-sc`)
secara eksplisit pada blok `volumeClaimTemplates` StatefulSet database kita.
Ringkasan #
- StatefulSet adalah Fondasi — Selalu gunakan objek StatefulSet untuk database guna menjamin kestabilan identitas jaringan dan eksklusivitas alokasi penyimpanan per Pod.
- Headless Service Wajib — Hubungkan StatefulSet dengan Headless Service (
clusterIP: None) agar CoreDNS dapat mendaftarkan FQDN stabil untuk masing-masing pod database.- Longgarkan Liveness Probes — Berikan buffer waktu yang cukup (
initialDelaySeconds) atau gunakan Startup Probes untuk memberi ruang database melakukan proses WAL recovery.- Gunakan Operator untuk HA — Terapkan Operator resmi (seperti CloudNativePG atau Strimzi) untuk mengelola kluster database kompleks dengan failover otomatis dan backup terotomatisasi.
- Bypass ke Managed DB — Jangan ragu untuk memilih layanan managed database cloud (RDS/Cloud SQL) jika tim Anda tidak memiliki spesialisasi day-2 operations database di Kubernetes.
- Terapkan podAntiAffinity — Lindungi kluster database dari kegagalan node fisik dengan menyebarkan replica Pod ke node worker yang berbeda secara eksplisit.
- Jangan Ekspos Database — Isolasi port database di dalam jaringan kluster menggunakan NetworkPolicy dan jangan pernah menggunakan LoadBalancer Service untuk mengeksposnya ke publik.