Skip to content

Commit 9bcb25a

Browse files
dmvolodFxKu
andauthored
Ability to set pod environment variables on cluster resource (zalando#1794)
* Ability to set pod environment variables on cluster resource Co-authored-by: Felix Kunde <[email protected]>
1 parent 43e1805 commit 9bcb25a

File tree

9 files changed

+204
-66
lines changed

9 files changed

+204
-66
lines changed

charts/postgres-operator/crds/postgresqls.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ spec:
196196
type: boolean
197197
enableShmVolume:
198198
type: boolean
199+
env:
200+
type: array
201+
nullable: true
202+
items:
203+
type: object
204+
x-kubernetes-preserve-unknown-fields: true
199205
init_containers:
200206
type: array
201207
description: deprecated

docs/administrator.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,29 @@ data:
706706
The key-value pairs of the Secret are all accessible as environment variables
707707
to the Postgres StatefulSet/pods.
708708

709+
### For individual cluster
710+
711+
It is possible to define environment variables directly in the Postgres cluster
712+
manifest to configure it individually. The variables must be listed under the
713+
`env` section in the same way you would do for [containers](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/).
714+
Global parameters served from a custom config map or secret will be overridden.
715+
716+
```yaml
717+
apiVersion: "acid.zalan.do/v1"
718+
kind: postgresql
719+
metadata:
720+
name: acid-test-cluster
721+
spec:
722+
env:
723+
- name: wal_s3_bucket
724+
value: my-custom-bucket
725+
- name: minio_secret_key
726+
valueFrom:
727+
secretKeyRef:
728+
name: my-custom-secret
729+
key: minio_secret_key
730+
```
731+
709732
## Limiting the number of min and max instances in clusters
710733

711734
As a preventive measure, one can restrict the minimum and the maximum number of

manifests/complete-postgres-manifest.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ spec:
4949
shared_buffers: "32MB"
5050
max_connections: "10"
5151
log_statement: "all"
52+
# env:
53+
# - name: wal_s3_bucket
54+
# value: my-custom-bucket
55+
5256
volume:
5357
size: 1Gi
5458
# storageClass: my-sc
@@ -120,7 +124,7 @@ spec:
120124
# database: foo
121125
# plugin: pgoutput
122126
ttl: 30
123-
loop_wait: &loop_wait 10
127+
loop_wait: 10
124128
retry_timeout: 10
125129
synchronous_mode: false
126130
synchronous_mode_strict: false

manifests/postgresql.crd.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ spec:
194194
type: boolean
195195
enableShmVolume:
196196
type: boolean
197+
env:
198+
type: array
199+
nullable: true
200+
items:
201+
type: object
202+
x-kubernetes-preserve-unknown-fields: true
197203
init_containers:
198204
type: array
199205
description: deprecated

pkg/apis/acid.zalan.do/v1/crds.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,16 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
311311
"enableShmVolume": {
312312
Type: "boolean",
313313
},
314+
"env": {
315+
Type: "array",
316+
Nullable: true,
317+
Items: &apiextv1.JSONSchemaPropsOrArray{
318+
Schema: &apiextv1.JSONSchemaProps{
319+
Type: "object",
320+
XPreserveUnknownFields: util.True(),
321+
},
322+
},
323+
},
314324
"init_containers": {
315325
Type: "array",
316326
Description: "deprecated",

pkg/apis/acid.zalan.do/v1/postgresql_type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type PostgresSpec struct {
8080
TLS *TLSDescription `json:"tls,omitempty"`
8181
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
8282
Streams []Stream `json:"streams,omitempty"`
83+
Env []v1.EnvVar `json:"env,omitempty"`
8384

8485
// deprecated json tags
8586
InitContainersOld []v1.Container `json:"init_containers,omitempty"`

pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cluster/k8sres.go

Lines changed: 45 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -649,8 +649,7 @@ func patchSidecarContainers(in []v1.Container, volumeMounts []v1.VolumeMount, su
649649
},
650650
},
651651
}
652-
mergedEnv := append(env, container.Env...)
653-
container.Env = deduplicateEnvVars(mergedEnv, container.Name, logger)
652+
container.Env = appendEnvVars(env, container.Env...)
654653
result = append(result, container)
655654
}
656655

