Merge 3e93fae514 into bbc3eab7e0
This commit is contained in:
commit
7963b4b671
1
Makefile
1
Makefile
|
|
@ -76,6 +76,7 @@ $(GENERATED_CRDS): $(GENERATED)
|
||||||
@$(SED) -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
|
@$(SED) -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
|
||||||
@hack/adjust_postgresql_crd.sh
|
@hack/adjust_postgresql_crd.sh
|
||||||
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
|
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
|
||||||
|
@hack/adjust_operatorconfiguration_crd.sh
|
||||||
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
|
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
|
||||||
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
||||||
@cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml
|
@cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml
|
||||||
|
|
|
||||||
|
|
@ -114,9 +114,15 @@ spec:
|
||||||
format: int32
|
format: int32
|
||||||
minimum: 1
|
minimum: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
connection_pooler_pod_security_context:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
connection_pooler_schema:
|
connection_pooler_schema:
|
||||||
default: pooler
|
default: pooler
|
||||||
type: string
|
type: string
|
||||||
|
connection_pooler_security_context:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
connection_pooler_user:
|
connection_pooler_user:
|
||||||
default: pooler
|
default: pooler
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -664,8 +670,7 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
maintenance_windows:
|
maintenance_windows:
|
||||||
items:
|
items:
|
||||||
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))\
|
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))\ *$'
|
||||||
*$
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
major_version_upgrade:
|
major_version_upgrade:
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,24 @@ configConnectionPooler:
|
||||||
connection_pooler_default_memory_request: 100Mi
|
connection_pooler_default_memory_request: 100Mi
|
||||||
connection_pooler_default_cpu_limit: "1"
|
connection_pooler_default_cpu_limit: "1"
|
||||||
connection_pooler_default_memory_limit: 100Mi
|
connection_pooler_default_memory_limit: 100Mi
|
||||||
|
# Override the pooler pod- and container-level securityContext. Useful for hardened
|
||||||
|
# images whose pgbouncer user/UID differs from the operator default (100/101) and which
|
||||||
|
# own /etc/pgbouncer, or to satisfy restricted Pod Security Standards. When unset the
|
||||||
|
# operator keeps its defaults (pod RunAsUser/RunAsGroup 100/101, container
|
||||||
|
# allowPrivilegeEscalation=false).
|
||||||
|
# connection_pooler_pod_security_context:
|
||||||
|
# runAsUser: 100
|
||||||
|
# runAsGroup: 101
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# fsGroup: 101
|
||||||
|
# seccompProfile:
|
||||||
|
# type: RuntimeDefault
|
||||||
|
# connection_pooler_security_context:
|
||||||
|
# allowPrivilegeEscalation: false
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
|
||||||
configPatroni:
|
configPatroni:
|
||||||
# enable Patroni DCS failsafe_mode feature
|
# enable Patroni DCS failsafe_mode feature
|
||||||
|
|
|
||||||
|
|
@ -1107,3 +1107,48 @@ operator being able to provide some reasonable defaults.
|
||||||
**connection_pooler_default_cpu_limit**
|
**connection_pooler_default_cpu_limit**
|
||||||
**connection_pooler_default_memory_limit**
|
**connection_pooler_default_memory_limit**
|
||||||
Default resource configuration for connection pooler deployment.
|
Default resource configuration for connection pooler deployment.
|
||||||
|
|
||||||
|
* **connection_pooler_pod_security_context**
|
||||||
|
Pod-level `securityContext` applied to the connection pooler deployment. The
|
||||||
|
value is a standard Kubernetes
|
||||||
|
[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#podsecuritycontext-v1-core)
|
||||||
|
object. Only fields you set are overridden; unset fields keep the operator
|
||||||
|
defaults (`runAsUser: 100`, `runAsGroup: 101`, and the cluster's `fsGroup`).
|
||||||
|
Settable **only** via the `OperatorConfiguration` CRD (not the ConfigMap).
|
||||||
|
Defaults to not set.
|
||||||
|
|
||||||
|
* **connection_pooler_security_context**
|
||||||
|
Container-level `securityContext` applied to the pgbouncer container. The
|
||||||
|
value is a standard Kubernetes
|
||||||
|
[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core)
|
||||||
|
object. Only fields you set are overridden; when `allowPrivilegeEscalation`
|
||||||
|
is unset it defaults to `false`. Settable **only** via the
|
||||||
|
`OperatorConfiguration` CRD (not the ConfigMap). Defaults to not set.
|
||||||
|
|
||||||
|
These two parameters are useful when running a hardened pooler image whose
|
||||||
|
pgbouncer user/UID differs from the operator default (100/101), or to satisfy
|
||||||
|
restricted [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/).
|
||||||
|
Because they are object-valued they cannot be expressed in the flat operator
|
||||||
|
ConfigMap; configure them through the `OperatorConfiguration` CRD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "acid.zalan.do/v1"
|
||||||
|
kind: OperatorConfiguration
|
||||||
|
metadata:
|
||||||
|
name: postgresql-operator-default-configuration
|
||||||
|
configuration:
|
||||||
|
connection_pooler:
|
||||||
|
connection_pooler_pod_security_context:
|
||||||
|
runAsUser: 100
|
||||||
|
runAsGroup: 101
|
||||||
|
runAsNonRoot: true
|
||||||
|
fsGroup: 101
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
connection_pooler_security_context:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Hack to adjust the generated operatorconfiguration CRD YAML file and add
|
||||||
|
# missing field settings which can not be expressed via kubebuilder markers.
|
||||||
|
#
|
||||||
|
# Injections:
|
||||||
|
#
|
||||||
|
# * type: string and pattern for the maintenance_windows items.
|
||||||
|
# The MaintenanceWindow type marshals to/from a string (see marshal.go) but
|
||||||
|
# the field is declared `+kubebuilder:validation:Schemaless` /
|
||||||
|
# `+kubebuilder:validation:Type=array`, so controller-gen emits a bare
|
||||||
|
# `type: array` without the required `items` schema. A structural CRD must
|
||||||
|
# specify `items` for every array, so the generated CRD is rejected with:
|
||||||
|
# spec.validation.openAPIV3Schema.properties[configuration]
|
||||||
|
# .properties[maintenance_windows].items: Required value: must be specified
|
||||||
|
|
||||||
|
file="${1:-"manifests/operatorconfiguration.crd.yaml"}"
|
||||||
|
|
||||||
|
sed -i '/^[[:space:]]*maintenance_windows:$/{
|
||||||
|
# Capture the indentation
|
||||||
|
s/^\([[:space:]]*\)maintenance_windows:$/\1maintenance_windows:\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"
|
||||||
|
|
@ -664,8 +664,7 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
maintenance_windows:
|
maintenance_windows:
|
||||||
items:
|
items:
|
||||||
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))\
|
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))\ *$'
|
||||||
*$
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
major_version_upgrade:
|
major_version_upgrade:
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,14 @@ type ConnectionPoolerConfiguration struct {
|
||||||
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
|
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
|
||||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||||
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
|
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
|
||||||
|
// +kubebuilder:validation:XPreserveUnknownFields
|
||||||
|
// +kubebuilder:validation:Type=object
|
||||||
|
// +kubebuilder:validation:Schemaless
|
||||||
|
PodSecurityContext *v1.PodSecurityContext `json:"connection_pooler_pod_security_context,omitempty"`
|
||||||
|
// +kubebuilder:validation:XPreserveUnknownFields
|
||||||
|
// +kubebuilder:validation:Type=object
|
||||||
|
// +kubebuilder:validation:Schemaless
|
||||||
|
SecurityContext *v1.SecurityContext `json:"connection_pooler_security_context,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperatorLogicalBackupConfiguration defines configuration for logical backup
|
// OperatorLogicalBackupConfiguration defines configuration for logical backup
|
||||||
|
|
|
||||||
|
|
@ -664,8 +664,7 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
maintenance_windows:
|
maintenance_windows:
|
||||||
items:
|
items:
|
||||||
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))\
|
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))\ *$'
|
||||||
*$
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
major_version_upgrade:
|
major_version_upgrade:
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,16 @@ func (in *ConnectionPoolerConfiguration) DeepCopyInto(out *ConnectionPoolerConfi
|
||||||
*out = new(int32)
|
*out = new(int32)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.PodSecurityContext != nil {
|
||||||
|
in, out := &in.PodSecurityContext, &out.PodSecurityContext
|
||||||
|
*out = new(corev1.PodSecurityContext)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.SecurityContext != nil {
|
||||||
|
in, out := &in.SecurityContext, &out.SecurityContext
|
||||||
|
*out = new(corev1.SecurityContext)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,51 @@ import (
|
||||||
var poolerRunAsUser = int64(100)
|
var poolerRunAsUser = int64(100)
|
||||||
var poolerRunAsGroup = int64(101)
|
var poolerRunAsGroup = int64(101)
|
||||||
|
|
||||||
|
// generateConnectionPoolerPodSecurityContext returns the pod-level securityContext for the
|
||||||
|
// connection pooler. When the operator configuration provides one it is used as the base
|
||||||
|
// (deep-copied); otherwise an empty context is used. The historical defaults (RunAsUser/RunAsGroup
|
||||||
|
// 100/101) and the spilo FSGroup are applied only for fields the configuration leaves unset, so
|
||||||
|
// behavior is unchanged when no override is configured.
|
||||||
|
func (c *Cluster) generateConnectionPoolerPodSecurityContext(spec *acidv1.PostgresSpec) *v1.PodSecurityContext {
|
||||||
|
var securityContext v1.PodSecurityContext
|
||||||
|
if c.OpConfig.ConnectionPooler.PodSecurityContext != nil {
|
||||||
|
securityContext = *c.OpConfig.ConnectionPooler.PodSecurityContext.DeepCopy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if securityContext.RunAsUser == nil {
|
||||||
|
securityContext.RunAsUser = &poolerRunAsUser
|
||||||
|
}
|
||||||
|
if securityContext.RunAsGroup == nil {
|
||||||
|
securityContext.RunAsGroup = &poolerRunAsGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
if securityContext.FSGroup == nil {
|
||||||
|
effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
|
||||||
|
if spec.SpiloFSGroup != nil {
|
||||||
|
effectiveFSGroup = spec.SpiloFSGroup
|
||||||
|
}
|
||||||
|
if effectiveFSGroup != nil {
|
||||||
|
securityContext.FSGroup = effectiveFSGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &securityContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateConnectionPoolerContainerSecurityContext returns the container-level securityContext for
|
||||||
|
// the pooler. A configured context is used as the base (deep-copied); AllowPrivilegeEscalation
|
||||||
|
// defaults to false when left unset, preserving prior behavior.
|
||||||
|
func (c *Cluster) generateConnectionPoolerContainerSecurityContext() *v1.SecurityContext {
|
||||||
|
var securityContext v1.SecurityContext
|
||||||
|
if c.OpConfig.ConnectionPooler.SecurityContext != nil {
|
||||||
|
securityContext = *c.OpConfig.ConnectionPooler.SecurityContext.DeepCopy()
|
||||||
|
}
|
||||||
|
if securityContext.AllowPrivilegeEscalation == nil {
|
||||||
|
securityContext.AllowPrivilegeEscalation = util.False()
|
||||||
|
}
|
||||||
|
return &securityContext
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectionPoolerObjects K8s objects that are belong to connection pooler
|
// ConnectionPoolerObjects K8s objects that are belong to connection pooler
|
||||||
type ConnectionPoolerObjects struct {
|
type ConnectionPoolerObjects struct {
|
||||||
AuthSecret *v1.Secret
|
AuthSecret *v1.Secret
|
||||||
|
|
@ -383,9 +428,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SecurityContext: &v1.SecurityContext{
|
SecurityContext: c.generateConnectionPoolerContainerSecurityContext(),
|
||||||
AllowPrivilegeEscalation: util.False(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var poolerVolumes []v1.Volume
|
var poolerVolumes []v1.Volume
|
||||||
|
|
@ -444,19 +487,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
poolerContainer.Env = envVars
|
poolerContainer.Env = envVars
|
||||||
poolerContainer.VolumeMounts = volumeMounts
|
poolerContainer.VolumeMounts = volumeMounts
|
||||||
tolerationsSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
|
tolerationsSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
|
||||||
securityContext := v1.PodSecurityContext{}
|
securityContext := c.generateConnectionPoolerPodSecurityContext(spec)
|
||||||
|
|
||||||
// determine the User, Group and FSGroup for the pooler pod
|
|
||||||
securityContext.RunAsUser = &poolerRunAsUser
|
|
||||||
securityContext.RunAsGroup = &poolerRunAsGroup
|
|
||||||
|
|
||||||
effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
|
|
||||||
if spec.SpiloFSGroup != nil {
|
|
||||||
effectiveFSGroup = spec.SpiloFSGroup
|
|
||||||
}
|
|
||||||
if effectiveFSGroup != nil {
|
|
||||||
securityContext.FSGroup = effectiveFSGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
podTemplate := &v1.PodTemplateSpec{
|
podTemplate := &v1.PodTemplateSpec{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
|
@ -469,7 +500,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
Containers: []v1.Container{poolerContainer},
|
Containers: []v1.Container{poolerContainer},
|
||||||
Tolerations: tolerationsSpec,
|
Tolerations: tolerationsSpec,
|
||||||
Volumes: poolerVolumes,
|
Volumes: poolerVolumes,
|
||||||
SecurityContext: &securityContext,
|
SecurityContext: securityContext,
|
||||||
ServiceAccountName: c.OpConfig.PodServiceAccountName,
|
ServiceAccountName: c.OpConfig.PodServiceAccountName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1064,6 +1064,137 @@ func TestPoolerTLS(t *testing.T) {
|
||||||
assert.Contains(t, poolerContainer.Env, v1.EnvVar{Name: "CONNECTION_POOLER_CLIENT_CA_FILE", Value: "/tls/ca.crt"})
|
assert.Contains(t, poolerContainer.Env, v1.EnvVar{Name: "CONNECTION_POOLER_CLIENT_CA_FILE", Value: "/tls/ca.crt"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// poolerSecurityContextOpConfig builds a minimal OpConfig usable for exercising the
|
||||||
|
// connection pooler securityContext behavior. Callers tweak the returned config's
|
||||||
|
// ConnectionPooler / Resources before passing it to syncedPoolerDeployment.
|
||||||
|
func poolerSecurityContextOpConfig() config.Config {
|
||||||
|
return config.Config{
|
||||||
|
PodManagementPolicy: "ordered_ready",
|
||||||
|
ProtectedRoles: []string{"admin"},
|
||||||
|
Auth: config.Auth{
|
||||||
|
SuperUsername: superUserName,
|
||||||
|
ReplicationUsername: replicationUserName,
|
||||||
|
},
|
||||||
|
Resources: config.Resources{
|
||||||
|
ClusterLabels: map[string]string{"application": "spilo"},
|
||||||
|
ClusterNameLabel: "cluster-name",
|
||||||
|
DefaultCPURequest: "300m",
|
||||||
|
DefaultCPULimit: "300m",
|
||||||
|
DefaultMemoryRequest: "300Mi",
|
||||||
|
DefaultMemoryLimit: "300Mi",
|
||||||
|
PodRoleLabel: "spilo-role",
|
||||||
|
},
|
||||||
|
ConnectionPooler: config.ConnectionPooler{
|
||||||
|
ConnectionPoolerDefaultCPURequest: "100m",
|
||||||
|
ConnectionPoolerDefaultCPULimit: "100m",
|
||||||
|
ConnectionPoolerDefaultMemoryRequest: "100Mi",
|
||||||
|
ConnectionPoolerDefaultMemoryLimit: "100Mi",
|
||||||
|
},
|
||||||
|
PodServiceAccountName: "postgres-pod",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncedPoolerDeployment provisions a pooler with the given OpConfig and returns the
|
||||||
|
// resulting master pooler Deployment.
|
||||||
|
func syncedPoolerDeployment(t *testing.T, opConfig config.Config) *appsv1.Deployment {
|
||||||
|
t.Helper()
|
||||||
|
client, _ := newFakeK8sPoolerTestClient()
|
||||||
|
clusterName := "acid-test-cluster"
|
||||||
|
namespace := "default"
|
||||||
|
|
||||||
|
pg := acidv1.Postgresql{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace},
|
||||||
|
Spec: acidv1.PostgresSpec{
|
||||||
|
TeamID: "myapp", NumberOfInstances: 1,
|
||||||
|
EnableConnectionPooler: util.True(),
|
||||||
|
Resources: &acidv1.Resources{
|
||||||
|
ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")},
|
||||||
|
ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")},
|
||||||
|
},
|
||||||
|
Volume: acidv1.Volume{Size: "1G"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := New(Config{OpConfig: opConfig}, client, pg, logger, eventRecorder)
|
||||||
|
|
||||||
|
_, err := cluster.createStatefulSet()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
cluster.ConnectionPooler = map[PostgresRole]*ConnectionPoolerObjects{
|
||||||
|
Master: {
|
||||||
|
Name: cluster.connectionPoolerName(Master),
|
||||||
|
ClusterName: clusterName,
|
||||||
|
Namespace: namespace,
|
||||||
|
Role: Master,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cluster.syncConnectionPoolerWorker(nil, &pg, Master)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
deploy, err := client.Deployments(namespace).Get(context.TODO(), cluster.connectionPoolerName(Master), metav1.GetOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
return deploy
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionPoolerDefaultSecurityContext(t *testing.T) {
|
||||||
|
deploy := syncedPoolerDeployment(t, poolerSecurityContextOpConfig())
|
||||||
|
|
||||||
|
podSc := deploy.Spec.Template.Spec.SecurityContext
|
||||||
|
assert.NotNil(t, podSc, "pod securityContext should be set")
|
||||||
|
assert.Equal(t, int64(100), *podSc.RunAsUser, "defaults to RunAsUser 100 for backward compatibility")
|
||||||
|
assert.Equal(t, int64(101), *podSc.RunAsGroup, "defaults to RunAsGroup 101 for backward compatibility")
|
||||||
|
|
||||||
|
containerSc := deploy.Spec.Template.Spec.Containers[constants.ConnectionPoolerContainer].SecurityContext
|
||||||
|
assert.NotNil(t, containerSc, "container securityContext should be set")
|
||||||
|
assert.Equal(t, false, *containerSc.AllowPrivilegeEscalation, "defaults AllowPrivilegeEscalation to false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionPoolerPodSecurityContextOverride(t *testing.T) {
|
||||||
|
runAsUser := int64(1000001)
|
||||||
|
runAsGroup := int64(1000001)
|
||||||
|
spiloFSGroup := int64(103)
|
||||||
|
|
||||||
|
opConfig := poolerSecurityContextOpConfig()
|
||||||
|
// FSGroup comes from the spilo config and must still apply when the override leaves it nil.
|
||||||
|
opConfig.Resources.SpiloFSGroup = &spiloFSGroup
|
||||||
|
opConfig.ConnectionPooler.PodSecurityContext = &v1.PodSecurityContext{
|
||||||
|
RunAsUser: &runAsUser,
|
||||||
|
RunAsGroup: &runAsGroup,
|
||||||
|
RunAsNonRoot: util.True(),
|
||||||
|
SeccompProfile: &v1.SeccompProfile{
|
||||||
|
Type: v1.SeccompProfileTypeRuntimeDefault,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy := syncedPoolerDeployment(t, opConfig)
|
||||||
|
podSc := deploy.Spec.Template.Spec.SecurityContext
|
||||||
|
|
||||||
|
assert.Equal(t, runAsUser, *podSc.RunAsUser, "honors configured RunAsUser")
|
||||||
|
assert.Equal(t, runAsGroup, *podSc.RunAsGroup, "honors configured RunAsGroup")
|
||||||
|
assert.Equal(t, true, *podSc.RunAsNonRoot, "honors configured RunAsNonRoot")
|
||||||
|
assert.Equal(t, v1.SeccompProfileTypeRuntimeDefault, podSc.SeccompProfile.Type, "honors configured SeccompProfile")
|
||||||
|
assert.Equal(t, spiloFSGroup, *podSc.FSGroup, "still applies spilo FSGroup when override leaves it nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionPoolerContainerSecurityContextOverride(t *testing.T) {
|
||||||
|
opConfig := poolerSecurityContextOpConfig()
|
||||||
|
opConfig.ConnectionPooler.SecurityContext = &v1.SecurityContext{
|
||||||
|
AllowPrivilegeEscalation: util.False(),
|
||||||
|
ReadOnlyRootFilesystem: util.True(),
|
||||||
|
Capabilities: &v1.Capabilities{
|
||||||
|
Drop: []v1.Capability{"ALL"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy := syncedPoolerDeployment(t, opConfig)
|
||||||
|
containerSc := deploy.Spec.Template.Spec.Containers[constants.ConnectionPoolerContainer].SecurityContext
|
||||||
|
|
||||||
|
assert.Equal(t, true, *containerSc.ReadOnlyRootFilesystem, "honors configured ReadOnlyRootFilesystem")
|
||||||
|
assert.Equal(t, false, *containerSc.AllowPrivilegeEscalation, "keeps AllowPrivilegeEscalation false")
|
||||||
|
assert.Equal(t, []v1.Capability{"ALL"}, containerSc.Capabilities.Drop, "honors dropped capabilities")
|
||||||
|
}
|
||||||
|
|
||||||
func TestConnectionPoolerServiceSpec(t *testing.T) {
|
func TestConnectionPoolerServiceSpec(t *testing.T) {
|
||||||
testName := "Test connection pooler service spec generation"
|
testName := "Test connection pooler service spec generation"
|
||||||
var cluster = New(
|
var cluster = New(
|
||||||
|
|
|
||||||
|
|
@ -285,5 +285,10 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
fromCRD.ConnectionPooler.MaxDBConnections,
|
fromCRD.ConnectionPooler.MaxDBConnections,
|
||||||
k8sutil.Int32ToPointer(constants.ConnectionPoolerMaxDBConnections))
|
k8sutil.Int32ToPointer(constants.ConnectionPoolerMaxDBConnections))
|
||||||
|
|
||||||
|
// Security contexts are nil unless explicitly configured; the cluster package falls back
|
||||||
|
// to the historical pooler defaults when they are not set.
|
||||||
|
result.ConnectionPooler.PodSecurityContext = fromCRD.ConnectionPooler.PodSecurityContext
|
||||||
|
result.ConnectionPooler.SecurityContext = fromCRD.ConnectionPooler.SecurityContext
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,13 @@ type ConnectionPooler struct {
|
||||||
ConnectionPoolerDefaultMemoryRequest string `name:"connection_pooler_default_memory_request"`
|
ConnectionPoolerDefaultMemoryRequest string `name:"connection_pooler_default_memory_request"`
|
||||||
ConnectionPoolerDefaultCPULimit string `name:"connection_pooler_default_cpu_limit"`
|
ConnectionPoolerDefaultCPULimit string `name:"connection_pooler_default_cpu_limit"`
|
||||||
ConnectionPoolerDefaultMemoryLimit string `name:"connection_pooler_default_memory_limit"`
|
ConnectionPoolerDefaultMemoryLimit string `name:"connection_pooler_default_memory_limit"`
|
||||||
|
// PodSecurityContext and SecurityContext let operators override the pooler
|
||||||
|
// pod- and container-level security contexts. They are populated only from the
|
||||||
|
// OperatorConfiguration CRD (name:"-" excludes them from the ConfigMap decoder,
|
||||||
|
// mirroring LivenessProbe). When nil, the operator falls back to the historical
|
||||||
|
// defaults (pod RunAsUser/RunAsGroup 100/101, container AllowPrivilegeEscalation=false).
|
||||||
|
PodSecurityContext *v1.PodSecurityContext `name:"-"`
|
||||||
|
SecurityContext *v1.SecurityContext `name:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config describes operator config
|
// Config describes operator config
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue