Sauvegardes de mon NAS avec snapshots btrfs et send/receive (via btrbk)

J’avais jusqu’ici fait la synchro des données de mon NAS avec SyncThing. Ca a bien marché pendant un bon moment, mais a montré ses limites quand il commençait à y avoir de la volumétrie et des modifications fréquentes. S’appuyer sur les fonctionnalités de btrfs est bien plus efficace, notamment dans mon cas de figure où ce NAS est l’emplacement de stockage des PVs de tout mon auto-hébergement k3s.

Problèmes avec SyncThing

SyncThing est un super outil, mais il n’était finalement pas adapté à mon besoin.

Le problème, dans mon cas de figure, c’est qu’il a besoin de scanner toute l’arborescence pour savoir ce qui est modifié, avant d’envoyer le delta (chiffré). Et ce scan est réalisé soit périodiquement, soit quand il est informé d’une modification.

Sur des arborescences avec beaucoup de fichiers, ça finit par consommer beaucoup de ressources. Un de mes petits serveurs finissait par ne plus faire que ça!

Principe

btrfs permet de prendre des snapshots d’un volume. Premier avantage: ça permet des sauvegardes locales faciles et instantanées, pour pouvoir revenir en arrière en cas d’erreur. De plus, un snapshot est une « photo » toujours consistante des données (notamment quand il s’agit des fichiers d’une base de données), contrairement à pas mal d’autres méthodes de sauvegardes (dont SyncThing).

Ensuite, btrfs est capable de connaître le « delta » entre 2 snapshots (au niveau blocs), ET d’envoyer ce delta à une machine distante (btrfs send/receive)

C’est cette capacité que j’ai trouvée géniale. Mon Turris Mox (qui fait office de NAS) stocke déjà ses données sur du btrfs. Il m’a suffit de mettre un disque dur formaté en btrfs sur une machine distante, et de faire un peu de configuration.

En fait, j’envoie les données plutôt sur 2 machines en parallèle: une « proche » pour servir de failover et de sauvegardes, et une physiquement distante, pour un éventuel PRA (et pour s’approcher du respect de la règle 3-2-1)

Utilisation de btrbk

btrbk est un « simple » script Perl qui automatise la création des snapshots, leur envoi, leur purge etc. https://digint.ch/btrbk/

Il faut d’abord créer des sous-volumes btrfs pour les répertoires du NAS. Dans mon cas, j’en ai créé 3 différents, en fonction de la fréquence de sauvegarde souhaitée (ces 3 répertoires correspondant à 3 storageClasses kubernetes): « realtime » (en fait, toutes les heures), normal (une fois par jour), et « rare » (une fois par semaine).

Comme le script est en Perl, il faut ajouter sur le turris les packages suivants pour le faire fonctionner: perl-cgi, perlbase-getopt, perlbase-time, perlbase-ipc

Dans /etc/crontabs/root, j’ai ajouté le lancement chaque heure du script bash ci-dessous (qui envoie aussi un e-mail en cas de problème, via le package « mailsend », à installer lui aussi):

#!/bin/bash
STDOUTERR_BTRBK=$(nice -10 /root/btrbk-0.32.6/btrbk -c /root/btrbk-0.32.6/btrbk.conf -q run 2>&1)
if [ -n "${STDOUTERR_BTRBK}" ]; then
        echo "${STDOUTERR_BTRBK}" | mailsend (...)
fi

Et j’utilise un fichier btrbk.conf du style:

# Enable transaction log
transaction_log            /var/log/btrbk.log

# Specify SSH private key for remote connections
ssh_identity               /root/.ssh/id_rsa_btrbk
ssh_user                   root

# Directory in which the btrfs snapshots are created. Relative to
# <volume-directory> of the volume section.
# If not set, the snapshots are created in <volume-directory>.
#
# If you want to set a custom name for the snapshot (and backups),
# use the "snapshot_name" option within the subvolume section.
#
# NOTE: btrbk does not automatically create this directory, and the
# snapshot creation will fail if it is not present.
#
snapshot_dir               _btrbk_snap

# Enable lock file support: Ensures that only one instance of btrbk
# can be run at a time.
lockfile                   /var/lock/btrbk.lock

snapshot_preserve_min   2d
snapshot_preserve       14d

target_preserve_min     no

# This is necessary to run on Turris OS
compat  busybox

# Always create snapshot, even if targets are unreachable
snapshot_create always

# Backup hard drive
volume /srv
  subvolume nfs_k8s_realtime_sync
    target ssh://server/mnt/hdd/_btrbk_snap
    target_preserve         24h 20d 10w 12m 1y
    target ssh://remoteserver/mnt/hdd/_btrbk_snap
    target_preserve         24h 20d 10w 12m 1y
  subvolume nfs_k8s
    target ssh://server/mnt/hdd/_btrbk_snap
    target_preserve         20d 10w 12m 1y
    target ssh://remoteserver/mnt/hdd/_btrbk_snap
    target_preserve         20d 10w 12m 1y
  subvolume nfs_k8s_rare_sync
    target ssh://server/mnt/hdd/_btrbk_snap
    target_preserve         10w 12m 1y
    target ssh://remoteserver/mnt/hdd/_btrbk_snap
    target_preserve         10w 12m 1y

Le premier lancement est très long, puisqu’il doit transférer toutes les données sur les machines cibles. Les suivants prennent en temps normal de 50 secondes à 4 minutes.

Désactivation du COW sur les répertoires de bases de données

