From 6cf6ec1608625af38462220911d0cf57aca64468 Mon Sep 17 00:00:00 2001 From: Cam Smith Date: Thu, 25 Jul 2024 10:45:24 +1200 Subject: [PATCH 1/3] Move VolumeSnapshot config up to 'provider' level. This fixes issues with using volumeSnapshots where the Cluster object didn't have an appropriate [VolumeSnapshotConfiguration](https://cloudnative-pg.io/documentation/1.23/cloudnative-pg.v1/#postgresql-cnpg-io-v1-VolumeSnapshotConfiguration) block. It also prevents the chart complaining that we haven't set azure/s3/google specific variables when we have chosen to use VolumeSnapshots. Signed-off-by: Cam Smith --- charts/cluster/README.md | 4 ++-- charts/cluster/templates/_backup.tpl | 6 ++++++ charts/cluster/templates/scheduled-backups.yaml | 7 ++++++- charts/cluster/values.schema.json | 11 ++++++++--- charts/cluster/values.yaml | 6 +++--- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/charts/cluster/README.md b/charts/cluster/README.md index 621a8442c..a08889abd 100644 --- a/charts/cluster/README.md +++ b/charts/cluster/README.md @@ -132,7 +132,7 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | backups.google.bucket | string | `""` | | | backups.google.gkeEnvironment | bool | `false` | | | backups.google.path | string | `"/"` | | -| backups.provider | string | `"s3"` | One of `s3`, `azure` or `google` | +| backups.provider | string | `"s3"` | One of `s3`, `azure`, `google`, or `volumeSnapshot` | | backups.retentionPolicy | string | `"30d"` | Retention policy for backups | | backups.s3.accessKey | string | `""` | | | backups.s3.bucket | string | `""` | | @@ -140,11 +140,11 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | backups.s3.region | string | `""` | | | backups.s3.secretKey | string | `""` | | | backups.scheduledBackups[0].backupOwnerReference | string | `"self"` | Backup owner reference | -| backups.scheduledBackups[0].method | string | `"barmanObjectStore"` | Backup method, can be `barmanObjectStore` (default) or `volumeSnapshot` | | backups.scheduledBackups[0].name | string | `"daily-backup"` | Scheduled backup name | | backups.scheduledBackups[0].schedule | string | `"0 0 0 * * *"` | Schedule in cron format | | backups.secret.create | bool | `true` | Whether to create a secret for the backup credentials | | backups.secret.name | string | `""` | Name of the backup credentials secret | +| backups.volumeSnapshot.className | string | `""` | The VolumeSnapshotClass to use for backups if provider is `volumeSnapshot` | | backups.wal.compression | string | `"gzip"` | WAL compression method. One of `` (for no compression), `gzip`, `bzip2` or `snappy`. | | backups.wal.encryption | string | `"AES256"` | Whether to instruct the storage provider to encrypt WAL files. One of `` (use the storage container default), `AES256` or `aws:kms`. | | backups.wal.maxParallel | int | `1` | Number of WAL files to be archived or restored in parallel. | diff --git a/charts/cluster/templates/_backup.tpl b/charts/cluster/templates/_backup.tpl index 8059618c4..bc2034471 100644 --- a/charts/cluster/templates/_backup.tpl +++ b/charts/cluster/templates/_backup.tpl @@ -3,6 +3,7 @@ backup: target: "prefer-standby" retentionPolicy: {{ .Values.backups.retentionPolicy }} + {{- if has .Values.backups.provider (list "s3" "azure" "google") }} barmanObjectStore: wal: compression: {{ .Values.backups.wal.compression }} @@ -15,5 +16,10 @@ backup: {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.backups "secretPrefix" "backup" }} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 2 }} + {{- else if eq .Values.backups.provider "volumeSnapshot" }} + volumeSnapshot: + online: true + className: {{ .Values.backups.volumeSnapshot.className }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/cluster/templates/scheduled-backups.yaml b/charts/cluster/templates/scheduled-backups.yaml index 850c27940..96133b543 100644 --- a/charts/cluster/templates/scheduled-backups.yaml +++ b/charts/cluster/templates/scheduled-backups.yaml @@ -1,4 +1,5 @@ {{ if .Values.backups.enabled }} +{{ $provider := .Values.backups.provider }} {{ $context := . -}} {{ range .Values.backups.scheduledBackups -}} --- @@ -10,7 +11,11 @@ metadata: spec: immediate: true schedule: {{ .schedule | quote }} - method: {{ .method }} + {{- if eq $provider "volumeSnapshot" }} + method: volumeSnapshot + {{- else }} + method: barmanObjectStore + {{- end }} backupOwnerReference: {{ .backupOwnerReference }} cluster: name: {{ include "cluster.fullname" $context }} diff --git a/charts/cluster/values.schema.json b/charts/cluster/values.schema.json index 9bcf7b4b3..ff1bbc398 100644 --- a/charts/cluster/values.schema.json +++ b/charts/cluster/values.schema.json @@ -125,9 +125,6 @@ "backupOwnerReference": { "type": "string" }, - "method": { - "type": "string" - }, "name": { "type": "string" }, @@ -148,6 +145,14 @@ } } }, + "volumeSnapshot": { + "type": "object", + "properties": { + "classname": { + "type": "string" + } + } + }, "wal": { "type": "object", "properties": { diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index c4a48d232..8c9e7e340 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -229,7 +229,7 @@ backups: # Azure: https://..core.windows.net/ # Google: gs:// destinationPath: "" - # -- One of `s3`, `azure` or `google` + # -- One of `s3`, `azure`, `google`, or `volumeSnapshot` provider: s3 s3: region: "" @@ -251,6 +251,8 @@ backups: bucket: "" gkeEnvironment: false applicationCredentials: "" + volumeSnapshot: + className: "" secret: # -- Whether to create a secret for the backup credentials create: true @@ -280,8 +282,6 @@ backups: schedule: "0 0 0 * * *" # -- Backup owner reference backupOwnerReference: self - # -- Backup method, can be `barmanObjectStore` (default) or `volumeSnapshot` - method: barmanObjectStore # -- Retention policy for backups retentionPolicy: "30d" From 5f1a1b2f3ad90c3a4bac00e62109a996cd19f491 Mon Sep 17 00:00:00 2001 From: Cam Smith Date: Thu, 25 Jul 2024 10:49:45 +1200 Subject: [PATCH 2/3] Add an example for backups via VolumeSnapshot. Signed-off-by: Cam Smith --- .../cluster/examples/backups-volume-snapshot.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 charts/cluster/examples/backups-volume-snapshot.yaml diff --git a/charts/cluster/examples/backups-volume-snapshot.yaml b/charts/cluster/examples/backups-volume-snapshot.yaml new file mode 100644 index 000000000..a59d02f05 --- /dev/null +++ b/charts/cluster/examples/backups-volume-snapshot.yaml @@ -0,0 +1,14 @@ +cluster: + instances: 1 + +backups: + enabled: true + provider: volumeSnapshot + volumeSnapshot: + # Points to our snapshot class, see https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes/ + className: "my-custom-snapshot-class" + scheduledBackups: + - name: daily-backup # Daily at midnight + schedule: "0 0 0 * * *" # Daily at midnight + backupOwnerReference: self + retentionPolicy: "30d" \ No newline at end of file From fe5cd70376f9a1dc08c51e7804296c0df8ccd050 Mon Sep 17 00:00:00 2001 From: Cam Smith Date: Fri, 26 Jul 2024 14:42:06 +1200 Subject: [PATCH 3/3] Add support for recovery from VolumeSnapshot, example + docs. Signed-off-by: Cam Smith --- charts/cluster/README.md | 4 +++- .../cluster/examples/backups-volume-snapshot.yaml | 4 ++++ .../cluster/examples/recovery-volume-snapshot.yaml | 14 ++++++++++++++ charts/cluster/templates/_bootstrap.tpl | 13 +++++++++++++ charts/cluster/values.schema.json | 11 +++++++++++ charts/cluster/values.yaml | 7 ++++++- 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 charts/cluster/examples/recovery-volume-snapshot.yaml diff --git a/charts/cluster/README.md b/charts/cluster/README.md index a08889abd..aae456c6b 100644 --- a/charts/cluster/README.md +++ b/charts/cluster/README.md @@ -204,7 +204,7 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.google.bucket | string | `""` | | | 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.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. * `volumeSnapshot` - Recovers a CNPG cluster from a volume snapshot. | | recovery.pitrTarget.time | string | `""` | Time in RFC3339 format | | recovery.provider | string | `"s3"` | One of `s3`, `azure` or `google` | | recovery.s3.accessKey | string | `""` | | @@ -214,6 +214,8 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.s3.secretKey | string | `""` | | | recovery.secret.create | bool | `true` | Whether to create a secret for the backup credentials | | recovery.secret.name | string | `""` | Name of the backup credentials secret | +| recovery.volumeSnapsnot.storageSnapshotName | string | "" | The name of the snapshot to recover from | +| recovery.volumeSnapsnot.walSnapshotName | string | "" | The name of the snapshot that holds the Write Ahead Log | | type | string | `"postgresql"` | Type of the CNPG database. Available types: * `postgresql` * `postgis` | ## Maintainers diff --git a/charts/cluster/examples/backups-volume-snapshot.yaml b/charts/cluster/examples/backups-volume-snapshot.yaml index a59d02f05..532a2e987 100644 --- a/charts/cluster/examples/backups-volume-snapshot.yaml +++ b/charts/cluster/examples/backups-volume-snapshot.yaml @@ -1,6 +1,10 @@ cluster: instances: 1 +storage: + # This storage class uses the CSI driver which allows for volume snapshots + storageClass: "my-storage-class" + backups: enabled: true provider: volumeSnapshot diff --git a/charts/cluster/examples/recovery-volume-snapshot.yaml b/charts/cluster/examples/recovery-volume-snapshot.yaml new file mode 100644 index 000000000..1747c361f --- /dev/null +++ b/charts/cluster/examples/recovery-volume-snapshot.yaml @@ -0,0 +1,14 @@ +mode: recovery + +cluster: + instances: 1 + storage: + # This storage class uses the CSI driver which allows for volume snapshots + storageClass: "my-storage-class" + +recovery: + method: volumeSnapshot + volumeSnapshot: + storageSnapshotName: "example-cluster-daily-backup-20240726021627" + # Note the `-wal` suffix + walSnapshotName: "example-cluster-daily-backup-20240726021627-wal" \ No newline at end of file diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index cd800bd3b..6c3f01f37 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -33,14 +33,27 @@ bootstrap: name: {{ .Values.recovery.backupName }} {{- else if eq .Values.recovery.method "object_store" }} source: objectStoreRecoveryCluster + {{- else if eq .Values.recovery.method "volumeSnapshot" }} + volumeSnapshots: + storage: + apiGroup: snapshot.storage.k8s.io + kind: VolumeSnapshot + name: {{ .Values.recovery.volumeSnapshot.storageSnapshotName }} + walStorage: + apiGroup: snapshot.storage.k8s.io + kind: VolumeSnapshot + name: {{ .Values.recovery.volumeSnapshot.walSnapshotName }} {{- end }} +{{- if eq .Values.recovery.method "object_store" }} externalClusters: - name: objectStoreRecoveryCluster barmanObjectStore: serverName: {{ default (include "cluster.fullname" .) .Values.recovery.clusterName }} {{- $d := dict "chartFullname" (include "cluster.fullname" .) "scope" .Values.recovery "secretPrefix" "recovery" -}} {{- include "cluster.barmanObjectStoreConfig" $d | nindent 4 }} +{{- end }} + {{- else }} {{ fail "Invalid cluster mode!" }} {{- end }} diff --git a/charts/cluster/values.schema.json b/charts/cluster/values.schema.json index ff1bbc398..58f48967e 100644 --- a/charts/cluster/values.schema.json +++ b/charts/cluster/values.schema.json @@ -469,6 +469,17 @@ "type": "string" } } + }, + "volumeSnapshot": { + "type": "object", + "properties": { + "storageSnapshotName": { + "type": "string" + }, + "walSnapshotName": { + "type": "string" + } + } } } }, diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index 8c9e7e340..760cb201d 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -22,7 +22,8 @@ recovery: # * `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 + # migrate databases to CloudNativePG, even from outside Kubernetes. + # * `volumeSnapshot` - Recovers a CNPG cluster from a volume snapshot. method: backup ## -- Point in time recovery target. Specify one of the following: @@ -81,6 +82,10 @@ recovery: # -- Name of the backup credentials secret name: "" + volumeSnapshot: + storageSnapshotName: "" + walSnapshotName: "" + cluster: # -- Number of instances