Support standby replication from GS (GCS) (#1446)

* Add support for manual gs_wal_path in standby
* Remove separate standby version configuration
* Remove setting standby path via cluster/uid/version
Picking up the version doesn't work reliably without making changes to
Spilo. It's clearer to just specify the full S3/GS bucket path.

Co-authored-by: Felix Kunde <felix-kunde@gmx.de>
This commit is contained in:
James McDonald 2021-12-03 11:24:29 +01:00 committed by GitHub
parent 1ed16fadca
commit def9e1d688
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 52 additions and 19 deletions

View File

@ -464,11 +464,11 @@ spec:
type: integer type: integer
standby: standby:
type: object type: object
required:
- s3_wal_path
properties: properties:
s3_wal_path: s3_wal_path:
type: string type: string
gs_wal_path:
type: string
teamId: teamId:
type: string type: string
tls: tls:

View File

@ -366,12 +366,16 @@ under the `clone` top-level key and do not affect the already running cluster.
## Standby cluster ## Standby cluster
On startup, an existing `standby` top-level key creates a standby Postgres On startup, an existing `standby` top-level key creates a standby Postgres
cluster streaming from a remote location. So far only streaming from a S3 WAL cluster streaming from a remote location. So far streaming from S3 and GCS WAL
archive is supported. archives is supported.
* **s3_wal_path** * **s3_wal_path**
the url to S3 bucket containing the WAL archive of the remote primary. the url to S3 bucket containing the WAL archive of the remote primary.
Required when the `standby` section is present. Optional, but `s3_wal_path` or `gs_wal_path` is required.
* **gs_wal_path**
the url to GS bucket containing the WAL archive of the remote primary.
Optional, but `s3_wal_path` or `gs_wal_path` is required.
## Volume properties ## Volume properties

View File

@ -798,8 +798,8 @@ different location than its source database. Unlike cloning, the PostgreSQL
version between source and target cluster has to be the same. version between source and target cluster has to be the same.
To start a cluster as standby, add the following `standby` section in the YAML To start a cluster as standby, add the following `standby` section in the YAML
file and specify the S3 bucket path. An empty path will result in an error and file. Specify the S3/GS bucket path. Omitting both settings will result in an error
no statefulset will be created. and no statefulset will be created.
```yaml ```yaml
spec: spec:
@ -807,6 +807,12 @@ spec:
s3_wal_path: "s3://<bucketname>/spilo/<source_db_cluster>/<UID>/wal/<PGVERSION>" s3_wal_path: "s3://<bucketname>/spilo/<source_db_cluster>/<UID>/wal/<PGVERSION>"
``` ```
```yaml
spec:
standby:
gs_wal_path: "gs://<bucketname>/spilo/<source_db_cluster>/<UID>/wal/<PGVERSION>"
```
At the moment, the operator only allows to stream from the WAL archive of the At the moment, the operator only allows to stream from the WAL archive of the
master. Thus, it is recommended to deploy standby clusters with only [one pod](https://github.com/zalando/postgres-operator/blob/master/manifests/standby-manifest.yaml#L10). master. Thus, it is recommended to deploy standby clusters with only [one pod](https://github.com/zalando/postgres-operator/blob/master/manifests/standby-manifest.yaml#L10).
You can raise the instance count when detaching. Note, that the same pod role You can raise the instance count when detaching. Note, that the same pod role

View File

@ -649,12 +649,14 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Type: "integer", Type: "integer",
}, },
"standby": { "standby": {
Type: "object", Type: "object",
Required: []string{"s3_wal_path"},
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"s3_wal_path": { "s3_wal_path": {
Type: "string", Type: "string",
}, },
"gs_wal_path": {
Type: "string",
},
}, },
}, },
"teamId": { "teamId": {

View File

@ -166,6 +166,7 @@ type Patroni struct {
// StandbyDescription contains s3 wal path // StandbyDescription contains s3 wal path
type StandbyDescription struct { type StandbyDescription struct {
S3WalPath string `json:"s3_wal_path,omitempty"` S3WalPath string `json:"s3_wal_path,omitempty"`
GSWalPath string `json:"gs_wal_path,omitempty"`
} }
// TLSDescription specs TLS properties // TLSDescription specs TLS properties

View File

@ -1058,8 +1058,9 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
sort.Slice(customPodEnvVarsList, sort.Slice(customPodEnvVarsList,
func(i, j int) bool { return customPodEnvVarsList[i].Name < customPodEnvVarsList[j].Name }) func(i, j int) bool { return customPodEnvVarsList[i].Name < customPodEnvVarsList[j].Name })
if spec.StandbyCluster != nil && spec.StandbyCluster.S3WalPath == "" { if spec.StandbyCluster != nil && spec.StandbyCluster.S3WalPath == "" &&
return nil, fmt.Errorf("s3_wal_path is empty for standby cluster") spec.StandbyCluster.GSWalPath == "" {
return nil, fmt.Errorf("one of s3_wal_path or gs_wal_path must be set for standby cluster")
} }
// backward compatible check for InitContainers // backward compatible check for InitContainers
@ -1874,17 +1875,36 @@ func (c *Cluster) generateCloneEnvironment(description *acidv1.CloneDescription)
func (c *Cluster) generateStandbyEnvironment(description *acidv1.StandbyDescription) []v1.EnvVar { func (c *Cluster) generateStandbyEnvironment(description *acidv1.StandbyDescription) []v1.EnvVar {
result := make([]v1.EnvVar, 0) result := make([]v1.EnvVar, 0)
if description.S3WalPath == "" { if description.S3WalPath == "" && description.GSWalPath == "" {
return nil return nil
} }
// standby with S3, find out the bucket to setup standby
msg := "Standby from S3 bucket using custom parsed S3WalPath from the manifest %s "
c.logger.Infof(msg, description.S3WalPath)
result = append(result, v1.EnvVar{ if description.S3WalPath != "" {
Name: "STANDBY_WALE_S3_PREFIX", // standby with S3, find out the bucket to setup standby
Value: description.S3WalPath, msg := "Standby from S3 bucket using custom parsed S3WalPath from the manifest %s "
}) c.logger.Infof(msg, description.S3WalPath)
result = append(result, v1.EnvVar{
Name: "STANDBY_WALE_S3_PREFIX",
Value: description.S3WalPath,
})
} else if description.GSWalPath != "" {
msg := "Standby from GS bucket using custom parsed GSWalPath from the manifest %s "
c.logger.Infof(msg, description.GSWalPath)
envs := []v1.EnvVar{
{
Name: "STANDBY_WALE_GS_PREFIX",
Value: description.GSWalPath,
},
{
Name: "STANDBY_GOOGLE_APPLICATION_CREDENTIALS",
Value: c.OpConfig.GCPCredentials,
},
}
result = append(result, envs...)
}
result = append(result, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) result = append(result, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"})
result = append(result, v1.EnvVar{Name: "STANDBY_WAL_BUCKET_SCOPE_PREFIX", Value: ""}) result = append(result, v1.EnvVar{Name: "STANDBY_WAL_BUCKET_SCOPE_PREFIX", Value: ""})