From 9eedb989545ea733658dab35e0aba13a4d0270e7 Mon Sep 17 00:00:00 2001 From: tcondeixa Date: Tue, 30 Jun 2026 10:47:57 +0200 Subject: [PATCH 1/5] trying to migrate from application to owner label --- pkg/cluster/cluster.go | 7 +++---- pkg/cluster/resources.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index eed4ee933..43c9e20b8 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -530,11 +530,10 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *appsv1.StatefulSet) *compa } if (c.Statefulset.Spec.Selector != nil) && (statefulSet.Spec.Selector != nil) { if !reflect.DeepEqual(c.Statefulset.Spec.Selector.MatchLabels, statefulSet.Spec.Selector.MatchLabels) { - // forbid introducing new labels in the selector on the new statefulset, as it would cripple replacements - // due to the fact that the new statefulset won't be able to pick up old pods with non-matching labels. if !util.MapContains(c.Statefulset.Spec.Selector.MatchLabels, statefulSet.Spec.Selector.MatchLabels) { - c.logger.Warningf("new statefulset introduces extra labels in the label selector, cannot continue") - return &compareStatefulsetResult{} + // new selector has labels the existing pods don't carry yet; pods will be + // relabeled before the StatefulSet is replaced so they can be adopted. + c.logger.Warningf("new statefulset introduces extra labels in the selector, pods will be relabeled before replacement") } needsReplace = true reasons = append(reasons, "new statefulset's selector does not match the current one") diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index 6053de471..b83067ac9 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -211,6 +211,38 @@ func (c *Cluster) updateStatefulSet(newStatefulSet *appsv1.StatefulSet) error { } // replaceStatefulSet deletes an old StatefulSet and creates the new using spec in the PostgreSQL CRD. +func (c *Cluster) relabelPodsForSelector(newSelector map[string]string) error { + pods, err := c.listPods() + if err != nil { + return fmt.Errorf("could not list pods for relabeling: %v", err) + } + + for _, pod := range pods { + // only patch pods that are missing one or more of the new selector labels + needsPatch := false + for k, v := range newSelector { + if pod.Labels[k] != v { + needsPatch = true + break + } + } + if !needsPatch { + continue + } + + patchData, err := metaLabelsPatch(newSelector) + if err != nil { + return fmt.Errorf("could not form label patch for pod %q: %v", pod.Name, err) + } + if _, err := c.KubeClient.Pods(pod.Namespace).Patch(context.TODO(), pod.Name, types.MergePatchType, patchData, metav1.PatchOptions{}); err != nil { + return fmt.Errorf("could not relabel pod %q: %v", pod.Name, err) + } + c.logger.Infof("relabeled pod %q with new selector labels", pod.Name) + } + + return nil +} + func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { c.setProcessName("replacing statefulset") if c.Statefulset == nil { @@ -220,6 +252,14 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { statefulSetName := util.NameFromMeta(c.Statefulset.ObjectMeta) c.logger.Debug("replacing statefulset") + // If the new selector has labels the existing pods don't carry, relabel them first + // so the new StatefulSet can adopt them after the cascade=orphan delete. + if !util.MapContains(c.Statefulset.Spec.Selector.MatchLabels, newStatefulSet.Spec.Selector.MatchLabels) { + if err := c.relabelPodsForSelector(newStatefulSet.Spec.Selector.MatchLabels); err != nil { + return fmt.Errorf("could not relabel pods before statefulset replacement: %v", err) + } + } + // Delete the current statefulset without deleting the pods deletePropagationPolicy := metav1.DeletePropagationOrphan oldStatefulset := c.Statefulset From e7ed314ffbcbeb18f8814d4bc5e2fe8b2dda7bf3 Mon Sep 17 00:00:00 2001 From: tcondeixa Date: Wed, 1 Jul 2026 14:32:34 +0200 Subject: [PATCH 2/5] relabel pods unsing the previous selector --- Makefile | 4 ++-- hack/adjust_postgresql_crd.sh | 4 ++-- pkg/cluster/resources.go | 13 ++++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index d85b74ed3..f67bbe62b 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,8 @@ $(GENERATED_CRDS): $(GENERATED) go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests @mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml @# hack to use lowercase kind and listKind - @sed -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml - @sed -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml + @sed -i.bak 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml && rm manifests/postgresql.crd.yaml.bak + @sed -i.bak 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml && rm manifests/postgresql.crd.yaml.bak @hack/adjust_postgresql_crd.sh @mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml @mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml diff --git a/hack/adjust_postgresql_crd.sh b/hack/adjust_postgresql_crd.sh index d06b74a2d..ff111b621 100755 --- a/hack/adjust_postgresql_crd.sh +++ b/hack/adjust_postgresql_crd.sh @@ -13,12 +13,12 @@ file="${1:-"manifests/postgresql.crd.yaml"}" -sed -i '/^[[:space:]]*standby:$/{ +sed -i '' '/^[[:space:]]*standby:$/{ # Capture the indentation s/^\([[:space:]]*\)standby:$/\1standby:\n\1 anyOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host\n\1 not:\n\1 required:\n\1 - s3_wal_path\n\1 - gs_wal_path/ }' "$file" -sed -i '/^[[:space:]]*maintenanceWindows:$/{ +sed -i '' '/^[[:space:]]*maintenanceWindows:$/{ # Capture the indentation s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/ }' "$file" diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index b83067ac9..ba23616ab 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -11,6 +11,7 @@ import ( v1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "github.com/zalando/postgres-operator/pkg/util" @@ -211,11 +212,17 @@ func (c *Cluster) updateStatefulSet(newStatefulSet *appsv1.StatefulSet) error { } // replaceStatefulSet deletes an old StatefulSet and creates the new using spec in the PostgreSQL CRD. -func (c *Cluster) relabelPodsForSelector(newSelector map[string]string) error { - pods, err := c.listPods() +func (c *Cluster) relabelPodsForSelector(oldSelector, newSelector map[string]string) error { + // list pods using the OLD selector — at this point c.labelsSet already reflects + // the new cluster_labels config, so listPods() would find nothing. + listOptions := metav1.ListOptions{ + LabelSelector: labels.Set(oldSelector).String(), + } + podList, err := c.KubeClient.Pods(c.Namespace).List(context.TODO(), listOptions) if err != nil { return fmt.Errorf("could not list pods for relabeling: %v", err) } + pods := podList.Items for _, pod := range pods { // only patch pods that are missing one or more of the new selector labels @@ -255,7 +262,7 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { // If the new selector has labels the existing pods don't carry, relabel them first // so the new StatefulSet can adopt them after the cascade=orphan delete. if !util.MapContains(c.Statefulset.Spec.Selector.MatchLabels, newStatefulSet.Spec.Selector.MatchLabels) { - if err := c.relabelPodsForSelector(newStatefulSet.Spec.Selector.MatchLabels); err != nil { + if err := c.relabelPodsForSelector(c.Statefulset.Spec.Selector.MatchLabels, newStatefulSet.Spec.Selector.MatchLabels); err != nil { return fmt.Errorf("could not relabel pods before statefulset replacement: %v", err) } } From b9920295289a7a7b55904605bf66053f93066b8f Mon Sep 17 00:00:00 2001 From: tcondeixa Date: Wed, 1 Jul 2026 17:06:37 +0200 Subject: [PATCH 3/5] the pvc also need to be relabeled --- pkg/cluster/resources.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index ba23616ab..c8270b388 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -250,6 +250,40 @@ func (c *Cluster) relabelPodsForSelector(oldSelector, newSelector map[string]str return nil } +func (c *Cluster) relabelPVCsForSelector(oldSelector, newSelector map[string]string) error { + listOptions := metav1.ListOptions{ + LabelSelector: labels.Set(oldSelector).String(), + } + pvcList, err := c.KubeClient.PersistentVolumeClaims(c.Namespace).List(context.TODO(), listOptions) + if err != nil { + return fmt.Errorf("could not list PVCs for relabeling: %v", err) + } + + for _, pvc := range pvcList.Items { + needsPatch := false + for k, v := range newSelector { + if pvc.Labels[k] != v { + needsPatch = true + break + } + } + if !needsPatch { + continue + } + + patchData, err := metaLabelsPatch(newSelector) + if err != nil { + return fmt.Errorf("could not form label patch for PVC %q: %v", pvc.Name, err) + } + if _, err := c.KubeClient.PersistentVolumeClaims(pvc.Namespace).Patch(context.TODO(), pvc.Name, types.MergePatchType, patchData, metav1.PatchOptions{}); err != nil { + return fmt.Errorf("could not relabel PVC %q: %v", pvc.Name, err) + } + c.logger.Infof("relabeled PVC %q with new selector labels", pvc.Name) + } + + return nil +} + func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { c.setProcessName("replacing statefulset") if c.Statefulset == nil { @@ -265,6 +299,9 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { if err := c.relabelPodsForSelector(c.Statefulset.Spec.Selector.MatchLabels, newStatefulSet.Spec.Selector.MatchLabels); err != nil { return fmt.Errorf("could not relabel pods before statefulset replacement: %v", err) } + if err := c.relabelPVCsForSelector(c.Statefulset.Spec.Selector.MatchLabels, newStatefulSet.Spec.Selector.MatchLabels); err != nil { + return fmt.Errorf("could not relabel PVCs before statefulset replacement: %v", err) + } } // Delete the current statefulset without deleting the pods From b85c3ed870b0ace2ff297c564ee32f489d64cb0a Mon Sep 17 00:00:00 2001 From: tcondeixa Date: Wed, 1 Jul 2026 17:39:44 +0200 Subject: [PATCH 4/5] sync pvc and service labels --- pkg/cluster/resources.go | 54 ++++++++++++++++++---------------------- pkg/cluster/volumes.go | 15 +++++++++++ 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index c8270b388..095c5a13e 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -222,25 +222,15 @@ func (c *Cluster) relabelPodsForSelector(oldSelector, newSelector map[string]str if err != nil { return fmt.Errorf("could not list pods for relabeling: %v", err) } - pods := podList.Items + patchData, err := metaLabelsPatch(newSelector) + if err != nil { + return fmt.Errorf("could not form label patch for pods: %v", err) + } - for _, pod := range pods { - // only patch pods that are missing one or more of the new selector labels - needsPatch := false - for k, v := range newSelector { - if pod.Labels[k] != v { - needsPatch = true - break - } - } - if !needsPatch { + for _, pod := range podList.Items { + if util.MapContains(pod.Labels, newSelector) { continue } - - patchData, err := metaLabelsPatch(newSelector) - if err != nil { - return fmt.Errorf("could not form label patch for pod %q: %v", pod.Name, err) - } if _, err := c.KubeClient.Pods(pod.Namespace).Patch(context.TODO(), pod.Name, types.MergePatchType, patchData, metav1.PatchOptions{}); err != nil { return fmt.Errorf("could not relabel pod %q: %v", pod.Name, err) } @@ -259,21 +249,14 @@ func (c *Cluster) relabelPVCsForSelector(oldSelector, newSelector map[string]str return fmt.Errorf("could not list PVCs for relabeling: %v", err) } - for _, pvc := range pvcList.Items { - needsPatch := false - for k, v := range newSelector { - if pvc.Labels[k] != v { - needsPatch = true - break - } - } - if !needsPatch { - continue - } + patchData, err := metaLabelsPatch(newSelector) + if err != nil { + return fmt.Errorf("could not form label patch for PVCs: %v", err) + } - patchData, err := metaLabelsPatch(newSelector) - if err != nil { - return fmt.Errorf("could not form label patch for PVC %q: %v", pvc.Name, err) + for _, pvc := range pvcList.Items { + if util.MapContains(pvc.Labels, newSelector) { + continue } if _, err := c.KubeClient.PersistentVolumeClaims(pvc.Namespace).Patch(context.TODO(), pvc.Name, types.MergePatchType, patchData, metav1.PatchOptions{}); err != nil { return fmt.Errorf("could not relabel PVC %q: %v", pvc.Name, err) @@ -434,6 +417,17 @@ func (c *Cluster) updateService(role PostgresRole, oldService *v1.Service, newSe } } + if !util.MapContains(oldService.Labels, newService.Labels) { + patchData, err := metaLabelsPatch(newService.Labels) + if err != nil { + return nil, fmt.Errorf("could not form patch for service %q labels: %v", oldService.Name, err) + } + svc, err = c.KubeClient.Services(serviceName.Namespace).Patch(context.TODO(), newService.Name, types.MergePatchType, patchData, metav1.PatchOptions{}) + if err != nil { + return nil, fmt.Errorf("could not patch labels for service %q: %v", oldService.Name, err) + } + } + return svc, nil } diff --git a/pkg/cluster/volumes.go b/pkg/cluster/volumes.go index e32e558e6..d1a282102 100644 --- a/pkg/cluster/volumes.go +++ b/pkg/cluster/volumes.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/zalando/postgres-operator/pkg/spec" + "github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util/constants" "github.com/zalando/postgres-operator/pkg/util/filesystems" "github.com/zalando/postgres-operator/pkg/util/k8sutil" @@ -198,6 +199,7 @@ func (c *Cluster) syncVolumeClaims() error { if err != nil { return fmt.Errorf("could not list persistent volume claims: %v", err) } + newLabels := c.labelsSet(true) for _, pvc := range pvcs { c.VolumeClaims[pvc.UID] = &pvc needsUpdate := false @@ -236,6 +238,19 @@ func (c *Cluster) syncVolumeClaims() error { } c.VolumeClaims[pvc.UID] = patchedPvc } + + if !util.MapContains(pvc.Labels, newLabels) { + patchData, err := metaLabelsPatch(newLabels) + if err != nil { + return fmt.Errorf("could not form patch for labels of persistent volume claim %q: %v", pvc.Name, err) + } + patchedPvc, err := c.KubeClient.PersistentVolumeClaims(pvc.Namespace).Patch(context.TODO(), pvc.Name, types.MergePatchType, patchData, metav1.PatchOptions{}) + if err != nil { + return fmt.Errorf("could not patch labels of persistent volume claim %q: %v", pvc.Name, err) + } + c.VolumeClaims[pvc.UID] = patchedPvc + c.logger.Infof("updated labels on persistent volume claim %q", pvc.Name) + } } c.logger.Debug("volume claims have been synced successfully") From 3c869943c31086863b61e4c892229e5db85fc704 Mon Sep 17 00:00:00 2001 From: tcondeixa Date: Wed, 1 Jul 2026 17:43:28 +0200 Subject: [PATCH 5/5] remove the changes to work on macos --- Makefile | 4 ++-- hack/adjust_postgresql_crd.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f67bbe62b..d85b74ed3 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,8 @@ $(GENERATED_CRDS): $(GENERATED) go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests @mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml @# hack to use lowercase kind and listKind - @sed -i.bak 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml && rm manifests/postgresql.crd.yaml.bak - @sed -i.bak 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml && rm manifests/postgresql.crd.yaml.bak + @sed -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml + @sed -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml @hack/adjust_postgresql_crd.sh @mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml @mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml diff --git a/hack/adjust_postgresql_crd.sh b/hack/adjust_postgresql_crd.sh index ff111b621..d06b74a2d 100755 --- a/hack/adjust_postgresql_crd.sh +++ b/hack/adjust_postgresql_crd.sh @@ -13,12 +13,12 @@ file="${1:-"manifests/postgresql.crd.yaml"}" -sed -i '' '/^[[:space:]]*standby:$/{ +sed -i '/^[[:space:]]*standby:$/{ # Capture the indentation s/^\([[:space:]]*\)standby:$/\1standby:\n\1 anyOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host\n\1 not:\n\1 required:\n\1 - s3_wal_path\n\1 - gs_wal_path/ }' "$file" -sed -i '' '/^[[:space:]]*maintenanceWindows:$/{ +sed -i '/^[[:space:]]*maintenanceWindows:$/{ # Capture the indentation s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/ }' "$file"