From 045132513933fe0409d8c8c097d08c118fc8a4ed Mon Sep 17 00:00:00 2001 From: inovindasari Date: Mon, 18 Aug 2025 15:50:11 +0200 Subject: [PATCH] Ensure compatibility with Spilo after WAL-E removal (support old and new versions) --- docs/administrator.md | 20 ++++++------- .../v1/operator_configuration_type.go | 2 +- pkg/cluster/k8sres.go | 25 +++++++++------- pkg/cluster/k8sres_test.go | 30 +++++++++---------- pkg/cluster/sync.go | 4 +-- pkg/cluster/sync_test.go | 14 ++++----- pkg/controller/operator_config.go | 2 +- pkg/util/config/config.go | 2 +- 8 files changed, 51 insertions(+), 48 deletions(-) diff --git a/docs/administrator.md b/docs/administrator.md index f394b70ab..d5f65c2f4 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -1000,8 +1000,8 @@ restoring WAL files. You can find the log files to the respective commands under `$HOME/pgdata/pgroot/pg_log/postgres-?.log`. ```bash -archive_command: `envdir "{WALE_ENV_DIR}" {WALE_BINARY} wal-push "%p"` -restore_command: `envdir "{{WALE_ENV_DIR}}" /scripts/restore_command.sh "%f" "%p"` +archive_command: `envdir "{WALG_ENV_DIR}" {WALG_BINARY} wal-push "%p"` +restore_command: `envdir "{{WALG_ENV_DIR}}" /scripts/restore_command.sh "%f" "%p"` ``` You can produce a basebackup manually with the following command and check @@ -1080,8 +1080,8 @@ variables: ```bash AWS_ENDPOINT='https://s3.eu-central-1.amazonaws.com:443' -WALE_S3_ENDPOINT='https+path://s3.eu-central-1.amazonaws.com:443' -WALE_S3_PREFIX=$WAL_S3_BUCKET/spilo/{WAL_BUCKET_SCOPE_PREFIX}{SCOPE}{WAL_BUCKET_SCOPE_SUFFIX}/wal/{PGVERSION} +WALG_S3_ENDPOINT='https+path://s3.eu-central-1.amazonaws.com:443' +WALG_S3_PREFIX=$WAL_S3_BUCKET/spilo/{WAL_BUCKET_SCOPE_PREFIX}{SCOPE}{WAL_BUCKET_SCOPE_SUFFIX}/wal/{PGVERSION} ``` The operator sets the prefix to an empty string so that spilo will generate it @@ -1092,11 +1092,11 @@ the [pod_environment_configmap](#custom-pod-environment-variables) you have to set `WAL_BUCKET_SCOPE_PREFIX = ""`, too. Otherwise Spilo will not find the physical backups on restore (next chapter). -When the `AWS_REGION` is set, `AWS_ENDPOINT` and `WALE_S3_ENDPOINT` are -generated automatically. `WALG_S3_PREFIX` is identical to `WALE_S3_PREFIX`. +When the `AWS_REGION` is set, `AWS_ENDPOINT` and `WALG_S3_ENDPOINT` are +generated automatically. `WALG_S3_PREFIX` is identical to `WALG_S3_PREFIX`. `SCOPE` is the Postgres cluster name. -:warning: If both `AWS_REGION` and `AWS_ENDPOINT` or `WALE_S3_ENDPOINT` are +:warning: If both `AWS_REGION` and `AWS_ENDPOINT` or `WALG_S3_ENDPOINT` are defined backups with WAL-E will fail. You can fix it by switching to WAL-G with `USE_WALG_BACKUP: "true"`. @@ -1195,7 +1195,7 @@ scope to just the WAL-E bucket. apiVersion: v1 kind: Secret metadata: - name: psql-wale-creds + name: psql-wal-creds namespace: default type: Opaque stringData: @@ -1203,13 +1203,13 @@ stringData: ``` -2. Setup your operator configuration values. With the `psql-wale-creds` +2. Setup your operator configuration values. With the `psql-wal-creds` resource applied to your cluster, ensure that the operator's configuration is set up like the following: ```yml ... aws_or_gcp: - additional_secret_mount: "psql-wale-creds" + additional_secret_mount: "psql-wal-creds" additional_secret_mount_path: "/var/secrets/google" # or where ever you want to mount the file # aws_region: eu-central-1 # kube_iam_role: "" 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 cd11b9173..1ac0b3880 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -152,7 +152,7 @@ type LoadBalancerConfiguration struct { // AWSGCPConfiguration defines the configuration for AWS // TODO complete Google Cloud Platform (GCP) configuration type AWSGCPConfiguration struct { - WALES3Bucket string `json:"wal_s3_bucket,omitempty"` + WALS3Bucket string `json:"wal_s3_bucket,omitempty"` AWSRegion string `json:"aws_region,omitempty"` WALGSBucket string `json:"wal_gs_bucket,omitempty"` GCPCredentials string `json:"gcp_credentials,omitempty"` diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index fedd6a917..249ee9c9f 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1052,8 +1052,8 @@ func (c *Cluster) generateSpiloPodEnvVars( // global variables derived from operator configuration opConfigEnvVars := make([]v1.EnvVar, 0) - if c.OpConfig.WALES3Bucket != "" { - opConfigEnvVars = append(opConfigEnvVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket}) + if c.OpConfig.WALS3Bucket != "" { + opConfigEnvVars = append(opConfigEnvVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALS3Bucket}) opConfigEnvVars = append(opConfigEnvVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))}) opConfigEnvVars = append(opConfigEnvVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""}) } @@ -2118,9 +2118,9 @@ func (c *Cluster) generateCloneEnvironment(description *acidv1.CloneDescription) if description.S3WalPath == "" { c.logger.Info("no S3 WAL path defined - taking value from global config", description.S3WalPath) - if c.OpConfig.WALES3Bucket != "" { - c.logger.Debugf("found WALES3Bucket %s - will set CLONE_WAL_S3_BUCKET", c.OpConfig.WALES3Bucket) - result = append(result, v1.EnvVar{Name: "CLONE_WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket}) + if c.OpConfig.WALS3Bucket != "" { + c.logger.Debugf("found WALS3Bucket %s - will set CLONE_WAL_S3_BUCKET", c.OpConfig.WALS3Bucket) + result = append(result, v1.EnvVar{Name: "CLONE_WAL_S3_BUCKET", Value: c.OpConfig.WALS3Bucket}) } else if c.OpConfig.WALGSBucket != "" { c.logger.Debugf("found WALGSBucket %s - will set CLONE_WAL_GS_BUCKET", c.OpConfig.WALGSBucket) result = append(result, v1.EnvVar{Name: "CLONE_WAL_GS_BUCKET", Value: c.OpConfig.WALGSBucket}) @@ -2139,19 +2139,21 @@ func (c *Cluster) generateCloneEnvironment(description *acidv1.CloneDescription) } else { c.logger.Debugf("use S3WalPath %s from the manifest", description.S3WalPath) + // add if result = append(result, v1.EnvVar{ - Name: "CLONE_WALE_S3_PREFIX", + Name: "CLONE_WALG_S3_PREFIX", Value: description.S3WalPath, }) } - result = append(result, v1.EnvVar{Name: "CLONE_METHOD", Value: "CLONE_WITH_WALE"}) + // if else wal-g + result = append(result, v1.EnvVar{Name: "CLONE_METHOD", Value: "CLONE_WITH_WALG"}) result = append(result, v1.EnvVar{Name: "CLONE_TARGET_TIME", Value: description.EndTimestamp}) result = append(result, v1.EnvVar{Name: "CLONE_WAL_BUCKET_SCOPE_PREFIX", Value: ""}) if description.S3Endpoint != "" { result = append(result, v1.EnvVar{Name: "CLONE_AWS_ENDPOINT", Value: description.S3Endpoint}) - result = append(result, v1.EnvVar{Name: "CLONE_WALE_S3_ENDPOINT", Value: description.S3Endpoint}) + result = append(result, v1.EnvVar{Name: "CLONE_WALG_S3_ENDPOINT", Value: description.S3Endpoint}) } if description.S3AccessKeyId != "" { @@ -2195,12 +2197,12 @@ func (c *Cluster) generateStandbyEnvironment(description *acidv1.StandbyDescript c.logger.Info("standby cluster streaming from WAL location") if description.S3WalPath != "" { result = append(result, v1.EnvVar{ - Name: "STANDBY_WALE_S3_PREFIX", + Name: "STANDBY_WALG_S3_PREFIX", Value: description.S3WalPath, }) } else if description.GSWalPath != "" { result = append(result, v1.EnvVar{ - Name: "STANDBY_WALE_GS_PREFIX", + Name: "STANDBY_WALG_GS_PREFIX", Value: description.GSWalPath, }) } else { @@ -2208,7 +2210,8 @@ func (c *Cluster) generateStandbyEnvironment(description *acidv1.StandbyDescript return result } - result = append(result, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) + // if use wal-g + result = append(result, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALG"}) result = append(result, v1.EnvVar{Name: "STANDBY_WAL_BUCKET_SCOPE_PREFIX", Value: ""}) } diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 137c24081..c7a4df603 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -665,7 +665,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { expectedCloneEnvSpec := []ExpectedValue{ { envIndex: 16, - envVarConstant: "CLONE_WALE_S3_PREFIX", + envVarConstant: "CLONE_WALG_S3_PREFIX", envVarValue: "s3://another-bucket", }, { @@ -687,7 +687,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { }, { envIndex: 17, - envVarConstant: "CLONE_WALE_S3_PREFIX", + envVarConstant: "CLONE_WALG_S3_PREFIX", envVarValue: "s3://another-bucket", }, { @@ -730,7 +730,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { expectedStandbyEnvSecret := []ExpectedValue{ { envIndex: 15, - envVarConstant: "STANDBY_WALE_GS_PREFIX", + envVarConstant: "STANDBY_WALG_GS_PREFIX", envVarValue: "gs://some/path/", }, { @@ -767,7 +767,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { { subTest: "will set WAL_S3_BUCKET env", opConfig: config.Config{ - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{}, standbyDescription: &acidv1.StandbyDescription{}, @@ -815,7 +815,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{}, standbyDescription: &acidv1.StandbyDescription{}, @@ -903,7 +903,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{ ClusterName: "test-cluster", @@ -923,7 +923,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{ ClusterName: "test-cluster", @@ -953,7 +953,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{ ClusterName: "test-cluster", @@ -971,7 +971,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { ResourceCheckInterval: time.Duration(testResourceCheckInterval), ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{ ClusterName: "test-cluster", @@ -989,7 +989,7 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { ResourceCheckInterval: time.Duration(testResourceCheckInterval), ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), }, - WALES3Bucket: "global-s3-bucket", + WALS3Bucket: "global-s3-bucket", }, cloneDescription: &acidv1.CloneDescription{}, standbyDescription: &acidv1.StandbyDescription{ @@ -1177,7 +1177,7 @@ func TestCloneEnv(t *testing.T) { EndTimestamp: "somewhen", }, env: v1.EnvVar{ - Name: "CLONE_WALE_S3_PREFIX", + Name: "CLONE_WALG_S3_PREFIX", Value: "s3://some/path/", }, envPos: 1, @@ -1191,7 +1191,7 @@ func TestCloneEnv(t *testing.T) { }, env: v1.EnvVar{ Name: "CLONE_WAL_S3_BUCKET", - Value: "wale-bucket", + Value: "wal-bucket", }, envPos: 1, }, @@ -1213,7 +1213,7 @@ func TestCloneEnv(t *testing.T) { var cluster = New( Config{ OpConfig: config.Config{ - WALES3Bucket: "wale-bucket", + WALS3Bucket: "wal-bucket", ProtectedRoles: []string{"admin"}, Auth: config.Auth{ SuperUsername: superUserName, @@ -1325,7 +1325,7 @@ func TestStandbyEnv(t *testing.T) { S3WalPath: "s3://some/path/", }, env: v1.EnvVar{ - Name: "STANDBY_WALE_S3_PREFIX", + Name: "STANDBY_WALG_S3_PREFIX", Value: "s3://some/path/", }, envPos: 0, @@ -1339,7 +1339,7 @@ func TestStandbyEnv(t *testing.T) { }, env: v1.EnvVar{ Name: "STANDBY_METHOD", - Value: "STANDBY_WITH_WALE", + Value: "STANDBY_WITH_WALG", }, envPos: 1, envLen: 3, diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 797e7a5aa..4c7754f89 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -1027,8 +1027,8 @@ func (c *Cluster) syncStandbyClusterConfiguration() error { standbyOptionsToSet := make(map[string]interface{}) if c.Spec.StandbyCluster != nil { c.logger.Infof("turning %q into a standby cluster", c.Name) - standbyOptionsToSet["create_replica_methods"] = []string{"bootstrap_standby_with_wale", "basebackup_fast_xlog"} - standbyOptionsToSet["restore_command"] = "envdir \"/run/etc/wal-e.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\"" + standbyOptionsToSet["create_replica_methods"] = []string{"bootstrap_standby_with_walg", "basebackup_fast_xlog"} + standbyOptionsToSet["restore_command"] = "envdir \"/run/etc/wal-g.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\"" } else { c.logger.Infof("promoting standby cluster and detach from source") diff --git a/pkg/cluster/sync_test.go b/pkg/cluster/sync_test.go index f9d1d7873..8acde084d 100644 --- a/pkg/cluster/sync_test.go +++ b/pkg/cluster/sync_test.go @@ -715,7 +715,7 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { mockClient.EXPECT().Get(gomock.Any()).Return(&response, nil).AnyTimes() // mocking a config after setConfig is called - standbyJson := `{"standby_cluster":{"create_replica_methods":["bootstrap_standby_with_wale","basebackup_fast_xlog"],"restore_command":"envdir \"/run/etc/wal-e.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\""}}` + standbyJson := `{"standby_cluster":{"create_replica_methods":["bootstrap_standby_with_walg","basebackup_fast_xlog"],"restore_command":"envdir \"/run/etc/wal-g.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\""}}` r = io.NopCloser(bytes.NewReader([]byte(standbyJson))) response = http.Response{ StatusCode: 200, @@ -741,7 +741,7 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { assert.NoError(t, err) // check that pods do not have a STANDBY_* environment variable - assert.NotContains(t, sts.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) + assert.NotContains(t, sts.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALG"}) // add standby section cluster.Spec.StandbyCluster = &acidv1.StandbyDescription{ @@ -751,13 +751,13 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { updatedSts := cluster.Statefulset // check that pods do not have a STANDBY_* environment variable - assert.Contains(t, updatedSts.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) + assert.Contains(t, updatedSts.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALG"}) // this should update the Patroni config err = cluster.syncStandbyClusterConfiguration() assert.NoError(t, err) - configJson = `{"standby_cluster":{"create_replica_methods":["bootstrap_standby_with_wale","basebackup_fast_xlog"],"restore_command":"envdir \"/run/etc/wal-e.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\""}, "ttl": 20}` + configJson = `{"standby_cluster":{"create_replica_methods":["bootstrap_standby_with_g","basebackup_fast_xlog"],"restore_command":"envdir \"/run/etc/wal-g.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\""}, "ttl": 20}` r = io.NopCloser(bytes.NewReader([]byte(configJson))) response = http.Response{ StatusCode: 200, @@ -773,8 +773,8 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { // ToDo extend GetConfig to return standy_cluster setting to compare /* defaultStandbyParameters := map[string]interface{}{ - "create_replica_methods": []string{"bootstrap_standby_with_wale", "basebackup_fast_xlog"}, - "restore_command": "envdir \"/run/etc/wal-e.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\"", + "create_replica_methods": []string{"bootstrap_standby_with_walg", "basebackup_fast_xlog"}, + "restore_command": "envdir \"/run/etc/wal-g.d/env-standby\" /scripts/restore_command.sh \"%f\" \"%p\"", } assert.True(t, reflect.DeepEqual(defaultStandbyParameters, standbyCluster)) */ @@ -784,7 +784,7 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { updatedSts2 := cluster.Statefulset // check that pods do not have a STANDBY_* environment variable - assert.NotContains(t, updatedSts2.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) + assert.NotContains(t, updatedSts2.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALG"}) // this should update the Patroni config again err = cluster.syncStandbyClusterConfiguration() diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 5739f6314..6e7277cb9 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -166,7 +166,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.ExternalTrafficPolicy = util.Coalesce(fromCRD.LoadBalancer.ExternalTrafficPolicy, "Cluster") // AWS or GCP config - result.WALES3Bucket = fromCRD.AWSGCP.WALES3Bucket + result.WALS3Bucket = fromCRD.AWSGCP.WALS3Bucket result.AWSRegion = fromCRD.AWSGCP.AWSRegion result.LogS3Bucket = fromCRD.AWSGCP.LogS3Bucket result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 30b967beb..79d006c97 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -185,7 +185,7 @@ type Config struct { MasterPodMoveTimeout time.Duration `name:"master_pod_move_timeout" default:"20m"` DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"` AWSRegion string `name:"aws_region" default:"eu-central-1"` - WALES3Bucket string `name:"wal_s3_bucket"` + WALS3Bucket string `name:"wal_s3_bucket"` LogS3Bucket string `name:"log_s3_bucket"` KubeIAMRole string `name:"kube_iam_role"` WALGSBucket string `name:"wal_gs_bucket"`