❯ infra · Running

De zéro vraie sauvegarde à une stratégie 3-2-1 opérationnelle — cluster, NAS et PC compris Zero Real Backups to a Working 3-2-1 Strategy — Across a Cluster, a NAS, and 3 PCs

Un constat d'audit sans détour — « le RAID n'est pas une sauvegarde, et ma seule copie hors-site pouvait être effacée par le compte qui l'écrivait » — a déclenché une refonte complète de la sauvegarde en 3-2-1-1-0, sur le cluster, le NAS, 3 PC et 2 téléphones. Architecture, un quasi-incident de token qui a fuité, et l'upload Nextcloud passé de 1 Mbit/s à 150 Mbit/s avec un seul changement de config. A blunt audit finding — 'RAID is not a backup, and my only off-site copy could be deleted by the same account that wrote it' — triggered a full 3-2-1-1-0 backup redesign across the cluster, the NAS, 3 PCs and 2 phones. Covers the architecture, a leaked-credential near-miss, and the Nextcloud upload that went from 1 Mbit/s to 150 Mbit/s with one config change.

· 12 min de lecture 12 min read ·
backupresticminiolonghorndisaster-recoverynextcloudinfra
🇬🇧 Read this article in English

Mi-juin, en préparant une revue d’infra, je me pose une question bête : si worker-2 grillait cette nuit, qu’est-ce que je perds vraiment ? Je liste mes « sauvegardes » à voix haute, et la réponse tient en une phrase gênante : je n’ai aucune vraie sauvegarde. Un RAID1, des snapshots Longhorn stockés sur le cluster lui-même, un tar de config Home Assistant posé sur le même array que ce qu’il est censé protéger. Que du théâtre.

Deux semaines plus tard, le 2 juillet, le dernier trou s’est refermé : les 93 Go de Nextcloud partaient enfin chiffrés vers une copie hors-site. Entre les deux, une refonte complète en 3-2-1-1-0, un jeton qui a fuité deux fois, une dépendance circulaire dans Longhorn, et un upload qui rampait à 1 Mbit/s. Voici le chemin, erreurs comprises.

Le constat : « aucune vraie sauvegarde »

Un audit honnête commence par nommer ce qui n’est pas une sauvegarde. Dans mon parc, presque tout tombait dans cette catégorie :

  • Le RAID1 du NAS (2× WD Red 6 To, ~5,5 To utiles) donne de la disponibilité : si un disque meurt, l’autre prend le relais. Mais un rm -rf malheureux, un ransomware ou une corruption logique est répliqué instantanément sur les deux disques. Le RAID protège contre la panne matérielle d’un disque, pas contre moi.
  • Le backup S3 du cluster vivait sur le cluster. Le backup-target de Longhorn pointait vers un stockage hébergé sur worker-2 — la même machine dont je voulais justement pouvoir récupérer les volumes. Perdre ce nœud, c’était potentiellement perdre les volumes et leurs sauvegardes d’un coup.
  • Le disque externe 4 To de ma copine — ses photos, ses documents — était une copie unique, zéro redondance. Une chute, un contrôleur USB qui lâche, et c’est une perte sèche.
  • Les photos des deux téléphones et le Nextcloud perso : irremplaçables, sans second exemplaire vérifié.

Le point commun de tout ça, c’est la confusion entre tolérance de panne et sauvegarde. Une vraie sauvegarde, c’est une copie découplée dans le temps et dans l’espace de l’original : que je puisse revenir à hier, et que la copie survive à la destruction de la source. Rien de ce que j’avais ne cochait ces deux cases en même temps.

La règle 3-2-1-1-0 et le tiering par valeur

Le cadre que j’ai retenu est une extension de la règle 3-2-1 classique :

3 copies · 2 supports différents · 1 hors-site · 1 hors-ligne ou immuable · 0 erreur (prouvée par un test de restauration).

