From 37b085f26ef73a5a5a892229835eac96c65b0f05 Mon Sep 17 00:00:00 2001 From: Pieter van der Giessen Date: Tue, 9 Apr 2024 18:42:23 +0200 Subject: [PATCH 1/8] Initial setup recovery using pg_basebackup Signed-off-by: Pieter van der Giessen --- charts/cluster/docs/Recovery.md | 10 +++--- .../examples/recovery-pg_basebackup.yaml | 14 ++++++++ charts/cluster/templates/_bootstrap.tpl | 36 +++++++++++++++++++ .../recovery-pgbasebackup-password.yaml | 8 +++++ charts/cluster/values.yaml | 22 ++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 charts/cluster/examples/recovery-pg_basebackup.yaml create mode 100644 charts/cluster/templates/recovery-pgbasebackup-password.yaml diff --git a/charts/cluster/docs/Recovery.md b/charts/cluster/docs/Recovery.md index 6a1be6593..1a7a9153c 100644 --- a/charts/cluster/docs/Recovery.md +++ b/charts/cluster/docs/Recovery.md @@ -10,7 +10,7 @@ You can find more information about the recovery process in the [CNPG documentat There are 3 types of recovery possible with CNPG: * Recovery from a backup object in the same Kubernetes namespace. * Recovery from a Barman Object Store, that could be located anywhere. -* Streaming replication from an operating cluster using `pg_basebackup` (not supported by the chart yet). +* Streaming replication from an operating cluster using `pg_basebackup`. When performing a recovery you are strongly advised to use the same configuration and PostgreSQL version as the original cluster. @@ -18,10 +18,10 @@ To begin, create a `values.yaml` that contains the following: 1. Set `mode: recovery` to indicate that you want to perform bootstrap the new cluster from an existing one. 2. Set the `recovery.method` to the type of recovery you want to perform. -3. Set either the `recovery.backupName` or the Barman Object Store configuration - i.e. `recovery.provider` and appropriate S3, Azure or GCS configuration. -4. Optionally set the `recovery.pitrTarget.time` in RFC3339 format to perform a point-in-time recovery. -4. Retain the identical PostgreSQL version and configuration as the original cluster. -5. Make sure you don't use the same backup section name as the original cluster. We advise you change the `path` within the storage location if you want to reuse the same storage location/bucket. +3. Set either the `recovery.backupName` or the Barman Object Store configuration - i.e. `recovery.provider` and appropriate S3, Azure or GCS configuration. In case of `pg_basebackup` complete the `recovery.pgBaseBackup` section. +4. Optionally set the `recovery.pitrTarget.time` in RFC3339 format to perform a point-in-time recovery (not applicable for `pgBaseBackup`). +5. Retain the identical PostgreSQL version and configuration as the original cluster. +6. Make sure you don't use the same backup section name as the original cluster. We advise you change the `path` within the storage location if you want to reuse the same storage location/bucket. One pattern is adding a version number at the end of the path, e.g. `/v1` or `/v2` after each recovery procedure. Example recovery configurations can be found in the [examples](../examples) directory. diff --git a/charts/cluster/examples/recovery-pg_basebackup.yaml b/charts/cluster/examples/recovery-pg_basebackup.yaml new file mode 100644 index 000000000..ef77857a3 --- /dev/null +++ b/charts/cluster/examples/recovery-pg_basebackup.yaml @@ -0,0 +1,14 @@ +mode: "recovery" + +recovery: + method: "pg_basebackup" + pgBaseBackup: + sourceHost: "source-db.foo.com" + sourceUsername: "streaming_replica" + existingPasswordSecret: "source-db-replica-password" + +cluster: + instances: 1 + +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 6147f3a77..5f5d7d567 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -22,6 +22,41 @@ bootstrap: {{- end -}} {{- end -}} {{- else if eq .Values.mode "recovery" }} +{{- if eq .Values.recovery.method "pg_basebackup" }} + pg_basebackup: + source: {{ .Values.recovery.pgBaseBackup.sourceName }} + + externalClusters: + - name: {{ .Values.recovery.pgBaseBackup.sourceName }} + connectionParameters: + host: {{ .Values.recovery.pgBaseBackup.sourceHost }} + user: {{ .Values.recovery.pgBaseBackup.sourceUsername }} + {{- if .Values.recovery.pgBaseBackup.TLS.enabled }} + sslmode: verify-full + {{- end }} + {{- if or .Values.recovery.pgBaseBackup.sourcePassword .Values.recovery.pgBaseBackup.existingPasswordSecret }} + password: + {{- if .Values.recovery.pgBaseBackup.sourcePassword }} + name: {{ include "cluster.fullname" . }}-source-db-password + {{- else }} + name: {{ .Values.recovery.pgBaseBackup.existingPasswordSecret }} + {{- end }} + key: password + {{- else if .Values.recovery.pgBaseBackup.TLS.enabled }} + sslKey: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.key }} + sslCert: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.key }} + sslRootCert: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.key }} + {{- else }} + {{ fail "No password or TLS secret defined for pg_basebackup" }} + {{- end }} + +{{- else }} recovery: {{- with .Values.recovery.pitrTarget.time }} recoveryTarget: @@ -40,6 +75,7 @@ externalClusters: serverName: {{ .Values.recovery.clusterName }} {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretSuffix" "-recovery" -}} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 4 }} +{{- end }} {{- else }} {{ fail "Invalid cluster mode!" }} {{- end }} diff --git a/charts/cluster/templates/recovery-pgbasebackup-password.yaml b/charts/cluster/templates/recovery-pgbasebackup-password.yaml new file mode 100644 index 000000000..cd7de5921 --- /dev/null +++ b/charts/cluster/templates/recovery-pgbasebackup-password.yaml @@ -0,0 +1,8 @@ +{{- if and (eq .Values.mode "recovery") (eq .Values.recovery.method "pg_basebackup") (.Values.recovery.pgBaseBackup.sourcePassword) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cluster.fullname" . }}-source-db-password +data: + password: {{ .Values.recovery.pgBaseBackup.sourcePassword | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 325d5f3cd..d78846615 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -76,6 +76,28 @@ recovery: gkeEnvironment: false applicationCredentials: "" + # Please see https://cloudnative-pg.io/documentation/1.22/bootstrap/#bootstrap-from-a-live-cluster-pg_basebackup + pgBaseBackup: + sourceName: "source-db" + sourceHost: "" + sourceUsername: "streaming_replica" + sourcePassword: "" + # -- The name of an existing secret with the password (must contain key `password`). + # When it's set, the `recovery.pg_basebackup.sourcePassword` parameter is ignored + existingPasswordSecret: "" + TLS: + enabled: false + sslKey: + secretName: "" + key: "" + sslCert: + secretName: "" + key: "" + sslRootCert: + secretName: "" + key: "" + + cluster: # -- Number of instances From fa5be43e2e2b0ca6330753fc2a3b990f700787d8 Mon Sep 17 00:00:00 2001 From: Pieter van der Giessen Date: Tue, 9 Apr 2024 19:14:27 +0200 Subject: [PATCH 2/8] Fix indentation of externalClusters Signed-off-by: Pieter van der Giessen --- charts/cluster/templates/_bootstrap.tpl | 54 ++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 5f5d7d567..170ef6c39 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -26,35 +26,35 @@ bootstrap: pg_basebackup: source: {{ .Values.recovery.pgBaseBackup.sourceName }} - externalClusters: - - name: {{ .Values.recovery.pgBaseBackup.sourceName }} - connectionParameters: - host: {{ .Values.recovery.pgBaseBackup.sourceHost }} - user: {{ .Values.recovery.pgBaseBackup.sourceUsername }} - {{- if .Values.recovery.pgBaseBackup.TLS.enabled }} - sslmode: verify-full - {{- end }} - {{- if or .Values.recovery.pgBaseBackup.sourcePassword .Values.recovery.pgBaseBackup.existingPasswordSecret }} - password: - {{- if .Values.recovery.pgBaseBackup.sourcePassword }} - name: {{ include "cluster.fullname" . }}-source-db-password - {{- else }} - name: {{ .Values.recovery.pgBaseBackup.existingPasswordSecret }} - {{- end }} - key: password - {{- else if .Values.recovery.pgBaseBackup.TLS.enabled }} - sslKey: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.key }} - sslCert: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.key }} - sslRootCert: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.key }} +externalClusters: +- name: {{ .Values.recovery.pgBaseBackup.sourceName }} + connectionParameters: + host: {{ .Values.recovery.pgBaseBackup.sourceHost }} + user: {{ .Values.recovery.pgBaseBackup.sourceUsername }} + {{- if .Values.recovery.pgBaseBackup.TLS.enabled }} + sslmode: verify-full + {{- end }} + {{- if or .Values.recovery.pgBaseBackup.sourcePassword .Values.recovery.pgBaseBackup.existingPasswordSecret }} + password: + {{- if .Values.recovery.pgBaseBackup.sourcePassword }} + name: {{ include "cluster.fullname" . }}-source-db-password {{- else }} - {{ fail "No password or TLS secret defined for pg_basebackup" }} + name: {{ .Values.recovery.pgBaseBackup.existingPasswordSecret }} {{- end }} + key: password + {{- else if .Values.recovery.pgBaseBackup.TLS.enabled }} + sslKey: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.key }} + sslCert: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.key }} + sslRootCert: + name: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.secretName }} + key: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.key }} + {{- else }} + {{ fail "No password or TLS secret defined for pg_basebackup" }} + {{- end }} {{- else }} recovery: From 033bb152d73ee4827e42aa6a6b0b4c1bff3de928 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 01:11:45 +0300 Subject: [PATCH 3/8] pg_basebackup working test suite Signed-off-by: Itay Grudev --- .../test/monitoring/chainsaw-test.yaml | 5 +- .../00-source-cluster-assert.yaml | 6 +++ .../00-source-cluster.yaml | 5 ++ .../01-data_write-assert.yaml | 6 +++ .../01-data_write.yaml | 30 ++++++++++++ .../01-pg_basebackup-cluster-assert.yaml | 6 +++ .../01-pg_basebackup-cluster.yaml | 21 +++++++++ .../chainsaw-test.yaml | 47 +++++++++++++++++++ 8 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml diff --git a/charts/cluster/test/monitoring/chainsaw-test.yaml b/charts/cluster/test/monitoring/chainsaw-test.yaml index fcbe50389..ce647a48d 100644 --- a/charts/cluster/test/monitoring/chainsaw-test.yaml +++ b/charts/cluster/test/monitoring/chainsaw-test.yaml @@ -1,6 +1,5 @@ ## -# This is a test that verifies that non-default configuration options are correctly propagated to the CNPG cluster. -# P.S. This test is not designed to have a good running configuration, it is designed to test the configuration propagation! +# This is a test that checks if PodMonitors, ConfigMaps and PrometheusRules are correctly provisioned when requested. apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: @@ -11,7 +10,7 @@ spec: assert: 20s cleanup: 30s steps: - - name: Install the non-default configuration cluster + - name: Install the monitoring cluster try: - script: content: | diff --git a/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml new file mode 100644 index 000000000..90ea90fd5 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: source-cluster +status: + readyInstances: 1 diff --git a/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml new file mode 100644 index 000000000..3d041f0f3 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/00-source-cluster.yaml @@ -0,0 +1,5 @@ +mode: "standalone" +cluster: + instances: 1 +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +status: + succeeded: 1 diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml new file mode 100644 index 000000000..fc5f0c8c9 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml @@ -0,0 +1,30 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: source-cluster-superuser + key: username + - name: DB_PASS + valueFrom: + secretKeyRef: + name: source-cluster-superuser + key: password + - name: DB_URI + value: postgres://$(DB_USER):$(DB_PASS)@source-cluster-rw:5432 + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client kubectl + psql "$DB_URI" -c "CREATE DATABASE mygooddb;" + psql "$DB_URI/mygooddb" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml new file mode 100644 index 000000000..9b953d44a --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: pg-basebackup-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml new file mode 100644 index 000000000..615ad3c80 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml @@ -0,0 +1,21 @@ +mode: "recovery" +recovery: + method: "pg_basebackup" + pgBaseBackup: + source: + host: "source-cluster-rw" + database: "mygooddb" + username: "streaming_replica" + sslMode: "require" + sslKeySecret: + name: source-cluster-replication + key: tls.key + sslCertSecret: + name: source-cluster-replication + key: tls.crt + +cluster: + instances: 2 + +backups: + enabled: false \ No newline at end of file diff --git a/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml new file mode 100644 index 000000000..065573bdc --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml @@ -0,0 +1,47 @@ +## +# This is a test that provisions a regular (non CNPG) PostgreSQL cluster and attempts to perform a pg_basebackup recovery. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: postgresql-pg-basebackup +spec: + timeouts: + apply: 1s + assert: 2m + cleanup: 1m + steps: + - name: Install the external PostgreSQL cluster + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./00-source-cluster.yaml \ + --wait \ + source ../../ + - assert: + file: ./00-source-cluster-assert.yaml + - apply: + file: ./01-data_write.yaml + - assert: + file: ./01-data_write-assert.yaml + - name: Install the pg_basebackup cluster + timeouts: + assert: 5m + try: + - script: + content: | + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-pg_basebackup-cluster.yaml \ + --wait \ + pg-basebackup ../../ + - assert: + file: ./01-pg_basebackup-cluster-assert.yaml + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE pg-basebackup From 191c31f726598be89be68008bbaf23fa65767d74 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 01:14:52 +0300 Subject: [PATCH 4/8] Implementation corrections Signed-off-by: Itay Grudev --- charts/cluster/README.md | 20 ++++- charts/cluster/templates/NOTES.txt | 44 ++++++---- charts/cluster/templates/_bootstrap.tpl | 55 +++++++----- .../recovery-pg_basebackup-password.yaml | 8 ++ .../recovery-pgbasebackup-password.yaml | 8 -- charts/cluster/values.schema.json | 84 +++++++++++++++++++ charts/cluster/values.yaml | 42 ++++++---- 7 files changed, 197 insertions(+), 64 deletions(-) create mode 100644 charts/cluster/templates/recovery-pg_basebackup-password.yaml delete mode 100644 charts/cluster/templates/recovery-pgbasebackup-password.yaml diff --git a/charts/cluster/README.md b/charts/cluster/README.md index 503adb5ba..bc7502666 100644 --- a/charts/cluster/README.md +++ b/charts/cluster/README.md @@ -167,7 +167,7 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.postgresGID | int | `26` | The GID of the postgres user inside the image, defaults to 26 | | cluster.postgresUID | int | `26` | The UID of the postgres user inside the image, defaults to 26 | | cluster.postgresql | object | `{}` | Configuration of the PostgreSQL server. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-PostgresConfiguration | -| cluster.primaryUpdateMethod | string | `"switchover"` | Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated. It can be switchover (default) or in-place (restart). | +| cluster.primaryUpdateMethod | string | `"switchover"` | Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated. It can be switchover (default) or restart. | | cluster.primaryUpdateStrategy | string | `"unsupervised"` | Strategy to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated: it can be automated (unsupervised - default) or manual (supervised) | | cluster.priorityClassName | string | `""` | | | cluster.resources | object | `{}` | Resources requirements of every generated Pod. Please refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more information. We strongly advise you use the same setting for limits and requests so that your cluster pods are given a Guaranteed QoS. See: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/ | @@ -207,6 +207,24 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.google.gkeEnvironment | bool | `false` | | | recovery.google.path | string | `"/"` | | | recovery.method | string | `"backup"` | Available recovery methods: * `backup` - Recovers a CNPG cluster from a CNPG backup (PITR supported) Needs to be on the same cluster in the same namespace. * `object_store` - Recovers a CNPG cluster from a barman object store (PITR supported). * `pg_basebackup` - Recovers a CNPG cluster viaa streaming replication protocol. Useful if you want to migrate databases to CloudNativePG, even from outside Kubernetes. # TODO | +| recovery.pgBaseBackup.database | string | `"app"` | | +| recovery.pgBaseBackup.owner | string | `""` | | +| recovery.pgBaseBackup.secret | string | `""` | | +| recovery.pgBaseBackup.source.database | string | `"app"` | | +| recovery.pgBaseBackup.source.host | string | `""` | | +| recovery.pgBaseBackup.source.passwordSecret.create | bool | `false` | Whether to create a secret for the password | +| recovery.pgBaseBackup.source.passwordSecret.key | string | `"password"` | The key in the secret containing the password | +| recovery.pgBaseBackup.source.passwordSecret.name | string | `""` | Name of the secret containing the password | +| recovery.pgBaseBackup.source.passwordSecret.value | string | `""` | The password value to use when creating the secret | +| recovery.pgBaseBackup.source.port | int | `5432` | | +| recovery.pgBaseBackup.source.sslCertSecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslCertSecret.name | string | `""` | | +| recovery.pgBaseBackup.source.sslKeySecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslKeySecret.name | string | `""` | | +| recovery.pgBaseBackup.source.sslMode | string | `"verify-full"` | | +| recovery.pgBaseBackup.source.sslRootCertSecret.key | string | `""` | | +| recovery.pgBaseBackup.source.sslRootCertSecret.name | string | `""` | | +| recovery.pgBaseBackup.source.username | string | `""` | | | recovery.pitrTarget.time | string | `""` | Time in RFC3339 format | | recovery.provider | string | `"s3"` | One of `s3`, `azure` or `google` | | recovery.s3.accessKey | string | `""` | | diff --git a/charts/cluster/templates/NOTES.txt b/charts/cluster/templates/NOTES.txt index dd5142ecc..ad1220154 100644 --- a/charts/cluster/templates/NOTES.txt +++ b/charts/cluster/templates/NOTES.txt @@ -42,21 +42,35 @@ Configuration {{ $scheduledBackups = printf "%s, %s" $scheduledBackups .name }} {{- end -}} -╭───────────────────┬────────────────────────────────────────────────────────╮ -│ Configuration │ Value │ -┝━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ -│ Cluster mode │ {{ (printf "%-54s" .Values.mode) }} │ -│ Type │ {{ (printf "%-54s" .Values.type) }} │ -│ Image │ {{ include "cluster.color-info" (printf "%-54s" (include "cluster.imageName" .)) }} │ -│ Instances │ {{ include (printf "%s%s" "cluster.color-" $redundancyColor) (printf "%-54s" (toString .Values.cluster.instances)) }} │ -│ Backups │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.backups.enabled)) (printf "%-54s" (ternary "Enabled" "Disabled" .Values.backups.enabled)) }} │ -│ Backup Provider │ {{ (printf "%-54s" (title .Values.backups.provider)) }} │ -│ Scheduled Backups │ {{ (printf "%-54s" $scheduledBackups) }} │ -│ Storage │ {{ (printf "%-54s" .Values.cluster.storage.size) }} │ -│ Storage Class │ {{ (printf "%-54s" (default "Default" .Values.cluster.storage.storageClass)) }} │ -│ PGBouncer │ {{ (printf "%-54s" (ternary "Enabled" "Disabled" .Values.pooler.enabled)) }} │ -│ Monitoring │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.cluster.monitoring.enabled)) (printf "%-54s" (ternary "Enabled" "Disabled" .Values.cluster.monitoring.enabled)) }} │ -╰───────────────────┴────────────────────────────────────────────────────────╯ +{{- $mode := .Values.mode -}} +{{- $source := "" -}} +{{- if eq .Values.mode "recovery" }} +{{- $mode = printf "%s (%s)" .Values.mode .Values.recovery.method -}} + {{- if eq .Values.recovery.method "pg_basebackup" }} + {{- $source = printf "postgresql://%s@%s:%.0f/%s" .Values.recovery.pgBaseBackup.source.username .Values.recovery.pgBaseBackup.source.host .Values.recovery.pgBaseBackup.source.port .Values.recovery.pgBaseBackup.source.database -}} + {{- end -}} +{{- end -}} + +╭───────────────────┬──────────────────────────────────────────────────────────╮ +│ Configuration │ Value │ +┝━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ +│ Cluster mode │ {{ printf "%-56s" $mode }} │ +│ Type │ {{ printf "%-56s" .Values.type }} │ +│ Image │ {{ include "cluster.color-info" (printf "%-56s" (include "cluster.imageName" .)) }} │ +{{- if eq .Values.mode "recovery" }} +│ Source │ {{ printf "%-56s" $source }} │ +{{- end }} +│ Instances │ {{ include (printf "%s%s" "cluster.color-" $redundancyColor) (printf "%-56s" (toString .Values.cluster.instances)) }} │ +│ Backups │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.backups.enabled)) (printf "%-56s" (ternary "Enabled" "Disabled" .Values.backups.enabled)) }} │ +{{- if .Values.backups.enabled }} +│ Backup Provider │ {{ printf "%-56s" (title .Values.backups.provider) }} │ +│ Scheduled Backups │ {{ printf "%-56s" $scheduledBackups }} │ +{{- end }} +│ Storage │ {{ printf "%-56s" .Values.cluster.storage.size }} │ +│ Storage Class │ {{ printf "%-56s" (default "Default" .Values.cluster.storage.storageClass) }} │ +│ PGBouncer │ {{ printf "%-56s" (ternary "Enabled" "Disabled" .Values.pooler.enabled) }} │ +│ Monitoring │ {{ include (printf "%s%s" "cluster.color-" (ternary "ok" "error" .Values.cluster.monitoring.enabled)) (printf "%-56s" (ternary "Enabled" "Disabled" .Values.cluster.monitoring.enabled)) }} │ +╰───────────────────┴──────────────────────────────────────────────────────────╯ {{ if not .Values.backups.enabled }} {{- include "cluster.color-error" "Warning! Backups not enabled. Recovery will not be possible! Do not use this configuration in production.\n" }} diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index eff987354..d099bfc91 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -25,36 +25,45 @@ bootstrap: bootstrap: {{- if eq .Values.recovery.method "pg_basebackup" }} pg_basebackup: - source: {{ .Values.recovery.pgBaseBackup.sourceName }} + source: pgBaseBackupSource + {{ with .Values.recovery.pgBaseBackup.database }} + database: {{ . }} + {{- end }} + {{ with .Values.recovery.pgBaseBackup.owner }} + owner: {{ . }} + {{- end }} + {{ with .Values.recovery.pgBaseBackup.secret }} + secret: + {{- toYaml . | nindent 6 }} + {{- end }} externalClusters: -- name: {{ .Values.recovery.pgBaseBackup.sourceName }} +- name: pgBaseBackupSource connectionParameters: - host: {{ .Values.recovery.pgBaseBackup.sourceHost }} - user: {{ .Values.recovery.pgBaseBackup.sourceUsername }} - {{- if .Values.recovery.pgBaseBackup.TLS.enabled }} - sslmode: verify-full - {{- end }} - {{- if or .Values.recovery.pgBaseBackup.sourcePassword .Values.recovery.pgBaseBackup.existingPasswordSecret }} + host: {{ .Values.recovery.pgBaseBackup.source.host | quote }} + port: {{ .Values.recovery.pgBaseBackup.source.port | quote }} + user: {{ .Values.recovery.pgBaseBackup.source.username | quote }} + dbname: {{ .Values.recovery.pgBaseBackup.source.database | quote }} + sslmode: {{ .Values.recovery.pgBaseBackup.source.sslMode | quote }} + {{- if .Values.recovery.pgBaseBackup.source.passwordSecret.name }} password: - {{- if .Values.recovery.pgBaseBackup.sourcePassword }} - name: {{ include "cluster.fullname" . }}-source-db-password - {{- else }} - name: {{ .Values.recovery.pgBaseBackup.existingPasswordSecret }} - {{- end }} - key: password - {{- else if .Values.recovery.pgBaseBackup.TLS.enabled }} + name: {{ default (printf "%s-pg-basebackup-password" (include "cluster.fullname" .)) .Values.recovery.pgBaseBackup.source.passwordSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.passwordSecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslKeySecret.name }} sslKey: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslKey.key }} + name: {{ .Values.recovery.pgBaseBackup.source.sslKeySecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslKeySecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslCertSecret.name }} sslCert: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslCert.key }} + name: {{ .Values.recovery.pgBaseBackup.source.sslCertSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslCertSecret.key }} + {{- end }} + {{- if .Values.recovery.pgBaseBackup.source.sslRootCertSecret.name }} sslRootCert: - name: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.secretName }} - key: {{ .Values.recovery.pgBaseBackup.TLS.sslRootCert.key }} - {{- else }} - {{ fail "No password or TLS secret defined for pg_basebackup" }} + name: {{ .Values.recovery.pgBaseBackup.source.sslRootCertSecret.name }} + key: {{ .Values.recovery.pgBaseBackup.source.sslRootCertSecret.key }} {{- end }} {{- else }} diff --git a/charts/cluster/templates/recovery-pg_basebackup-password.yaml b/charts/cluster/templates/recovery-pg_basebackup-password.yaml new file mode 100644 index 000000000..456ee75d9 --- /dev/null +++ b/charts/cluster/templates/recovery-pg_basebackup-password.yaml @@ -0,0 +1,8 @@ +{{- if and (eq .Values.mode "recovery") (eq .Values.recovery.method "pg_basebackup") .Values.recovery.pgBaseBackup.source.passwordSecret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ default (printf "%s-pg-basebackup-password" (include "cluster.fullname" .)) .Values.recovery.pgBaseBackup.source.passwordSecret.name }} +data: + {{ .Values.recovery.pgBaseBackup.source.passwordSecret.key }}: {{ required ".Values.recovery.pgBaseBackup.source.passwordSecret.value required when creating a password secret." .Values.recovery.pgBaseBackup.source.passwordSecret.value | b64enc | quote }} +{{- end }} diff --git a/charts/cluster/templates/recovery-pgbasebackup-password.yaml b/charts/cluster/templates/recovery-pgbasebackup-password.yaml deleted file mode 100644 index cd7de5921..000000000 --- a/charts/cluster/templates/recovery-pgbasebackup-password.yaml +++ /dev/null @@ -1,8 +0,0 @@ -{{- if and (eq .Values.mode "recovery") (eq .Values.recovery.method "pg_basebackup") (.Values.recovery.pgBaseBackup.sourcePassword) }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "cluster.fullname" . }}-source-db-password -data: - password: {{ .Values.recovery.pgBaseBackup.sourcePassword | b64enc | quote }} -{{- end }} \ No newline at end of file diff --git a/charts/cluster/values.schema.json b/charts/cluster/values.schema.json index a2354a8f8..1ef5bba79 100644 --- a/charts/cluster/values.schema.json +++ b/charts/cluster/values.schema.json @@ -423,6 +423,90 @@ "method": { "type": "string" }, + "pgBaseBackup": { + "type": "object", + "properties": { + "database": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "database": { + "type": "string" + }, + "host": { + "type": "string" + }, + "passwordSecret": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "port": { + "type": "integer" + }, + "sslCertSecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "sslKeySecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "sslMode": { + "type": "string" + }, + "sslRootCertSecret": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "username": { + "type": "string" + } + } + } + } + }, "pitrTarget": { "type": "object", "properties": { diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 56cde1480..87d32bb72 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -81,29 +81,37 @@ recovery: # -- Name of the backup credentials secret name: "" - # Please see https://cloudnative-pg.io/documentation/1.22/bootstrap/#bootstrap-from-a-live-cluster-pg_basebackup + # See https://cloudnative-pg.io/documentation/1.22/bootstrap/#bootstrap-from-a-live-cluster-pg_basebackup pgBaseBackup: - sourceName: "source-db" - sourceHost: "" - sourceUsername: "streaming_replica" - sourcePassword: "" - # -- The name of an existing secret with the password (must contain key `password`). - # When it's set, the `recovery.pg_basebackup.sourcePassword` parameter is ignored - existingPasswordSecret: "" - TLS: - enabled: false - sslKey: - secretName: "" + database: app + owner: "" # Defaults to the database name + secret: "" # Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + source: + host: "" + port: 5432 + username: "" + database: "app" + sslMode: "verify-full" + passwordSecret: + # -- Whether to create a secret for the password + create: false + # -- Name of the secret containing the password + name: "" + # -- The key in the secret containing the password + key: "password" + # -- The password value to use when creating the secret + value: "" + sslKeySecret: + name: "" key: "" - sslCert: - secretName: "" + sslCertSecret: + name: "" key: "" - sslRootCert: - secretName: "" + sslRootCertSecret: + name: "" key: "" - cluster: # -- Number of instances instances: 3 From 1cc5734fa83e2a090250bb0e9baef7489f80bc81 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 01:48:16 +0300 Subject: [PATCH 5/8] restructered tests Signed-off-by: Itay Grudev --- ...l => 02-pg_basebackup-cluster-assert.yaml} | 0 ...ter.yaml => 02-pg_basebackup-cluster.yaml} | 0 .../03-data_test-assert.yaml | 6 +++++ .../03-data_test.yaml | 23 +++++++++++++++++++ .../chainsaw-test.yaml | 21 +++++++++++++++-- 5 files changed, 48 insertions(+), 2 deletions(-) rename charts/cluster/test/postgresql-pg_basebackup/{01-pg_basebackup-cluster-assert.yaml => 02-pg_basebackup-cluster-assert.yaml} (100%) rename charts/cluster/test/postgresql-pg_basebackup/{01-pg_basebackup-cluster.yaml => 02-pg_basebackup-cluster.yaml} (100%) create mode 100644 charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml create mode 100644 charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster-assert.yaml similarity index 100% rename from charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster-assert.yaml rename to charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster-assert.yaml diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml b/charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster.yaml similarity index 100% rename from charts/cluster/test/postgresql-pg_basebackup/01-pg_basebackup-cluster.yaml rename to charts/cluster/test/postgresql-pg_basebackup/02-pg_basebackup-cluster.yaml diff --git a/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml b/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml new file mode 100644 index 000000000..04df941e4 --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/03-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test +status: + succeeded: 1 diff --git a/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml b/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml new file mode 100644 index 000000000..40eb9029a --- /dev/null +++ b/charts/cluster/test/postgresql-pg_basebackup/03-data_test.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: pg-basebackup-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + test "$(psql "${DB_URI}mygooddb" -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" diff --git a/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml index 065573bdc..85f2d9743 100644 --- a/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml +++ b/charts/cluster/test/postgresql-pg_basebackup/chainsaw-test.yaml @@ -35,13 +35,30 @@ spec: helm upgrade \ --install \ --namespace $NAMESPACE \ - --values ./01-pg_basebackup-cluster.yaml \ + --values ./02-pg_basebackup-cluster.yaml \ --wait \ pg-basebackup ../../ - assert: - file: ./01-pg_basebackup-cluster-assert.yaml + file: ./02-pg_basebackup-cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - name: Verify the data from step 1 exists + try: + - apply: + file: ./03-data_test.yaml + - assert: + file: ./03-data_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test - name: Cleanup try: - script: content: | + helm uninstall --namespace $NAMESPACE source helm uninstall --namespace $NAMESPACE pg-basebackup From 8a0cc318c99165dcb1af40ee35f67141820843f0 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 01:59:00 +0300 Subject: [PATCH 6/8] None message when there are no Scheduled Backups in NOTES.txt Signed-off-by: Itay Grudev --- charts/cluster/templates/NOTES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/charts/cluster/templates/NOTES.txt b/charts/cluster/templates/NOTES.txt index ad1220154..ba44e95df 100644 --- a/charts/cluster/templates/NOTES.txt +++ b/charts/cluster/templates/NOTES.txt @@ -41,6 +41,9 @@ Configuration {{- range (rest .Values.backups.scheduledBackups) -}} {{ $scheduledBackups = printf "%s, %s" $scheduledBackups .name }} {{- end -}} +{{- if eq (len .Values.backups.scheduledBackups) 0 }} + {{- $scheduledBackups = "None" -}} +{{- end -}} {{- $mode := .Values.mode -}} {{- $source := "" -}} From 7c720b827cd05dae0f2c851b5a0904e2368e8379 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 16:48:41 +0300 Subject: [PATCH 7/8] Bug Fix: bootstrap discrepancy Signed-off-by: Itay Grudev --- charts/cluster/templates/_bootstrap.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index d099bfc91..81665d2e2 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -83,7 +83,7 @@ externalClusters: - name: objectStoreRecoveryCluster barmanObjectStore: serverName: {{ .Values.recovery.clusterName }} - {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretSuffix" "-recovery" -}} + {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretPrefix" "recovery" -}} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 4 }} {{- end }} {{- else }} From e19ae5424d77d7d55fd34c69b210c5f3fcf64058 Mon Sep 17 00:00:00 2001 From: Itay Grudev Date: Wed, 28 Aug 2024 17:23:45 +0300 Subject: [PATCH 8/8] Fixed lint issues. Improved documentation Signed-off-by: Itay Grudev --- charts/cluster/values.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 972129f56..0ee58d7da 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -83,9 +83,12 @@ recovery: # See https://cloudnative-pg.io/documentation/1.22/bootstrap/#bootstrap-from-a-live-cluster-pg_basebackup pgBaseBackup: + # -- Name of the database used by the application. Default: `app`. database: app - owner: "" # Defaults to the database name - secret: "" # Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + # -- Name of the owner of the database in the instance to be used by applications. Defaults to the value of the `database` key. + secret: "" + # -- Name of the secret containing the initial credentials for the owner of the user database. If empty a new secret will be created from scratch + owner: "" source: host: "" port: 5432