Zum Inhalt springen

Postgres mit CloudNativePG betreiben

Die meisten echten Anwendungen brauchen Postgres – und es auf Kubernetes gut zu betreiben war lange das Argument dagegen, es überhaupt auf Kubernetes zu betreiben. CloudNativePG (CNPG) hat das geändert: ein CNCF-Operator, der PostgreSQL-Cluster deklarativ verwaltet – Provisionierung, Replikation, Failover, Backups – aus einem kurzen Manifest. Dieser Guide führt dich von null zu einer gesicherten Datenbank, die deine App nutzen kann. Die Schritte funktionieren auf jedem konformen Cluster; als Beispiel dient ein PaaSbox-Cluster.

  • Einen Cluster und seine kubeconfig – der Quickstart bringt dich in zehn Minuten dorthin.
  • kubectl lokal installiert.
  • Für den Backup-Abschnitt: einen S3-kompatiblen Bucket samt Access-Keys – Hetzner Object Storage passt hier gut (es liegt an denselben Standorten wie dein Cluster und landet auf deiner eigenen Hetzner-Rechnung), genauso wie jeder andere S3-kompatible Speicher.
Terminal-Fenster
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml

Das installiert den Operator in cnpg-system (Release 1.30, Stand: 2026-07-09 – die aktuelle Version findest du bei den CNPG-Releases). Ab jetzt sind Postgres-Cluster einfach Kubernetes-Ressourcen.

2. Eine Datenbank mit einer Instanz (Dev, Staging)

Abschnitt betitelt „2. Eine Datenbank mit einer Instanz (Dev, Staging)“

Speichere als pg.yaml – genau in dieser Form startet eine kleine App:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg
namespace: myapp
spec:
instances: 1
# A zero-eviction PDB would block node drains and protects nothing with a
# single replica. Re-enable (the default) when instances > 1.
enablePDB: false
bootstrap:
initdb:
database: myapp
owner: myapp
storage:
size: 2Gi
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
Terminal-Fenster
kubectl create namespace myapp
kubectl apply -f pg.yaml
kubectl -n myapp wait --for=condition=Ready cluster/pg --timeout=300s

Das 2-Gi-Volume wird als Hetzner-Volume in deinem Projekt provisioniert – es erscheint auf deiner Hetzner-Rechnung, lässt sich online vergrößern, wenn du storage.size änderst, und überdauert den Ruhezustand – deine Daten überleben also einen schlafenden Cluster.

CNPG erzeugt alles, was deine App braucht. Für einen Cluster namens pg bekommst du die Services pg-rw (Primary, Read-Write) und pg-ro (Replicas, Read-Only) sowie ein Secret pg-app mit fertigen Zugangsdaten – inklusive eines uri-Keys der Form postgresql://myapp:…@pg-rw.myapp:5432/myapp. Die meisten Frameworks können das direkt verwenden:

env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: pg-app
key: uri

Kein Passwort-Handling, kein manuell angelegtes Secret – der Operator besitzt und rotiert die Zugangsdaten.

Für die Produktion änderst du eine einzige Zahl:

spec:
instances: 3

CNPG betreibt einen Primary und zwei Streaming-Replicas, verteilt sie über die Nodes und führt automatisch ein Failover durch, wenn der Node des Primary ausfällt – der Service pg-rw zeigt immer auf den aktuellen Primary, an deiner App ändert sich nichts. Entferne die Zeile enablePDB: false (das Standard-PodDisruptionBudget schützt dich jetzt bei Node-Drains) und stelle sicher, dass der Cluster mindestens drei einplanbare Worker hat – siehe Workloads skalieren und autoskalieren.

Volumes schützen vor Pod-Neustarts, nicht vor DROP TABLE. CNPG macht kontinuierliches WAL-Archiving plus geplante Basis-Backups in den Object Storage. Lege ein Secret mit den Zugangsdaten deines Buckets an und ergänze dann den Backup-Abschnitt:

Terminal-Fenster
kubectl -n myapp create secret generic pg-backup-creds \
--from-literal=ACCESS_KEY_ID=<your-access-key> \
--from-literal=ACCESS_SECRET_KEY=<your-secret-key>
spec:
backup:
barmanObjectStore:
destinationPath: s3://my-backup-bucket/pg
endpointURL: https://nbg1.your-objectstorage.com # your S3-compatible endpoint
s3Credentials:
accessKeyId:
name: pg-backup-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: pg-backup-creds
key: ACCESS_SECRET_KEY
retentionPolicy: 30d

Plane anschließend ein nächtliches Basis-Backup:

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: pg-nightly
namespace: myapp
spec:
cluster:
name: pg
schedule: "0 0 3 * * *" # 03:00 daily (six-field cron, seconds first)

Weil das WAL-Archiving zwischen den Basis-Backups durchgehend läuft, bekommst du Point-in-Time-Recovery: Ein neues Cluster-Manifest mit einem bootstrap.recovery-Abschnitt stellt jeden beliebigen Zeitpunkt innerhalb deines Aufbewahrungsfensters wieder her – das lohnt sich einmal zu proben, bevor du es im Ernstfall brauchst.

Ein deklaratives Postgres mit automatischem Failover, Zugangsdaten, die deine App aus einem Secret liest, und Backups außerhalb des Clusters mit Point-in-Time-Recovery – die Checkliste, die früher die Rechnung für eine Managed Database gerechtfertigt hat. Was dir bleibt, ist die Semantik: Schema-Migrationen, Datenqualität und das Testen des Restore-Pfads. (Die Plattform sichert die Control Plane deines Clusters; deine Datenbankinhalte sind genau die Art Anwendungsdaten, deren Schutz bei dir liegt – und dieses Setup ist der Weg dazu.)

Wie das in einer vollständigen Anwendung aussieht, zeigt der Guide Ein Django-SaaS deployen – er nutzt exakt dieses CNPG-Muster als Datenbank-Schicht.

Terminal-Fenster
kubectl delete namespace myapp # removes the cluster and its volumes (backups in the bucket remain)