Le piège, quand on découvre cette règle, c’est de vouloir l’appliquer à tout. Or je n’ai pas les moyens de mettre 5,5 To de médiathèque dans un cloud de quelques téraoctets — et je n’en ai surtout pas besoin. La clé, c’est de classer les données par valeur et de ne pas les traiter pareil :

TierDonnéesStratégieRemplaçable ?
T0 — IrremplaçablePhotos/vidéos perso, docs, Nextcloud, données 4 To, coffres de mots de passe3-2-1 strict, versionné, chiffré, hors-siteJamais
T1 — Difficile à recréerDonnées apps du cluster (dépôts Gitea, base n8n, config Home Assistant), profils PCBackup auto quotidien + hors-sitePénible
T2 — Recréable via codeManifests, docker-compose, .env, IaCGit (Gitea + miroir GitHub)Reconstructible
T3 — Re-téléchargeableMédiathèque (films/séries)RAID1 suffit + sauver seulement les métadonnées (bases arr/Jellyfin), pas les blobsOui

Ce tableau a fait le plus gros du travail de conception. Il dit noir sur blanc que la médiathèque n’a pas besoin de partir hors-site — juste ses métadonnées, quelques mégaoctets qui permettent de reconstruire les bibliothèques après un re-téléchargement. Résultat : le périmètre réellement précieux tient dans quelques téraoctets, pas quelques dizaines.

L’architecture : le NAS comme hub de convergence

Plutôt que de sauvegarder chaque machine vers le cloud indépendamment, tout converge d’abord sur le NAS, qui devient l’unique point à sauvegarder vers l’extérieur.

Deux idées structurent le schéma :

  1. Les postes et téléphones poussent leurs données vers Nextcloud (clients desktop et mobile). Les photos des téléphones atterrissent automatiquement sur le NAS ; les dossiers Documents/Bureau/Images des PC y sont synchronisés. Bonus non négligeable : le versioning Nextcloud permet déjà de récupérer un fichier écrasé.
  2. Le cluster ne stocke pas ses propres sauvegardes — il les pousse vers le NAS. Un script sur master-1 ([email protected]) réalise chaque nuit les dumps des bases et un snapshot etcd, puis les envoie par SSH vers /mnt/storage/backups sur le NAS (10.0.0.30) :
