Veröffentlicht am

PostgreSQL Operator: CloudNativePG vs. Crunchy vs. Zalando

Teilen:
Authors

PostgreSQL auf Kubernetes mit Operatoren: CloudNativePG, Crunchy und Zalando im Vergleich

TL;DR

  • PostgreSQL-Operatoren automatisieren Failover, Backups und Replikation auf Kubernetes. Manuelle DBA-Eingriffe werden dadurch drastisch reduziert.
  • CloudNativePG, Crunchy PGO und der Zalando Postgres Operator sind die drei relevanten Open-Source-Optionen. Jeder hat eigene Staerken.
  • Failover dauert mit allen drei Operatoren typischerweise unter 30 Sekunden. Der entscheidende Unterschied liegt bei Backup-Optionen und Day-2-Operations.
  • Fuer die meisten Teams ist CloudNativePG der beste Einstieg: saubere API, aktive Community, gute Dokumentation.
  • Persistent Volumes mit einer StorageClass, die Volume Snapshots unterstuetzt, sind Voraussetzung fuer sinnvolle Backup-Strategien.

Warum ein Operator fuer PostgreSQL

PostgreSQL auf Kubernetes zu betreiben ist nicht trivial. Datenbanken sind stateful, brauchen stabile Netzwerk-Identitaeten und persistenten Speicher. Ein einfaches Deployment-Manifest reicht nicht -- man braucht Replikation, automatisches Failover, Backup-Scheduling und Connection Pooling.

All das kann man manuell bauen. StatefulSets, init-Container fuer die Replikation, CronJobs fuer Backups, ein Sidecar fuer Health Checks. Das funktioniert, ist aber fehleranfaellig und schwer wartbar. Genau hier setzen Operatoren an.

Ein PostgreSQL-Operator implementiert das Operator Pattern: Er erweitert die Kubernetes-API um Custom Resources (z.B. Cluster bei CloudNativePG) und steuert ueber einen Controller-Loop den gesamten Lebenszyklus der Datenbank. Der Operator erkennt ausgefallene Pods, promoted automatisch eine Replica zum Primary und passt die Service-Endpoints an.

Das spart nicht nur Zeit, sondern reduziert vor allem Fehler. Ein manuelles Failover unter Stress um 3 Uhr morgens ist fehleranfaellig. Ein Operator fuehrt die gleichen Schritte jedes Mal identisch aus.

Die drei grossen Operatoren im Vergleich

FeatureCloudNativePGCrunchy PGO v5Zalando Postgres Operator
LizenzApache 2.0Apache 2.0MIT
PostgreSQL-Versionen12-1712-1712-16
Failover-MechanismusBuilt-in (pg_rewind)PatroniPatroni
Backup-ToolBarmanpgBackRestWAL-E / WAL-G
Point-in-Time RecoveryJaJaJa
Connection PoolingPgBouncer (integriert)PgBouncer (integriert)Nicht integriert
MonitoringPrometheus-ExporterPrometheus-ExporterPrometheus-Exporter
Volume SnapshotsJa (nativ)JaNein
Declarative Roles/DBsJaJaJa
Community-AktivitaetSehr aktiv (CNCF Sandbox)AktivModerat
Major-Version-UpgradesIn-Place (seit 1.22)pg_upgrade via JobManuell

CloudNativePG hat den Vorteil, von Grund auf fuer Kubernetes gebaut zu sein. Es nutzt kein externes Consensus-System (kein Patroni, kein etcd), sondern verlaesst sich auf die Kubernetes-API als einzige Source of Truth. Das reduziert die Komplexitaet und beseitigt eine ganze Kategorie von Failure Modes (Split-Brain durch DCS-Probleme).

Crunchy PGO setzt auf Patroni fuer Failover und pgBackRest fuer Backups. pgBackRest ist extrem ausgereift und bietet Features wie inkrementelle Backups, parallele Kompression und Delta-Restores. Wer hohe Anforderungen an Backup-Granularitaet hat oder sehr grosse Datenbanken betreibt (500 GB+), findet hier die meisten Optionen.

Der Zalando Operator ist der aelteste der drei. Er wurde fuer die interne Nutzung bei Zalando gebaut und ist gut fuer Teams, die bereits Patroni kennen. Allerdings ist die Weiterentwicklung langsamer als bei den anderen beiden, und einige Features (Connection Pooling, Volume Snapshots) fehlen.

Cluster-Definition mit CloudNativePG

Das folgende Manifest definiert einen PostgreSQL-Cluster mit einem Primary und zwei Replicas:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: app-db
  namespace: production
spec:
  instances: 3
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2

  postgresql:
    parameters:
      max_connections: "200"
      shared_buffers: "256MB"
      effective_cache_size: "768MB"
      work_mem: "4MB"
      wal_level: "replica"
      log_statement: "ddl"
      log_min_duration_statement: "500"

  storage:
    size: 50Gi
    storageClass: fast-ssd

  resources:
    requests:
      memory: "512Mi"
      cpu: "500m"
    limits:
      memory: "1Gi"
      cpu: "2"

  backup:
    barmanObjectStore:
      destinationPath: "s3://pg-backups/app-db/"
      s3Credentials:
        accessKeyId:
          name: s3-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: s3-creds
          key: ACCESS_SECRET_KEY
      wal:
        compression: gzip
    retentionPolicy: "14d"

  monitoring:
    enablePodMonitor: true

