Organisation de mes sauvegardes

mar. 15 févr. 2022 by Marmotte

La gestion des sauvegardes est un sujet très commun en informatique. Comme beaucoup de monde, je redoute de perdre des données un jour. J'ai donc depuis très longtemps pris l'habitude de sauvegarder mes données, améliorant de temps en temps les méthodes pour sécuriser au mieux mes données.

Je décris dans cet article l'organisation à laquelle je suis arrivé aujourd'hui pour gérer les sauvegardes de mes machines personnelles, de la création des archives à la surveillance automatisée.

Résumé

Pour ceux qui s'intéressent uniquement au résultat final, voici une version résumée de cet article :

Mise en place des sauvegardes système :

  • Service systemd : /usr/local/lib/systemd/system/backup@.service
    • Montage automatique du volume en cas de sauvegarde locale
    • Création d'une archive avec borg create
    • Application de la politique de rétention avec borg prune
    • Rapport envoyé par email
    • Envoi des métriques dans InfluxDB
    • Démontage automatique du volume en cas de sauvegarde locale
  • Timer systemd : /usr/local/lib/systemd/system/backup@.timer
    • Déclenchement de chaque sauvegarde une heure après la fin de la dernière
  • Initialisation d'un dépôt : sudo -i borg init <--options> <repo_path>
  • Activation des sauvegardes : sudo systemctl enable --now backup@escaped_repo_path.timer
  • Alerte via Grafana si la quantité de données sauvegardées varie trop vite
  • Alerte via Grafana s'il manque des sauvegardes

Répartition des sauvegardes : Une sur le NAS, une hors de mon domicile, éventuellement une sur le disque de la machine.

Documents importants : Un script lancé manuellement, qui réalise des sauvegardes supplémentaires de quelques répertoires spécifiques vers différents endroits.

Un peu d'historique

Mon organisation actuelle n'est bien évidemment pas apparue d'un seul coup. Avant d'arriver dans l'état actuel, je suis passé par différentes phases, et j'ai probablement perdu toute trace de certaines données, heureusement sans importance pour moi aujourd'hui.

Au tout début... rien

Comme un grand nombre de personnes, j'ai débuté sans aucune sauvegarde. Je ne connaissais même pas ce concept, étant enfant et en pleine découverte de l'informatique. Cependant, je ne manipulais aucune donnée importante, mon ordinateur n'était alors qu'une plateforme d'expérimentation. Je pouvais ainsi réinstaller le système d'exploitation en formatant totalement le disque dur sans crainte en cas de problème (ce qui arrivait assez souvent) et l'absence de sauvegarde n'était pas gênante.

Note : Bien évidemment, l'absence de sauvegarde n'est pas vraiment un moyen fiable de conserver ses données.

Puis des périphériques amovibles

J'ai commencé à vouloir éviter de perdre des données en arrivant au collège, avec la réalisation de mes premiers documents, exposés, etc. À ce moment, mes sauvegardes étaient de simples duplication des fichiers sur des disquettes, qui sont probablement illisibles depuis longtemps.

J'ai ensuite acheté un disque dur externe, sur lequel je copiais la quasi intégralité de mes données, principalement pour bénéficier d'une plus grande capacité et de meilleures performances qu'avec des disquettes. La copie des données, d'abord manuelle et laborieuse, est ensuite devenue plus fiable et simple quand j'ai appris à utiliser des outils comme rsync.

Note : J'utilisais aussi mon disque dur comme sockage nomade pour transporter des fichiers à l'école ou chez des amis. Cela réduit donc fortement ses qualités de dispositif de sauvegarde :

  • Augmentation du risque de le perdre, ou même simplement d'être victime d'un problème détruisant son contenu (virus, défaillance matérielle, chute, etc.).
  • Possibilité pour n'importe qui en possession du disque d'altérer (volontairement ou non) mes sauvegardes

