PostgreSQL
    Operator

    Deploy CloudNativePG on Kubernetes

    Run highly available PostgreSQL with the CloudNativePG operator on RamNode VPS Kubernetes nodes — storage classes, failover, and object-store backups.

    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:

    shell
    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

    shell
    kubectl apply -f \
      https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml

    Check 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:

    shell
    helm repo add cnpg https://cloudnative-pg.github.io/charts
    helm repo update
    helm upgrade --install cnpg cnpg/cloudnative-pg \
      --namespace cnpg-system --create-namespace

    Verify:

    shell
    kubectl -n cnpg-system get pods
    kubectl get crd | grep postgresql.cnpg.io

    3. Create a basic HA Postgres cluster

    shell
    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-credentials

    Create the credentials secret first:

    shell
    kubectl create secret generic pg-main-app-credentials \
      --from-literal=username=app \
      --from-literal=password="$(openssl rand -base64 24)"

    Apply:

    shell
    kubectl apply -f cluster.yaml
    kubectl get clusters.postgresql.cnpg.io
    kubectl -n default get pods -l cnpg.io/cluster=pg-main

    This 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:
    shell
    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: password

    5. 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.

    shell
    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:

    shell
    kubectl apply -f - <<EOF
    apiVersion: postgresql.cnpg.io/v1
    kind: Backup
    metadata:
      name: pg-main-backup-manual
    spec:
      cluster:
        name: pg-main
    EOF

    Confirm scheduled backups with a ScheduledBackup resource for anything production — don't rely solely on manual triggers.


    6. Verification checklist

    shell
    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 present

    Install the kubectl-cnpg plugin for easier day-2 ops:

    shell
    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-main

    Test failover deliberately once before relying on it:

    shell
    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.