← Retour au blog

Velero + Restic : stratégie de backup pour clusters K3s

Pourquoi Velero est devenu notre standard de backup K3s, comment on l'orchestre avec Restic pour les volumes, et les hooks SQL qu'il faut absolument écrire pour que la restauration fonctionne pour de vrai.

Un cluster K3s sans stratégie de backup, c’est une bombe à retardement. Pas une question de “si”, mais de “quand”. On a vu suffisamment de clients perdre des données pour avoir une discipline non négociable : Velero + Restic, sur tous les clusters de prod, sans exception. Voici notre stack et nos retours.

Le problème avec les “backups Kubernetes”

Beaucoup d’équipes pensent qu’un backup K8s, c’est etcdctl snapshot save et basta. Erreur. Un snapshot etcd sauvegarde la définition des ressources (deployments, services, configmaps), mais pas les données qui vivent dans les volumes persistants.

Si vous restaurez un etcd snapshot sur un cluster reconstitué, vous avez les manifests mais zéro donnée applicative. Les pods seront créés, ils chercheront leurs PVCs, et ils trouveront du vide.

Le vrai backup K8s couvre trois couches :

  1. Les ressources cluster (manifests, secrets, configmaps)
  2. Les volumes persistants (PVs, donc les fichiers)
  3. La cohérence applicative (une BDD doit être dumpée à un état stable)

C’est exactement ce que Velero fait, à condition de bien le configurer.

Pourquoi Velero plutôt qu’un autre

On a évalué Kasten K10 (excellent mais onéreux), Stash (abandonné par AppsCode pour Stash by KubeDB), TrilioVault, et des solutions DIY basées sur restic + cron. Velero gagne pour quatre raisons :

  • Open source CNCF (sandbox, mais tracking actif)
  • CRDs déclaratifs : Backup, Schedule, Restore — tout est en YAML, donc versionnable et GitOpsable
  • Plugins providers : S3, Azure Blob, GCS, OVH Object Storage, MinIO local
  • File System Backup natif depuis Velero 1.10, qui utilise restic ou kopia sous le capot pour les volumes

Le surcoût de licence : zéro. Le coût opérationnel : ~30 minutes d’install la première fois.

Notre stack actuelle

Sur tous nos clusters de prod :

# velero install (résumé)
velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.9.0 \
  --bucket k3s-backups-prod \
  --backup-location-config region=gra,s3ForcePathStyle=true,s3Url=https://s3.gra.cloud.ovh.net \
  --snapshot-location-config region=gra \
  --secret-file ./credentials-velero \
  --use-node-agent \
  --uploader-type restic \
  --default-volumes-to-fs-backup

Points clés :

  • OVH Object Storage comme cible : data en France, pas de lock-in AWS, prix raisonnable (~ 8 € / mois pour 1 To stocké)
  • --use-node-agent : déploie un DaemonSet qui gère le filesystem backup
  • --uploader-type restic : on garde restic pour la dédup et le chiffrement bout-en-bout
  • --default-volumes-to-fs-backup : tous les volumes sont sauvegardés par défaut, on opt-out via annotation si nécessaire

Le hook SQL, l’élément critique

Un backup brut d’un volume PostgreSQL pendant que la BDD écrit, c’est garantir une restauration corrompue. WAL incomplet, fichiers de table incohérents, et au restore, le moteur refuse de démarrer.

La parade : un PreHook Velero qui exécute pg_dump dans le pod avant le snapshot du volume. Exemple sur un Deployment Drupal :

metadata:
  annotations:
    pre.hook.backup.velero.io/container: "postgres"
    pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "pg_dump -U drupal drupal > /backup/drupal.sql"]'
    pre.hook.backup.velero.io/timeout: "5m"
    pre.hook.backup.velero.io/on-error: "Fail"

Ce hook crée un dump SQL cohérent dans /backup/drupal.sql, qui est lui sur un volume monté dans le pod. Velero sauvegarde ce fichier, et au restore, vous restaurez un SQL textuel — toujours plus fiable qu’un snapshot binaire.