# cron sur master-1 — 45 2 * * *
pg_dump gitea       > /tmp/gitea-$(date +%F).sql
pg_dump n8n         > /tmp/n8n-$(date +%F).sql
k3s etcd-snapshot save          # snapshot etcd frais
rsync -a /tmp/*.sql /var/lib/rancher/k3s/server/db/snapshots/ \
      [email protected]:/mnt/storage/backups/db/

La base MariaDB de Nextcloud, elle, est dumpée localement sur le NAS (30 2 * * *). Conséquence directe : le cluster peut être perdu entièrement sans emporter ni les bases, ni l’etcd. C’est exactement la séparation que le vieux montage n’avait pas.

Une fois tout convergé sur le NAS, celui-ci se sauvegarde vers deux cibles avec restic : un disque local (restauration rapide) et un cloud hors-site (chiffré, dédupliqué, incrémental) via rclone. Le dépôt hors-site est chiffré de bout en bout — le fournisseur cloud ne voit que des blobs opaques, jamais le contenu. Ça compte, parce que mon home sur le NAS contient aussi des secrets applicatifs qu’il est hors de question de pousser en clair.

Longhorn qui cassait sa propre dépendance circulaire

Le point le plus vicieux de l’ancien montage méritait sa propre correction. Le backup-target S3 de Longhorn — là où atterrissent les sauvegardes de volumes — tournait dans le cluster, sur worker-2. Autrement dit : la sauvegarde censée me protéger de la perte d’un nœud partageait sa zone de panne avec ce nœud. Un incendie, une carte SD morte, une erreur de ma part sur worker-2, et je perdais l’original et sa copie dans le même geste.

Le correctif : déplacer la cible hors du cluster, sur le NAS. J’ai déployé un MinIO en conteneur Docker sur le NAS (données sur le RAID1, bucket longhorn-backups), puis re-scellé le secret S3 de Longhorn avec kubeseal pour pointer sur le nouvel endpoint :

# secret Longhorn re-scellé — endpoint hors-cluster
AWS_ENDPOINTS: http://10.0.0.30:9000

Après sync ArgoCD, le BackupTarget par défaut repasse available: true, et — le seul test qui compte — une sauvegarde réelle apparaît bien dans le bucket du NAS, pas dans un stockage du cluster. Les anciennes sauvegardes in-cluster restent un moment comme filet, mais la dépendance circulaire est cassée. (Si le sujet MinIO vous intéresse en soi, j’en avais posé les bases dans MinIO sur le cluster, et le stockage Longhorn est détaillé dans Longhorn sur K3s.)

Le secret vu en clair : l’incident évité de justesse

Pendant la mise en place de la patte hors-site, un jeton d’accès au fournisseur cloud est apparu deux fois en clair dans les échanges de configuration, avant même d’avoir servi. Personne d’hostile ne l’a vu — mais ce n’est pas le sujet.

La bonne réaction n’est pas « je ferai attention à ne plus le recoller ». Un secret qui a été exposé en clair, ne serait-ce qu’une fois, doit être considéré comme compromis : il faut le révoquer et en régénérer un nouveau, point. La procédure appliquée :

  1. Terminer d’abord l’opération en cours pour ne pas casser un transfert à moitié fait.
  2. Révoquer l’accès tiers côté fournisseur (retirer l’autorisation de l’application).
  3. Régénérer un identifiant neuf et ré-autoriser l’outil avec celui-ci.

Ça paraît évident écrit ainsi, et pourtant la tentation de « juste ne plus le réutiliser » est réelle quand tout marche. La règle que je me suis fixée : la seule question à se poser sur un secret vu en clair, c’est quand le roter, pas si.

Le goulot d’étranglement à 1 Mbit/s

Premier vrai backup hors-site du petit périmètre critique (dumps, configs, etcd — 432 Mio) : impeccable, quelques secondes. Puis vient le gros morceau, les 93 Go de données Nextcloud. Et là, le débit s’effondre à ~1 Mbit/s. À ce rythme, le seed initial se comptait en jours, transfert que la moindre coupure aurait fait repartir de trop loin.

Le diagnostic n’était pas côté réseau ni côté NAS. Le client rclone utilisait, par défaut, des identifiants applicatifs partagés — mutualisés entre tous les utilisateurs de l’outil, et à ce titre fortement rate-limités par le fournisseur. Le tuyau n’était pas bouché : on m’avait juste assigné le plus petit robinet.

Le correctif : créer un identifiant applicatif OAuth dédié côté fournisseur, avec une portée réduite aux seuls fichiers créés par l’application. Un piège au passage, qui vaut d’être connu : cette portée « fichiers de l’application » est cloisonnée par application — un nouvel identifiant ne voit pas les fichiers déjà uploadés par l’ancien, donc le dépôt restic existant apparaissait « vide ». Il a fallu élargir temporairement la portée à l’accès complet pour que le nouveau client retrouve le dépôt déjà en place. Aucune migration de données, juste le bon réglage de visibilité.

Le résultat, mesuré :

Avant (identifiants partagés)Après (identifiant dédié)
Débit~1 Mbit/s90–150 Mbit/s
Seed 91 Goplusieurs jours (projeté)1 h 16

Un facteur ~100 pour un changement de configuration, sans toucher au matériel ni à l’abonnement. Les 4 977 fichiers du seed (91,3 Gio) sont passés en une heure et quart, et grâce à la déduplication de restic, le run quotidien suivant n’a quasiment rien eu à ré-uploader.

Immuabilité : la leçon qui manque encore

Voici l’angle mort que je n’ai pas comblé, et qu’il serait malhonnête de présenter comme réglé. Aujourd’hui, mon backup hors-site est chiffré et découplé — mais le compte qui l’écrit peut aussi l’effacer. Un ransomware qui prendrait le contrôle du NAS pourrait, avec les mêmes identifiants, supprimer la copie cloud juste après avoir chiffré l’original.

Or une sauvegarde que la source compromise peut détruire n’est pas vraiment une sauvegarde hors-ligne. C’est tout le sens du « 1 » supplémentaire dans 3-2-1-1-0 : une copie immuable, ou hors-ligne, qui survit à la compromission de la source. Deux pistes concrètes sont sur ma liste :

  • Une cible en mode append-only, ou un jeton sans droit de suppression, de sorte qu’un attaquant sur le NAS puisse écrire mais pas effacer.
  • À terme, une cible avec verrouillage d’objet (object-lock) — quelques euros par mois — pour le seul T0, garantissant qu’une version ne peut pas être supprimée avant son échéance.

Je le documente ici volontairement comme dette assumée plutôt que de la cacher. La stratégie est opérationnelle ; l’immuabilité stricte est la prochaine marche.

Vérifier, sinon ce n’est pas une sauvegarde

Un backup qu’on ne surveille pas est un backup de Schrödinger : on ne sait s’il marche qu’au moment où on en a désespérément besoin. Deux disciplines ferment cette boucle.

Le heartbeat. Chaque job de sauvegarde écrit un horodatage « dernier backup OK ». Le monitoring existant (VictoriaMetrics + vmalert, décrits ici) scrappe cette métrique, et une règle déclenche une alerte Telegram si le dernier backup réussi dépasse 26 heures. Un backup manqué devient ainsi un événement qui me notifie, au lieu d’un silence que je découvre trois semaines plus tard. Le principe est le même que pour les températures de disques que je surveille sur le NAS : mesurer en continu, alerter sur un seuil.

Les tests de restauration. C’est la partie que tout le monde saute et qui est pourtant la seule qui prouve le « 0 erreur ». La cadence que je me suis fixée :

  • Mensuel : restaurer un fichier et un service au hasard, vérifier restic check.
  • Trimestriel : un drill de restauration plus complet (un volume, un service de bout en bout).

Un backup jamais restauré n’est qu’une hypothèse. Le premier test mensuel a d’ailleurs déjà servi à valider que la chaîne break-glass tenait : les clés maîtres qui déchiffrent les secrets, le token du cluster et la config Nextcloud vivent dans un coffre de secrets dédié (break-glass), dont la racine de confiance — le kit d’urgence papier — est rangée hors-site. Sans elle, aucune reprise n’est possible, quel que soit le nombre de copies.

Bilan chiffré

Deux semaines, du constat brutal à une chaîne complète et vérifiée. Les cadences en place :

FréquenceAction
ContinuSync Nextcloud (PC, téléphones)
4×/jourSnapshots Longhorn (cluster)
Quotidien (nuit)pg_dump cluster → NAS · restic NAS → disque local · restic NAS → cloud · snapshot etcd → NAS
Hebdorestic check · export métadonnées média · Longhorn weekly backup
Mensuelrestic prune · test de restauration (1 fichier + 1 service)
TrimestrielDrill de restauration complet

Et le chiffre qui me plaît le plus : coût total ≈ 0 €. Tout réutilise l’existant — Nextcloud déjà self-hosté, l’offre cloud familiale déjà payée, restic/rclone gratuits, le NAS déjà là, le monitoring déjà en place. Les seules dépenses envisagées sont optionnelles : un second disque externe (~80 €) pour une copie physique tournante, et un stockage à verrouillage d’objet (~5 €/mois) le jour où je durcirai l’immuabilité du T0.

La vraie bascule n’a pas été technique. restic et rclone s’installent en dix minutes. Le déclic, c’est d’avoir arrêté d’appeler « sauvegarde » ce qui n’était que de la disponibilité — et d’avoir accepté que la seule sauvegarde qui compte est celle qu’on a déjà restaurée pour de vrai.

backupresticminiolonghorndisaster-recoverynextcloudinfra