diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index fe74f8e49..f007240d7 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -31,6 +31,7 @@ configKubernetes: # node_readiness_label: "" # oauth_token_secret_name: postgresql-operator # pod_environment_configmap: "" + # spilo_fsgroup: "103" pod_management_policy: "ordered_ready" pdb_name_format: "postgres-{cluster}-pdb" pod_role_label: spilo-role diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 2edc9ac73..2768f02aa 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -58,6 +58,13 @@ These parameters are grouped directly under the `spec` key in the manifest. custom docker image that overrides the **docker_image** operator parameter. It should be a [Spilo](https://github.com/zalando/spilo) image. Optional. +* **spiloFSGroup** + the Persistent Volumes for the spilo pods in the StatefulSet will be owned + and writable by the group ID specified. This will override the **spilo_fsgroup** + operator parameter. This is required to run Spilo as a non-root process, but + requires a custom spilo image. Note the FSGroup of a Pod cannot be changed + without recreating a new Pod. + * **enableMasterLoadBalancer** boolean flag to override the operator defaults (set by the `enable_master_load_balancer` parameter) to define whether to enable the load diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 5e139e327..9c14b8669 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -228,6 +228,11 @@ configuration they are grouped under the `kubernetes` key. that should be assigned to the Postgres pods. The priority class itself must be defined in advance. Default is empty (use the default priority class). +* **spilo_fsgroup** + the Persistent Volumes for the spilo pods in the StatefulSet will be owned and writable by the group ID specified. + This is required to run Spilo as a non-root process, but requires a custom spilo image. Note the FSGroup of a Pod + cannot be changed without recreating a new Pod. + * **spilo_privileged** whether the Spilo container should run in privileged mode. Privileged mode is used for AWS volume resizing and not required if you don't need that diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index 421f5f613..793ca7026 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -37,6 +37,7 @@ spec: limits: cpu: 300m memory: 300Mi + # spiloFSGroup: 103 patroni: initdb: encoding: "UTF8" diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index c87d22dfd..956ab7f0f 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -24,6 +24,7 @@ configuration: cluster_domain: cluster.local oauth_token_secret_name: postgresql-operator pod_role_label: spilo-role + # spilo_fsgroup: 103 spilo_privileged: false cluster_labels: application: spilo diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index 8f34a9786..136be7843 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -46,6 +46,7 @@ type KubernetesMetaConfiguration struct { PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"` PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"` SpiloPrivileged bool `json:"spilo_privileged,omitemty"` + SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` WatchedNamespace string `json:"watched_namespace,omitempty"` PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"` SecretNameTemplate config.StringTemplate `json:"secret_name_template,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 87a079da9..1a191786e 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -30,6 +30,8 @@ type PostgresSpec struct { TeamID string `json:"teamId"` DockerImage string `json:"dockerImage,omitempty"` + SpiloFSGroup *int64 `json:"spiloFSGroup,omitempty"` + // vars that enable load balancers are pointers because it is important to know if any of them is omitted from the Postgres manifest // in that case the var evaluates to nil and the value is taken from the operator config EnableMasterLoadBalancer *bool `json:"enableMasterLoadBalancer,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 7a27bb794..282fb311f 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -66,6 +66,11 @@ func (in *CloneDescription) DeepCopy() *CloneDescription { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfiguration) { *out = *in + if in.SpiloFSGroup != nil { + in, out := &in.SpiloFSGroup, &out.SpiloFSGroup + *out = new(int64) + **out = **in + } out.OAuthTokenSecretName = in.OAuthTokenSecretName out.InfrastructureRolesSecretName = in.InfrastructureRolesSecretName if in.ClusterLabels != nil { @@ -402,6 +407,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { out.Volume = in.Volume in.Patroni.DeepCopyInto(&out.Patroni) out.Resources = in.Resources + if in.SpiloFSGroup != nil { + in, out := &in.SpiloFSGroup, &out.SpiloFSGroup + *out = new(int64) + **out = **in + } if in.EnableMasterLoadBalancer != nil { in, out := &in.EnableMasterLoadBalancer, &out.EnableMasterLoadBalancer *out = new(bool) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 3aa5c3f07..aefa0404a 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -432,6 +432,7 @@ func generatePodTemplate( initContainers []v1.Container, sidecarContainers []v1.Container, tolerationsSpec *[]v1.Toleration, + spiloFSGroup *int64, nodeAffinity *v1.Affinity, terminateGracePeriod int64, podServiceAccountName string, @@ -445,6 +446,11 @@ func generatePodTemplate( terminateGracePeriodSeconds := terminateGracePeriod containers := []v1.Container{*spiloContainer} containers = append(containers, sidecarContainers...) + securityContext := v1.PodSecurityContext{} + + if spiloFSGroup != nil { + securityContext.FSGroup = spiloFSGroup + } podSpec := v1.PodSpec{ ServiceAccountName: podServiceAccountName, @@ -452,6 +458,7 @@ func generatePodTemplate( Containers: containers, InitContainers: initContainers, Tolerations: *tolerationsSpec, + SecurityContext: &securityContext, } if shmVolume { @@ -831,6 +838,12 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*v1beta1.State tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration) effectivePodPriorityClassName := util.Coalesce(spec.PodPriorityClassName, c.OpConfig.PodPriorityClassName) + // determine the FSGroup for the spilo pod + effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup + if spec.SpiloFSGroup != nil { + effectiveFSGroup = spec.SpiloFSGroup + } + // generate pod template for the statefulset, based on the spilo container and sidecars if podTemplate, err = generatePodTemplate( c.Namespace, @@ -839,6 +852,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*v1beta1.State spec.InitContainers, sidecarContainers, &tolerationSpec, + effectiveFSGroup, nodeAffinity(c.OpConfig.NodeReadinessLabel), int64(c.OpConfig.PodTerminateGracePeriod.Seconds()), c.OpConfig.PodServiceAccountName, @@ -1340,6 +1354,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) { []v1.Container{}, []v1.Container{}, &[]v1.Toleration{}, + nil, nodeAffinity(c.OpConfig.NodeReadinessLabel), int64(c.OpConfig.PodTerminateGracePeriod.Seconds()), c.OpConfig.PodServiceAccountName, diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 80c9b94cb..66d8bddd6 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -42,6 +42,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap result.PodTerminateGracePeriod = time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod) result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged + result.SpiloFSGroup = fromCRD.Kubernetes.SpiloFSGroup result.ClusterDomain = fromCRD.Kubernetes.ClusterDomain result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index b059e1d38..82249eb46 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -25,6 +25,7 @@ type Resources struct { PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` + SpiloFSGroup *int64 `name:"spilo_fsgroup"` PodPriorityClassName string `name:"pod_priority_class_name"` ClusterDomain string `name:"cluster_domain" default:"cluster.local"` SpiloPrivileged bool `name:"spilo_privileged" default:"false"`