@@ -769,6 +768,7 @@ func (c *Cluster) generateSpiloPodEnvVars(
769768
cloneDescription *acidv1.CloneDescription,
770769
standbyDescription *acidv1.StandbyDescription,
771770
customPodEnvVarsList []v1.EnvVar) []v1.EnvVar {
771+
772772
envVars := []v1.EnvVar{
773773
{
774774
Name: "SCOPE",
@@ -843,6 +843,11 @@ func (c *Cluster) generateSpiloPodEnvVars(
843843
Value: c.OpConfig.PamRoleName,
844844
},
845845
}
846+
847+
if c.OpConfig.EnableSpiloWalPathCompat {
848+
envVars = append(envVars, v1.EnvVar{Name: "ENABLE_WAL_PATH_COMPAT", Value: "true"})
849+
}
850+
846851
if c.OpConfig.EnablePgVersionEnvVar {
847852
envVars = append(envVars, v1.EnvVar{Name: "PGVERSION", Value: c.GetDesiredMajorVersion()})
848853
}
@@ -874,73 +879,67 @@ func (c *Cluster) generateSpiloPodEnvVars(
874879
envVars = append(envVars, c.generateStandbyEnvironment(standbyDescription)...)
875880
}
876881

882+
if len(c.Spec.Env) > 0 {
883+
envVars = appendEnvVars(envVars, c.Spec.Env...)
884+
}
885+
877886
// add vars taken from pod_environment_configmap and pod_environment_secret first
878887
// (to allow them to override the globals set in the operator config)
879888
if len(customPodEnvVarsList) > 0 {
880-
envVars = append(envVars, customPodEnvVarsList...)
889+
envVars = appendEnvVars(envVars, customPodEnvVarsList...)
881890
}
882891

883892
if c.OpConfig.WALES3Bucket != "" {
884-
envVars = append(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket})
885-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
886-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
893+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket})
894+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
895+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
887896
}
888897

889898
if c.OpConfig.WALGSBucket != "" {
890-
envVars = append(envVars, v1.EnvVar{Name: "WAL_GS_BUCKET", Value: c.OpConfig.WALGSBucket})
891-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
892-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
899+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_GS_BUCKET", Value: c.OpConfig.WALGSBucket})
900+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
901+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
893902
}
894903

895904
if c.OpConfig.WALAZStorageAccount != "" {
896-
envVars = append(envVars, v1.EnvVar{Name: "AZURE_STORAGE_ACCOUNT", Value: c.OpConfig.WALAZStorageAccount})
897-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
898-
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
905+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "AZURE_STORAGE_ACCOUNT", Value: c.OpConfig.WALAZStorageAccount})
906+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
907+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
899908
}
900909

901910
if c.OpConfig.GCPCredentials != "" {
902-
envVars = append(envVars, v1.EnvVar{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: c.OpConfig.GCPCredentials})
911+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: c.OpConfig.GCPCredentials})
903912
}
904913

905914
if c.OpConfig.LogS3Bucket != "" {
906-
envVars = append(envVars, v1.EnvVar{Name: "LOG_S3_BUCKET", Value: c.OpConfig.LogS3Bucket})
907-
envVars = append(envVars, v1.EnvVar{Name: "LOG_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
908-
envVars = append(envVars, v1.EnvVar{Name: "LOG_BUCKET_SCOPE_PREFIX", Value: ""})
915+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "LOG_S3_BUCKET", Value: c.OpConfig.LogS3Bucket})
916+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "LOG_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
917+
envVars = appendEnvVars(envVars, v1.EnvVar{Name: "LOG_BUCKET_SCOPE_PREFIX", Value: ""})
909918
}
910919

911920
return envVars
912921
}
913922

