Sauvegardes Kubernetes avec Velero : guide testé

Installer Velero sur un cluster Kubernetes avec un bucket S3 OVHcloud ou Scaleway, sauvegarder les volumes CSI et tester vraiment la restauration.

Gros plan sur les tiroirs noirs d'un boîtier de stockage avec baies de disques durs verrouillables

Réponse courte : sauvegarder un cluster Kubernetes, c'est sauvegarder trois choses distinctes — les objets de l'API (Deployments, Services, Secrets, CRD), les volumes persistants, et les données applicatives dans leur format logique (un pg_dump, pas seulement un snapshot de bloc). Velero couvre les deux premières : installé avec un bucket S3 chez OVHcloud ou Scaleway, il coûte environ 1 à 5 € par mois de stockage pour un cluster de PME et sauvegarde un namespace en quelques minutes. La troisième reste à votre charge, et c'est celle qui vous sauvera vraiment.

La discussion commence toujours de la même manière. Le cluster tourne, la CI déploie, les volumes sont sur du block storage répliqué chez le fournisseur — donc « c'est sauvegardé ». Puis quelqu'un supprime le mauvais namespace avec un kubectl delete ns un vendredi soir, ou un helm uninstall emporte un PVC marqué Delete en reclaimPolicy, et on découvre que le block storage répliqué protège contre la panne d'un disque, pas contre une erreur humaine.

Cet article décrit l'installation que nous déployons par défaut sur les clusters que nous exploitons, et surtout la procédure de restauration que nous rejouons périodiquement. Parce qu'une sauvegarde jamais restaurée n'est pas une sauvegarde : c'est une ligne dans un tableau de bord.

Gros plan sur les tiroirs noirs d'un boîtier de stockage avec baies de disques durs verrouillables

Ce qu'il faut sauvegarder dans un cluster, exactement

Découpons le problème. Dans un cluster Kubernetes qui sert une application de PME, il y a quatre catégories de données, et elles ne se sauvegardent pas pareil.

CatégorieExempleOutil adapté
Objets de l'APIDeployments, Ingress/HTTPRoute, ConfigMaps, Secrets, CRDVelero (export vers S3)
Volumes persistantsuploads utilisateurs, index de recherche, cacheVelero + snapshots CSI, ou File System Backup (Kopia)
Bases de donnéesPostgreSQL, Redis persistantsauvegarde logique dédiée (pg_dump, archivage WAL)
Infrastructure du clusternœuds, réseau, IAMTerraform / OpenTofu, pas une sauvegarde

Deux pièges classiques dans ce tableau.

Le premier : un snapshot de volume n'est pas une sauvegarde de base de données cohérente. Un snapshot CSI pris pendant que PostgreSQL écrit donne, au mieux, l'équivalent d'un arrêt brutal : PostgreSQL rejouera son journal au démarrage et s'en sortira généralement, mais vous n'avez aucune garantie de restauration à un instant donné, et aucune protection contre une corruption logique (un UPDATE sans WHERE qui part en production). La documentation de PostgreSQL ne considère le snapshot de système de fichiers comme valide que si le volume est unique et le snapshot atomique — une condition rarement vérifiée en pratique sur du stockage réseau. Pour la base, on garde un archivage WAL en continu, ce qui est précisément ce que gère un opérateur comme CloudNativePG ; nous avons détaillé les arbitrages de coût dans notre comparaison entre PostgreSQL managé et PostgreSQL dans le cluster.

Le second : le cluster lui-même ne doit pas être dans la sauvegarde. Si la seule façon de reconstruire votre cluster est de restaurer un snapshot d'etcd, vous avez un problème d'infrastructure, pas de sauvegarde. Les nœuds, le réseau, les groupes de sécurité et les rôles IAM doivent venir d'un dépôt Terraform ou OpenTofu. Velero reconstruit les charges de travail, pas le plan de contrôle.

Installer Velero avec un bucket S3 OVHcloud ou Scaleway

Velero est un projet de la CNCF, aujourd'hui en incubation, développé initialement par Heptio puis VMware. Il fonctionne avec n'importe quel stockage objet compatible S3, ce qui inclut OVHcloud Object Storage (ancienne et nouvelle offre S3), Scaleway Object Storage et évidemment Amazon S3.

Créez d'abord le bucket et un jeu de clés d'accès dédié. Chez Scaleway, l'endpoint est de la forme https://s3.fr-par.scw.cloud avec la région fr-par ; chez OVHcloud, https://s3.gra.io.cloud.ovh.net avec la région gra pour l'offre S3 compatible. Vérifiez toujours la valeur exacte dans la console du fournisseur, les endpoints ont changé plusieurs fois.

