Menyusun StatefulSet PostgreSQL dengan volumeClaimTemplates di atas StorageClass Longhorn, menyetel fsGroup dan securityContext yang benar, lalu membuktikan persistensi: menulis data, menghapus Pod, dan memastikan data kembali utuh setelah Pod restart.

Di episode 5 kita membuat PVC pertama dan melihat dynamic provisioning bekerja. Sekarang waktunya menggabungkan semuanya untuk studi kasus nyata: menjalankan PostgreSQL sebagai StatefulSet dengan data persisten di atas Longhorn.
Mengapa episode ini menjadi tonggak? Karena di sinilah kalian membuktikan bahwa block storage RWO benar-benar berfungsi untuk database: tulis data, crash/delete Pod, dan data harus tetap hidup. Ini juga pertama kalinya kita memakai volumeClaimTemplates yang menghasilkan PVC per-Pod secara otomatis — fondasi arsitektur StatefulSet.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: default
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
securityContext:
fsGroup: 999
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
- name: POSTGRES_DB
value: laravel
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-postgres-0
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: longhorn-ha
resources:
requests:
storage: 10GiPerhatikan: di blok volumeClaimTemplates, template bernama data menghasilkan PVC data-postgres-0 untuk Pod postgres-0. Jika suatu saat kalian menambah replica (postgres-1), akan dibuat data-postgres-1 — setiap Pod punya PVC sendiri. Ini alasan StatefulSet + volumeClaimTemplates adalah fondasi yang tepat untuk database di Kubernetes.
kubectl apply -f postgres-sts.yaml
kubectl get pvcHasil yang diharapkan:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
data-postgres-0 Bound ... 10Gi RWO longhorn-haStatefulSet butuh Service headless (clusterIP: None) agar Pod bisa diakses dengan nama DNS stabil seperti postgres-0.postgres.default.svc:
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432Ini bukan soal Poin HA — tapi identitas network yang stabil untuk Pod yang memang identitasnya stabil.
Image postgres:16 berjalan sebagai user postgres (UID 999). Volume Longhorn yang di-mount — tanpa fsGroup — bisa memiliki permission yang tidak sesuai, membuat PostgreSQL tidak bisa menulis data page dan gagal start. Dengan fsGroup: 999, Kubernetes men-set ACL SE/group pada mount sehingga container process (UID 999) dapat menulis.
Tambahkan juga runAsNonRoot dan allowPrivilegeEscalation: false untuk hardening:
spec:
securityContext:
fsGroup: 999
runAsNonRoot: true
containers:
- name: postgres
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 999Catatan: readOnlyRootFilesystem: true untuk Postgres umumnya perlu mount tambahan untuk /tmp, /run/postgresql, dan pg_stat_tmp. Untuk lab, cukup pakai fsGroup + runAsUser dan biarkan ro volumen diskusi di episode keamanan.
Warning
Kesalahan paling umum: UID container tidak cocok dengan permission volume. Gejala: Pod CrashLoopBackOff dengan log could not open directory "/var/lib/postgresql/data": Permission denied. Solusi: tambahkan fsGroup yang sesuai (biasanya UID process database, 999 untuk postgres, 70± untuk MySQL).
Sekarang bagian paling penting: buktikan data bertahan.
kubectl exec -it postgres-0 -- psql -U postgres -d laravel -c \
"CREATE TABLE IF NOT EXISTS demo (id serial PRIMARY KEY, note text);"
kubectl exec -it postgres-0 -- psql -U postgres -d laravel -c \
"INSERT INTO demo (note) VALUES ('awet selamanya');"kubectl delete pod postgres-0 --wait=falseStatefulSet langsung membuat Pod baru dengan nama yang sama. PVC data-postgres-0 tidak ikut terhapus karena volumeClaimPersist.
kubectl get pod -l app=postgres
kubectl exec -it postgres-0 -- psql -U postgres -d laravel -c \
"SELECT * FROM demo;"Jika output menampilkan awet selamanya, persistensi terbukti. Di dashboard Longhorn, kalian juga akan melihat volume data-postgres-0 berpindah Attached ketika Pod baru dijadwalkan — replicanya tetap pada node yang sama secara best-effort, atau pindah bila node yang lama tidak sehat.
Tip
Untuk uji yang lebih brutal: cordon node worker-1 lalu delete Pod. Longhorn akan me-migrasi volume ke node sehat, dan PostgreSQL jalan kembali — membuktikan tidak hanya persistensi, tapi juga failover antar node. Kita bahas detail drain/cordon di episode 22.
Inti yang harus dibawa pulang:
volumeClaimTemplates menghasilkan PVC per-Pod (data-postgres-0, dst).fsGroup dan UID process database agar container bisa menulis volume.Di episode 7 selanjutnya kita akan men-deploy Laravel + PostgreSQL di atas Longhorn — menyusun Deployment Laravel, menghubungkan lewat ConfigMap & Secret, dan menjalankan migrasi database sebagai Job. Sampai jumpa di episode 7!