CloudNativePG (CNPG) is a Kubernetes operator for running PostgreSQL with HA, backups, and failover managed declaratively. This is a PostgreSQL operator — it is intended for new services that use Postgres.
Assumption: same cluster as the Longhorn/KEDA guides — kubeadm on RamNode KVM VPS nodes. CNPG pairs well with Longhorn for storage, so that guide's StorageClass setup is a prerequisite here if you don't already have a suitable block-storage class.
1. Storage prerequisites
Postgres is I/O-sensitive. If you're using Longhorn as your storage backend (see the Longhorn guide), create a dedicated StorageClass for database workloads rather than reusing the default one, tuned for lower replica overhead and faster IO:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-postgres
provisioner: driver.longhorn.io
allowVolumeExpansion: true
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "30"
fromBackup: ""
fsType: "ext4"
dataLocality: "strict-local"dataLocality: strict-local keeps a replica on the same node as the pod
using it — reduces network hops for the DB's own I/O, while Longhorn still
keeps a second copy elsewhere for durability. Don't use numberOfReplicas: 1
here — CNPG's own replication handles Postgres-level HA, but you still want
storage-level redundancy under each instance.
If you're not using Longhorn and instead have local-disk-backed StorageClasses per node, ensure each region has locally-provisioned fast storage available — CNPG generally performs better on local NVMe/SSD than network block storage when latency matters.
2. Install the CNPG operator
kubectl apply -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yamlCheck https://github.com/cloudnative-pg/cloudnative-pg/releases for the current release tag before running this — pin to a specific version rather than tracking
main.
Or via Helm:
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm upgrade --install cnpg cnpg/cloudnative-pg \
--namespace cnpg-system --create-namespaceVerify:
kubectl -n cnpg-system get pods
kubectl get crd | grep postgresql.cnpg.io3. Create a basic HA Postgres cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-main
namespace: default
spec:
instances: 3
storage:
storageClass: longhorn-postgres
size: 20Gi
postgresql:
parameters:
max_connections: "200"
shared_buffers: "512MB"
affinity:
topologyKey: topology.kubernetes.io/zone # keep replicas within a region
bootstrap:
initdb:
database: app
owner: app
secret:
name: pg-main-app-credentialsCreate the credentials secret first:
kubectl create secret generic pg-main-app-credentials \
--from-literal=username=app \
--from-literal=password="$(openssl rand -base64 24)"Apply:
kubectl apply -f cluster.yaml
kubectl get clusters.postgresql.cnpg.io
kubectl -n default get pods -l cnpg.io/cluster=pg-mainThis gives you 1 primary + 2 replicas, automatic failover, and a
pg-main-rw / pg-main-ro / pg-main-r service trio for
read-write/read-only/any-replica routing respectively.
4. Regional placement (RamNode-specific)
Like Longhorn, Postgres streaming replication is latency-sensitive. Recommended approach when nodes span RamNode regions (ATL, EWR, PNW, LAX, NLX):
- Run one CNPG cluster per region, matching the "one Longhorn deployment per region" pattern.
- If you genuinely need a cross-region DR copy (not a live HA member), use CNPG's replica cluster feature — an asynchronous standby cluster in a second region, promoted manually or via automation during a regional outage, rather than treating it as a synchronous HA member:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-main-dr-nyc
spec:
instances: 1
storage:
storageClass: longhorn-postgres
size: 20Gi
replica:
enabled: true
source: pg-main-atl
externalClusters:
- name: pg-main-atl
connectionParameters:
host: pg-main-atl-rw.default.svc
user: streaming_replica
password:
name: pg-main-atl-replication
key: password5. Backups
CNPG uses Barman under the hood, backing up to S3-compatible object storage — same consideration as Longhorn: stand up a MinIO instance or use an external provider.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-main
spec:
# ...existing spec...
backup:
barmanObjectStore:
destinationPath: s3://pg-backups/pg-main
endpointURL: https://minio.internal.ramnode:9000
s3Credentials:
accessKeyId:
name: minio-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: minio-creds
key: SECRET_ACCESS_KEY
retentionPolicy: "30d"Trigger an on-demand backup:
kubectl apply -f - <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: pg-main-backup-manual
spec:
cluster:
name: pg-main
EOFConfirm scheduled backups with a ScheduledBackup resource for anything
production — don't rely solely on manual triggers.
6. Verification checklist
kubectl get clusters.postgresql.cnpg.io # STATUS: Cluster in healthy state
kubectl cnpg status pg-main # requires kubectl-cnpg plugin
kubectl -n default get pods -l cnpg.io/cluster=pg-main # 1 primary, N replicas Running
kubectl get svc | grep pg-main # -rw/-ro/-r services presentInstall the kubectl-cnpg plugin for easier day-2 ops:
curl -sSfL https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/hack/install-cnpg-plugin.sh | sudo sh -s -- -b /usr/local/bin
kubectl cnpg status pg-mainTest failover deliberately once before relying on it:
kubectl cnpg promote pg-main <replica-pod-name>7. Notes for your environment
- If any workload actually needs MariaDB/MySQL rather than Postgres, CNPG doesn't apply — that stays on your existing MariaDB automation.
- Pin the operator version and review release notes before upgrading. entirely separate in your mental model and in Ansible role naming — they will otherwise get confused given how much overlap there is in "database automation" terminology in your existing playbooks.
- Given your CentOS 6/7 + Ubuntu 18 fleet is mid-upgrade, only schedule CNPG workloads onto nodes that are already on modern kernels/OS versions — Postgres under CNPG has no special tolerance for the legacy hosts you're still migrating off of.