Nach kubectl apply passiert Folgendes: Der Operator erstellt drei Pods (einen Primary, zwei Replicas), konfiguriert Streaming Replication, richtet Barman fuer WAL-Archivierung ein und erstellt Services fuer Read-Write (app-db-rw) und Read-Only (app-db-ro) Zugriff.

Beachten Sie die Resource Limits und die Logging-Parameter. Ohne Limits riskieren Sie, dass ein PostgreSQL-Pod den gesamten Node-Speicher beansprucht. Ohne log_min_duration_statement finden Sie langsame Queries erst, wenn Nutzer sich beschweren.

Failover testen

Ein Operator ist nur so gut wie sein Failover-Verhalten. Testen Sie es in einer Staging-Umgebung, bevor der Ernstfall eintritt:

# Primary-Pod ermitteln
kubectl get pods -n production -l cnpg.io/cluster=app-db \
  -o jsonpath='{.items[?(@.metadata.labels.role=="primary")].metadata.name}'

# Primary-Pod loeschen (simuliert Node-Ausfall)
kubectl delete pod app-db-1 -n production

# Failover beobachten
kubectl get pods -n production -l cnpg.io/cluster=app-db -w

# Nach ca. 10-20 Sekunden: neue Primary pruefen
kubectl exec -n production app-db-2 -- psql -c "SELECT pg_is_in_recovery();"
# Ergebnis: f (false = ist Primary)

# Cluster-Status pruefen
kubectl get cluster app-db -n production -o yaml | \
  grep -A5 "status:"

Waehrend des Failovers leitet der Operator den Read-Write-Service auf die promoted Replica um. Anwendungen, die ueber den Service verbinden, erleben eine kurze Unterbrechung (typischerweise 5-15 Sekunden), aber keinen Datenverlust bei synchroner Replikation.

Fuehren Sie diesen Test regelmaessig durch -- idealerweise automatisiert als Teil Ihrer Chaos-Engineering-Praxis. Ein Failover, das vor sechs Monaten funktioniert hat, funktioniert nach Konfigurationsaenderungen nicht zwingend noch.

Backup und Point-in-Time Recovery

Backups sind konfiguriert, aber funktionieren sie auch? Das laesst sich pruefen:

# Manuelles Backup ausloesen
kubectl cnpg backup app-db -n production --backup-name manual-test

# Backup-Status pruefen
kubectl get backups -n production

# WAL-Archivierung pruefen (letzte archivierte WAL)
kubectl exec -n production app-db-1 -- \
  psql -c "SELECT last_archived_wal, last_archived_time FROM pg_stat_archiver;"

Fuer ein Point-in-Time Recovery (PITR) erstellt man einen neuen Cluster aus dem Backup:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: app-db-restored
  namespace: production
spec:
  instances: 1
  bootstrap:
    recovery:
      source: app-db
      recoveryTarget:
        targetTime: "2026-02-10T14:30:00Z"
  externalClusters:
    - name: app-db
      barmanObjectStore:
        destinationPath: "s3://pg-backups/app-db/"
        s3Credentials:
          accessKeyId:
            name: s3-creds
            key: ACCESS_KEY_ID
          secretAccessKey:
            name: s3-creds
            key: ACCESS_SECRET_KEY
  storage:
    size: 50Gi
    storageClass: fast-ssd

Damit wird ein neuer Cluster erstellt, der die Daten bis zum angegebenen Zeitpunkt wiederherstellt. Das ist nuetzlich bei versehentlichen DELETE-Statements oder Datenkorruption. Der wiederhergestellte Cluster laeuft neben dem Original -- so koennen Sie die Daten pruefen, bevor Sie den alten Cluster ersetzen.

Testen Sie PITR regelmaessig. Ein Backup, das nicht wiederhergestellt werden kann, ist kein Backup.

Connection Pooling mit PgBouncer

PostgreSQL erstellt pro Verbindung einen eigenen Betriebssystem-Prozess. Bei Microservice-Architekturen mit vielen kleinen Services explodiert die Verbindungszahl schnell. 50 Pods mit je 10 Verbindungen sind 500 PostgreSQL-Prozesse -- das belastet Speicher und CPU erheblich.

CloudNativePG integriert PgBouncer als Pooler-Resource:

apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
  name: app-db-pooler-rw
  namespace: production
spec:
  cluster:
    name: app-db
  instances: 2
  type: rw
  pgbouncer:
    poolMode: transaction
    parameters:
      max_client_conn: "1000"
      default_pool_size: "25"