De plus, si on déborde vers le sujet de la sécurité, cette méthode permettait à toute personne ayant un accès physique à mon disque dur externe de consulter l'intégralité de mes données.

Note : En plus de son flagrant manque de fiabilité et de sécurité, cette méthode n'est pas utilisable sur une machine distante.

Adoption d'un vrai système de sauvegarde

Après une (beaucoup trop) longue période de copie manuelle de fichiers sur mon disque dur externe, j'ai cherché à améliorer la robustesse de mes sauvegardes en utilisant un logiciel adapté. J'ai alors expérimenté différents outils, et arès avoir tenté Bup, je me suis finalement tourné vers Borg. Cela a aussi été l'occasion d'automatiser le processus, et donc d'abandonner le disque dur externe, puisque le branchement de celui-ci nécessite une action manuelle. Il est en effet impensable pour moi de le laisser connecté et alimenté en permanence.

Note : Comme précisé dans l'article sur Borg, ce mode de sauvegarde m'a apporté beaucoup d'avantages, comme la présence de plusieurs archives et le chiffrement. De plus, les données sauvegardées n'étant plus accessibles directement, cela les protège aussi d'une modification accidentelle.

Sauvegardes système

Lors de tout ajout d'un élément dans mon infrastructure, je commence par réfléchir aux objectifs qu'il devra remplir. Pour les sauvegardes, mes objectifs sont :

  1. Pallier tout problème à mon domicile : Au moins une sauvegarde sera réalisée à distance
  2. Éviter de répliquer une corruption : Les sauvegardes locales et distantes ne seront pas synchronisées
  3. Être certain de ne pas oublier : Les sauvegardes seront automatisées
  4. Pouvoir y accéder rapidement : Au moins une sauvegarde sera disponible en local ou via un réseau "rapide"
  5. Limiter l'accès aux dépôts :
    • En local, le volume sera monté uniquement lorsqu'une sauvegarde est en cours
    • En SSH, l'accès sera restreint à la commande Borg (pas d'accès aux fichiers du dépôt)
  6. Être prévenu en cas d'erreur : Un rapport envoyé par email à la fin d'une sauvegarde sera suffisant
  7. Pouvoir suivre l'évolution dans le temps : Des métriques seront envoyées dans mon système de supervision

Note : Sans appliquer directement la "règle 3-2-1" dont beaucoup d'articles font mention, les contraintes que je m'impose en respectent globalement les principes.

Les sauvegardes sont réalisées par un service systemd, ce qui permet de bénéficier de l'intégration avec d'autres composants de la suite systemd comme les timers et le journal.

# /usr/local/lib/systemd/system/backup@.service
[Unit]
Description=Full system backup to %I

[Service]
Type=oneshot
SyslogIdentifier=%p
ExecStart=/usr/bin/borg create --stats --compression zstd,9 --checkpoint-interval 300 --exclude-from /root/.config/borg/ignore --exclude-caches --exclude-if-present .NOBACKUP --keep-exclude-tags %I::{hostname}-full-{now:%%Y%%m%%d-%%H%%M%%S} /
ExecStart=/usr/bin/borg prune --stats --keep-within=2m --keep-daily=31 --keep-weekly=4 --keep-monthly=6 --prefix {hostname}-full- %I
TimeoutStopSec=10min

La commande borg create utilise le fichier d'exclusions /root/.config/borg/ignore, qui contient les chemins à ne pas sauvegarder, comme les systèmes de fichiers virtuels et temporaires, ou certains répertoires contenant des données que je peux me permettre de perdre. En plus de ce fichier d'exclusions, le paramètre --exclude-if-present .NOBACKUP me permet d'exclure facilement n'importe quel répertoire en créant un fichier nommé .NOBACKUP dedans.

