· Kubernetes  · 8 Min. Lesezeit

Zalando-Postgres-Operator: adoptieren und die PVC-Falle

Der Operator löscht PersistentVolumeClaims beim Entfernen des Cluster-Manifests standardmäßig mit, unabhängig von der Retention-Policy des StatefulSets. Welche Schalter Sie vorher setzen, wie weit der eingebaute Löschschutz trägt, und warum es keinen dokumentierten Weg gibt, eine bestehende Instanz einfach zu übernehmen.

Der Operator löscht PersistentVolumeClaims beim Entfernen des Cluster-Manifests standardmäßig mit, unabhängig von der Retention-Policy des StatefulSets. Welche Schalter Sie vorher setzen, wie weit der eingebaute Löschschutz trägt, und warum es keinen dokumentierten Weg gibt, eine bestehende Instanz einfach zu übernehmen.

Der zentrale Satz für diesen Beitrag steht in der Parameter-Referenz des Operators: Der Operator löscht PersistentVolumeClaims, wenn das Cluster-Manifest entfernt wird, und zwar unabhängig davon, ob die persistent_volume_claim_retention_policy des StatefulSets auf retain steht. Der Standardwert von enable_persistent_volume_claim_deletion ist true. Nachzulesen in der Referenz der Operator-Parameter.

Das ist keine Fehlfunktion, sondern eine bewusste Entscheidung des Projekts. Es bedeutet aber, dass der übliche Schutzreflex aus der Kubernetes-Welt ins Leere greift. Wer die Retention-Policy des StatefulSets setzt und sich danach sicher fühlt, hat den falschen Schalter bedient. Dieser Beitrag zeigt die Reihenfolge der Einführung: erst die Schutzschalter, dann die Installation, dann der erste Cluster, dann die Übergabe an GitOps.

Wann dieser Operator das richtige Werkzeug ist

Der Operator lohnt sich, wenn Sie mehrere Postgres-Instanzen auf Kubernetes betreiben und deren Lebenszyklus deklarativ führen wollen. Er übernimmt StatefulSet, Volumes, Zugangsdaten und die Rolle des Leaders. Volume-Vergrößerung läuft ohne Rolling Update, In-Place-Upgrades auf eine neue Major-Version sind vorgesehen.

Er ist das falsche Werkzeug, wenn Sie eine bereits laufende Datenbank unter Kubernetes-Verwaltung stellen wollen, ohne die Daten zu bewegen. Das ist die wichtigste Grenze überhaupt.

Versionsstand und Kompatibilität

Aktueller Stand ist v1.15.1 vom 18.12.2025. Die Kompatibilitätsmatrix aus dem README:

OperatorPostgreSQLKubernetes
next (Entwicklungsstand)14 bis 181.27+
v1.15.113 bis 171.27+
v1.14.013 bis 171.27+
v1.10.110 bis 151.21+

Zwei Dinge fallen auf: der Sprung bei den Kubernetes-Anforderungen, der ältere Cluster ausschließt, und dass v1.14.0 Postgres 12 entfernt hat. Beides prüfen Sie vor einem Upgrade, nicht danach. Version v1.15.0 installieren Sie nicht, die Release-Notes verweisen direkt auf v1.15.1, weil für v1.15.0 die Images für UI und Logical Backup fehlen.

Was “adoptieren” hier bedeutet, und was nicht

Die Frage lautet: die vorhandene Instanz weiterlaufen lassen und den Operator nachträglich die Verwaltung übernehmen lassen. Einen solchen Adoptionspfad, bei dem der Operator eine fremd erzeugte Instanz oder eine bestehende PVC übernimmt, beschreibt die offizielle Dokumentation nicht. Dokumentiert ist ausschließlich das Klonen, entweder aus dem WAL-Archiv in S3 oder per pg_basebackup gegen den Quellcluster. In beiden Fällen entsteht ein neuer Cluster mit neuen Volumes, und Sie schwenken danach die Anwendung um. Planen Sie also eine Datenmigration, keine Übernahme.

Schritt 1: Voraussetzungen prüfen

