11 KiB
11 KiB
13 — Backups et reprise après sinistre
Stratégie de backup
| Composant | Méthode | Fréquence | Rétention | Destination |
|---|---|---|---|---|
| PostgreSQL | pg_dump via CronJob |
Quotidien 3h00 | 30 jours | Hetzner Object Storage |
| PostgreSQL WAL | Streaming (si self-hosted) | Continue | 7 jours | Object Storage |
| Redis | RDB snapshot + AOF | Chaque 5 min | 24h | Volume local |
| Secrets Kubernetes | Export manuel chiffré | Avant chaque changement | Illimité | Hors-cluster (coffre) |
| Fichiers S3 | Versioning objet | Permanent | Voir lifecycle | Object Storage |
| Configs K8s | GitOps dans le repo | À chaque commit | Git history | GitHub |
Objectifs :
- RPO (Recovery Point Objective) : 24h max (vous pouvez perdre au plus 24h de données)
- RTO (Recovery Time Objective) : 4h max (vous pouvez reconstruire en moins de 4h)
Backup PostgreSQL — Option A (Neon.tech)
Si vous utilisez Neon.tech, les backups sont automatiques :
- Point-in-time recovery (PITR) sur 7 jours (plan Free) ou 30 jours (plan Pro)
- Pas de CronJob à gérer
Pour créer un backup manuel :
# Installer la CLI Neon
npm install -g neonctl
neonctl auth
# Créer un point de restauration (branch)
neonctl branches create \
--project-id <project_id> \
--name "backup-$(date +%Y%m%d)" \
--parent main
Backup PostgreSQL — Option B (self-hosted)
CronJob Kubernetes de backup
# k8s/backup-postgres-cronjob.yaml
---
apiVersion: v1
kind: Secret
metadata:
name: backup-credentials
namespace: xpeditis-prod
type: Opaque
stringData:
# Même credentials que le backend pour Object Storage
AWS_ACCESS_KEY_ID: "<HETZNER_ACCESS_KEY>"
AWS_SECRET_ACCESS_KEY: "<HETZNER_SECRET_KEY>"
AWS_S3_ENDPOINT: "https://fsn1.your-objectstorage.com"
AWS_S3_BUCKET: "xpeditis-prod"
# Credentials PostgreSQL
PGPASSWORD: "<PG_PASSWORD>"
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
namespace: xpeditis-prod
spec:
schedule: "0 3 * * *" # 3h00 chaque nuit
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: postgres:15-alpine
command:
- /bin/sh
- -c
- |
set -e
echo "=== Démarrage backup PostgreSQL $(date) ==="
# Variables
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="/tmp/xpeditis_${TIMESTAMP}.sql.gz"
S3_KEY="backups/postgres/$(date +%Y/%m)/xpeditis_${TIMESTAMP}.sql.gz"
# Dump PostgreSQL compressé
pg_dump \
-h ${PGHOST} \
-p ${PGPORT:-5432} \
-U ${PGUSER} \
-d ${PGDATABASE} \
--no-password \
--clean \
--if-exists \
--format=custom \
| gzip > ${BACKUP_FILE}
BACKUP_SIZE=$(du -sh ${BACKUP_FILE} | cut -f1)
echo "Dump créé: ${BACKUP_FILE} (${BACKUP_SIZE})"
# Upload vers Hetzner Object Storage
apk add --no-cache aws-cli 2>/dev/null || pip install awscli 2>/dev/null
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
aws s3 cp ${BACKUP_FILE} s3://${AWS_S3_BUCKET}/${S3_KEY} \
--endpoint-url ${AWS_S3_ENDPOINT}
echo "✅ Backup uploadé: s3://${AWS_S3_BUCKET}/${S3_KEY}"
# Nettoyage local
rm ${BACKUP_FILE}
# Vérifier les anciens backups (garder 30 jours)
echo "Backups existants:"
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
aws s3 ls s3://${AWS_S3_BUCKET}/backups/postgres/ \
--endpoint-url ${AWS_S3_ENDPOINT} \
--recursive | tail -10
echo "=== Backup terminé $(date) ==="
env:
- name: PGHOST
value: "10.0.1.100" # IP privée serveur PostgreSQL
- name: PGPORT
value: "5432"
- name: PGUSER
value: "xpeditis"
- name: PGDATABASE
value: "xpeditis_prod"
envFrom:
- secretRef:
name: backup-credentials
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Appliquer
kubectl apply -f k8s/backup-postgres-cronjob.yaml
# Tester manuellement (créer un Job depuis le CronJob)
kubectl create job --from=cronjob/postgres-backup test-backup -n xpeditis-prod
kubectl logs -l job-name=test-backup -n xpeditis-prod -f
# Vérifier que le fichier est arrivé dans S3
aws s3 ls s3://xpeditis-prod/backups/postgres/ \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
--recursive
Procédure de restauration PostgreSQL
Restauration complète (catastrophe totale)
# Étape 1 : Lister les backups disponibles
aws s3 ls s3://xpeditis-prod/backups/postgres/ \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
--recursive | sort -r | head -10
# Étape 2 : Télécharger le backup le plus récent
aws s3 cp \
s3://xpeditis-prod/backups/postgres/2026/03/xpeditis_20260323_030001.sql.gz \
/tmp/restore.sql.gz \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# Étape 3 : Décompresser et restaurer
# ⚠️ Cette commande EFFACE les données existantes
gunzip -c /tmp/restore.sql.gz | pg_restore \
-h <POSTGRES_HOST> \
-U xpeditis \
-d xpeditis_prod \
--clean \
--if-exists \
--no-privileges \
--no-owner
# Étape 4 : Vérifier l'intégrité
psql -h <POSTGRES_HOST> -U xpeditis -d xpeditis_prod \
-c "SELECT COUNT(*) as bookings FROM bookings;"
psql -h <POSTGRES_HOST> -U xpeditis -d xpeditis_prod \
-c "SELECT COUNT(*) as users FROM users;"
# Étape 5 : Redémarrer les pods pour reconnecter
kubectl rollout restart deployment/xpeditis-backend -n xpeditis-prod
Backup des Secrets Kubernetes
Les secrets ne sont pas dans Git (intentionnel). Sauvegardez-les chiffrés.
#!/bin/bash
# scripts/backup-secrets.sh
set -e
BACKUP_DIR="$HOME/.xpeditis-secrets-backup"
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)
# Exporter les secrets (encodés base64)
kubectl get secret backend-secrets -n xpeditis-prod -o yaml > /tmp/backend-secrets-${DATE}.yaml
kubectl get secret frontend-secrets -n xpeditis-prod -o yaml > /tmp/frontend-secrets-${DATE}.yaml
kubectl get secret ghcr-credentials -n xpeditis-prod -o yaml > /tmp/ghcr-creds-${DATE}.yaml
# Chiffrer avec GPG (ou utiliser un password)
tar czf - /tmp/*-${DATE}.yaml | gpg --symmetric --cipher-algo AES256 \
> "${BACKUP_DIR}/k8s-secrets-${DATE}.tar.gz.gpg"
# Nettoyage des fichiers temporaires
rm /tmp/*-${DATE}.yaml
echo "✅ Secrets sauvegardés dans ${BACKUP_DIR}/k8s-secrets-${DATE}.tar.gz.gpg"
# Lister les backups existants
ls -la "$BACKUP_DIR"/
# Restaurer les secrets depuis un backup
gpg --decrypt "${BACKUP_DIR}/k8s-secrets-20260323_120000.tar.gz.gpg" | tar xzf -
kubectl apply -f /tmp/backend-secrets-20260323_120000.yaml
Runbook — Reprise après sinistre complète
Procédure si vous perdez tout le cluster (serveurs détruits) :
Étape 1 : Recréer l'infrastructure (30 min)
# 1. Recréer le réseau
hcloud network create --name xpeditis-network --ip-range 10.0.0.0/16
hcloud network add-subnet xpeditis-network --type cloud --network-zone eu-central --ip-range 10.0.1.0/24
# 2. Recréer le firewall
# (répéter les commandes du doc 03)
# 3. Recréer le cluster k3s
hetzner-k3s create --config ~/.xpeditis/cluster.yaml
# 4. Configurer kubectl
export KUBECONFIG=~/.kube/kubeconfig-xpeditis-prod
Étape 2 : Restaurer les secrets (15 min)
# Créer le namespace
kubectl apply -f k8s/00-namespaces.yaml
# Restaurer les secrets depuis le backup chiffré
gpg --decrypt "$HOME/.xpeditis-secrets-backup/k8s-secrets-XXXXXXXX.tar.gz.gpg" | tar xzf -
kubectl apply -f /tmp/backend-secrets-*.yaml
kubectl apply -f /tmp/frontend-secrets-*.yaml
kubectl apply -f /tmp/ghcr-creds-*.yaml
# Recréer le secret GHCR
kubectl create secret docker-registry ghcr-credentials \
--namespace xpeditis-prod \
--docker-server=ghcr.io \
--docker-username=<GITHUB_USERNAME> \
--docker-password=<GITHUB_PAT>
Étape 3 : Restaurer les services (15 min)
# Installer cert-manager
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.15.3 --set installCRDs=true
kubectl apply -f /tmp/cluster-issuers.yaml
# Déployer l'application
kubectl apply -f k8s/
# Attendre
kubectl rollout status deployment/xpeditis-backend -n xpeditis-prod --timeout=300s
Étape 4 : Restaurer la base de données (30 min)
# Si PostgreSQL self-hosted :
# (Recréer le serveur PostgreSQL si nécessaire, doc 07)
# Puis restaurer depuis le backup S3
# Télécharger le backup le plus récent
LATEST=$(aws s3 ls s3://xpeditis-prod/backups/postgres/ \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
--recursive | sort -r | head -1 | awk '{print $4}')
aws s3 cp s3://xpeditis-prod/$LATEST /tmp/restore.sql.gz \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# Restaurer
gunzip -c /tmp/restore.sql.gz | pg_restore \
-h <POSTGRES_HOST> -U xpeditis -d xpeditis_prod \
--clean --if-exists --no-privileges --no-owner
Étape 5 : Vérification finale (15 min)
# Health checks
curl https://api.xpeditis.com/api/v1/health
curl https://app.xpeditis.com/
# Test login
curl -X POST https://api.xpeditis.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@test.com","password":"test"}' | jq .
# Vérifier les données
kubectl exec -it deployment/xpeditis-backend -n xpeditis-prod -- \
node -e "console.log('Database OK')"
echo "✅ Système opérationnel. RTO: $(date)"
Test régulier des backups (mensuel)
#!/bin/bash
# scripts/test-backup-restore.sh
# À exécuter en environnement de test, JAMAIS en production
echo "🧪 Test de restauration du backup PostgreSQL"
# 1. Créer une DB de test
psql -h <TEST_HOST> -U postgres -c "CREATE DATABASE xpeditis_restore_test;"
# 2. Télécharger le dernier backup
LATEST=$(aws s3 ls s3://xpeditis-prod/backups/postgres/ \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com \
--recursive | sort -r | head -1 | awk '{print $4}')
aws s3 cp s3://xpeditis-prod/$LATEST /tmp/test-restore.sql.gz \
--profile hetzner \
--endpoint-url https://fsn1.your-objectstorage.com
# 3. Restaurer dans la DB de test
gunzip -c /tmp/test-restore.sql.gz | pg_restore \
-h <TEST_HOST> -U postgres -d xpeditis_restore_test
# 4. Vérifier
BOOKING_COUNT=$(psql -h <TEST_HOST> -U postgres -d xpeditis_restore_test \
-t -c "SELECT COUNT(*) FROM bookings;" | xargs)
echo "✅ Restauration réussie. Nombre de bookings: $BOOKING_COUNT"
# 5. Nettoyage
psql -h <TEST_HOST> -U postgres -c "DROP DATABASE xpeditis_restore_test;"
rm /tmp/test-restore.sql.gz
echo "✅ Test de backup/restore réussi le $(date)"