Note : Je préfère sauvegarder l'ensemble du système en excluant uniquement certains répertoires spécifiques. De cette manière, je suis certain de ne pas oublier un répertoire utile, qui pourrait me manquer en cas de besoin. De plus, je ne vois pas l'intérêt de ne pas inclure certains répertoires comme /etc, qui est souvent "oublié", malgré qu'il n'occupe que peu d'espace et change très peu.

Note : Les commandes exécutées sont peu nombreuses et restent assez simples, écrire un script ou utiliser borgmatic ajouterait donc inutilement de la complexité. Sur certaines machines, je réalise des sauvegardes de bases de données en plus du système brut. Pour cela, j'ajoute simplement un drop-in dans le répertoire /usr/local/lib/systemd/system/backup@.service.d/ avec les deux commandes borg create et borg prune supplémentaire à exécuter. Cependant, les commandes deviennent alors plus complexes, et cela pourrait justifier l'utilisation d'un script séparé lancé par le service systemd.

Initialisation d'un nouveau dépôt de sauvegarde

Je n'ai que peu de machines personnelles à sauvegarder, initialiser manuellement les dépôts de sauvegarde n'est donc pas un problème. Pour cela, il suffit d'utiliser la commande sudo -i borg init <--options> <repo_path>.

Après l'initialisation du dépôt, il est préférable de sauvegarder la clé du dépôt avec la commande borg key export, surtout lors de l'utilisation d'un mode keyfile, puisque la clé n'est alors stockée que sur la machine source des sauvegardes.

Emplacement des sauvegardes

Chacune de mes machine dispose au moins de deux sauvegardes :

  • Une sur le NAS situé à mon domicile
  • Une autre sur un serveur distant

La sauvegarde sur le NAS permet de pallier toute panne matérielle sur la machine source. La sauvegarde distante me permet d'avoir toujours deux sauvegardes situées dans deux endroits géographiques différents. Le choix du service distant (Kimsufi, Hetzner, BorgBase, chez un ami, ...) peut dépendre de nombreux facteurs comme le prix, la fiabilité, la flexibilité, la quantité d'espace de stockage disponible, etc.

Les machines qui ont assez d'espace de stockage ont en plus une troisième sauvegarde sur un disque local.

Sauvegardes indépendantes

Afin d'éviter tout risque de propager une éventuelle corruption d'une sauvegarde à une autre, chacune est réalisée séparément. Cela implique donc que les nouvelles données seront traitées autant de fois qu'il y a de sauvegardes pour chaque machine. La charge induite sur les machines reste cependant faible, Borg ne traitant que les données modifiées depuis la dernière sauvegarde.

Le service backup@.service utilise le système de template de systemd afin de pouvoir l'utiliser sans modification pour réaliser séparément la même sauvegarde dans plusieurs dépôts. Par exemple sudo systemctl start backup@-mnt-backups.service pour une sauvegarde locale ou sudo systemctl start backup@hostname:path.service pour une sauvegarde distante par SSH.

Automatisation

L'exécution automatique des sauvegardes est gérée par un timer systemd, qui se contente de déclencher le service associé régulièrement.

# /usr/local/lib/systemd/system/backup@.timer
[Unit]
Description=Full system backup timer for %I

[Timer]
OnBootSec=30m
OnUnitInactiveSec=1h

[Install]
WantedBy=timers.target

Par défaut, un timer exécute un service portant le même nom (template inclus), il n'est donc pas nécessaire de préciser le service à lancer. La commande sudo systemctl enable --now backup@escaped_repo_path.timer permet d'activer le timer immédiatement ainsi que son activation automatique lors de chaque prochain démarrage du système.

Ce timer dispose de deux conditions de déclenchement :

  • Premier déclenchement : Trente minutes après le démarrage du système
  • Déclenchements suivants : Une heure après la fin de la dernière sauvegarde