Bevor Sie installieren oder aktualisieren, klären Sie Cluster-Version und Bestand an Postgres-Versionen.

kubectl version
kubectl get postgresql -A \
  -o custom-columns=NAME:.metadata.name,VERSION:.spec.postgresql.version

Prüfung: Für alle Releases ab v1.11.0 nennt das README Kubernetes 1.27 oder neuer. In der zweiten Ausgabe darf keine Zeile mit 12 erscheinen, wenn Sie auf v1.14.0 oder neuer gehen wollen. Erst die Datenbanken heben, dann den Operator.

Schritt 2: Den Löschschutz konfigurieren, bevor der erste Cluster steht

Dieser Schritt entscheidet über Datenverlust oder Weiterleben, und er gehört vor die erste Installation. Die relevanten Schalter und ihre Standardwerte:

ParameterStandardBedeutung des Standards
enable_persistent_volume_claim_deletiontruePVCs werden beim Entfernen des Manifests gelöscht
enable_secrets_deletiontrueSecrets werden beim Entfernen des Manifests gelöscht
enable_owner_referencesfalseeingeschaltet werden die Löschschutz-Prüfungen deaktiviert
delete_annotation_date_keyleerdie Datumsprüfung beim Löschen ist abgeschaltet
delete_annotation_name_keyleerdie Namensprüfung beim Löschen ist abgeschaltet

Der Löschschutz ist also ab Werk in keiner Form aktiv. Bei Konfiguration über die ConfigMap setzen Sie:

apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-operator
data:
  enable_persistent_volume_claim_deletion: "false"
  enable_secrets_deletion: "false"
  delete_annotation_date_key: "delete-date"
  delete_annotation_name_key: "delete-clustername"

Sind die Annotationsschlüssel gesetzt, muss jedes Cluster-Manifest die passenden Annotationen tragen, damit ein kubectl delete pg den Löschvorgang auslöst. Der Datumswert wird gegen das aktuelle Datum im Format YYYY-MM-DD geprüft, der Namenswert gegen den Clusternamen. Details in der Administrator-Dokumentation.

Prüfung:

kubectl get configmap postgres-operator \
  -o jsonpath='{.data.enable_persistent_volume_claim_deletion}'

Wer über die CRD konfiguriert, liest denselben Wert unter .configuration.kubernetes der operatorconfiguration. Er muss false liefern. Prüfen Sie im selben Zug enable_owner_references, das deaktiviert die Löschschutz-Prüfungen.

Schritt 3: Operator installieren

Per Helm mit dem offiziellen Chart-Repo:

helm repo add postgres-operator-charts \
  https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator postgres-operator-charts/postgres-operator

Ohne Helm gehen die mitgelieferten Manifeste, deren Reihenfolge im Quickstart steht.

Prüfung:

kubectl get pod -l name=postgres-operator
kubectl get deploy postgres-operator \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

Erwartet wird ghcr.io/zalando/postgres-operator:v1.15.1. Ab v1.15.1 wird nicht mehr nach registry.opensource.zalan.do gepusht, eine Referenz dorthin ist ein Fehler.

Schritt 4: Den ersten Cluster anlegen

Der relevante Ausschnitt eines Cluster-Manifests, mit den Annotationen aus Schritt 2:

apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
  name: acid-minimal-cluster
  annotations:
    delete-date: "2026-07-21"
    delete-clustername: "acid-minimal-cluster"
spec:
  volume:
    size: 2Gi
  postgresql:
    version: "17"

Ein vollständiges Beispielmanifest liegt im Ordner manifests des Repositories. 17 ist der Standardwert von target_major_version. Die Größe unter spec.volume.size wählen Sie konservativ, denn der Operator kann Volumes nur vergrößern.

Prüfung:

kubectl get postgresql
kubectl get pods -l application=spilo -L spilo-role

Die Spalte spilo-role zeigt, welcher Pod der Master ist. Für einen Verbindungstest leiten Sie dessen Port mit kubectl port-forward weiter und melden sich per psql an.

Schritt 5: Daten einer bestehenden Instanz übernehmen