Der Transaction-Modus ist fuer die meisten Anwendungen die richtige Wahl. Er gibt Verbindungen nach jeder Transaktion zurueck in den Pool, statt sie fuer die gesamte Session zu halten. Vorsicht: Prepared Statements funktionieren im Transaction-Modus nicht. Wenn Ihre Anwendung diese nutzt, weichen Sie auf Session-Modus aus oder verwenden Sie DEALLOCATE ALL nach jeder Transaktion.

Monitoring einrichten

Alle drei Operatoren liefern Prometheus-Metriken. Bei CloudNativePG aktiviert enablePodMonitor: true automatisch einen PodMonitor fuer den Prometheus Operator. Wichtige Metriken:

MetrikBeschreibungAlert-Schwelle
cnpg_pg_replication_lagReplikationsverzoegerung in Sekunden> 5s
cnpg_pg_stat_activity_countAktive Verbindungen> 80% von max_connections
cnpg_collector_upCollector erreichbar0
cnpg_pg_database_size_bytesDatenbankgroesseAbhaengig vom Storage
cnpg_pg_stat_archiver_failed_countFehlgeschlagene WAL-Archivierungen> 0
cnpg_pg_stat_bgwriter_buffers_allocBuffer-AllokationenTrendbeobachtung

Ein Grafana-Dashboard fuer CloudNativePG gibt es als Community-Template (ID 20417). Es zeigt Replikations-Lag, Verbindungspools, WAL-Archivierung und Speicherverbrauch auf einen Blick.

Besonders kritisch ist die Metrik cnpg_pg_stat_archiver_failed_count. Wenn WAL-Archivierung fehlschlaegt, waechst pg_wal auf dem Primary unkontrolliert und fuellt die Disk. Richten Sie dafuer unbedingt einen Alert ein.

Storage richtig waehlen

Die StorageClass entscheidet ueber Performance und Backup-Moeglichkeiten. Fuer PostgreSQL gelten folgende Empfehlungen:

SSDs sind Pflicht. Datenbanken sind I/O-intensiv. HDDs fuehren zu inakzeptablen Latenzen, besonders bei WAL-Writes und Index-Scans.

Volume Snapshots aktivieren. CloudNativePG kann Volume Snapshots fuer Backups nutzen, was bei grossen Datenbanken (100 GB+) deutlich schneller ist als logische Backups.

ReadWriteOnce reicht aus. Jeder Pod hat sein eigenes PV. ReadWriteMany wird nicht benoetigt und wuerde die Storage-Auswahl unnoetig einschraenken.

Storage-Groesse grosszuegig planen. PVCs lassen sich in den meisten Umgebungen vergroessern, aber nicht verkleinern. Planen Sie mindestens 50% Reserve ueber dem erwarteten Bedarf ein. Beachten Sie auch den Speicher fuer WAL-Dateien, die bei fehlgeschlagener Archivierung schnell wachsen.

Cloud-ProviderEmpfohlene StorageClassIOPSSnapshot-Support
AWSgp3 (mit angepassten IOPS)3000-16000Ja
AzurePremium SSD v2KonfigurierbarJa
GCPpd-ssdSkaliert mit GroesseJa
On-PremiseLocal PV oder Ceph RBDAbhaengig von HardwareCeph: Ja

Haeufige Stolperfallen

Kein Pod Disruption Budget konfiguriert. Ohne PDB kann ein kubectl drain alle PostgreSQL-Pods gleichzeitig terminieren. CloudNativePG erstellt standardmaessig PDBs, aber pruefen Sie das bei Upgrades und in Multi-Operator-Setups.

Connection Pooling vergessen. PostgreSQL erstellt pro Verbindung einen OS-Prozess. Bei vielen Microservices explodiert die Verbindungszahl. PgBouncer als Sidecar oder separater Pool ist in Produktion unverzichtbar.

Synchrone Replikation ohne Bedacht aktivieren. Synchrone Replikation garantiert Zero Data Loss, aber erhoeht die Write-Latenz. Fuer die meisten Anwendungen ist asynchrone Replikation mit WAL-Archivierung der bessere Kompromiss zwischen Performance und Datensicherheit.

Monitoring nicht eingerichtet. Ein Operator ohne Monitoring ist Blindflug. Richten Sie Alerts fuer Replikations-Lag, fehlgeschlagene Backups, WAL-Archivierungsfehler und hohe Verbindungszahlen ein.

Backup nie getestet. Ein Backup, das nie wiederhergestellt wurde, ist kein Backup. Fuehren Sie monatlich einen PITR-Test durch.

Ressourcen nicht begrenzt. Ohne Memory Limits nutzt PostgreSQL shared_buffers plus den gesamten OS-Cache. Das kann andere Pods auf dem gleichen Node verdraengen. Setzen Sie immer explizite Limits.

Weitergehende Themen


Brauchen Sie Unterstuetzung beim Betrieb von PostgreSQL auf Kubernetes -- von der Operator-Auswahl bis zum Produktions-Rollout? Kontaktieren Sie uns unter /kontakt fuer ein unverbindliches Gespraech.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen