From 292353f10387aa8d062d4b26680a29fd715739dd Mon Sep 17 00:00:00 2001 From: matthias Date: Wed, 3 Sep 2025 20:47:48 +0200 Subject: [PATCH 01/12] added liveness probe for postgres-container --- .../v1/operator_configuration_type.go | 1 + pkg/cluster/cluster.go | 2 ++ pkg/cluster/k8sres.go | 21 +++++++++++++++++++ pkg/controller/operator_config.go | 1 + pkg/util/config/config.go | 1 + 5 files changed, 26 insertions(+) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go index a76036776..229524b77 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go @@ -102,6 +102,7 @@ type KubernetesMetaConfiguration struct { PodManagementPolicy string `json:"pod_management_policy,omitempty"` PersistentVolumeClaimRetentionPolicy map[string]string `json:"persistent_volume_claim_retention_policy,omitempty"` EnableReadinessProbe bool `json:"enable_readiness_probe,omitempty"` + EnableLivenessProbe bool `json:"enable_liveness_probe,omitempty"` EnableCrossNamespaceSecret bool `json:"enable_cross_namespace_secret,omitempty"` } diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index f97611a1d..d7483af89 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -676,6 +676,8 @@ func (c *Cluster) compareContainers(description string, setA, setB []v1.Containe func(a, b v1.Container) bool { return a.Name != b.Name }), newCheck("new statefulset %s's %s (index %d) readiness probe does not match the current one", func(a, b v1.Container) bool { return !reflect.DeepEqual(a.ReadinessProbe, b.ReadinessProbe) }), + newCheck("new statefulset %s's %s (index %d) liveness probe does not match the current one", + func(a, b v1.Container) bool { return !reflect.DeepEqual(a.LivenessProbe, b.LivenessProbe) }), newCheck("new statefulset %s's %s (index %d) ports do not match the current one", func(a, b v1.Container) bool { return !comparePorts(a.Ports, b.Ports) }), newCheck("new statefulset %s's %s (index %d) resources do not match the current ones", diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index a8878a724..7465552e1 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1292,6 +1292,23 @@ func generateSpiloReadinessProbe() *v1.Probe { } } +func generatePatroniLivenessProbe() *v1.Probe { + return &v1.Probe{ + FailureThreshold: 6, + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/health", + Port: intstr.IntOrString{IntVal: patroni.ApiPort}, + Scheme: v1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: 30, + PeriodSeconds: 10, + TimeoutSeconds: 5, + SuccessThreshold: 1, + } +} + func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.StatefulSet, error) { var ( @@ -1451,6 +1468,10 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu if c.OpConfig.EnableReadinessProbe { spiloContainer.ReadinessProbe = generateSpiloReadinessProbe() } + // + if c.OpConfig.EnableLivenessProbe { + spiloContainer.LivenessProbe = generatePatroniLivenessProbe() + } // generate container specs for sidecars specified in the cluster manifest clusterSpecificSidecars := []v1.Container{} diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 12f27a536..8517f6a3f 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -121,6 +121,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *cpov1.OperatorConfigura result.PodManagementPolicy = util.Coalesce(fromCRD.Kubernetes.PodManagementPolicy, "ordered_ready") result.PersistentVolumeClaimRetentionPolicy = fromCRD.Kubernetes.PersistentVolumeClaimRetentionPolicy result.EnableReadinessProbe = fromCRD.Kubernetes.EnableReadinessProbe + result.EnableLivenessProbe = fromCRD.Kubernetes.EnableLivenessProbe result.MasterPodMoveTimeout = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout), "10m") result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity result.PodAntiAffinityTopologyKey = util.Coalesce(fromCRD.Kubernetes.PodAntiAffinityTopologyKey, "kubernetes.io/hostname") diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 2e37a0b65..1ababb192 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -248,6 +248,7 @@ type Config struct { PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"` EnableReadinessProbe bool `name:"enable_readiness_probe" default:"false"` + EnableLivenessProbe bool `name:"enable_liveness_probe" default:"true"` ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"` PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""` SetMemoryRequestToLimit bool `name:"set_memory_request_to_limit" default:"false"` From a149991c84ed1641016356bc3ee583fd76807229 Mon Sep 17 00:00:00 2001 From: matthias Date: Thu, 4 Sep 2025 19:12:49 +0200 Subject: [PATCH 02/12] add SecuityContext for Monitoring-Sidecar --- .../v1/postgresql_type.go | 11 ++++++----- .../v1/zz_generated.deepcopy.go | 5 +++++ pkg/cluster/k8sres.go | 16 ++++++++++++---- pkg/cluster/resources.go | 6 ++++++ pkg/cluster/sync.go | 6 ++++++ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go index 4070e1e82..ee47546b2 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go @@ -216,11 +216,12 @@ type CloneDescription struct { // Sidecar defines a container to be run in the same pod as the Postgres container. type Sidecar struct { - *Resources `json:"resources,omitempty"` - Name string `json:"name,omitempty"` - DockerImage string `json:"image,omitempty"` - Ports []v1.ContainerPort `json:"ports,omitempty"` - Env []v1.EnvVar `json:"env,omitempty"` + *Resources `json:"resources,omitempty"` + Name string `json:"name,omitempty"` + DockerImage string `json:"image,omitempty"` + Ports []v1.ContainerPort `json:"ports,omitempty"` + Env []v1.EnvVar `json:"env,omitempty"` + SecurityContext *v1.SecurityContext `json:"securityContext,omitempty"` } // UserFlags defines flags (such as superuser, nologin) that could be assigned to individual users diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go index 9daa74e86..0f483088f 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go @@ -1497,6 +1497,11 @@ func (in *Sidecar) DeepCopyInto(out *Sidecar) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(corev1.SecurityContext) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 7465552e1..09500fb6f 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -736,7 +736,7 @@ func (c *Cluster) generateSidecarContainers(sidecars []cpov1.Sidecar, } // adds common fields to sidecars -func patchSidecarContainers(in []v1.Container, volumeMounts []v1.VolumeMount, superUserName string, credentialsSecretName string, logger *logrus.Entry) []v1.Container { +func patchSidecarContainers(in []v1.Container, volumeMounts []v1.VolumeMount, superUserName string, credentialsSecretName string, logger *logrus.Entry, privilegedMode bool, privilegeEscalationMode *bool, additionalPodCapabilities *v1.Capabilities) []v1.Container { result := []v1.Container{} for _, container := range in { @@ -777,6 +777,7 @@ func patchSidecarContainers(in []v1.Container, volumeMounts []v1.VolumeMount, su }, } container.Env = appendEnvVars(env, container.Env...) + result = append(result, container) } @@ -1243,6 +1244,7 @@ func getSidecarContainer(sidecar cpov1.Sidecar, index int, resources *v1.Resourc Resources: *resources, Env: sidecar.Env, Ports: sidecar.Ports, + SecurityContext: sidecar.SecurityContext, } } @@ -1527,7 +1529,7 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu containerName, containerName) } - sidecarContainers = patchSidecarContainers(sidecarContainers, volumeMounts, c.OpConfig.SuperUsername, c.credentialSecretName(c.OpConfig.SuperUsername), c.logger) + sidecarContainers = patchSidecarContainers(sidecarContainers, volumeMounts, c.OpConfig.SuperUsername, c.credentialSecretName(c.OpConfig.SuperUsername), c.logger, c.OpConfig.Resources.SpiloPrivileged, c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, generateCapabilities(c.OpConfig.AdditionalPodCapabilities)) tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration) topologySpreadConstraintsSpec := topologySpreadConstraints(&spec.TopologySpreadConstraints) @@ -1536,7 +1538,7 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu podAnnotations := c.generatePodAnnotations(spec) if spec.GetBackup().Pgbackrest != nil { - initContainers = append(initContainers, c.generatePgbackrestRestoreContainer(spec, repo_host_mode, volumeMounts, resourceRequirements)) + initContainers = append(initContainers, c.generatePgbackrestRestoreContainer(spec, repo_host_mode, volumeMounts, resourceRequirements, c.OpConfig.Resources.SpiloPrivileged, c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, generateCapabilities(c.OpConfig.AdditionalPodCapabilities))) additionalVolumes = append(additionalVolumes, c.generatePgbackrestConfigVolume(spec.Backup.Pgbackrest, false)) @@ -1639,7 +1641,7 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu return statefulSet, nil } -func (c *Cluster) generatePgbackrestRestoreContainer(spec *cpov1.PostgresSpec, repo_host_mode bool, volumeMounts []v1.VolumeMount, resourceRequirements *v1.ResourceRequirements) v1.Container { +func (c *Cluster) generatePgbackrestRestoreContainer(spec *cpov1.PostgresSpec, repo_host_mode bool, volumeMounts []v1.VolumeMount, resourceRequirements *v1.ResourceRequirements, privilegedMode bool, privilegeEscalationMode *bool, additionalPodCapabilities *v1.Capabilities) v1.Container { isOptional := true pgbackrestRestoreEnvVars := []v1.EnvVar{ { @@ -1719,6 +1721,12 @@ func (c *Cluster) generatePgbackrestRestoreContainer(spec *cpov1.PostgresSpec, r Env: pgbackrestRestoreEnvVars, VolumeMounts: volumeMounts, Resources: *resourceRequirements, + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: privilegeEscalationMode, + Privileged: &privilegedMode, + ReadOnlyRootFilesystem: util.False(), + Capabilities: additionalPodCapabilities, + }, } } diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index 9a3361217..76d8608b8 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -92,6 +92,12 @@ func (c *Cluster) createStatefulSet() (*appsv1.StatefulSet, error) { }, }, Env: c.generateMonitoringEnvVars(), + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, + Privileged: &c.OpConfig.Resources.SpiloPrivileged, + ReadOnlyRootFilesystem: util.True(), + Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), + }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index a12117d97..33f3dd734 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -514,6 +514,12 @@ func (c *Cluster) syncStatefulSet() error { }, }, Env: c.generateMonitoringEnvVars(), + SecurityContext: &v1.SecurityContext{ + AllowPrivilegeEscalation: c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, + Privileged: &c.OpConfig.Resources.SpiloPrivileged, + ReadOnlyRootFilesystem: util.True(), + Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), + }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } From 82cfc2a750760590deb202b365059e4997ccc394 Mon Sep 17 00:00:00 2001 From: matthias Date: Thu, 4 Sep 2025 20:20:44 +0200 Subject: [PATCH 03/12] prepare for ReadOnlyRootFilesystem - add /tmp & /run emptyDir - add ReadOnlyRootFilesystem-parameter --- .../cpo.opensource.cybertec.at/v1/postgresql_type.go | 1 + .../v1/zz_generated.deepcopy.go | 7 +++++++ pkg/cluster/k8sres.go | 12 +++++++++++- pkg/cluster/resources.go | 6 ++++++ pkg/cluster/sync.go | 6 ++++++ pkg/util/config/config.go | 1 + 6 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go index ee47546b2..60eaa5b8b 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go @@ -222,6 +222,7 @@ type Sidecar struct { Ports []v1.ContainerPort `json:"ports,omitempty"` Env []v1.EnvVar `json:"env,omitempty"` SecurityContext *v1.SecurityContext `json:"securityContext,omitempty"` + VolumeMounts []v1.VolumeMount `json:"volumeMounts,omitempty"` } // UserFlags defines flags (such as superuser, nologin) that could be assigned to individual users diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go index 0f483088f..c058de772 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go @@ -1502,6 +1502,13 @@ func (in *Sidecar) DeepCopyInto(out *Sidecar) { *out = new(corev1.SecurityContext) (*in).DeepCopyInto(*out) } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]corev1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 09500fb6f..00cbbe6ee 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -874,7 +874,7 @@ func (c *Cluster) generatePodTemplate( podSpec.PriorityClassName = priorityClassName } - if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars { + if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars || c.OpConfig.ReadOnlyRootFilesystem { addVarRunVolume(&podSpec) } @@ -1245,6 +1245,7 @@ func getSidecarContainer(sidecar cpov1.Sidecar, index int, resources *v1.Resourc Env: sidecar.Env, Ports: sidecar.Ports, SecurityContext: sidecar.SecurityContext, + VolumeMounts: sidecar.VolumeMounts, } } @@ -1441,6 +1442,15 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu } additionalVolumes = append(additionalVolumes, tlsVolumes...) } + // if monitoring is enabled, add a empty volume + if c.Postgresql.Spec.Monitoring != nil { + additionalVolumes = append(additionalVolumes, cpov1.AdditionalVolume{ + Name: "exporter-tmp", + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + }) + } repo_host_mode := false // Add this envVar so that it is not added to the pgbackrest initcontainer if specHasPgbackrestPVCRepo(spec) { diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index 76d8608b8..139f5c808 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -98,6 +98,12 @@ func (c *Cluster) createStatefulSet() (*appsv1.StatefulSet, error) { ReadOnlyRootFilesystem: util.True(), Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "exporter-tmp", + MountPath: "/tmp", + }, + }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 33f3dd734..296bf0aa0 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -520,6 +520,12 @@ func (c *Cluster) syncStatefulSet() error { ReadOnlyRootFilesystem: util.True(), Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), }, + VolumeMounts: []v1.VolumeMount{ + { + Name: "exporter-tmp", + MountPath: "/tmp", + }, + }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 1ababb192..a48e0804a 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -38,6 +38,7 @@ type Resources struct { SpiloPrivileged bool `name:"spilo_privileged" default:"false"` SpiloAllowPrivilegeEscalation *bool `name:"spilo_allow_privilege_escalation" default:"true"` AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` + ReadOnlyRootFilesystem bool `name:"container_readonly_root_filesystem" default:"true"` ClusterLabels map[string]string `name:"cluster_labels" default:"application:cpo"` InheritedLabels []string `name:"inherited_labels" default:""` InheritedAnnotations []string `name:"inherited_annotations" default:""` From afb7aff0daf098d474ef8cb9f1ff2d9cd144273d Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 09:37:28 +0200 Subject: [PATCH 04/12] add func for emptyDir-Volumes - preparation for ReadOnlyRootFilesystem True - exporter changed to ReadOnlyRootFilesystem=true --- .../v1/operator_configuration_type.go | 1 + pkg/cluster/k8sres.go | 46 +++++++++++++++---- pkg/cluster/resources.go | 6 --- pkg/cluster/sync.go | 6 --- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go index 229524b77..e1c3efeb0 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go @@ -62,6 +62,7 @@ type KubernetesMetaConfiguration struct { PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"` SpiloPrivileged bool `json:"spilo_privileged,omitempty"` SpiloAllowPrivilegeEscalation *bool `json:"spilo_allow_privilege_escalation,omitempty"` + ReadOnlyRootFilesystem bool `json:"container_readonly_root_filesystem" default:"true"` SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 00cbbe6ee..1f3b45fae 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -874,8 +874,13 @@ func (c *Cluster) generatePodTemplate( podSpec.PriorityClassName = priorityClassName } + if c.Postgresql.Spec.Monitoring != nil { + addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp") + } + if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars || c.OpConfig.ReadOnlyRootFilesystem { addVarRunVolume(&podSpec) + } if additionalSecretMount != "" { @@ -1443,14 +1448,16 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu additionalVolumes = append(additionalVolumes, tlsVolumes...) } // if monitoring is enabled, add a empty volume - if c.Postgresql.Spec.Monitoring != nil { - additionalVolumes = append(additionalVolumes, cpov1.AdditionalVolume{ - Name: "exporter-tmp", - VolumeSource: v1.VolumeSource{ - EmptyDir: &v1.EmptyDirVolumeSource{}, - }, - }) - } + // if c.Postgresql.Spec.Monitoring != nil { + // additionalVolumes = append(additionalVolumes, cpov1.AdditionalVolume{ + // Name: "exporter-tmp", + // MountPath: "/tmp", + // VolumeSource: v1.VolumeSource{ + // EmptyDir: &v1.EmptyDirVolumeSource{}, + // }, + // TargetContainers: []string{"postgres-exporter"}, + // }) + // } repo_host_mode := false // Add this envVar so that it is not added to the pgbackrest initcontainer if specHasPgbackrestPVCRepo(spec) { @@ -1734,7 +1741,7 @@ func (c *Cluster) generatePgbackrestRestoreContainer(spec *cpov1.PostgresSpec, r SecurityContext: &v1.SecurityContext{ AllowPrivilegeEscalation: privilegeEscalationMode, Privileged: &privilegedMode, - ReadOnlyRootFilesystem: util.False(), + ReadOnlyRootFilesystem: util.True(), Capabilities: additionalPodCapabilities, }, } @@ -2198,6 +2205,27 @@ func addShmVolume(podSpec *v1.PodSpec) { podSpec.Volumes = volumes } +func addEmptyDirVolume(podSpec *v1.PodSpec, volumeName string, containerName string, path string) { + vol := v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + } + podSpec.Volumes = append(podSpec.Volumes, vol) + + mount := v1.VolumeMount{ + Name: vol.Name, + MountPath: path, + } + + for i := range podSpec.Containers { + if podSpec.Containers[i].Name == containerName { + podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, mount) + } + } +} + func addVarRunVolume(podSpec *v1.PodSpec) { volumes := append(podSpec.Volumes, v1.Volume{ Name: "postgresql-run", diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index 139f5c808..76d8608b8 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -98,12 +98,6 @@ func (c *Cluster) createStatefulSet() (*appsv1.StatefulSet, error) { ReadOnlyRootFilesystem: util.True(), Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), }, - VolumeMounts: []v1.VolumeMount{ - { - Name: "exporter-tmp", - MountPath: "/tmp", - }, - }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 296bf0aa0..33f3dd734 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -520,12 +520,6 @@ func (c *Cluster) syncStatefulSet() error { ReadOnlyRootFilesystem: util.True(), Capabilities: generateCapabilities(c.OpConfig.AdditionalPodCapabilities), }, - VolumeMounts: []v1.VolumeMount{ - { - Name: "exporter-tmp", - MountPath: "/tmp", - }, - }, } c.Spec.Sidecars = append(c.Spec.Sidecars, *sidecar) //populate the sidecar spec so that the sidecar is automatically created } From 03305a2de5a1ba58c3429c5934afc543f8604054 Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 09:55:17 +0200 Subject: [PATCH 05/12] modifications for ReadOnlyRootFilesystem --- .../v1/operator_configuration_type.go | 2 +- .../cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go | 5 +++++ pkg/cluster/k8sres.go | 7 ++++++- pkg/controller/operator_config.go | 1 + pkg/util/config/config.go | 2 +- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go index e1c3efeb0..ceaf58707 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go @@ -62,7 +62,7 @@ type KubernetesMetaConfiguration struct { PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"` SpiloPrivileged bool `json:"spilo_privileged,omitempty"` SpiloAllowPrivilegeEscalation *bool `json:"spilo_allow_privilege_escalation,omitempty"` - ReadOnlyRootFilesystem bool `json:"container_readonly_root_filesystem" default:"true"` + ReadOnlyRootFilesystem *bool `json:"container_readonly_root_filesystem" default:"true"` SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go index c058de772..7cb588816 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/zz_generated.deepcopy.go @@ -236,6 +236,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura *out = new(bool) **out = **in } + if in.ReadOnlyRootFilesystem != nil { + in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + *out = new(bool) + **out = **in + } if in.SpiloRunAsUser != nil { in, out := &in.SpiloRunAsUser, &out.SpiloRunAsUser *out = new(int64) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 1f3b45fae..408dc9310 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -878,7 +878,12 @@ func (c *Cluster) generatePodTemplate( addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp") } - if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars || c.OpConfig.ReadOnlyRootFilesystem { + var readOnly bool + if c.OpConfig.ReadOnlyRootFilesystem != nil { + readOnly = *c.OpConfig.ReadOnlyRootFilesystem + } + + if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars || readOnly { addVarRunVolume(&podSpec) } diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 8517f6a3f..da410935c 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -75,6 +75,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *cpov1.OperatorConfigura result.PodTerminateGracePeriod = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod), "5m") result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged result.SpiloAllowPrivilegeEscalation = util.CoalesceBool(fromCRD.Kubernetes.SpiloAllowPrivilegeEscalation, util.True()) + result.ReadOnlyRootFilesystem = util.CoalesceBool(fromCRD.Kubernetes.ReadOnlyRootFilesystem, util.True()) result.SpiloRunAsUser = fromCRD.Kubernetes.SpiloRunAsUser result.SpiloRunAsGroup = fromCRD.Kubernetes.SpiloRunAsGroup result.SpiloFSGroup = fromCRD.Kubernetes.SpiloFSGroup diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index a48e0804a..5381feb7d 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -38,7 +38,7 @@ type Resources struct { SpiloPrivileged bool `name:"spilo_privileged" default:"false"` SpiloAllowPrivilegeEscalation *bool `name:"spilo_allow_privilege_escalation" default:"true"` AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` - ReadOnlyRootFilesystem bool `name:"container_readonly_root_filesystem" default:"true"` + ReadOnlyRootFilesystem *bool `name:"container_readonly_root_filesystem" default:"true"` ClusterLabels map[string]string `name:"cluster_labels" default:"application:cpo"` InheritedLabels []string `name:"inherited_labels" default:""` InheritedAnnotations []string `name:"inherited_annotations" default:""` From b8f098e34d85cd401681d1d8c52f01dac7f0554b Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 10:38:02 +0200 Subject: [PATCH 06/12] change pgbackrest jobs to readOnlyRootFilesystem:true --- pkg/cluster/k8sres.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 408dc9310..b2d3af9f4 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -3327,6 +3327,9 @@ func (c *Cluster) generatePgbackrestJob(backup *cpov1.Pgbackrest, repo *cpov1.Re nil, ) + // Patch securityContext - readOnlyRootFilesystem + pgbackrestContainer.SecurityContext.ReadOnlyRootFilesystem = util.True() + podAffinityTerm := v1.PodAffinityTerm{ LabelSelector: c.roleLabelsSelector(Master), TopologyKey: "kubernetes.io/hostname", From 93d3fae61eb1a500b16536050cf0d03897100e34 Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 10:51:09 +0200 Subject: [PATCH 07/12] use emptyDir for /run - preparation for ReadOnlyRootFilesystem:true --- pkg/cluster/k8sres.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index b2d3af9f4..5d5f23875 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -878,14 +878,12 @@ func (c *Cluster) generatePodTemplate( addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp") } - var readOnly bool if c.OpConfig.ReadOnlyRootFilesystem != nil { - readOnly = *c.OpConfig.ReadOnlyRootFilesystem + addRunVolume(&podSpec, "postgres-run", "postgres", "/run") } - if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars || readOnly { + if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars { addVarRunVolume(&podSpec) - } if additionalSecretMount != "" { @@ -2231,6 +2229,27 @@ func addEmptyDirVolume(podSpec *v1.PodSpec, volumeName string, containerName str } } +func addRunVolume(podSpec *v1.PodSpec, volumeName string, containerName string, path string) { + vol := v1.Volume{ + Name: volumeName, + VolumeSource: v1.VolumeSource{ + EmptyDir: &v1.EmptyDirVolumeSource{}, + }, + } + podSpec.Volumes = append(podSpec.Volumes, vol) + + mount := v1.VolumeMount{ + Name: vol.Name, + MountPath: path, + } + + for i := range podSpec.Containers { + if podSpec.Containers[i].Name == containerName { + podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, mount) + } + } +} + func addVarRunVolume(podSpec *v1.PodSpec) { volumes := append(podSpec.Volumes, v1.Volume{ Name: "postgresql-run", From 63e1183177bdef692584884507a84bda1af4aafe Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 13:30:20 +0200 Subject: [PATCH 08/12] add tmp for postgres-container if ReadOnlyRootFilesystem=true --- pkg/cluster/k8sres.go | 1 + pkg/controller/controller.go | 79 +++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 5d5f23875..5cda325cf 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -880,6 +880,7 @@ func (c *Cluster) generatePodTemplate( if c.OpConfig.ReadOnlyRootFilesystem != nil { addRunVolume(&podSpec, "postgres-run", "postgres", "/run") + addEmptyDirVolume(&podSpec, "postgres-tmp", "postgres", "/tmp") } if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars { diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 8e55b4982..075671051 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/sirupsen/logrus" cpov1 "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/apis/cpo.opensource.cybertec.at/v1" "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/apiserver" "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/cluster" @@ -22,6 +21,8 @@ import ( "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/util/constants" "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/util/k8sutil" "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/util/ringlog" + "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,6 +73,59 @@ type Controller struct { PodServiceAccount *v1.ServiceAccount PodServiceAccountRoleBinding *rbacv1.RoleBinding + + curReconcile sync.Map +} + +// preparation for better reconcile +type runningReconcile struct { + cancel context.CancelFunc +} + +func (c *Controller) statefulSetDelete(obj interface{}) { + sts, ok := obj.(*appsv1.StatefulSet) + if !ok { + c.logger.Warn("statefulSetDelete: received object is not a StatefulSet") + return + } + + clusterName, exists := sts.Labels[c.opConfig.ClusterNameLabel] + if !exists { + c.logger.Warnf("StatefulSet %s/%s has no cluster name label %q", sts.Namespace, sts.Name, c.opConfig.ClusterNameLabel) + return + } + + c.logger.Infof("StatefulSet deleted: %s/%s, triggering reconcile", sts.Namespace, sts.Name) + + // Vorherige laufende Reconcile abbrechen + if val, exists := c.curReconcile.Load(clusterName); exists { + c.logger.Infof("Aborting running reconcile for cluster %s", clusterName) + val.(runningReconcile).cancel() + } + + // neuen Reconcile starten + ctx, cancel := context.WithCancel(context.Background()) + c.curReconcile.Store(clusterName, runningReconcile{cancel: cancel}) + + go func() { + defer c.curReconcile.Delete(clusterName) + c.reconcileCluster(ctx, clusterName) + }() +} + +func (c *Controller) reconcileCluster(ctx context.Context, clusterName string) { + c.logger.Infof("Starting reconcile for cluster %s", clusterName) + + // Hier implementierst du das, was normal auch beim CRD-Reconcile passiert + // z.B. Cluster prüfen, StatefulSet erstellen, Pods erstellen etc. + + select { + case <-ctx.Done(): + c.logger.Infof("Reconcile for cluster %s canceled", clusterName) + return + default: + // continue normal reconcile + } } // NewController creates a new controller @@ -398,6 +452,23 @@ func (c *Controller) initSharedInformers() { }) } + // STS + statefulSetLW := &cache.ListWatch{ + ListFunc: c.statefulSetListFunc, + WatchFunc: c.statefulSetWatchFunc, + } + + c.statefulSetInformer = cache.NewSharedIndexInformer( + statefulSetLW, + &appsv1.StatefulSet{}, + constants.QueueResyncPeriodPod, // oder eigenes Intervall + cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, + ) + + c.statefulSetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + DeleteFunc: c.statefulSetDelete, + }) + // Pods podLw := &cache.ListWatch{ ListFunc: c.podListFunc, @@ -452,6 +523,12 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { } wg.Add(5 + util.Bool2Int(c.opConfig.EnablePostgresTeamCRD)) + + wg.Add(1) + go func() { + defer wg.Done() + c.statefulSetInformer.Run(stopCh) + }() go c.runPodInformer(stopCh, wg) go c.runPostgresqlInformer(stopCh, wg) go c.clusterResync(stopCh, wg) From 1e686ebf606f393db99993e2e620763b8391a726 Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 13:38:55 +0200 Subject: [PATCH 09/12] correct wrong changes --- pkg/controller/controller.go | 77 ------------------------------------ 1 file changed, 77 deletions(-) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 075671051..931eacb05 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -22,7 +22,6 @@ import ( "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/util/k8sutil" "github.com/cybertec-postgresql/cybertec-pg-operator/pkg/util/ringlog" "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -73,59 +72,6 @@ type Controller struct { PodServiceAccount *v1.ServiceAccount PodServiceAccountRoleBinding *rbacv1.RoleBinding - - curReconcile sync.Map -} - -// preparation for better reconcile -type runningReconcile struct { - cancel context.CancelFunc -} - -func (c *Controller) statefulSetDelete(obj interface{}) { - sts, ok := obj.(*appsv1.StatefulSet) - if !ok { - c.logger.Warn("statefulSetDelete: received object is not a StatefulSet") - return - } - - clusterName, exists := sts.Labels[c.opConfig.ClusterNameLabel] - if !exists { - c.logger.Warnf("StatefulSet %s/%s has no cluster name label %q", sts.Namespace, sts.Name, c.opConfig.ClusterNameLabel) - return - } - - c.logger.Infof("StatefulSet deleted: %s/%s, triggering reconcile", sts.Namespace, sts.Name) - - // Vorherige laufende Reconcile abbrechen - if val, exists := c.curReconcile.Load(clusterName); exists { - c.logger.Infof("Aborting running reconcile for cluster %s", clusterName) - val.(runningReconcile).cancel() - } - - // neuen Reconcile starten - ctx, cancel := context.WithCancel(context.Background()) - c.curReconcile.Store(clusterName, runningReconcile{cancel: cancel}) - - go func() { - defer c.curReconcile.Delete(clusterName) - c.reconcileCluster(ctx, clusterName) - }() -} - -func (c *Controller) reconcileCluster(ctx context.Context, clusterName string) { - c.logger.Infof("Starting reconcile for cluster %s", clusterName) - - // Hier implementierst du das, was normal auch beim CRD-Reconcile passiert - // z.B. Cluster prüfen, StatefulSet erstellen, Pods erstellen etc. - - select { - case <-ctx.Done(): - c.logger.Infof("Reconcile for cluster %s canceled", clusterName) - return - default: - // continue normal reconcile - } } // NewController creates a new controller @@ -452,23 +398,6 @@ func (c *Controller) initSharedInformers() { }) } - // STS - statefulSetLW := &cache.ListWatch{ - ListFunc: c.statefulSetListFunc, - WatchFunc: c.statefulSetWatchFunc, - } - - c.statefulSetInformer = cache.NewSharedIndexInformer( - statefulSetLW, - &appsv1.StatefulSet{}, - constants.QueueResyncPeriodPod, // oder eigenes Intervall - cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, - ) - - c.statefulSetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - DeleteFunc: c.statefulSetDelete, - }) - // Pods podLw := &cache.ListWatch{ ListFunc: c.podListFunc, @@ -523,12 +452,6 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { } wg.Add(5 + util.Bool2Int(c.opConfig.EnablePostgresTeamCRD)) - - wg.Add(1) - go func() { - defer wg.Done() - c.statefulSetInformer.Run(stopCh) - }() go c.runPodInformer(stopCh, wg) go c.runPostgresqlInformer(stopCh, wg) go c.clusterResync(stopCh, wg) From 6ede0732e0ffcd400251b71df444b178b8e3c089 Mon Sep 17 00:00:00 2001 From: matthias Date: Fri, 5 Sep 2025 15:28:46 +0200 Subject: [PATCH 10/12] add ReadOnlyRootFilesystem for postgres container and add needed env for nss_wrapper - cleanup --- .../v1/operator_configuration_type.go | 2 +- pkg/cluster/k8sres.go | 22 +++++++++++++++++-- pkg/controller/operator_config.go | 2 +- pkg/util/config/config.go | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go index ceaf58707..2cb153458 100644 --- a/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go +++ b/pkg/apis/cpo.opensource.cybertec.at/v1/operator_configuration_type.go @@ -62,7 +62,7 @@ type KubernetesMetaConfiguration struct { PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"` SpiloPrivileged bool `json:"spilo_privileged,omitempty"` SpiloAllowPrivilegeEscalation *bool `json:"spilo_allow_privilege_escalation,omitempty"` - ReadOnlyRootFilesystem *bool `json:"container_readonly_root_filesystem" default:"true"` + ReadOnlyRootFilesystem *bool `json:"container_readonly_root_filesystem" default:"false"` SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 5cda325cf..d78affba7 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -677,6 +677,7 @@ func generateContainer( volumeMounts []v1.VolumeMount, privilegedMode bool, privilegeEscalationMode *bool, + readOnlyRootFilesystem *bool, additionalPodCapabilities *v1.Capabilities, ) *v1.Container { return &v1.Container{ @@ -703,7 +704,7 @@ func generateContainer( SecurityContext: &v1.SecurityContext{ AllowPrivilegeEscalation: privilegeEscalationMode, Privileged: &privilegedMode, - ReadOnlyRootFilesystem: util.False(), + ReadOnlyRootFilesystem: readOnlyRootFilesystem, Capabilities: additionalPodCapabilities, }, } @@ -878,7 +879,7 @@ func (c *Cluster) generatePodTemplate( addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp") } - if c.OpConfig.ReadOnlyRootFilesystem != nil { + if c.OpConfig.ReadOnlyRootFilesystem != nil && *c.OpConfig.ReadOnlyRootFilesystem { addRunVolume(&podSpec, "postgres-run", "postgres", "/run") addEmptyDirVolume(&podSpec, "postgres-tmp", "postgres", "/tmp") } @@ -998,6 +999,19 @@ func (c *Cluster) generateSpiloPodEnvVars( Name: "HUMAN_ROLE", Value: c.OpConfig.PamRoleName, }, + // NSS WRAPPER + { + Name: "LD_PRELOAD", + Value: "/usr/lib64/libnss_wrapper.so", + }, + { + Name: "NSS_WRAPPER_PASSWD", + Value: "/tmp/nss_wrapper/passwd", + }, + { + Name: "NSS_WRAPPER_GROUP", + Value: "/tmp/nss_wrapper/group", + }, } if c.OpConfig.EnableSpiloWalPathCompat { @@ -1484,6 +1498,7 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu volumeMounts, c.OpConfig.Resources.SpiloPrivileged, c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, + c.OpConfig.Resources.ReadOnlyRootFilesystem, generateCapabilities(c.OpConfig.AdditionalPodCapabilities), ) @@ -1806,6 +1821,7 @@ func (c *Cluster) generateRepoHostStatefulSet(spec *cpov1.PostgresSpec) (*appsv1 volumeMounts, c.OpConfig.Resources.SpiloPrivileged, c.OpConfig.Resources.SpiloAllowPrivilegeEscalation, + c.OpConfig.Resources.ReadOnlyRootFilesystem, generateCapabilities(c.OpConfig.AdditionalPodCapabilities), ) @@ -2818,6 +2834,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { []v1.VolumeMount{}, c.OpConfig.SpiloPrivileged, // use same value as for normal DB pods c.OpConfig.SpiloAllowPrivilegeEscalation, + util.False(), nil, ) @@ -3344,6 +3361,7 @@ func (c *Cluster) generatePgbackrestJob(backup *cpov1.Pgbackrest, repo *cpov1.Re []v1.VolumeMount{}, c.OpConfig.SpiloPrivileged, // use same value as for normal DB pods c.OpConfig.SpiloAllowPrivilegeEscalation, + c.OpConfig.Resources.ReadOnlyRootFilesystem, nil, ) diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index da410935c..c49fd97f3 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -75,7 +75,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *cpov1.OperatorConfigura result.PodTerminateGracePeriod = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod), "5m") result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged result.SpiloAllowPrivilegeEscalation = util.CoalesceBool(fromCRD.Kubernetes.SpiloAllowPrivilegeEscalation, util.True()) - result.ReadOnlyRootFilesystem = util.CoalesceBool(fromCRD.Kubernetes.ReadOnlyRootFilesystem, util.True()) + result.ReadOnlyRootFilesystem = util.CoalesceBool(fromCRD.Kubernetes.ReadOnlyRootFilesystem, util.False()) result.SpiloRunAsUser = fromCRD.Kubernetes.SpiloRunAsUser result.SpiloRunAsGroup = fromCRD.Kubernetes.SpiloRunAsGroup result.SpiloFSGroup = fromCRD.Kubernetes.SpiloFSGroup diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 5381feb7d..8a5d3b63a 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -38,7 +38,7 @@ type Resources struct { SpiloPrivileged bool `name:"spilo_privileged" default:"false"` SpiloAllowPrivilegeEscalation *bool `name:"spilo_allow_privilege_escalation" default:"true"` AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` - ReadOnlyRootFilesystem *bool `name:"container_readonly_root_filesystem" default:"true"` + ReadOnlyRootFilesystem *bool `name:"container_readonly_root_filesystem" default:"false"` ClusterLabels map[string]string `name:"cluster_labels" default:"application:cpo"` InheritedLabels []string `name:"inherited_labels" default:""` InheritedAnnotations []string `name:"inherited_annotations" default:""` From a7fee6f86fd510c5b92d4b82aaf281b63828858e Mon Sep 17 00:00:00 2001 From: matthias Date: Thu, 9 Oct 2025 14:05:35 +0200 Subject: [PATCH 11/12] change LivenessProbe to patroni /liveness api-endpoint --- pkg/cluster/k8sres.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index d78affba7..c62613f86 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1323,7 +1323,7 @@ func generatePatroniLivenessProbe() *v1.Probe { FailureThreshold: 6, ProbeHandler: v1.ProbeHandler{ HTTPGet: &v1.HTTPGetAction{ - Path: "/health", + Path: "/liveness", Port: intstr.IntOrString{IntVal: patroni.ApiPort}, Scheme: v1.URISchemeHTTP, }, From 04f85198006c390638376ad0b137b0e0e09fd554 Mon Sep 17 00:00:00 2001 From: matthias Date: Thu, 9 Oct 2025 14:10:24 +0200 Subject: [PATCH 12/12] cleanup old code --- pkg/cluster/k8sres.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index c62613f86..bff42e3de 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1465,17 +1465,7 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu } additionalVolumes = append(additionalVolumes, tlsVolumes...) } - // if monitoring is enabled, add a empty volume - // if c.Postgresql.Spec.Monitoring != nil { - // additionalVolumes = append(additionalVolumes, cpov1.AdditionalVolume{ - // Name: "exporter-tmp", - // MountPath: "/tmp", - // VolumeSource: v1.VolumeSource{ - // EmptyDir: &v1.EmptyDirVolumeSource{}, - // }, - // TargetContainers: []string{"postgres-exporter"}, - // }) - // } + repo_host_mode := false // Add this envVar so that it is not added to the pgbackrest initcontainer if specHasPgbackrestPVCRepo(spec) {