Le fichier de credentials, au format AWS classique :

# velero-credentials
[default]
aws_access_key_id=SCWXXXXXXXXXXXXXXXXX
aws_secret_access_key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Puis l'installation, avec la CLI Velero (disponible sur les releases GitHub du projet) :

velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.11.0 \
  --bucket studio-cluster-backup \
  --secret-file ./velero-credentials \
  --backup-location-config \
      region=fr-par,s3ForcePathStyle=true,s3Url=https://s3.fr-par.scw.cloud \
  --use-node-agent \
  --uploader-type kopia \
  --default-volumes-to-fs-backup=false \
  --namespace velero

Trois options méritent une explication.

  • --use-node-agent déploie un DaemonSet qui sait lire le contenu des volumes montés sur chaque nœud. C'est indispensable pour la sauvegarde au niveau fichier (File System Backup) des volumes dont le driver CSI ne gère pas les snapshots.
  • --uploader-type kopia sélectionne Kopia plutôt que Restic. Restic est déprécié côté Velero depuis la v1.15 ; sur un cluster neuf, Kopia est le choix par défaut.
  • --default-volumes-to-fs-backup=false évite de tout sauvegarder au niveau fichier par défaut : on privilégie les snapshots CSI, beaucoup plus rapides, et on bascule en mode fichier au cas par cas avec une annotation.

Vérifiez que l'emplacement de sauvegarde est joignable avant d'aller plus loin :

kubectl -n velero get backupstoragelocation
# NAME      PHASE       LAST VALIDATED   AGE
# default   Available   12s              2m

Un PHASE: Unavailable signifie neuf fois sur dix un endpoint erroné, un s3ForcePathStyle oublié, ou des clés sans droit d'écriture sur le bucket.

Activer les snapshots CSI

Pour que Velero demande des VolumeSnapshot au driver du fournisseur plutôt que de recopier les fichiers, il faut une VolumeSnapshotClass étiquetée :

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-block-snapclass
  labels:
    velero.io/csi-volumesnapshot-class: "true"
driver: csi.cinder.openstack.org   # adapter au driver du fournisseur
deletionPolicy: Retain

Attention à un point souvent découvert trop tard : chez beaucoup de fournisseurs, un snapshot de volume reste dans la même région, voire la même zone, que le volume d'origine. Si le but est de survivre à la perte d'une zone, il faut activer le DataMover de Velero (snapshotMoveData: true), qui lit le snapshot et le pousse vers le bucket objet. C'est plus lent, mais c'est la seule configuration qui met réellement vos données hors du cluster.

Les planifications que nous mettons en place

Une seule planification « tout le cluster, une fois par nuit » est insuffisante : les namespaces n'ont ni la même valeur ni le même rythme de changement. Nous utilisons trois Schedule.

apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: prod-hourly
  namespace: velero
spec:
  schedule: "17 * * * *"
  template:
    includedNamespaces:
      - app-prod
    snapshotVolumes: true
    snapshotMoveData: true
    ttl: 72h
---
apiVersion: velero.io/v1
kind: Schedule
metadata:
  name: full-cluster-daily
  namespace: velero
spec:
  schedule: "25 2 * * *"
  template:
    excludedNamespaces:
      - kube-system
      - velero
    snapshotVolumes: true
    ttl: 720h

Remarques issues de l'exploitation :

  • Jamais 0 * * * *. Toutes les tâches planifiées du monde tournent à l'heure pile ; un décalage de quelques minutes évite les contentions sur le stockage objet.
  • Le ttl pilote la rétention, et c'est lui qui pilote la facture. 72 h de sauvegardes horaires plus 30 jours de sauvegardes quotidiennes est un compromis raisonnable pour une application de PME.
  • Les Secret sont inclus par défaut. Ils partent donc en clair dans le bucket, chiffré côté serveur mais lisible par quiconque a les clés d'accès. Pour les données sensibles, Velero sait chiffrer côté client via le dépôt Kopia (repositoryMaintenance et mot de passe du dépôt) : à configurer explicitement, ce n'est pas le réglage par défaut.

Câbles réseau bleus branchés sur un commutateur de baie serveur avec voyants verts allumés

Ce que ça coûte réellement

Prenons un cluster typique de nos clients : trois nœuds, un namespace de production, 60 Go de volumes persistants dont 25 Go de base PostgreSQL et 35 Go d'uploads.