J'ai choisi de réaliser des sauvegardes très souvent, puisque la faible charge de mes machines le permet. Le déclenchement basé sur une durée après la fin d'une sauvegarde comporte plusieurs caractéristiques intéressantes :

  • Une sauvegarde ne sera pas déclenchée alors que la précédente est toujours en cours vers le même dépôt.
  • Le système sera assuré d'avoir un temps de repos entre deux sauvegardes consécutives vers le même dépôt.
  • L'exécution des sauvegardes vers les dépôts séparés se lisse automatiquement dans le temps, sans devoir définir une planification précise.

Montage et démontage automatique du volume local

Afin de réduire la disponibilité du dépôt de sauvegarde local, le volume contenant le dépôt n'est monté que pendant l'exécution des opérations de sauvegarde. Ce comportement est piloté par l'intégration des points de montage dans systemd.

Le paramètre RequiresMountsFor=%I sur le service de sauvegarde déclenchera le montage automatique du ou des points de montage nécessaires pour atteindre l'emplacement du dépôt lors du démarrage du service. Je l'ajoute dans un drop-in uniquement pour les sauvegardes exécutées sur un disque local.

# /usr/local/lib/systemd/system/backup@-mnt-backups-backups.service.d/mountpoint.conf
[Unit]
RequiresMountsFor=%I

Lors de l'arrêt du service, le paramètre StopWhenUnneeded=true sur le point de montage du volume permet de déclencher son démontage automatique.

# /usr/local/lib/systemd/system/mnt-backups.mount
[Unit]
Description=Mount ext4:/dev/marmotte/backups on /mnt/backups
Requires=systemd-fsck@dev-marmotte-backups.service
After=systemd-fsck@dev-marmotte-backups.service
StopWhenUnneeded=true

[Mount]
Where=/mnt/backups
What=/dev/marmotte/backups
Type=ext4
Options=defaults,noauto
TimeoutSec=30

Restriction d'accès par SSH

Pour empêcher l'accès direct aux fichiers des sauvegardes réalisées par SSH, la commande borg serve est forcée dans le fichier .ssh/authorized_keys sur le serveur recevant les sauvegardes.

command="cd /srv/backups/repos/<hostname>; borg serve --restrict-to-path /srv/backups/repos/<hostname>",restrict ssh-rsa <ssh_key> username@hostname

La commande cd permet de ne pas avoir besoin de préciser le chemin absolu du dépôt sur la machine distante.

Rapport par email

À la fin d'une sauvegarde, un email contenant le détail de l'opération est envoyé automatiquement. Il consiste en une simple commande ExecStopPost ajoutée dans le service systemd, afin d'être envoyé quel que soit le résultat de l'exécution de la sauvegarde.

ExecStopPost=/bin/bash -c 'echo -e "Subject: Backup $SERVICE_RESULT ($EXIT_CODE, $EXIT_STATUS) for %H (%n)\nContent-Type: text/plain; charset=\"utf-8\"\n\n$(journalctl _SYSTEMD_INVOCATION_ID=$INVOCATION_ID --output cat)" | msmtp -a backup username@example.com'

Les variables $SERVICE_RESULT, $EXIT_CODE et $EXIT_STATUS sont définies automatiquement par systemd selon le résultat de la dernière commande exécutée. Dans le cas où la commande borg create termine en erreur, le service n'exécute pas borg prune et le sujet du email contient alors le code d'erreur renvoyé par la création d'archive échouée. Les codes de retour retournés par Borg sont 0 (exécution sans erreur), 1 (erreurs non bloquantes) ou 2 (erreur bloquante), ce qui permet de définir simplement un filtre dans le client email pour mieux remarquer les erreurs rencontrées.

Lors de l'exécution d'un service systemd, la sortie standard est enregistrée dans le journal systemd. Pour intégrer la sortie des commandes dans le corps de l'email, il suffit donc de la lire à l'aide de la commande journalctl, en utilisant la variable $INVOCATION_ID, définie automatiquement lors de l'exécution d'un service, qui identifie chaque exécution de manière unique.

Métriques et supervision

Borg propose un paramètre --json permettant de transmettre les informations à propos d'un dépôt ou d'une archive à un programme. J'ai donc développé un webservice simple recevant les données json générées par Borg pour enregistrer les métriques intéressantes dans InfluxDB. Un autre article y est d'ailleurs dédié.

L'envoi des données vers ce webservice est réalisé par une nouvelle commande ajoutée dans le service systemd :

# Uniquement les archives
ExecStopPost=-/bin/bash -c '/usr/bin/borg info --json --last 1 %I | /usr/bin/curl --max-time 3600 --request POST --header "Content-Type: application/json" --data @- http://monitoring-server:5000/backup'
# Archives et dépôt (permet d'avoir le nombre d'archives)
ExecStopPost=-/bin/bash -c '(echo "["; /usr/bin/borg info --json --last 1 %I; echo ", "; /usr/bin/borg list --json %I; echo "]") | /usr/bin/curl --max-time 3600 --request POST --header "Content-Type: application/json" --data @- http://monitoring-server:5000/backup'

En plus du simple affichage des données, j'ai configuré deux alertes dans Grafana, afin d'être prévenu si :

  • La taille des données sauvegardées varie de plus de 5% (en plus ou en moins)
  • Un dépôt reçoit moins de trois nouvelles sauvegardes dans les six dernières heures

Sauvegardes utilisateur

En plus des sauvegardes automatisées du système complet de chaque machine, j'effectue parfois des sauvegardes supplémentaires pour certaines données importantes. Par exemple, le répertoire de mon ordinateur principal, contenant tous mes documents administratifs (contrats de travail, déclarations d'impôts, factures, etc.), déjà inclus dans la sauvegarde système, est sauvegardé en plus par un script que je lance manuellement de temps en temps.

Ces sauvegardes supplémentaires visent à diversifier les supports et emplacements pour sécuriser d'autant plus certaines données, tout en ayant un dépôt plus petit, qui est donc plus rapide à parcourir et restaurer.

Bien qu'elles ne soient pas automatisées actuellement, il serait très simple de le faire, en ajoutant un service utilisateur systemd sur le même modèle que celui que j'utilise pour les sauvegardes du système.

Pistes d'amélioration

Adepte de l'amélioration continue, je ne considère jamais mes systèmes ou mon organisation comme parfaits, et je recherche constamment des moyens de les améliorer.

Vérification de l'intégrité des données

Borg propose une commande borg check permettant d'effectuer une vérification de l'intégrité du dépôt (metadonnées et présence des fichiers), des archives (présence de toutes les données), voire de l'intégralité des données en déchiffrant chaque archive.

Cependant, Borg ne permet pas de réaliser plusieurs opérations simultanément sur le dépôt. La vérification étant très longue à exécuter, elle empêcherait la réalisation correcte des sauvegardes.

Je l'exécute actuellement à la main une fois de temps en temps, en sélectionnant les éléments à vérifier au cas par cas, afin de réduire la durée de l'opération.

Tests de restauration

Tout comme pour la vérification de l'intégrité, je ne teste pas automatiquement la restauration des données sauvegardées. Cependant, j'effectue des tests manuels quand j'y pense, en extrayant quelques fichiers au hasard de l'un ou l'autre de mes dépôts Borg, au moyen des commandes borg extract ou borg mount.

Réaliser ces tests automatiquement me semble difficilement applicable pour différentes raisons : verrouillage du dépôt Borg lors de l'opération, sélection des archives à tester ou des fichiers à extraire, vérification du contenu des fichiers extraits, etc.

Protéger les dépôts distants contre la suppression d'archives

En l'état actuel, une machine compromise pourrait permettre à un attaquant de supprimer tout le contenu des dépôts distants. Borg dispose bien d'un mode append-only, mais je le trouve actuellement trop contraignant.