Pour MySQL/MariaDB, on utilise mysqldump --single-transaction. Pour MongoDB, mongodump. Pour Redis, on snapshot via BGSAVE puis on copie le dump.rdb.

Les Schedules qu’on déploie

Sur un cluster prod typique :

apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: nightly-full
  namespace: velero
spec:
  schedule: "0 2 * * *"  # 02h00 UTC tous les jours
  template:
    ttl: 720h0m0s        # rétention 30 jours
    includedNamespaces:
      - "*"
    excludedNamespaces:
      - kube-system
      - velero

Et un schedule hebdomadaire avec une rétention plus longue :

spec:
  schedule: "0 3 * * 0"  # dimanche 03h00 UTC
  template:
    ttl: 4380h0m0s       # rétention 6 mois

La règle GFS classique (Grandfather-Father-Son) : daily 30 jours, weekly 6 mois, monthly 2 ans. Adaptez selon vos contraintes réglementaires.

La restauration mensuelle, non négociable

Un backup non testé n’est pas un backup. Une fois par mois, on déclenche une procédure de DR drill :

  1. Créer un cluster K3s temporaire (k3s-dr-test)
  2. Installer Velero avec le même bucket S3 que la prod, en read-only
  3. Lancer velero restore create --from-backup <backup-name>
  4. Vérifier que les pods se lancent, que les BDD répondent, que les données sont là
  5. Détruire le cluster temporaire

Si la restauration échoue, on a une procédure de remédiation documentée. Ce n’est pas optionnel : la première fois qu’on teste pour de vrai, on découvre toujours quelque chose qui ne marche pas comme prévu. Mieux vaut le découvrir un mardi midi en mode formation que un jeudi 22h après un ransomware.

Le monitoring qui va bien

On expose les métriques Velero dans Prometheus :

# ServiceMonitor
- targetLabels: [app.kubernetes.io/name]
  endpoints:
    - port: monitoring
      path: /metrics

Les alertes critiques :

  • velero_backup_failure_total > 0 (un backup a échoué dans les dernières 24h)
  • time() - velero_backup_last_successful_timestamp > 86400 (pas de backup réussi depuis plus de 24h)
  • velero_volume_snapshot_failure_total > 0 (un snapshot de volume a échoué)

Sur les clusters multi-tenant, on a aussi un alerting par namespace pour détecter qu’un client précis n’est plus sauvegardé.

Le coût réel

Sur un cluster qui héberge 30 services, avec des bases de données représentant ~ 50 Go de données chaudes :

  • Stockage S3 OVH : ~ 350 Go au rolling 30 jours = ~ 3 € HT / mois
  • Bandwidth out lors des restores : variable, négligeable si on reste en intra-OVH
  • Compute Velero : un pod ~ 300 Mo RAM, un node-agent par worker ~ 200 Mo chacun

On parle de quelques euros par mois pour ne plus jamais perdre de données. Le ROI est instantané.

Les pièges qu’on a rencontrés

Les CRDs qui drift entre versions

Velero 1.13 → 1.14, le schéma de Backup a légèrement bougé. Si vous gérez Velero via ArgoCD, attention au sync wave, et testez les upgrades sur un cluster de staging avant la prod.

Les volumes hostPath

Velero ne sauvegarde pas les hostPath. C’est par design — un hostPath n’est pas un PV portable. Si vous avez besoin de sauvegarder du contenu sur un hostPath, migrez vers du local-path ou du NFS.

Restic vs Kopia

Velero supporte les deux uploaders. Kopia est plus rapide sur les gros volumes (> 100 Go), restic est plus mature sur les small files. On a testé : pour < 50 Go, restic suffit largement.

Pour finir

Un cluster K3s avec Velero correctement configuré, c’est l’équivalent d’avoir une assurance habitation pour votre prod. Vous payez une petite prime mensuelle, vous priez que ça ne serve jamais, mais le jour où ça sert, vous êtes content de l’avoir mise en place avant l’incident.

Si vous opérez du K8s en prod sans backup volume + hook SQL, c’est maintenant qu’il faut s’y mettre. Pas le mois prochain. Maintenant.

Vous avez un projet sur ces sujets ?

Nous contacter →