From cc0e8d8aad5fd316e72def3aefb521a42d4377d1 Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Tue, 28 Jan 2020 12:17:54 +0100 Subject: [PATCH 1/6] Added option to create streaming standby cluster. --- docs/reference/cluster_manifest.md | 21 ++++++- manifests/postgresql.crd.yaml | 10 ++- manifests/standby-manifest.yaml | 8 ++- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 6 +- pkg/cluster/k8sres.go | 65 +++++++++++++------- 5 files changed, 81 insertions(+), 29 deletions(-) diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index bf6df681b..be399677e 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -284,12 +284,29 @@ under the `clone` top-level key and do not affect the already running cluster. ## Standby cluster 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 -archive is supported. +cluster streaming from a remote location. There are two options supported +for a streaming cluster. Continious streaming from S3 WAL storage, and +streaming directly from host. * **s3_wal_path** the url to S3 bucket containing the WAL archive of the remote primary. Required when the `standby` section is present. + +* **standby_host** + Standby host is IP or hostname from which standby clustere is streaming + WAL records. This should be specified when using `streaming_host` standby method. + +* **standby_method** + Specify desired method: `s3_wal` or `streaming_host`. Required. + +* **standby_port** + Specify port that should be used when connecting to primary. + This should be specified when using `streaming_host` standby method. + +* **standby_secret_name** + Specify secret name that is used to look-up for a password that + is need to connect to primary host. Password should be under `password` key + of a secret. This is *required* be specified when using `streaming_host` standby method. ## EBS volume resizing diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index 3b0f652ea..01c4b0b1e 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -241,10 +241,18 @@ spec: standby: type: object required: - - s3_wal_path + - standby_method properties: s3_wal_path: type: string + standby_host: + type: string + standby_method: + type: string + standby_port: + type: string + standby_secret_name: + type: string teamId: type: string tolerations: diff --git a/manifests/standby-manifest.yaml b/manifests/standby-manifest.yaml index 2b621bd10..9fe0caa0c 100644 --- a/manifests/standby-manifest.yaml +++ b/manifests/standby-manifest.yaml @@ -10,6 +10,10 @@ spec: numberOfInstances: 1 postgresql: version: "11" -# Make this a standby cluster and provide the s3 bucket path of source cluster for continuous streaming. +# It is possible to create standby cluster from streaming host, or standby that will fetch wal files from s3 standby: - s3_wal_path: "s3://path/to/bucket/containing/wal/of/source/cluster/" + s3_wal_path: "s3://path/to/bucket/containing/wal/of/source/cluster/" # Make this a standby cluster and provide the s3 bucket path of source cluster for continuous streaming. + standby_method: "s3_wal" # s3_wal or streaming_host; + # standby_host: "" + # standby_port: "" + # standby_secret_name: "" \ No newline at end of file diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 515a73ff0..e36fbd22e 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -122,7 +122,11 @@ type Patroni struct { //StandbyCluster type StandbyDescription struct { - S3WalPath string `json:"s3_wal_path,omitempty"` + S3WalPath string `json:"s3_wal_path,omitempty"` + StandbyHost string `json:"standby_host,omitempty"` + StandbyMethod string `json:"standby_method,omitempty"` + StandbyPort string `json:"standby_port,omitempty"` + StandbySecretName string `json:"standby_secret_name,omitempty"` } // CloneDescription describes which cluster the new should clone and up to which point in time diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index aed0c6e83..cffaa7894 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -562,17 +562,6 @@ func (c *Cluster) generateSpiloPodEnvVars(uid types.UID, spiloConfiguration stri Name: "PGUSER_STANDBY", Value: c.OpConfig.ReplicationUsername, }, - { - Name: "PGPASSWORD_STANDBY", - ValueFrom: &v1.EnvVarSource{ - SecretKeyRef: &v1.SecretKeySelector{ - LocalObjectReference: v1.LocalObjectReference{ - Name: c.credentialSecretName(c.OpConfig.ReplicationUsername), - }, - Key: "password", - }, - }, - }, { Name: "PAM_OAUTH2", Value: c.OpConfig.PamConfiguration, @@ -582,6 +571,24 @@ func (c *Cluster) generateSpiloPodEnvVars(uid types.UID, spiloConfiguration stri Value: c.OpConfig.PamRoleName, }, } + + var standbySecretName string + if standbyDescription != nil && standbyDescription.StandbyMethod == "streaming_host" { + standbySecretName = standbyDescription.StandbySecretName + } else { + standbySecretName = c.credentialSecretName(c.OpConfig.ReplicationUsername) + } + + envVars = append(envVars, v1.EnvVar{Name: "PGPASSWORD_STANDBY", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: standbySecretName, + }, + Key: "password", + }, + }}) + if spiloConfiguration != "" { envVars = append(envVars, v1.EnvVar{Name: "SPILO_CONFIGURATION", Value: spiloConfiguration}) } @@ -808,8 +815,8 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef sort.Slice(customPodEnvVarsList, func(i, j int) bool { return customPodEnvVarsList[i].Name < customPodEnvVarsList[j].Name }) } - if spec.StandbyCluster != nil && spec.StandbyCluster.S3WalPath == "" { - return nil, fmt.Errorf("s3_wal_path is empty for standby cluster") + if spec.StandbyCluster != nil && spec.StandbyCluster.StandbyMethod == "" { + return nil, fmt.Errorf("standby_method is empty for standby cluster") } // backward compatible check for InitContainers @@ -1403,20 +1410,32 @@ func (c *Cluster) generateCloneEnvironment(description *acidv1.CloneDescription) func (c *Cluster) generateStandbyEnvironment(description *acidv1.StandbyDescription) []v1.EnvVar { result := make([]v1.EnvVar, 0) - if description.S3WalPath == "" { + if description.StandbyMethod == "" { 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{ - Name: "STANDBY_WALE_S3_PREFIX", - Value: description.S3WalPath, - }) + if description.StandbyMethod == "s3_wal" { + // 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{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_WALE_S3_PREFIX", + Value: description.S3WalPath, + }) + + result = append(result, v1.EnvVar{Name: "STANDBY_METHOD", Value: "STANDBY_WITH_WALE"}) + result = append(result, v1.EnvVar{Name: "STANDBY_WAL_BUCKET_SCOPE_PREFIX", Value: ""}) + } else if description.StandbyMethod == "streaming_host" { + // standby with streaming replication from standby host + msg := "Standby using streaming replication from standby host: %s " + c.logger.Infof(msg, description.StandbyHost) + + result = append(result, v1.EnvVar{Name: "STANDBY_HOST", Value: description.StandbyHost}) + result = append(result, v1.EnvVar{Name: "STANDBY_PORT", Value: description.StandbyPort}) + result = append(result, v1.EnvVar{Name: "STANDBY_CLUSTER", Value: "True"}) + + } return result } From 127cbcf6ad69da53d173d9a209c665dc47b7b3a1 Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Fri, 15 Jan 2021 11:09:16 +0100 Subject: [PATCH 2/6] Forgot during a merge. --- pkg/cluster/k8sres.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 71e15f5ea..4a935713b 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1017,6 +1017,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef } if spec.StandbyCluster != nil && spec.StandbyCluster.StandbyMethod == "" { return nil, fmt.Errorf("standby_method is empty for standby cluster") + } // fetch env vars from custom ConfigMap secretEnvVarsList, err := c.getPodEnvironmentSecretVariables() From 1cc716630ca9968cbfb85bc669877bc9e2e78eca Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Fri, 15 Jan 2021 19:05:54 +0100 Subject: [PATCH 3/6] We want to allow standby cluster connecting to remote master --- pkg/cluster/k8sres.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 4a935713b..7675b7604 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1031,10 +1031,6 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef sort.Slice(customPodEnvVarsList, func(i, j int) bool { return customPodEnvVarsList[i].Name < customPodEnvVarsList[j].Name }) - if spec.StandbyCluster != nil && spec.StandbyCluster.S3WalPath == "" { - return nil, fmt.Errorf("s3_wal_path is empty for standby cluster") - } - // backward compatible check for InitContainers if spec.InitContainersOld != nil { msg := "Manifest parameter init_containers is deprecated." From b6ce92e629f65c25bac0f48ded57b00f4ce801c2 Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Fri, 19 Feb 2021 12:49:44 +0100 Subject: [PATCH 4/6] Changed validation crds so it represents new standby field values. --- pkg/apis/acid.zalan.do/v1/crds.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 19430e78d..8925ee366 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -565,11 +565,23 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ }, "standby": { Type: "object", - Required: []string{"s3_wal_path"}, + Required: []string{"standby_method"}, Properties: map[string]apiextv1.JSONSchemaProps{ "s3_wal_path": { Type: "string", }, + "standby_host": { + Type: "string", + }, + "standby_method": { + Type: "string", + }, + "standby_port": { + Type: "string", + }, + "standby_secret_name": { + Type: "string", + }, }, }, "teamId": { From 2d6598f5f8dca93f740da97f3525e635b13fbdfd Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Mon, 1 Nov 2021 09:52:34 +0100 Subject: [PATCH 5/6] Implemented backward compatibility, if standby_method not specified, it will fall back to s3_wal --- manifests/postgresql.crd.yaml | 2 -- pkg/cluster/k8sres.go | 8 +++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index 5b099c32e..129ad5126 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -462,8 +462,6 @@ spec: type: integer standby: type: object - required: - - standby_method properties: s3_wal_path: type: string diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index be76787cd..d48cdaba7 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1053,7 +1053,13 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef return nil, err } if spec.StandbyCluster != nil && spec.StandbyCluster.StandbyMethod == "" { - return nil, fmt.Errorf("standby_method is empty for standby cluster") + // In order to be backward compatible, we fallback to s3_wal when standby_method is not specified + if spec.StandbyCluster.S3WalPath != "" { + c.logger.Warningf("Fallback to a s3_wal as standby_method is not specified.") + spec.StandbyCluster.StandbyMethod = "s3_wal" + } else { + return nil, fmt.Errorf("standby_method is and s3_wal_path are empty, what standby method to use!?") + } } // fetch env vars from custom ConfigMap From a5bcc5b9a93d1bf74c5ea5172fc300b68488e6a9 Mon Sep 17 00:00:00 2001 From: Armin Nesiren Date: Mon, 1 Nov 2021 10:04:23 +0100 Subject: [PATCH 6/6] Removed unnesesary comment, clean-up --- pkg/apis/acid.zalan.do/v1/crds.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 40dc2ce77..4034bf97b 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -649,8 +649,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ Type: "integer", }, "standby": { - Type: "object", - Required: []string{"standby_method"}, + Type: "object", Properties: map[string]apiextv1.JSONSchemaProps{ "s3_wal_path": { Type: "string",