PosteVolumeCoût mensuel indicatif
Stockage objet (sauvegardes Velero, ~90 Go après dédup Kopia)90 Go0,70 à 2 €
Snapshots de block storage conservés en région60 Go2 à 5 €
Trafic sortant lors d'une restauration complète60 Go ponctuels0 € chez OVHcloud et Scaleway
Temps d'exploitation (test de restauration trimestriel)2 h / trimestrele vrai coût

Les grilles publiques de Scaleway et OVHcloud situent le stockage objet autour de 0,01 € par Go et par mois en classe standard, et les deux fournisseurs ne facturent pas la sortie de données — un écart notable avec AWS S3, où le transfert sortant est facturé et où une restauration de 60 Go se chiffre en quelques euros. Vérifiez toujours la grille du jour : ces prix bougent.

Autrement dit : la sauvegarde d'un cluster de PME coûte moins de 10 € par mois en infrastructure. Le poste réel, c'est l'heure d'ingénieur passée à vérifier qu'elle fonctionne. C'est aussi ce que nous intégrons dans les contrats d'exploitation décrits sur notre page services d'infrastructure.

La restauration, étape par étape

Voici la procédure que nous rejouons, en conditions réelles, sur un namespace de recette.

1. Lister les sauvegardes disponibles et inspecter la cible.

velero backup get
velero backup describe prod-hourly-20261109021700 --details

Le --details affiche les ressources incluses et l'état de chaque volume. Une sauvegarde en PartiallyFailed est fréquente et souvent bénigne (une ressource de webhook non sauvegardable), mais il faut lire les avertissements avant de s'en servir.

2. Restaurer dans un namespace neuf, pas par-dessus la production.

velero restore create recette-20261109 \
  --from-backup prod-hourly-20261109021700 \
  --namespace-mappings app-prod:app-recette \
  --wait

Le remappage de namespace est la clé d'un test non destructif : on obtient une copie complète de la production dans app-recette, sans toucher à app-prod.

3. Neutraliser ce qui ne doit pas se réveiller.

Une copie de production restaurée garde ses CronJobs, ses hooks sortants et ses identifiants SMTP. Avant de la rendre joignable, suspendez les CronJobs et coupez les intégrations externes :

kubectl -n app-recette get cronjob -o name \
  | xargs -I{} kubectl -n app-recette patch {} \
      -p '{"spec":{"suspend":true}}'

C'est aussi là qu'on vérifie les objets d'exposition réseau : un HTTPRoute restauré avec le même nom d'hôte que la production créera un conflit de routage. Nous décrivons ces objets en détail dans l'article sur le remplacement d'ingress-nginx par la Gateway API.

4. Vérifier les données, pas les pods.

Le piège classique du test de restauration : tous les pods sont Running, on déclare la victoire, et personne n'a ouvert la base. Nos trois vérifications minimales :

# le compte de lignes de la table la plus volumineuse
kubectl -n app-recette exec deploy/postgres -- \
  psql -U app -d app -c "select count(*) from orders;"

# la date du dernier enregistrement
kubectl -n app-recette exec deploy/postgres -- \
  psql -U app -d app -c "select max(created_at) from orders;"

# un fichier connu dans le volume d'uploads
kubectl -n app-recette exec deploy/web -- \
  ls -l /app/uploads/2026/

La deuxième commande donne le vrai RPO (perte de données maximale) : l'écart entre max(created_at) et l'heure de la sauvegarde. Si vous visez un RPO d'une heure et que le dernier enregistrement date de quatre heures avant la sauvegarde, il y a un problème dans la chaîne, pas dans la restauration.

5. Chronométrer et noter.

Notez la durée réelle : c'est votre RTO. Sur les clusters que nous exploitons, une restauration complète d'un namespace de 60 Go avec DataMover prend de 20 à 45 minutes, dont l'essentiel en transfert depuis le stockage objet. Si votre engagement est « rétabli en 30 minutes », vous venez de découvrir qu'il est faux.

6. Supprimer la copie.

velero restore delete recette-20261109
kubectl delete ns app-recette

Femme avec une tablette devant des baies de serveurs éclairées en bleu dans un centre de données

