From e01c1c11d903efab574f7a0273a89af1138f9762 Mon Sep 17 00:00:00 2001 From: Baptiste Guillin Date: Thu, 3 Sep 2026 22:19:38 +0200 Subject: [PATCH] Add off-cluster PostgreSQL backups --- apps/naviya/31-offsite-backup.yaml | 36 ++++++ apps/naviya/32-offsite-cronjob.yaml | 191 ++++++++++++++++++++++++++++ apps/naviya/kustomization.yaml | 2 + 3 files changed, 229 insertions(+) create mode 100644 apps/naviya/31-offsite-backup.yaml create mode 100644 apps/naviya/32-offsite-cronjob.yaml diff --git a/apps/naviya/31-offsite-backup.yaml b/apps/naviya/31-offsite-backup.yaml new file mode 100644 index 0000000..4c887e0 --- /dev/null +++ b/apps/naviya/31-offsite-backup.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: naviya-postgres-backups-offsite +spec: + capacity: + storage: 100Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + mountOptions: + - hard + - nfsvers=4.2 + - timeo=600 + - retrans=2 + - nosuid + - nodev + - noexec + nfs: + server: 172.16.2.252 + path: /Volume1/Backup/naviya-kubernetes/postgresql +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: application-metier-postgres-backups-offsite + namespace: application-metier +spec: + accessModes: + - ReadWriteMany + storageClassName: "" + volumeName: naviya-postgres-backups-offsite + resources: + requests: + storage: 100Gi diff --git a/apps/naviya/32-offsite-cronjob.yaml b/apps/naviya/32-offsite-cronjob.yaml new file mode 100644 index 0000000..b869a38 --- /dev/null +++ b/apps/naviya/32-offsite-cronjob.yaml @@ -0,0 +1,191 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: application-metier-postgres-offsite-backup + namespace: application-metier +spec: + schedule: "47 2 * * *" + timeZone: Etc/UTC + concurrencyPolicy: Forbid + startingDeadlineSeconds: 3600 + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + activeDeadlineSeconds: 1800 + ttlSecondsAfterFinished: 86400 + template: + metadata: + labels: + app.kubernetes.io/name: application-metier + app.kubernetes.io/component: backup + naviya.io/backup-target: offsite + spec: + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 70 + runAsGroup: 70 + fsGroup: 70 + seccompProfile: + type: RuntimeDefault + containers: + - name: pg-dump + image: postgres:17.10-alpine + imagePullPolicy: IfNotPresent + command: ["sh", "-ec"] + args: + - | + umask 077 + + stamp="$(date -u +%Y%m%dT%H%M%SZ)" + name="application-metier-${stamp}.dump" + target="/offsite/daily/${name}" + tmp="${target}.tmp.$$" + checksum="${target}.sha256" + checksum_tmp="${checksum}.tmp.$$" + + cleanup() { + rm -f "$tmp" "$checksum_tmp" + } + trap cleanup EXIT + + pg_dump \ + --format=custom \ + --no-owner \ + --no-acl \ + --file="$tmp" \ + "$DATABASE_URL" + + test -s "$tmp" + pg_restore --list "$tmp" >/dev/null + mv "$tmp" "$target" + + digest="$(sha256sum "$target" | awk '{print $1}')" + + printf '%s %s\n' \ + "$digest" \ + "$name" \ + > "$checksum_tmp" + + mv "$checksum_tmp" "$checksum" + + ( + cd /offsite/daily + sha256sum -c "$(basename "$checksum")" + ) + + pg_restore \ + --file=/dev/null \ + "$target" + + echo "Sauvegarde quotidienne validée : $target" + + copy_tmp="" + copy_checksum_tmp="" + + cleanup() { + rm -f "$tmp" "$checksum_tmp" + + test -z "$copy_tmp" || + rm -f "$copy_tmp" + + test -z "$copy_checksum_tmp" || + rm -f "$copy_checksum_tmp" + } + + copy_archive() { + tier="$1" + tier_dir="/offsite/${tier}" + tier_target="${tier_dir}/${name}" + copy_tmp="${tier_target}.tmp.$$" + tier_checksum="${tier_target}.sha256" + copy_checksum_tmp="${tier_checksum}.tmp.$$" + + cp "$target" "$copy_tmp" + + copy_digest="$(sha256sum "$copy_tmp" | + awk '{print $1}')" + + test "$copy_digest" = "$digest" + mv "$copy_tmp" "$tier_target" + + printf '%s %s\n' \ + "$digest" \ + "$name" \ + > "$copy_checksum_tmp" + + mv \ + "$copy_checksum_tmp" \ + "$tier_checksum" + + ( + cd "$tier_dir" + sha256sum -c "$(basename "$tier_checksum")" + ) + + copy_tmp="" + copy_checksum_tmp="" + + echo "Copie ${tier} validée : $tier_target" + } + + if test "$(date -u +%u)" = "7"; then + copy_archive weekly + fi + + if test "$(date -u +%d)" = "01"; then + copy_archive monthly + fi + + find /offsite/daily \ + -type f \ + \( -name '*.dump' -o -name '*.dump.sha256' \) \ + -mtime +14 \ + -delete + + find /offsite/weekly \ + -type f \ + \( -name '*.dump' -o -name '*.dump.sha256' \) \ + -mtime +56 \ + -delete + + find /offsite/monthly \ + -type f \ + \( -name '*.dump' -o -name '*.dump.sha256' \) \ + -mtime +370 \ + -delete + + find /offsite \ + -type f \ + -name '*.tmp.*' \ + -mtime +1 \ + -delete + + trap - EXIT + echo "Sauvegarde externe terminée avec succès" + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: application-metier-secrets + key: DATABASE_URL + resources: + requests: + cpu: 25m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + volumeMounts: + - name: offsite + mountPath: /offsite + volumes: + - name: offsite + persistentVolumeClaim: + claimName: application-metier-postgres-backups-offsite diff --git a/apps/naviya/kustomization.yaml b/apps/naviya/kustomization.yaml index 0c5be50..42e04cc 100644 --- a/apps/naviya/kustomization.yaml +++ b/apps/naviya/kustomization.yaml @@ -7,6 +7,8 @@ resources: - 10-configmap.yaml - 20-data.yaml - 30-backup.yaml +- 31-offsite-backup.yaml +- 32-offsite-cronjob.yaml - 40-migrate.yaml - 50-api.yaml - 60-frontend.yaml