Dafür gibt es nur den Klon-Weg. Aus dem WAL-Archiv, mit Zeitpunkt:

spec:
  clone:
    uid: "efd12e58-5786-11e8-b5a7-06148230260c"
    cluster: "acid-minimal-cluster"
    timestamp: "2025-12-19T12:40:33+01:00"

Ohne uid und timestamp klont der Operator per pg_basebackup direkt gegen den Quellcluster im selben Namespace.

Prüfung:

kubectl get pods -l application=spilo,cluster-name=<zielcluster> -L spilo-role

Erst wenn der Zielcluster einen Master zeigt und die Datenbanken vollständig sind, schwenken Sie die Anwendung um.

Schritt 6: Übergabe an GitOps

Jetzt erst kommt das Repository ins Spiel. Zwei Punkte sind vor dem ersten Sync zu klären.

Erstens die CRDs. Der Operator registriert Postgresql und OperatorConfiguration beim Start selbst und aktualisiert sie. Wollen Sie die CRDs getrennt verwalten, damit nicht Operator und GitOps-Ebene dieselbe Ressource schreiben, setzen Sie enable_crd_registration auf false. Der Standard ist true.

Zweitens das Pruning. In ArgoCD schützt die Sync-Option Prune=false eine Ressource davor, entfernt zu werden:

metadata:
  annotations:
    argocd.argoproj.io/sync-options: Prune=false

Dieselbe Option gibt es unter syncPolicy.syncOptions auf Application-Ebene, die Annotation an der Ressource gewinnt aber immer. Die Folge ist dokumentiert und dauerhaft: Die Application meldet OutOfSync, solange ArgoCD eine Ressource entfernen möchte und nicht darf. Nachzulesen bei den ArgoCD Sync-Options.

Prüfung:

kubectl get operatorconfiguration postgresql-operator-configuration \
  -o jsonpath='{.configuration.enable_crd_registration}'

Das grundsätzliche Vorgehen beim Übergeben gewachsener Workloads an ArgoCD steht im Beitrag Ein gewachsenes Cluster nach ArgoCD holen.

Die Schutzebenen im Vergleich

Die Mechanismen schützen unterschiedliche Dinge, und keiner ersetzt die anderen.

MechanismusSchütztGrenze
enable_persistent_volume_claim_deletion: falsePVC und damit die Datenschützt die postgresql-Ressource selbst nicht
enable_secrets_deletion: falsedie Zugangsdatendito
Löschschutz-AnnotationenAufräumen der Kindressourcenblockiert das Löschen nicht, meldet nur im Log. Wird durch enable_owner_references deaktiviert
ArgoCD Prune=falsedie Ressource vor ArgoCDApplication bleibt dauerhaft OutOfSync

Die postgresql-Ressource und der PersistentVolumeClaim mit den Daten. Wird das Cluster-Manifest entfernt, löscht der Operator die PVC standardmäßig mit, unabhängig von der Retention-Policy des StatefulSets.

Die drei folgenreichsten Stolpersteine

1. Die Daten sind weg, obwohl die Retention-Policy auf retain stand

Symptom: Nach dem Entfernen der postgresql-Ressource ist der PersistentVolumeClaim verschwunden, obwohl die persistent_volume_claim_retention_policy des StatefulSets auf retain gesetzt war.

Ursache: Der Operator räumt die PVCs selbst auf und ignoriert dabei die Retention-Policy des StatefulSets. enable_persistent_volume_claim_deletion ist per Default true.

Prüfung: Den Wert wie in Schritt 2 auslesen, danach ein Testlöschen in einem Wegwerf-Namespace und kubectl get pvc -l application=spilo.

Lösung: enable_persistent_volume_claim_deletion auf false setzen, bevor der erste produktive Cluster entsteht. Gleich enable_secrets_deletion mit, sonst überleben die Volumes und die Zugangsdaten dazu sind weg.

2. Der Löschschutz wirkt plötzlich nicht mehr

Symptom: Die Annotationen stehen unverändert im Manifest, der Schutz greift trotzdem nicht.

