Skip to content

Commit 2b0def5

Browse files
alfredw33FxKu
andauthored
Support for GCS WAL-E backups (zalando#620)
* Support for WAL_GS_BUCKET and GOOGLE_APPLICATION_CREDENTIALS environtment variables * Fixed merge issue but also removed all changes to support macos. * Updated test to new format * Missed macos specific changes * Added documentation and addressed comments * Update docs/administrator.md * Update docs/administrator.md * Update e2e/run.sh Co-authored-by: Felix Kunde <[email protected]>
1 parent 0fa61a6 commit 2b0def5

File tree

12 files changed

+223
-0
lines changed

12 files changed

+223
-0
lines changed

charts/postgres-operator/values-crd.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,18 @@ configAwsOrGcp:
202202
# AWS region used to store ESB volumes
203203
aws_region: eu-central-1
204204

205+
# GCP credentials that will be used by the operator / pods
206+
# gcp_credentials: ""
207+
205208
# AWS IAM role to supply in the iam.amazonaws.com/role annotation of Postgres pods
206209
# kube_iam_role: ""
207210

208211
# S3 bucket to use for shipping postgres daily logs
209212
# log_s3_bucket: ""
210213

214+
# GCS bucket to use for shipping WAL segments with WAL-E
215+
# wal_gs_bucket: ""
216+
211217
# S3 bucket to use for shipping WAL segments with WAL-E
212218
# wal_s3_bucket: ""
213219

charts/postgres-operator/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ configAwsOrGcp:
200200
# S3 bucket to use for shipping WAL segments with WAL-E
201201
# wal_s3_bucket: ""
202202

203+
# GCS bucket to use for shipping WAL segments with WAL-E
204+
# wal_gs_bucket: ""
205+
206+
# GCP credentials for setting the GOOGLE_APPLICATION_CREDNETIALS environment variable
207+
# gcp_credentials: ""
208+
203209
# configure K8s cron job managed by the operator
204210
configLogicalBackup:
205211
# image for pods of the logical backup job (example runs pg_dumpall)

docs/administrator.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,57 @@ A secret can be pre-provisioned in different ways:
518518
* Automatically provisioned via a custom K8s controller like
519519
[kube-aws-iam-controller](https://github.com/mikkeloscar/kube-aws-iam-controller)
520520

521+
## Google Cloud Platform setup
522+
523+
To configure the operator on GCP there are some prerequisites that are needed:
524+
525+
* A service account with the proper IAM setup to access the GCS bucket for the WAL-E logs
526+
* The credentials file for the service account.
527+
528+
The configuration paramaters that we will be using are:
529+
530+
* `additional_secret_mount`
531+
* `additional_secret_mount_path`
532+
* `gcp_credentials`
533+
* `wal_gs_bucket`
534+
535+
### Generate a K8 secret resource
536+
537+
Generate the K8 secret resource that will contain your service account's
538+
credentials. It's highly recommended to use a service account and limit its
539+
scope to just the WAL-E bucket.
540+
541+
```yaml
542+
apiVersion: v1
543+
kind: Secret
544+
metadata:
545+
name: psql-wale-creds
546+
namespace: default
547+
type: Opaque
548+
stringData:
549+
key.json: |-
550+
<GCP .json credentials>
551+
```
552+
553+
### Setup your operator configuration values
554+
555+
With the `psql-wale-creds` resource applied to your cluster, ensure that
556+
the operator's configuration is set up like the following:
557+
558+
```yml
559+
...
560+
aws_or_gcp:
561+
additional_secret_mount: "pgsql-wale-creds"
562+
additional_secret_mount_path: "/var/secrets/google" # or where ever you want to mount the file
563+
# aws_region: eu-central-1
564+
# kube_iam_role: ""
565+
# log_s3_bucket: ""
566+
# wal_s3_bucket: ""
567+
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
568+
gcp_credentials: "/var/secrets/google/key.json" # combination of the mount path & key in the K8 resource. (i.e. key.json)
569+
...
570+
```
571+
521572
## Sidecars for Postgres clusters
522573

523574
A list of sidecars is added to each cluster created by the operator. The default

docs/reference/operator_parameters.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,20 @@ yet officially supported.
451451
present and accessible by Postgres pods. At the moment, supported services by
452452
Spilo are S3 and GCS. The default is empty.
453453

454+
* **wal_gs_bucket**
455+
GCS bucket to use for shipping WAL segments with WAL-E. A bucket has to be
456+
present and accessible by Postgres pods. Note, only the name of the bucket is
457+
required. At the moment, supported services by Spilo are S3 and GCS.
458+
The default is empty.
459+
460+
* **gcp_credentials**
461+
Used to set the GOOGLE_APPLICATION_CREDENTIALS environment variable for the pods.
462+
This is used in with conjunction with the `additional_secret_mount` and
463+
`additional_secret_mount_path` to properly set the credentials for the spilo
464+
containers. This will allow users to use specific
465+
[service accounts](https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform).
466+
The default is empty
467+
454468
* **log_s3_bucket**
455469
S3 bucket to use for shipping Postgres daily logs. Works only with S3 on AWS.
456470
The bucket has to be present and accessible by Postgres pods. The default is

manifests/configmap.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ data:
4545
# enable_team_superuser: "false"
4646
enable_teams_api: "false"
4747
# etcd_host: ""
48+
# gcp_credentials: ""
4849
# kubernetes_use_configmaps: "false"
4950
# infrastructure_roles_secret_name: postgresql-infrastructure-roles
5051
# inherited_labels: application,environment
@@ -100,6 +101,7 @@ data:
100101
# team_api_role_configuration: "log_statement:all"
101102
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
102103
# toleration: ""
104+
# wal_gs_bucket: ""
103105
# wal_s3_bucket: ""
104106
watched_namespace: "*" # listen to all namespaces
105107
workers: "4"

manifests/operatorconfiguration.crd.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,14 @@ spec:
216216
type: string
217217
aws_region:
218218
type: string
219+
gcp_credentials:
220+
type: string
219221
kube_iam_role:
220222
type: string
221223
log_s3_bucket:
222224
type: string
225+
wal_gs_bucket:
226+
type: string
223227
wal_s3_bucket:
224228
type: string
225229
logical_backup:

manifests/postgresql-operator-default-configuration.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ configuration:
8888
# additional_secret_mount: "some-secret-name"
8989
# additional_secret_mount_path: "/some/dir"
9090
aws_region: eu-central-1
91+
# gcp_credentials: ""
9192
# kube_iam_role: ""
9293
# log_s3_bucket: ""
94+
# wal_gs_bucket: ""
9395
# wal_s3_bucket: ""
9496
logical_backup:
9597
logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:master-58"

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ type LoadBalancerConfiguration struct {
111111
type AWSGCPConfiguration struct {
112112
WALES3Bucket string `json:"wal_s3_bucket,omitempty"`
113113
AWSRegion string `json:"aws_region,omitempty"`
114+
WALGSBucket string `json:"wal_gs_bucket,omitempty"`
115+
GCPCredentials string `json:"gcp_credentials,omitempty"`
114116
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
115117
KubeIAMRole string `json:"kube_iam_role,omitempty"`
116118
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`

pkg/cluster/k8sres.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,12 +714,23 @@ func (c *Cluster) generateSpiloPodEnvVars(uid types.UID, spiloConfiguration stri
714714
if spiloConfiguration != "" {
715715
envVars = append(envVars, v1.EnvVar{Name: "SPILO_CONFIGURATION", Value: spiloConfiguration})
716716
}
717+
717718
if c.OpConfig.WALES3Bucket != "" {
718719
envVars = append(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket})
719720
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
720721
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
721722
}
722723

724+
if c.OpConfig.WALGSBucket != "" {
725+
envVars = append(envVars, v1.EnvVar{Name: "WAL_GS_BUCKET", Value: c.OpConfig.WALGSBucket})
726+
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
727+
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
728+
}
729+
730+
if c.OpConfig.GCPCredentials != "" {
731+
envVars = append(envVars, v1.EnvVar{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: c.OpConfig.GCPCredentials})
732+
}
733+
723734
if c.OpConfig.LogS3Bucket != "" {
724735
envVars = append(envVars, v1.EnvVar{Name: "LOG_S3_BUCKET", Value: c.OpConfig.LogS3Bucket})
725736
envVars = append(envVars, v1.EnvVar{Name: "LOG_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})

pkg/cluster/k8sres_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ import (
2020
policyv1beta1 "k8s.io/api/policy/v1beta1"
2121
"k8s.io/apimachinery/pkg/api/resource"
2222
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/types"
2324
"k8s.io/apimachinery/pkg/util/intstr"
2425
)
2526

27+
// For testing purposes
28+
type ExpectedValue struct {
29+
envIndex int
30+
envVarConstant string
31+
envVarValue string
32+
}
33+
2634
func toIntStr(val int) *intstr.IntOrString {
2735
b := intstr.FromInt(val)
2836
return &b
@@ -93,6 +101,119 @@ func TestGenerateSpiloJSONConfiguration(t *testing.T) {
93101
}
94102
}
95103

104+
func TestGenerateSpiloPodEnvVars(t *testing.T) {
105+
var cluster = New(
106+
Config{
107+
OpConfig: config.Config{
108+
WALGSBucket: "wale-gs-bucket",
109+
ProtectedRoles: []string{"admin"},
110+
Auth: config.Auth{
111+
SuperUsername: superUserName,
112+
ReplicationUsername: replicationUserName,
113+
},
114+
},
115+
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
116+
117+
expectedValuesGSBucket := []ExpectedValue{
118+
ExpectedValue{
119+
envIndex: 14,
120+
envVarConstant: "WAL_GS_BUCKET",
121+
envVarValue: "wale-gs-bucket",
122+
},
123+
ExpectedValue{
124+
envIndex: 15,
125+
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
126+
envVarValue: "/SomeUUID",
127+
},
128+
ExpectedValue{
129+
envIndex: 16,
130+
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
131+
envVarValue: "",
132+
},
133+
}
134+
135+
expectedValuesGCPCreds := []ExpectedValue{
136+
ExpectedValue{
137+
envIndex: 14,
138+
envVarConstant: "WAL_GS_BUCKET",
139+
envVarValue: "wale-gs-bucket",
140+
},
141+
ExpectedValue{
142+
envIndex: 15,
143+
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
144+
envVarValue: "/SomeUUID",
145+
},
146+
ExpectedValue{
147+
envIndex: 16,
148+
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
149+
envVarValue: "",
150+
},
151+
ExpectedValue{
152+
envIndex: 17,
153+
envVarConstant: "GOOGLE_APPLICATION_CREDENTIALS",
154+
envVarValue: "some_path_to_credentials",
155+
},
156+
}
157+
158+
testName := "TestGenerateSpiloPodEnvVars"
159+
tests := []struct {
160+
subTest string
161+
opConfig config.Config
162+
uid types.UID
163+
spiloConfig string
164+
cloneDescription *acidv1.CloneDescription
165+
standbyDescription *acidv1.StandbyDescription
166+
customEnvList []v1.EnvVar
167+
expectedValues []ExpectedValue
168+
}{
169+
{
170+
subTest: "Will set WAL_GS_BUCKET env",
171+
opConfig: config.Config{
172+
WALGSBucket: "wale-gs-bucket",
173+
},
174+
uid: "SomeUUID",
175+
spiloConfig: "someConfig",
176+
cloneDescription: &acidv1.CloneDescription{},
177+
standbyDescription: &acidv1.StandbyDescription{},
178+
customEnvList: []v1.EnvVar{},
179+
expectedValues: expectedValuesGSBucket,
180+
},
181+
{
182+
subTest: "Will set GOOGLE_APPLICATION_CREDENTIALS env",
183+
opConfig: config.Config{
184+
WALGSBucket: "wale-gs-bucket",
185+
GCPCredentials: "some_path_to_credentials",
186+
},
187+
uid: "SomeUUID",
188+
spiloConfig: "someConfig",
189+
cloneDescription: &acidv1.CloneDescription{},
190+
standbyDescription: &acidv1.StandbyDescription{},
191+
customEnvList: []v1.EnvVar{},
192+
expectedValues: expectedValuesGCPCreds,
193+
},
194+
}
195+
196+
for _, tt := range tests {
197+
cluster.OpConfig = tt.opConfig
198+
199+
actualEnvs := cluster.generateSpiloPodEnvVars(tt.uid, tt.spiloConfig, tt.cloneDescription, tt.standbyDescription, tt.customEnvList)
200+
201+
for _, ev := range tt.expectedValues {
202+
env := actualEnvs[ev.envIndex]
203+
204+
if env.Name != ev.envVarConstant {
205+
t.Errorf("%s %s: Expected env name %s, have %s instead",
206+
testName, tt.subTest, ev.envVarConstant, env.Name)
207+
}
208+
209+
if env.Value != ev.envVarValue {
210+
t.Errorf("%s %s: Expected env value %s, have %s instead",
211+
testName, tt.subTest, ev.envVarValue, env.Value)
212+
}
213+
}
214+
}
215+
}
216+
96217
func TestCreateLoadBalancerLogic(t *testing.T) {
97218
var cluster = New(
98219
Config{

0 commit comments

Comments
 (0)