Oui, vous verrez ce conseil un peu partout. btrfs fait du « copy-on-write » par défaut: quand un bloc est modifié, il écrit le nouveau bloc ailleurs plutôt que modifier le bloc existant. Ca a plein d’avantages, mais n’est pas du tout adapté aux bases de données (qui ont leur propre mécanisme de COW interne). Voir par exemple le paragraphe « COW on COW: Don’t do it! » de https://wiki.debian.org/Btrfs.

A faire sur les répertoires concernés du NAS. S’il y a déjà des données, cela passe par une copie complète: https://wiki.archlinux.org/title/Btrfs#Disabling_CoW

Failover

Donc mes données sont envoyées sur les serveurs cibles, c’est cool. Donc je devrais facilement pouvoir m’en servir de serveurs de failover ou PRA?

Pas directement, parce que les snapshots reçus sont readonly.

J’ai fait le script suivant pour, au besoin, créer un snapshot read-write à partir du dernier snapshot local (readonly). Je l’ai testé récemment (en urgence car mon NAS était subitement indisponible): ça a bien fonctionné

#!/bin/bash
echo "Remove old snapshots"
btrfs subvolume delete /mnt/hdd/nfs_k8s/
btrfs subvolume delete /mnt/hdd/nfs_k8s_rare_sync/
btrfs subvolume delete /mnt/hdd/nfs_k8s_realtime_sync/
for LAST_BACKUP_FILE in /mnt/hdd/_btrbk_snap/nfs_k8s.*; do : ; done
echo "Re-create nfs_k8s snapshot from ${LAST_BACKUP_FILE}"
btrfs subvolume snapshot ${LAST_BACKUP_FILE} /mnt/hdd/nfs_k8s
for LAST_RARE_BACKUP_FILE in /mnt/hdd/_btrbk_snap/nfs_k8s_rare_sync.*; do : ; done
echo "Re-create nfs_k8s_rare_sync snapshot from ${LAST_RARE_BACKUP_FILE}"
btrfs subvolume snapshot ${LAST_RARE_BACKUP_FILE} /mnt/hdd/nfs_k8s_rare_sync
for LAST_REALTIME_BACKUP_FILE in /mnt/hdd/_btrbk_snap/nfs_k8s_realtime_sync.*; do : ; done
echo "Re-create nfs_k8s_realtime_sync snapshot from ${LAST_REALTIME_BACKUP_FILE}"
btrfs subvolume snapshot ${LAST_REALTIME_BACKUP_FILE} /mnt/hdd/nfs_k8s_realtime_sync

Points à améliorer

Je fais tourner les btrfs send/receive sous le compte root actuellement, et avec un accès ssh via le compte root: pas idéal niveau sécurité. Il vaudrait mieux faire tourner ça sous un autre compte, avec uniquement les droits nécessaires.

D’autre part, je pourrais envisager d’inverser le sens de connexion: que les serveurs distants viennent chercher les données sur le NAS, plutôt que l’inverse. De sorte qu’une éventuelle compromission du NAS ne permette pas à l’attaquant d’effacer (ou chiffrer) les données de sauvegardes également.

Annexe: attention aux ressources nécessaires aux lignes de commandes btrfs

J’adore toutes les fonctionnalités de btrfs par rapport à ext4, mais elles ont tout de même un prix…

Premier exemple: conversion en btrfs d’un filesystem ext4 avec btrfs-convert

Il y a une ligne de commande btrfs-convert faite pour ça. Attention à ne pas utiliser une version trop ancienne. Sous debian buster, il faut utiliser les backports.

Ca peut tourner très longtemps, et consommer beaucoup de mémoire vive. Dans mon cas, avec un disque de 3.6 To (dont 2 To utilisées, avec beaucoup de petits fichiers), mon premier lancement a échoué: après 2 jours, il a été OOMkillé par l’OS à l’étape « creating ext2 image file » (il utilisait plus de 1.8 Go de RAM sur un système avec 2 Go en tout). Pour autant, le disque pouvait ensuite être monté en ext4 (comme avant), comme si on n’avait pas lancé la conversion. Cela s’explique par le mode de fonctionnement de cette conversion, qui ne touche pas aux blocs utilisés dans un premier temps, et ne modifie que des blocs libres (super idée, au passage).

Deuxième essai, sur un PC avec plus de ressources: ça a pris ~30 heures, et consommé un peu plus de 4Go de RAM. Mais ça a fonctionné.

Ensuite, enlever le sous-volume ext2_saved est instantané.

Autres exemples de lignes de commande consommatrices en ressources

Un btrfs check sur le même gros disque dur prend plus d’1h, et prend plus de 1.5 Go de RAM (à l’étape 4/7 « checking fs roots »), donc finit OOMkillé sur ma petite machine… En le relançant avec l’option « –mode=lowmem », ça ne prend qu’environ 0.5 Go, et finit au bout d’environ 30 heures (avec des erreurs à l’étape 4/7, donc apparemment il ne fait pas les étapes suivantes).

Un btrfs filesystem defragment dure des heures et des heures.

Le montage du filesystem est très long aussi (autour d’une minute, là où c’est quasi-instantané en ext4).

Conclusion

Malgré les quelques défauts de btrfs, qu’il faut connaitre, il est très adapté à mon besoin. Je suis ravi que mes sauvegardes soient devenues bien plus efficaces qu’avant.

Une réflexion sur « Sauvegardes de mon NAS avec snapshots btrfs et send/receive (via btrbk) »

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *