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, mit Provisionierung, Replikation, Failover und Backups in 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, auch auf einem PaaSbox-Cluster.
Was du brauchst
Abschnitt betitelt „Was du brauchst“- Einen beliebigen Kubernetes-Cluster und seine kubeconfig.
kubectllokal installiert.- Für den Backup-Abschnitt: einen S3-kompatiblen Bucket samt Access-Keys, zum Beispiel bei Hetzner Object Storage, das über dein eigenes Hetzner-Konto abgerechnet wird.
Auf einem PaaSbox-Cluster prüfst du vorher eines. Läuft auf dem Cluster das Add-on PaaSbox Platform
(im Labor gebaut, noch nicht veröffentlicht) mit dem Profil saas-http01 oder saas, ist CloudNativePG schon installiert: Überspring
Schritt 1 und installiere den Operator kein zweites Mal. Eine App auf der Plattform fordert ihre
Datenbank in ihrer SaaSApplication an (data.postgres), mit Backups nach S3; siehe Schritt 2 des
Lernpfads, Deine erste App deployen.
1. Operator installieren (einmal pro Cluster)
Abschnitt betitelt „1. Operator installieren (einmal pro Cluster)“kubectl apply --server-side -f \ https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yamlDas 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 Kubernetes-Ressourcen.
2. Eine Datenbank mit einer Instanz
Abschnitt betitelt „2. Eine Datenbank mit einer Instanz“Speichere als pg.yaml. Mit dieser Konfiguration startet eine kleine App:
apiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: pg namespace: myappspec: 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: 512Mikubectl create namespace myappkubectl apply -f pg.yamlkubectl -n myapp wait --for=condition=Ready cluster/pg --timeout=300sWo das 2-Gi-Volume liegt, hängt von der Standard-Storage-Class des Clusters ab.
- Auf einem PaaSbox-Cluster ist die Standardklasse
local-lvm-thin: Das Volume liegt auf der eigenen Platte des Servers, mit der Geschwindigkeit der lokalen Platte und ohne Zusatzkosten. Es übersteht einen Neustart, einen Reboot und ein Image-Update an Ort und Stelle, nicht einen ersetzten Server. - Für ein Volume, das die Pods und den Server überdauert, schaltest du das Add-on Hetzner volumes
an (
hcloud-csi, siehe Add-ons auswählen) und ergänzt unterstoragedie ZeilestorageClass: hcloud-volumes. Das Volume ist dann ein Hetzner-Volume in deinem Projekt, mindestens 10 GB groß, auf deiner Hetzner-Rechnung. Löschst du den PaaSbox-Cluster, wird es mitgelöscht. Auf einem Cluster, dessen Standardklasse der Volume-Treiber von Hetzner ist, bekommt ein Claim ein solches Volume, ohne die Klasse zu nennen.
3. Deine Anwendung anbinden
Abschnitt betitelt „3. Deine Anwendung anbinden“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: uriKein Passwort-Handling, kein manuell angelegtes Secret. Der Operator besitzt und rotiert die Zugangsdaten.
4. Hochverfügbarkeit: drei Instanzen
Abschnitt betitelt „4. Hochverfügbarkeit: drei Instanzen“Für die Produktion änderst du eine einzige Zahl:
spec: instances: 3CNPG 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 Nodes hat, die sie ausführen können.
Ein PaaSbox-Cluster hat einen Node. Drei Instanzen würden sich dort einen Server teilen: Das Failover fängt dann einen abgestürzten Postgres-Prozess ab, nicht einen verlorenen Server, und der Speicher verdreifacht sich. Auf einem einzelnen Node bleibst du bei einer Instanz und verlässt dich auf die Backups aus Schritt 5.
5. Backups auf S3-kompatiblen Storage
Abschnitt betitelt „5. Backups auf S3-kompatiblen Storage“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:
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: 30dPlane anschließend ein nächtliches Basis-Backup:
apiVersion: postgresql.cnpg.io/v1kind: ScheduledBackupmetadata: name: pg-nightly namespace: myappspec: 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. Probe das
einmal, bevor du es brauchst.
Was du jetzt hast
Abschnitt betitelt „Was du jetzt hast“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 Liste, die früher die Rechnung für eine Managed Database gerechtfertigt hat. Was dir bleibt, ist der Inhalt: Schema-Migrationen, Datenqualität und das Testen des Restore-Pfads. (Ein Snapshot eines PaaSbox-Clusters enthält den Zustand des Clusters, seine Kubernetes-Objekte, nicht die Inhalte deiner Datenbank. Die sind Anwendungsdaten, und sie zu schützen ist deine Aufgabe. Dieses Setup ist der Weg dazu.)
Wie das in einer vollständigen Anwendung aussieht, zeigt der Guide Eine Django-SaaS deployen. Er nutzt genau dieses CNPG-Muster als Datenbank-Schicht.
Aufräumen
Abschnitt betitelt „Aufräumen“kubectl delete namespace myapp # removes the cluster and its volumes (backups in the bucket remain)