Ursache: enable_owner_references wurde eingeschaltet. Die Dokumentation warnt ausdrücklich, dass diese Option die konfigurierten Löschschutz-Prüfungen deaktiviert.

Prüfung: Den Wert unter .configuration.kubernetes.enable_owner_references der operatorconfiguration auslesen.

Lösung: Entscheiden Sie sich. Entweder Owner References aus lassen, das ist der Standard, und den Annotationsschutz nutzen. Oder Owner References einschalten und den Schutz allein auf Prune=false und Backups stützen. Beides zusammen funktioniert nicht.

3. Postgres-12-Cluster starten nach dem Operator-Upgrade nicht mehr

Symptom: Nach dem Sprung auf v1.14.0 oder neuer kommen Cluster mit Postgres 12 nicht mehr hoch.

Ursache: v1.14.0 hat die Unterstützung für Postgres 12 entfernt.

Prüfung: Die Major-Versionen wie in Schritt 1 auflisten, vor dem Operator-Upgrade.

Lösung: Die Reihenfolge ist zwingend. Erst die Datenbanken auf eine unterstützte Major-Version heben, dann den Operator aktualisieren. Das In-Place-Upgrade steuert major_version_upgrade_mode mit den Werten off, manual oder full, der Standard ist manual.

Die übrigen Stolpersteine

Diese drei sind die folgenreichsten, aber nicht alle. Die vollständige Sammlung der Stolpersteine steht in einem eigenen Beitrag: Zalando-Postgres-Operator: alle Stolpersteine im Überblick. Dort kommen acht weitere Fälle dazu, darunter die nach einem Helm-Upgrade nicht mitgezogenen CRDs, der Registry-Wechsel nach ghcr.io, die Voraussetzungen für ein Major-Upgrade und der Wechsel von Endpoints auf ConfigMaps für die Leader-Wahl.

Grenzen

Der Operator übernimmt keine bestehende Instanz. Für eine laufende Datenbank beschreibt die Dokumentation nur den Weg über das Klonen. Sie brauchen dafür einen Umschaltzeitpunkt und planen die Sache als Migration, nicht als Handgriff im Wartungsfenster.

Der eingebaute Löschschutz greift enger, als der Name vermuten lässt. Er verhindert das Aufräumen der Ressourcen, nicht das Löschen selbst. Für eine wirksame Blockade brauchen Sie eine Admission-Policy oder die GitOps-Ebene darüber.

Der Zuständigkeitsbereich des Operators endet außerdem vor der Datensicherung. Er übernimmt Failover, Volume-Verwaltung und Versionswechsel. Für die Wiederherstellbarkeit der Daten sorgt er nicht. Ich richte deshalb zuerst ein getestetes Backup außerhalb des Clusters ein und übergebe die Datenbank erst danach an eine Automatik, die Ressourcen selbständig entfernen kann. Warum erst der Restore-Test echten Schutz macht, steht im Beitrag Backup, das wirklich funktioniert.

Sie betreiben Datenbanken und wollen sicher sein, dass die Daten einen Fehlgriff überleben? Ich baue Datenhaltung mit getestetem Backup und klarer Wiederherstellung, auf Kubernetes wie klassisch. Mehr unter Infrastruktur & Cloud, das Erstgespräch ist unverbindlich.

Alex Jabi, Jabi IT

Alex Jabi

Ich betreue IT, Informationssicherheit und Datenschutz für KMU und Praxen an der Bergstraße und im Odenwald, persönlich, dokumentiert und ohne Cloud-Zwang.

Mehr über mich
Zurück zum Blog

Passende Artikel

Alle Artikel »
Zalando-Postgres-Operator: alle Stolpersteine im Überblick

Zalando-Postgres-Operator: alle Stolpersteine im Überblick

Die vollständige Fallsammlung zum Zalando-Postgres-Operator. Gelöschte PVCs, ein Löschschutz der nicht blockiert, CRD-Konflikte mit ArgoCD, der Registry-Wechsel nach ghcr.io, entfallene Postgres-Versionen und der anstehende Wechsel der Leader-Wahl. Jeder Fall im Schema Symptom, Ursache, Prüfung, Lösung.