Ensure compatibility with Spilo after WAL-E removal (support old and new versions)

This commit is contained in:
inovindasari 2025-08-18 15:50:11 +02:00
parent 51135b07db
commit 0451325139
8 changed files with 51 additions and 48 deletions

View File

@ -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:
<GCP .json credentials>
```
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: ""

View File

@ -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"`

View File

@ -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: ""})
}

View File

@ -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,

View File

@ -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")

View File

@ -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()

View File

@ -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

View File

@ -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"`