From d70eecc0964ee6721c4f6404bfb9247c9fb51a1a Mon Sep 17 00:00:00 2001 From: Alex Lebens Date: Mon, 22 Dec 2025 22:46:37 -0600 Subject: [PATCH] bundle external secrets for backups --- charts/postgres-cluster/Chart.yaml | 2 +- charts/postgres-cluster/README.md | 7 +-- .../postgres-cluster/templates/_helpers.tpl | 28 +++++++++++- .../templates/external-secret.yaml | 44 +++++++++++++++++++ .../templates/object-store.yaml | 16 +++---- charts/postgres-cluster/values.yaml | 31 +++++++++---- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/charts/postgres-cluster/Chart.yaml b/charts/postgres-cluster/Chart.yaml index 58a3e93..c71a4d5 100644 --- a/charts/postgres-cluster/Chart.yaml +++ b/charts/postgres-cluster/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: postgres-cluster -version: 7.2.1 +version: 7.3.0 description: Cloudnative-pg Cluster keywords: - database diff --git a/charts/postgres-cluster/README.md b/charts/postgres-cluster/README.md index 88b3478..66b8630 100644 --- a/charts/postgres-cluster/README.md +++ b/charts/postgres-cluster/README.md @@ -1,6 +1,6 @@ # postgres-cluster -![Version: 7.2.1](https://img.shields.io/badge/Version-7.2.1-informational?style=flat-square) ![AppVersion: v1.28.0](https://img.shields.io/badge/AppVersion-v1.28.0-informational?style=flat-square) +![Version: 7.3.0](https://img.shields.io/badge/Version-7.3.0-informational?style=flat-square) ![AppVersion: v1.28.0](https://img.shields.io/badge/AppVersion-v1.28.0-informational?style=flat-square) Cloudnative-pg Cluster @@ -19,9 +19,10 @@ Cloudnative-pg Cluster | Key | Type | Default | Description | |-----|------|---------|-------------| -| backup | object | `{"method":"objectStore","objectStore":[],"scheduledBackups":[]}` | Backup settings | +| backup | object | `{"externalSecret":{"enabled":true},"method":"objectStore","objectStore":[{"destinationBucket":"postres-backups","externalSecretCredentialPath":"/garage/home-infra/postgres-backups","index":1,"isWALArchiver":true,"name":"garage-local","retentionPolicy":"3d"}],"scheduledBackups":[]}` | Backup settings | +| backup.externalSecret | object | `{"enabled":true}` | Use generated External Secrets, credentialPath points at path in cluster store that contains the keys ACCESS_KEY_ID and ACCESS_SECRET_KEY | | backup.method | string | `"objectStore"` | Method to create backups, options currently are only objectStore | -| backup.objectStore | list | `[]` | Options for object store backups | +| backup.objectStore | list | `[{"destinationBucket":"postres-backups","externalSecretCredentialPath":"/garage/home-infra/postgres-backups","index":1,"isWALArchiver":true,"name":"garage-local","retentionPolicy":"3d"}]` | Options for object store backups | | backup.scheduledBackups | list | `[]` | List of scheduled backups | | cluster | object | `{"additionalLabels":{},"affinity":{"enablePodAntiAffinity":true,"topologyKey":"kubernetes.io/hostname"},"annotations":{},"certificates":{},"enablePDB":true,"enableSuperuserAccess":false,"image":{"repository":"ghcr.io/cloudnative-pg/postgresql","tag":"18.1-standard-trixie"},"imagePullPolicy":"IfNotPresent","imagePullSecrets":[],"initdb":{"database":"app","owner":"app"},"instances":3,"logLevel":"info","monitoring":{"customQueries":[],"customQueriesSecret":[],"disableDefaultQueries":false,"enabled":true,"podMonitor":{"enabled":true,"metricRelabelings":[],"relabelings":[]},"prometheusRule":{"enabled":true,"excludeRules":["CNPGClusterLastFailedArchiveTimeWarning"]}},"postgresGID":-1,"postgresUID":-1,"postgresql":{"ldap":{},"parameters":{"hot_standby_feedback":"on","max_slot_wal_keep_size":"2000MB","shared_buffers":"128MB"},"pg_hba":[],"pg_ident":[],"shared_preload_libraries":[],"synchronous":{}},"primaryUpdateMethod":"switchover","primaryUpdateStrategy":"unsupervised","priorityClassName":"","resources":{"limits":{"hugepages-2Mi":"256Mi"},"requests":{"cpu":"100m","memory":"256Mi"}},"roles":[],"serviceAccountTemplate":{},"services":{},"storage":{"size":"10Gi","storageClass":"local-path"},"superuserSecret":"","walStorage":{"enabled":true,"size":"2Gi","storageClass":"local-path"}}` | Cluster settings | | cluster.affinity | object | `{"enablePodAntiAffinity":true,"topologyKey":"kubernetes.io/hostname"}` | Affinity/Anti-affinity rules for Pods. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-AffinityConfiguration | diff --git a/charts/postgres-cluster/templates/_helpers.tpl b/charts/postgres-cluster/templates/_helpers.tpl index bc8c210..8289718 100644 --- a/charts/postgres-cluster/templates/_helpers.tpl +++ b/charts/postgres-cluster/templates/_helpers.tpl @@ -91,7 +91,7 @@ Generate recovery destination path {{- if .Values.recovery.objectStore.destinationPathOverride -}} {{- .Values.recovery.objectStore.destinationPathOverride -}} {{- else -}} - {{- printf "s3://%s/%s/%s/%s" (.Values.recovery.objectStore.destinationBucket) (.Values.kubernetesClusterName) (include "cluster.namespace" .) (include "cluster.name" .) | trunc 63 | trimSuffix "-" -}} + {{- printf "s3://%s/%s/%s/%s-cluster" (.Values.recovery.objectStore.destinationBucket) (.Values.kubernetesClusterName) (include "cluster.namespace" .) (include "cluster.name" .) | trimSuffix "-" -}} {{- end }} {{- end }} @@ -105,3 +105,29 @@ Generate recovery credentials name {{- printf "%s-recovery-secret" (include "cluster.name" .) -}} {{- end }} {{- end }} + +{{/* +Generate backup destination path +*/}} +{{- define "cluster.backupDestinationPath" -}} + {{- if .instance.destinationPathOverride -}} + {{- .instance.destinationPathOverride -}} + {{- else if .instance.destinationBucket -}} + {{- printf "s3://%s/%s/%s/%s-cluster" .instance.destinationBucket .global.Values.kubernetesClusterName (include "cluster.namespace" .global) (include "cluster.name" .global) | trimSuffix "-" -}} + {{- else -}} + {{ fail "Invalid destination path!" }} + {{- end -}} +{{- end }} + +{{/* +Generate backup destination path +*/}} +{{- define "cluster.backupSecretName" -}} + {{- if .instance.endpointCredentialsOverride -}} + {{- .instance.endpointCredentialsOverride -}} + {{- else if .instance.name -}} + {{- printf "%s-backup-%s-secret" (include "cluster.name" .global) .instance.name | trunc 63 | trimSuffix "-" -}} + {{- else -}} + {{ fail "Invalid backup secret name!" }} + {{- end -}} +{{- end }} diff --git a/charts/postgres-cluster/templates/external-secret.yaml b/charts/postgres-cluster/templates/external-secret.yaml index 1c6312a..f11b55c 100644 --- a/charts/postgres-cluster/templates/external-secret.yaml +++ b/charts/postgres-cluster/templates/external-secret.yaml @@ -1,3 +1,47 @@ +{{ if and (eq .Values.backup.method "objectStore") (.Values.backup.externalSecret.enabled) }} +{{ $context := . -}} +{{ range .Values.backup.objectStore -}} +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ include "cluster.backupSecretName" (dict "instance" . "global" $context) }} + namespace: {{ include "cluster.namespace" $context }} + labels: + {{- include "cluster.labels" $context | nindent 4 }} + app.kubernetes.io/name: {{ include "cluster.backupSecretName" (dict "instance" . "global" $context) }} + {{- with $context.Values.cluster.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + secretStoreRef: + kind: ClusterSecretStore + name: vault + data: + - secretKey: ACCESS_REGION + remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: {{ .externalSecretCredentialPath | required "External Secret Credential local path is required" }} + metadataPolicy: None + property: ACCESS_REGION + - secretKey: ACCESS_KEY_ID + remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: {{ .externalSecretCredentialPath | required "External Secret Credential local path is required" }} + metadataPolicy: None + property: ACCESS_KEY_ID + - secretKey: ACCESS_SECRET_KEY + remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: {{ .externalSecretCredentialPath| required "External Secret Credential local path is required" }} + metadataPolicy: None + property: ACCESS_SECRET_KEY +{{ end -}} +{{ end }} + {{- if and (eq .Values.recovery.method "objectStore") (.Values.recovery.objectStore.externalSecret.enabled) }} --- apiVersion: external-secrets.io/v1 diff --git a/charts/postgres-cluster/templates/object-store.yaml b/charts/postgres-cluster/templates/object-store.yaml index 3ca034d..4270ceb 100644 --- a/charts/postgres-cluster/templates/object-store.yaml +++ b/charts/postgres-cluster/templates/object-store.yaml @@ -5,19 +5,19 @@ apiVersion: barmancloud.cnpg.io/v1 kind: ObjectStore metadata: - name: "{{ include "cluster.name" $context }}-{{ .name }}-backup" + name: {{ include "cluster.name" $context }}-backup-{{ .name }} namespace: {{ include "cluster.namespace" $context }} labels: {{- include "cluster.labels" $context | nindent 4 }} - app.kubernetes.io/name: "{{ include "cluster.name" $context }}-{{ .name }}-backup" - {{- with .Values.cluster.additionalLabels }} + app.kubernetes.io/name: {{ include "cluster.name" $context }}-backup-{{ .name }} + {{- with $context.Values.cluster.additionalLabels }} {{- toYaml . | nindent 4 }} {{- end }} spec: retentionPolicy: {{ .retentionPolicy | default "30d" }} configuration: - destinationPath: {{ .destinationPath | required "Destination path is required" }} - endpointURL: {{ .endpointURL | default "https://nyc3.digitaloceanspaces.com" }} + destinationPath: {{ include "cluster.backupDestinationPath" (dict "instance" . "global" $context) }} + endpointURL: {{ .endpointURL | default "http://garage-main.garage:3900" }} {{- if .endpointCA }} endpointCA: name: {{ .endpointCA.name }} @@ -41,14 +41,14 @@ spec: {{- end }} s3Credentials: accessKeyId: - name: {{ .endpointCredentials | default (printf "%s-cluster-backup-secret" (include "cluster.name" $context) | trunc 63 | trimSuffix "-") }} + name: {{ include "cluster.backupSecretName" (dict "instance" . "global" $context) }} key: ACCESS_KEY_ID secretAccessKey: - name: {{ .endpointCredentials | default (printf "%s-cluster-backup-secret" (include "cluster.name" $context) | trunc 63 | trimSuffix "-") }} + name: {{ include "cluster.backupSecretName" (dict "instance" . "global" $context) }} key: ACCESS_SECRET_KEY {{- if .endpointCredentialsIncludeRegion }} region: - name: {{ .endpointCredentials | default (printf "%s-cluster-backup-secret" (include "cluster.name" $context) | trunc 63 | trimSuffix "-") }} + name: {{ include "cluster.backupSecretName" (dict "instance" . "global" $context) }} key: ACCESS_REGION {{- end }} {{ end -}} diff --git a/charts/postgres-cluster/values.yaml b/charts/postgres-cluster/values.yaml index 25fb942..5266c51 100644 --- a/charts/postgres-cluster/values.yaml +++ b/charts/postgres-cluster/values.yaml @@ -432,23 +432,42 @@ backup: # -- Method to create backups, options currently are only objectStore method: objectStore + # -- Use generated External Secrets, credentialPath points at path in cluster store that contains the keys ACCESS_KEY_ID and ACCESS_SECRET_KEY + externalSecret: + enabled: true + # -- Options for object store backups - objectStore: [] + objectStore: + - name: garage-local + index: 1 + retentionPolicy: "3d" + destinationBucket: postres-backups + externalSecretCredentialPath: /garage/home-infra/postgres-backups + isWALArchiver: true # - # # -- Object store backup name # name: external + # # -- Desitination bucket + # destinationBucket: postgres-backups + # # -- Overrides the provider specific default path. Defaults to: # # S3: s3:// # # Azure: https://..core.windows.net/ # # Google: gs:// - # destinationPath: "" + # destinationPathOverride: "" # # -- Overrides the provider specific default endpoint. Defaults to: - # # https://nyc3.digitaloceanspaces.com + # # http://garage-main.garage:3900 # endpointURL: "" + # # -- Override secret name that contains S3 credentials, should contain the keys ACCESS_KEY_ID and ACCESS_SECRET_KEY + # endpointCredentialsOverride: "" + + # # -- Path points at path in cluster store that contains the keys ACCESS_KEY_ID and ACCESS_SECRET_KEY + # externalSecretCredentialPath + # # -- Specifies a CA bundle to validate a privately signed certificate. # endpointCA: # # -- Creates a secret with the given value if true, otherwise uses an existing secret. @@ -460,12 +479,6 @@ backup: # # -- Generate external cluster name, uses: {{ .Release.Name }}-postgresql--backup-index-{{ index }} # index: 1 - # # -- Override the name of the backup cluster, defaults to "cluster.name" - # clusterName: "" - - # # -- Specifies secret that contains S3 credentials, should contain the keys ACCESS_KEY_ID and ACCESS_SECRET_KEY - # endpointCredentials: "" - # # -- Retention policy for backups # retentionPolicy: "30d"