· 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 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:
| Operator | PostgreSQL | Kubernetes |
|---|---|---|
| next (Entwicklungsstand) | 14 bis 18 | 1.27+ |
| v1.15.1 | 13 bis 17 | 1.27+ |
| v1.14.0 | 13 bis 17 | 1.27+ |
| v1.10.1 | 10 bis 15 | 1.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.versionPrü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:
| Parameter | Standard | Bedeutung des Standards |
|---|---|---|
enable_persistent_volume_claim_deletion | true | PVCs werden beim Entfernen des Manifests gelöscht |
enable_secrets_deletion | true | Secrets werden beim Entfernen des Manifests gelöscht |
enable_owner_references | false | eingeschaltet werden die Löschschutz-Prüfungen deaktiviert |
delete_annotation_date_key | leer | die Datumsprüfung beim Löschen ist abgeschaltet |
delete_annotation_name_key | leer | die 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-operatorOhne 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-roleDie 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-roleErst 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=falseDieselbe 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.
| Mechanismus | Schützt | Grenze |
|---|---|---|
enable_persistent_volume_claim_deletion: false | PVC und damit die Daten | schützt die postgresql-Ressource selbst nicht |
enable_secrets_deletion: false | die Zugangsdaten | dito |
| Löschschutz-Annotationen | Aufräumen der Kindressourcen | blockiert das Löschen nicht, meldet nur im Log. Wird durch enable_owner_references deaktiviert |
ArgoCD Prune=false | die Ressource vor ArgoCD | Application bleibt dauerhaft OutOfSync |

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