L'utiliser nécessiterait de trouver un moyen de déclencher un commit sur le dépôt de temps en temps, afin de réellement effacer les données supprimées du dépôt par la politique de rétention.

Un de mes objectifs étant l'automatisation totale du processus, cela poserait donc deux problèmes :

  • Pour ne pas rendre son utilisation inutile, il est nécessaire de vérifier que le dépôt ne contient pas d'opération malveillante avant, ce qui est difficile à automatiser.
  • Même en utilisant une seconde clé SSH protégée, l'automatisation nécessiterait que le mot de passe de cette clé se trouve quelque part sur la machine, ce qui annule l'intérêt de séparer les droits. Cette opération devrait alors être réalisé depuis une autre machine, ce qui forcerait la reconstruction complète du cache sur la machine source, une opération qui peut s'avérer longue, surtout via ma connexion ADSL.

Diversifier les outils

Une des faiblesses de mon organisation actuelle est d'être restreint à l'utilisation seule de Borg. En effet, si un bug affecte la validité des sauvegardes, alors elles sont toutes touchées.

Pour réduire ce risque, il est possible de réaliser des sauvegardes avec un ou plusieurs autres outils, afin de disposer de plusieurs copies effectuées par des moyens différents. Le gros inconvénient de cela est d'augmenter encore le nombre de sauvegardes réalisées, et donc la charge induite sur les machines, le but étant quand même de pouvoir les utiliser pour faire autre chose que des sauvegardes.

Kopia

Dans ma quête d'un autre outil, j'ai évalué Kopia, qui a des fonctionnalités assez proche de Borg (chiffrement, compression, déduplication). Il fonctionne correctement, et j'aurais très bien pu l'utiliser comme seul outil.

La possibilité de réaliser les sauvegardes de plusieurs machines dans un même dépôt Kopia est très intéressante, ainsi que la possibilité de définir des permissions très précises.

Cependant, il utilise une configuration des exclusions complètement différente, ce qui m'empêche de l'utiliser comme système de sauvegarde secondaire. En effet, en plus de devoir gérer la configuration des exclusions globales en double, j'aurais aussi dû modifier tous les fichiers d'exclusion spécifiques .NOBACKUP (sans oublier les prochains), ce qui rend l'utilisation simultanée de Borg et Kopia inconcevable.

Restic

J'ai ensuite testé Restic, malgré l'absence de gestion de la compression dans ses fonctionnalités. La possibilité de stocker les sauvegardes de plusieurs machines dans le même dépôt compense en partie la différence d'espace disque. Le fonctionnement de Restic permet de réaliser plusieurs opérations en même temps sur un dépôt, il est donc possible de l'utiliser sans que les différentes machines qui réalisent leurs sauvegardes ne se gênent entre elles. Le seul inconvénient de mutualiser le dépôt est que chaque machine peut alors agir (lecture et suppression) sur toutes les sauvegardes qu'il contient.

Sa configuration d'exclusion étant parfaitement identique à celle de Borg, il peut utiliser mes fichiers /root/.config/borg/ignore et .NOBACKUP sans modification. L'utilisation des deux outils simultanément est donc possible sans risquer d'oublier d'adapter la configuration des exclusions.

Pour utiliser un dépôt distant, Restic propose de passer par un accès SFTP, mais cela ne me convient pas, puisque l'accès au dépôt en mode fichier est alors possible. J'ai donc opté pour son second composant, permettant de lancer un serveur Restic proposant une API REST.

L'accès aux dépôts est conditionné par une authentification basique HTTP, et il dispose d'un mode append-only. Comme on peut réaliser plusieurs opérations en même temps, il est possible de déporter l'application de la politique de rétention ailleurs que sur la machine réalisant les sauvegardes sans gêner son fonctionnement. J'ai donc pu activer le mode append-only, et la politique de rétention est appliquée par un service exécutant les commandes restic forget et restic prune depuis le serveur hébergeant le dépôt Restic.