From 7dc4a9e98ff5139e1a5e555c8725d543d5597d7a Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 28 Feb 2019 18:27:34 +0000 Subject: [PATCH 1/7] Codegen for Postgres CRD --- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 1 + pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 1a191786e..a75ab94aa 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -111,6 +111,7 @@ type Patroni struct { RetryTimeout uint32 `json:"retry_timeout"` MaximumLagOnFailover float32 `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213 Slots map[string]map[string]string `json:"slots"` + StandbyCluster map[string]interface{} `json:"standby_cluster"` } // CloneDescription describes which cluster the new should clone and up to which point in time diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 282fb311f..62dcabe01 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -371,6 +371,17 @@ func (in *Patroni) DeepCopyInto(out *Patroni) { (*out)[key] = outVal } } + if in.StandbyCluster != nil { + in, out := &in.StandbyCluster, &out.StandbyCluster + *out = make(map[string]interface{}, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = val.DeepCopyinterface{}() + } + } + } return } From 6da42ca2a1e91a441dd6d0a85fba1cad53973927 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 10 Jan 2019 19:35:58 +0000 Subject: [PATCH 2/7] Pass patroni.stanby_cluster to Patroni --- docs/reference/cluster_manifest.md | 3 +++ pkg/cluster/k8sres.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 2768f02aa..fb47809d0 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -199,6 +199,9 @@ explanation of `ttl` and `loop_wait` parameters. automatically created by Patroni for cluster members and permanent replication slots. Optional. +* **standby_cluster** + initialises cluster as a standby creating a cascading replication, where elected master is streaming from specified remote location + ## Postgres container resources Those parameters define [CPU and memory requests and diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index a16e810ed..859a58559 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -45,6 +45,7 @@ type patroniDCS struct { MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` PGBootstrapConfiguration map[string]interface{} `json:"postgresql,omitempty"` Slots map[string]map[string]string `json:"slots,omitempty"` + StandbyClusterCfg map[string]interface{} `json:"standy_cluster,omitempty"` } type pgBootstrap struct { @@ -157,6 +158,8 @@ func generateSpiloJSONConfiguration(pg *acidv1.PostgresqlParam, patroni *acidv1. config.Bootstrap.Initdb = []interface{}{map[string]string{"auth-host": "md5"}, map[string]string{"auth-local": "trust"}} + config.Bootstrap.StandbyClusterCfg = patroni.StandbyCluster + initdbOptionNames := []string{} for k := range patroni.InitDB { From 9bc088b02576becb1e731f01ba336e8fbce218c8 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 10 Jan 2019 20:13:54 +0000 Subject: [PATCH 3/7] DCS --- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 14 +++++++- .../acid.zalan.do/v1/zz_generated.deepcopy.go | 33 ++++++++++++------- pkg/cluster/k8sres.go | 4 +-- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index a75ab94aa..b74ffc673 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -111,7 +111,19 @@ type Patroni struct { RetryTimeout uint32 `json:"retry_timeout"` MaximumLagOnFailover float32 `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213 Slots map[string]map[string]string `json:"slots"` - StandbyCluster map[string]interface{} `json:"standby_cluster"` + StandbyCluster PatroniStandbyCluster `json:"standby_cluster"` +} + +// PatroniStandbyCluster container Patroni's standby_cluster +// based on https://github.com/zalando/patroni/blob/929ff08bfde51b28cf90d7bea92db0a5838a4284/patroni/config.py#L50-L58 +type PatroniStandbyCluster struct { + PrimarySlotName string `json:"primary_slot_name"` + CreateReplicaMethods []string `json:"create_replica_methods"` + RestoreCommand string `json:"restore_command"` + ArchiveCleanupCommand string `json:"archive_cleanup_command"` + RecoveryMinApplyDelay string `json:"recovery_min_apply_delay"` + Host string `json:"host"` + Port string `json:"port"` } // CloneDescription describes which cluster the new should clone and up to which point in time diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 62dcabe01..6d1850c4f 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -371,17 +371,7 @@ func (in *Patroni) DeepCopyInto(out *Patroni) { (*out)[key] = outVal } } - if in.StandbyCluster != nil { - in, out := &in.StandbyCluster, &out.StandbyCluster - *out = make(map[string]interface{}, len(*in)) - for key, val := range *in { - if val == nil { - (*out)[key] = nil - } else { - (*out)[key] = val.DeepCopyinterface{}() - } - } - } + in.StandbyCluster.DeepCopyInto(&out.StandbyCluster) return } @@ -395,6 +385,27 @@ func (in *Patroni) DeepCopy() *Patroni { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatroniStandbyCluster) DeepCopyInto(out *PatroniStandbyCluster) { + *out = *in + if in.CreateReplicaMethods != nil { + in, out := &in.CreateReplicaMethods, &out.CreateReplicaMethods + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatroniStandbyCluster. +func (in *PatroniStandbyCluster) DeepCopy() *PatroniStandbyCluster { + if in == nil { + return nil + } + out := new(PatroniStandbyCluster) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresPodResourcesDefaults) DeepCopyInto(out *PostgresPodResourcesDefaults) { *out = *in diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 859a58559..36bf3f0e6 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -45,7 +45,7 @@ type patroniDCS struct { MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` PGBootstrapConfiguration map[string]interface{} `json:"postgresql,omitempty"` Slots map[string]map[string]string `json:"slots,omitempty"` - StandbyClusterCfg map[string]interface{} `json:"standy_cluster,omitempty"` + StandbyClusterCfg acidv1.PatroniStandbyCluster `json:"standy_cluster,omitempty"` } type pgBootstrap struct { @@ -158,7 +158,7 @@ func generateSpiloJSONConfiguration(pg *acidv1.PostgresqlParam, patroni *acidv1. config.Bootstrap.Initdb = []interface{}{map[string]string{"auth-host": "md5"}, map[string]string{"auth-local": "trust"}} - config.Bootstrap.StandbyClusterCfg = patroni.StandbyCluster + config.Bootstrap.DCS.StandbyClusterCfg = patroni.StandbyCluster initdbOptionNames := []string{} From 1eed7db31f78a3aa0e4881f37d53e0535941fad1 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Fri, 11 Jan 2019 11:40:39 +0000 Subject: [PATCH 4/7] stadnby: typoe --- pkg/cluster/k8sres.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 36bf3f0e6..cbd805e1f 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -45,7 +45,7 @@ type patroniDCS struct { MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` PGBootstrapConfiguration map[string]interface{} `json:"postgresql,omitempty"` Slots map[string]map[string]string `json:"slots,omitempty"` - StandbyClusterCfg acidv1.PatroniStandbyCluster `json:"standy_cluster,omitempty"` + StandbyClusterCfg acidv1.PatroniStandbyCluster `json:"standby_cluster,omitempty"` } type pgBootstrap struct { From 78c23b2402cb7444a13a0bd514bebea6c53f7685 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Fri, 11 Jan 2019 15:13:00 +0000 Subject: [PATCH 5/7] standby: Make standby_cluster optional --- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 2 +- pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go | 6 +++++- pkg/cluster/k8sres.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index b74ffc673..d9de73976 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -111,7 +111,7 @@ type Patroni struct { RetryTimeout uint32 `json:"retry_timeout"` MaximumLagOnFailover float32 `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213 Slots map[string]map[string]string `json:"slots"` - StandbyCluster PatroniStandbyCluster `json:"standby_cluster"` + StandbyCluster *PatroniStandbyCluster `json:"standby_cluster"` } // PatroniStandbyCluster container Patroni's standby_cluster diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 6d1850c4f..ded350f38 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -371,7 +371,11 @@ func (in *Patroni) DeepCopyInto(out *Patroni) { (*out)[key] = outVal } } - in.StandbyCluster.DeepCopyInto(&out.StandbyCluster) + if in.StandbyCluster != nil { + in, out := &in.StandbyCluster, &out.StandbyCluster + *out = new(PatroniStandbyCluster) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index cbd805e1f..b047c5a75 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -45,7 +45,7 @@ type patroniDCS struct { MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` PGBootstrapConfiguration map[string]interface{} `json:"postgresql,omitempty"` Slots map[string]map[string]string `json:"slots,omitempty"` - StandbyClusterCfg acidv1.PatroniStandbyCluster `json:"standby_cluster,omitempty"` + StandbyClusterCfg *acidv1.PatroniStandbyCluster `json:"standby_cluster,omitempty"` } type pgBootstrap struct { From 581406a0753fb9afd16bfe88bf8cc1af6e26ba67 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 28 Feb 2019 18:44:03 +0000 Subject: [PATCH 6/7] Fix marshalling tests --- pkg/apis/acid.zalan.do/v1/util_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/apis/acid.zalan.do/v1/util_test.go b/pkg/apis/acid.zalan.do/v1/util_test.go index 537619aaf..6a2bbcae6 100644 --- a/pkg/apis/acid.zalan.do/v1/util_test.go +++ b/pkg/apis/acid.zalan.do/v1/util_test.go @@ -148,7 +148,7 @@ var unmarshalCluster = []struct { // This error message can vary between Go versions, so compute it for the current version. Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(), }, - marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), + marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null, "standby_cluster": null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), err: nil}, // example with /status subresource { @@ -167,7 +167,7 @@ var unmarshalCluster = []struct { // This error message can vary between Go versions, so compute it for the current version. Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(), }, - marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`), + marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null, "standby_cluster": null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`), err: nil}, // example with detailed input manifest { @@ -311,7 +311,7 @@ var unmarshalCluster = []struct { }, Error: "", }, - marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`), + marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}, "standby_cluster": null},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`), err: nil}, // example with teamId set in input { @@ -328,7 +328,7 @@ var unmarshalCluster = []struct { Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid}, Error: errors.New("name must match {TEAM}-{NAME} format").Error(), }, - marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`), + marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null, "standby_cluster": null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`), err: nil}, // clone example { @@ -350,7 +350,7 @@ var unmarshalCluster = []struct { }, Error: "", }, - marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`), + marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null, "standby_cluster": null},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`), err: nil}, // erroneous examples { From e4f2f18cfdb28b28336f636470c4273905c3b94c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 13 Jun 2019 11:07:47 +0100 Subject: [PATCH 7/7] Updated complete postgres manifest with standby_cluster example --- manifests/complete-postgres-manifest.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index de66be6f7..857e394fe 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -70,6 +70,15 @@ spec: # run periodic backups with k8s cron jobs # enableLogicalBackup: true # logicalBackupSchedule: "30 00 * * *" + # + # # standby cluster example. + # # See https://patroni.readthedocs.io/en/latest/replica_bootstrap.html#standby-cluster. + # + # standby_cluster: + # host: master.hostname + # port: "5432" + # primary_slot_name: dmz2 + maintenanceWindows: - 01:00-06:00 #UTC - Sat:00:00-04:00