Les quatre erreurs que nous voyons le plus souvent

  1. La reclaimPolicy: Delete sur les StorageClass de production. Un helm uninstall ou un kubectl delete pvc détruit alors le volume sous-jacent, immédiatement. Passez les StorageClass qui portent des données en Retain, quitte à nettoyer manuellement les volumes orphelins.
  2. Les sauvegardes dans le même compte ou projet que le cluster. Si la compromission d'un jeu de clés permet de supprimer à la fois le cluster et le bucket de sauvegarde, vous n'avez pas de sauvegarde. Utilisez un projet distinct, des clés en écriture seule, et le verrouillage d'objets (Object Lock) quand le fournisseur le propose.
  3. Aucune supervision des Schedule. Une sauvegarde qui échoue silencieusement pendant trois semaines est le scénario le plus courant. Velero expose des métriques Prometheus : velero_backup_last_successful_timestamp et velero_backup_failure_total suffisent pour une alerte « dernière sauvegarde réussie de plus de 26 h ».
  4. Les secrets qui ne sont pas dans la sauvegarde parce qu'ils viennent d'ailleurs. Avec External Secrets Operator ou Sealed Secrets, les Secret restaurés peuvent être vides ou invalides. Testez la restauration avec le coffre indisponible pour mesurer la dépendance — c'est exactement le genre de détail qu'on découvre en même temps que la panne, comme l'authentification au registre privé évoquée dans notre guide sur les images GitLab privées sur Kubernetes.

Questions fréquentes

Velero remplace-t-il la sauvegarde de ma base PostgreSQL ?

Non. Velero sait prendre un snapshot du volume de la base, ce qui vous protège d'une suppression accidentelle du PVC, mais il ne donne ni restauration à un instant donné ni garantie de cohérence transactionnelle. Gardez une chaîne dédiée : archivage WAL continu avec un opérateur, ou sauvegardes automatiques de la base managée. En pratique, nous excluons les volumes de base de données des sauvegardes Velero (annotation backup.velero.io/backup-volumes-excludes) pour éviter de payer deux fois le stockage.

Faut-il sauvegarder etcd en plus ?

Sur un cluster managé (OVHcloud Managed Kubernetes, Scaleway Kapsule, EKS), non : le plan de contrôle est l'affaire du fournisseur et vous n'y avez pas accès. Sur un cluster auto-géré, un snapshot etcdctl snapshot save quotidien est utile pour restaurer un cluster complet, mais il restaure aussi toutes les erreurs. Velero reste préférable pour revenir en arrière de façon ciblée.

À quelle fréquence tester une restauration ?

Une fois par trimestre au minimum, et après tout changement de version majeure de Kubernetes ou du driver CSI. Le test prend deux heures quand il est scripté. Nous l'inscrivons comme tâche planifiée dans le pipeline, avec le compte-rendu archivé : date, sauvegarde utilisée, RTO mesuré, anomalies.

Peut-on utiliser Velero pour migrer un cluster vers un autre fournisseur ?

Oui, et c'est l'un de ses usages les plus utiles : sauvegarde depuis le cluster source avec snapshotMoveData: true, lecture du même bucket depuis le cluster cible, puis velero restore create. Les points de friction sont les StorageClass (noms différents, à remapper avec un ConfigMap de type change-storage-class-config), les LoadBalancer et les annotations spécifiques au fournisseur. Comptez une bonne journée de mise au point, pas une commande.

Où placer le test de restauration dans la CI ?

Dans une tâche planifiée hebdomadaire, séparée du pipeline applicatif : restauration dans un namespace jetable, requêtes de vérification, suppression, publication du RTO en métrique. Le modèle de pipeline que nous utilisons est décrit dans notre article sur le pipeline GitLab CI/CD pour Next.js sur Kubernetes.

En résumé

  • Un cluster se sauvegarde sur trois plans : objets de l'API, volumes persistants, données logiques. Velero couvre les deux premiers, pas le troisième.
  • Installez Velero avec Kopia (Restic est déprécié), un bucket S3 chez OVHcloud ou Scaleway, et snapshotMoveData: true si vous voulez survivre à la perte d'une zone.
  • Trois planifications valent mieux qu'une : horaire sur la production avec un ttl court, quotidienne sur tout le cluster avec une rétention longue.
  • Le coût d'infrastructure est faible — moins de 10 € par mois pour 60 Go de données sur un cluster de PME ; le coût réel est le temps de test.
  • Restaurez toujours dans un namespace remappé, suspendez les CronJobs, puis vérifiez les données (compte de lignes, date du dernier enregistrement), pas l'état des pods.
  • Mesurez RPO et RTO à chaque test et écrivez-les. Un engagement de rétablissement non chronométré est une hypothèse.
  • Passez les StorageClass porteuses de données en reclaimPolicy: Retain, isolez le bucket de sauvegarde du compte du cluster, et alertez sur velero_backup_last_successful_timestamp.

Si vous voulez faire auditer votre chaîne de sauvegarde, ou simplement faire chronométrer une restauration réelle sur votre cluster, parlez-en à notre équipe ou écrivez-nous via la page contact.