Instalasi PostgreSQL + Patroni¶
Status: SUDAH DIIMPLEMENTASIKAN (8 Juli 2026)
Cluster PostgreSQL 3-node (dikelola Patroni) sudah berjalan sehat di namespace
postgres-system, berdampingan dengan Galera (bukan pengganti) — sesuai keputusan
Admin. Backup WAL-G ke S3 aktif, uji failover lulus.
1. Keputusan Teknis Awal¶
| Item | Keputusan |
|---|---|
| Operator | Zalando postgres-operator — salah satu dari sedikit operator yang benar-benar memakai Patroni (beda dari CloudNativePG yang pakai instance manager sendiri) |
| Namespace | postgres-system — terpisah dari galera-system, berjalan paralel |
| Jumlah node | 3 (1 Leader + 1 Sync Standby + 1 Replica async, dikelola Patroni) |
| Sizing per node | Request 0,5 vCPU / 1GB — Limit 1 vCPU / 2GB (konsisten dengan Galera) |
| Storage per node | 50Gi, StorageClass longhorn-db-single (reuse dari setup Galera) |
| PostgreSQL version | 16 |
synchronous_mode |
true (konsistensi ketat — sesuai keputusan Admin, trade-off sedikit latency demi zero data loss saat failover) |
2. Install Operator¶
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator postgres-operator-charts/postgres-operator -n postgres-system --create-namespace
Berbeda dari mariadb-operator: CRD ikut otomatis
Tidak seperti mariadb-operator yang butuh chart CRD terpisah, chart ini langsung
menyertakan CRD (postgresqls.acid.zalan.do, dll.) — operator langsung Running tanpa
perlu langkah tambahan.
3. Deploy Cluster (Custom Resource postgresql)¶
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: postgres-cluster
namespace: postgres-system
spec:
teamId: "oneke"
numberOfInstances: 3
postgresql:
version: "16"
volume:
size: 50Gi
storageClass: longhorn-db-single
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: 1000m, memory: 2Gi }
users:
appuser: ["createdb"]
databases:
appdb: appuser
patroni:
synchronous_mode: true
synchronous_mode_strict: false
Pod anti-affinity otomatis aktif secara default (operator built-in) — terkonfirmasi 3 pod tersebar di 3 node fisik berbeda tanpa konfigurasi tambahan.
4. Verifikasi Kesehatan Cluster ✅¶
kubectl exec -n postgres-system postgres-cluster-0 -- patronictl list
Hasil:
| Member | Role | State | Lag |
|---|---|---|---|
| postgres-cluster-0 | Leader | running | — |
| postgres-cluster-1 | Sync Standby | streaming | 0 MB |
| postgres-cluster-2 | Replica | streaming | 0 MB |
Status "Sync Standby" mengonfirmasi synchronous_mode: true aktif dan bekerja.
5. Database & User Aplikasi ✅¶
Dibuat otomatis oleh operator dari field users/databases di CR:
| Item | Nilai |
|---|---|
| Database | appdb |
| User | appuser (privilege createdb, owner appdb) |
| Password | Auto-generated, tersimpan di Secret appuser.postgres-cluster.credentials.postgresql.acid.zalan.do |
Nama sama dengan Galera — disengaja
Database/user di sini bernama sama (appdb/appuser) seperti di cluster Galera, sesuai
instruksi Admin. Kedua cluster berjalan di namespace terpisah (postgres-system vs
galera-system) jadi tidak ada konflik teknis — hanya perlu diperhatikan saat memilih
cluster mana yang dituju dari sisi aplikasi/connection string.
6. Backup ke S3 via WAL-G ✅ (Native, Lebih Kaya dari Galera)¶
6.1 Konfigurasi¶
Reuse endpoint oneke-s3.ums.id dan kredensial oneke-svc yang sama seperti Galera, dengan
prefix folder terpisah (postgres-dumps/) di bucket oneke-backups yang sama:
kubectl create secret generic postgres-pod-env -n postgres-system \
--from-literal=WAL_S3_BUCKET='oneke-backups' \
--from-literal=WALG_S3_PREFIX='s3://oneke-backups/postgres-dumps' \
--from-literal=AWS_ENDPOINT='https://oneke-s3.ums.id' \
--from-literal=AWS_ACCESS_KEY_ID='oneke-svc' \
--from-literal=AWS_SECRET_ACCESS_KEY='<secret>' \
--from-literal=AWS_S3_FORCE_PATH_STYLE='true' \
--from-literal=USE_WALG_BACKUP='true' \
--from-literal=USE_WALG_RESTORE='true'
kubectl patch operatorconfiguration postgres-operator -n postgres-system --type merge \
-p '{"configuration":{"kubernetes":{"pod_environment_secret":"postgres-pod-env"}}}'
kubectl rollout restart deployment postgres-operator -n postgres-system
Setelah operator restart, ia otomatis melakukan rolling-update seluruh pod cluster untuk menyuntikkan env var baru — tidak perlu trigger manual per-pod.
Keuntungan dibanding Galera: tidak ada bug, plus dapat WAL archiving kontinu
Berbeda dari mariadb-operator yang Backup CRD bawaannya crash (lihat
Instalasi MariaDB Galera §8.2),
mekanisme WAL-G bawaan Spilo (image resmi operator ini) bekerja langsung tanpa masalah.
Selain base backup terjadwal otomatis, WAL archiving kontinu juga aktif — memberi
kemampuan Point-in-Time Recovery (PITR) yang tidak dimiliki setup Galera saat ini.
6.2 Verifikasi¶
kubectl exec -n postgres-system postgres-cluster-1 -- \
su postgres -c 'bash /scripts/postgres_backup.sh /home/postgres/pgdata/pgroot/data'
Gotcha: jangan panggil wal-g langsung via kubectl exec
Memanggil wal-g backup-push langsung sebagai user root (default kubectl exec) gagal
— Postgres auth peer/local butuh OS-user postgres, bukan root. Gunakan
su postgres -c '...' atau jalankan lewat script resmi /scripts/postgres_backup.sh yang
sudah menangani ini dengan benar.
Hasil verifikasi: backup + WAL archiving terkonfirmasi ada di
oneke-backups/postgres-dumps/basebackups_005/ dan wal_005/ via mc ls --recursive.
7. Uji Failover ✅¶
kubectl delete pod postgres-cluster-1 -n postgres-system --force --grace-period=0
Hasil: Timeline (TL) naik dari 2 → 3 (konfirmasi promosi otomatis oleh Patroni benar-benar
terjadi), cluster tetap tersedia sepanjang proses, seluruh node kembali streaming dengan
0 MB lag. Database appdb tetap utuh — tanpa intervensi manual.
8. Ringkasan Kredensial¶
| Secret | Namespace | Isi |
|---|---|---|
appuser.postgres-cluster.credentials.postgresql.acid.zalan.do |
postgres-system |
Password appuser (auto-generated) |
postgres.postgres-cluster.credentials.postgresql.acid.zalan.do |
postgres-system |
Password superuser postgres (auto-generated) |
postgres-pod-env |
postgres-system |
Kredensial S3 untuk WAL-G (reuse oneke-svc) |
kubectl get secret appuser.postgres-cluster.credentials.postgresql.acid.zalan.do \
-n postgres-system -o jsonpath='{.data.password}' | base64 -d
9. pgAdmin ✅¶
Dipasang untuk kebutuhan penelitian (pola sama seperti phpMyAdmin) — akses via
https://oneke.ums.id/pqadmin/.
9.1 Deployment¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgadmin
namespace: postgres-system
spec:
replicas: 1
selector:
matchLabels: { app: pgadmin }
template:
metadata:
labels: { app: pgadmin }
spec:
containers:
- name: pgadmin
image: dpage/pgadmin4:latest
env:
- name: PGADMIN_DEFAULT_EMAIL
valueFrom: { secretKeyRef: { name: pgadmin-credentials, key: email } }
- name: PGADMIN_DEFAULT_PASSWORD
valueFrom: { secretKeyRef: { name: pgadmin-credentials, key: password } }
- name: SCRIPT_NAME
value: /pqadmin
volumeMounts:
- name: servers-config
mountPath: /pgadmin4/servers.json
subPath: servers.json
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 1500m, memory: 1Gi } # lihat gotcha CPU di bawah
volumes:
- name: servers-config
configMap: { name: pgadmin-servers }
Koneksi ke postgres-cluster.postgres-system.svc.cluster.local sudah pre-konfigurasi
otomatis lewat servers.json (ConfigMap) — begitu login, server sudah muncul di daftar,
tinggal masukkan password appuser.
9.2 Routing — Beda dari phpMyAdmin/Dashboard/Docs!¶
Gotcha: JANGAN pakai stripPrefix untuk pgAdmin
Berbeda dari semua path lain (/plan/, /phpmyadmin/, /oneke-dashboard/) yang memakai
middleware stripPrefix, pgAdmin justru butuh path lengkap /pqadmin/... tetap utuh
sampai ke container — ia strip prefix-nya sendiri secara internal berdasarkan env
SCRIPT_NAME/header X-Script-Name. Memakai stripPrefix seperti biasa menghasilkan
error 500: "Configuration problem: Request path '/' does not start with SCRIPT_NAME
'/pqadmin'".
Middleware yang benar: hanya redirect trailing-slash + headers (menambahkan
X-Script-Name: /pqadmin) — tanpa stripPrefix:
yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: pqadmin-headers
namespace: postgres-system
spec:
headers:
customRequestHeaders:
X-Script-Name: /pqadmin
9.3 Gotcha Lain: Validasi Email & CPU Throttling¶
Email .local ditolak
Percobaan awal pakai admin@oneke.local ditolak validator email pgAdmin (TLD .local
tidak dianggap valid domain publik). Diganti ke admin@oneke.ums.id (domain asli) — beres.
CPU limit 500m terlalu kecil untuk startup
Limit CPU awal (500m, sama seperti phpMyAdmin) membuat pgAdmin sangat lambat saat inisialisasi pertama (build SQLite config db + load server config) — sempat terlihat seperti macet selama >1 menit. Dinaikkan ke 1500m (limit) — startup selesai dalam hitungan detik setelahnya. phpMyAdmin tidak mengalami ini karena lebih ringan (PHP, bukan Python+Flask+SQLite init).
9.4 Status Keamanan¶
Sama seperti phpMyAdmin — proteksi tambahan (IP whitelist/Basic Auth) sengaja ditunda untuk fase penelitian. Login form pgAdmin sendiri (email+password) jadi satu-satunya gerbang.
Kredensial login tersimpan di Secret pgadmin-credentials (namespace postgres-system,
key email dan password) — tidak dituliskan plaintext di sini.
Ringkasan Eksekusi¶
| Langkah | Status |
|---|---|
Namespace postgres-system |
✅ |
postgres-operator + CRDs (otomatis) |
✅ |
Cluster PostgreSQL 3 node, synchronous_mode |
✅ Healthy, 1 Leader + 1 Sync Standby + 1 Replica |
| Anti-affinity | ✅ Otomatis, 3 node fisik berbeda |
Database appdb + user appuser |
✅ Auto-created |
| Backup WAL-G ke S3 + WAL archiving kontinu (PITR) | ✅ Native, tanpa workaround |
| Uji failover | ✅ Lulus, timeline naik, 0 lag, 0 data loss |
| Berjalan paralel dengan Galera | ✅ Namespace terpisah, tidak ada konflik |
pgAdmin (/pqadmin/) |
✅ Live, pre-configured connection, proteksi tambahan tertunda |
Diimplementasikan 8 Juli 2026. Lihat Instalasi MariaDB Galera untuk perbandingan langsung kedua database yang kini berjalan berdampingan di cluster oneKE.