914-
// deduplicateEnvVars makes sure there are no duplicate in the target envVar array. While Kubernetes already
915-
// deduplicates variables defined in a container, it leaves the last definition in the list and this behavior is not
916-
// well-documented, which means that the behavior can be reversed at some point (it may also start producing an error).
917-
// Therefore, the merge is done by the operator, the entries that are ahead in the passed list take priority over those
918-
// that are behind, and only the name is considered in order to eliminate duplicates.
919-
func deduplicateEnvVars(input []v1.EnvVar, containerName string, logger *logrus.Entry) []v1.EnvVar {
920-
result := make([]v1.EnvVar, 0)
921-
names := make(map[string]int)
922-
923-
for i, va := range input {
924-
if names[va.Name] == 0 {
925-
names[va.Name]++
926-
result = append(result, input[i])
927-
} else if names[va.Name] == 1 {
928-
names[va.Name]++
929-
930-
// Some variables (those to configure the WAL_ and LOG_ shipping) may be overwritten, only log as info
931-
if strings.HasPrefix(va.Name, "WAL_") || strings.HasPrefix(va.Name, "LOG_") {
932-
logger.Infof("global variable %q has been overwritten by configmap/secret for container %q",
933-
va.Name, containerName)
934-
} else {
935-
logger.Warningf("variable %q is defined in %q more than once, the subsequent definitions are ignored",
936-
va.Name, containerName)
937-
}
923+
func appendEnvVars(envs []v1.EnvVar, appEnv ...v1.EnvVar) []v1.EnvVar {
924+
jenvs := envs
925+
for _, env := range appEnv {
926+
if !isEnvVarPresent(jenvs, env.Name) {
927+
jenvs = append(jenvs, env)
938928
}
939929
}
940-
return result
930+
return jenvs
931+
}
932+
933+
func isEnvVarPresent(envs []v1.EnvVar, key string) bool {
934+
for _, env := range envs {
935+
if env.Name == key {
936+
return true
937+
}
938+
}
939+
return false
941940
}
942941

943-
// Return list of variables the pod recieved from the configured ConfigMap
942+
// Return list of variables the pod received from the configured ConfigMap
944943
func (c *Cluster) getPodEnvironmentConfigMapVariables() ([]v1.EnvVar, error) {
945944
configMapPodEnvVarsList := make([]v1.EnvVar, 0)
946945

@@ -1105,16 +1104,6 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
11051104
initContainers = spec.InitContainers
11061105
}
11071106

1108-
spiloCompathWalPathList := make([]v1.EnvVar, 0)
1109-
if c.OpConfig.EnableSpiloWalPathCompat {
1110-
spiloCompathWalPathList = append(spiloCompathWalPathList,
1111-
v1.EnvVar{
1112-
Name: "ENABLE_WAL_PATH_COMPAT",
1113-
Value: "true",
1114-
},
1115-
)
1116-
}
1117-
11181107
// fetch env vars from custom ConfigMap
11191108
configMapEnvVarsList, err := c.getPodEnvironmentConfigMapVariables()
11201109
if err != nil {
@@ -1128,8 +1117,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
11281117
}
11291118

11301119
// concat all custom pod env vars and sort them
1131-
customPodEnvVarsList := append(spiloCompathWalPathList, configMapEnvVarsList...)
1132-
customPodEnvVarsList = append(customPodEnvVarsList, secretEnvVarsList...)
1120+
customPodEnvVarsList := append(configMapEnvVarsList, secretEnvVarsList...)
11331121
sort.Slice(customPodEnvVarsList,
11341122
func(i, j int) bool { return customPodEnvVarsList[i].Name < customPodEnvVarsList[j].Name })
11351123

@@ -1210,7 +1198,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
12101198
// use the same filenames as Secret resources by default
12111199
certFile := ensurePath(spec.TLS.CertificateFile, mountPath, "tls.crt")
12121200
privateKeyFile := ensurePath(spec.TLS.PrivateKeyFile, mountPath, "tls.key")
1213-
spiloEnvVars = append(
1201+
spiloEnvVars = appendEnvVars(
12141202
spiloEnvVars,
12151203
v1.EnvVar{Name: "SSL_CERTIFICATE_FILE", Value: certFile},
12161204
v1.EnvVar{Name: "SSL_PRIVATE_KEY_FILE", Value: privateKeyFile},
@@ -1224,7 +1212,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
12241212
}
12251213

12261214
caFile := ensurePath(spec.TLS.CAFile, mountPathCA, "")
1227-
spiloEnvVars = append(
1215+
spiloEnvVars = appendEnvVars(
12281216
spiloEnvVars,
12291217
v1.EnvVar{Name: "SSL_CA_FILE", Value: caFile},
12301218
)
@@ -1249,7 +1237,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
12491237
spiloContainer := generateContainer(constants.PostgresContainerName,
12501238
&effectiveDockerImage,
12511239
resourceRequirements,
1252-
deduplicateEnvVars(spiloEnvVars, constants.PostgresContainerName, c.logger),
1240+
spiloEnvVars,
12531241
volumeMounts,
12541242
c.OpConfig.Resources.SpiloPrivileged,
12551243
c.OpConfig.Resources.SpiloAllowPrivilegeEscalation,

0 commit comments

Comments
 (0)