Sommaire
« Btrfs et openSUSE » est une série de journaux sur le système de fichiers Btrfs, basée sur ma propre expérience d'utilisateur d'openSUSE. Au menu :
- des généralités sur Btrfs ☑
- des noms qui pètent : sous-volumes, snapshots, rollbacks ☑
- du snapper ☑
- du grub ☑
- de la mise à jour incrémentale
- des quotas
- de la maintenance ☑
- des trucs spécifiques à openSUSE
- des tentatives désespérées pour rester applicable à d'autres distributions
- des erreurs (pas taper)
- des bugs
- des cris
- des larmes
- et bien plus ! ou bien moins
Aujourd'hui, l'épisode 4 : le transfert de sous-volumes.
Note : cette épisode s'inspire de cet article que j'ai publié sur le forum Alionet.
La base
Btrfs sait faire plein de trucs
Parmi les fonctionnalités de Btrfs, il y en a une que je trouve particulièrement intéressante : le transfert de sous-volume, c'est-à-dire la copie d'un sous-volume en lecture seule d'un système de fichiers à un autre.
À quoi ça sert ?
Cela permet par exemple de faire des sauvegardes de ses instantanés plutôt que de les garder à même le système de fichiers d'origine.
Mieux encore, il est possible d'envoyer uniquement les différences entre deux snapshots. Cela permet de faire de la sauvegarde incrémentale. Un peu comme avec rsync mais en plus efficace, en particulier pour des systèmes de fichiers de grande taille. Du moins, selon les auteurs de Btrfs.
La fonctionnalité a été introduite dans la version 3.16 du noyau Linux et est considérée comme stable.
Au commencement, l'instantané
J'ai créé un premier snapshot de mon /
. Ou snapper l'a créé pour moi :
~# # On peut aussi utiliser snapper ls
~# btrfs subvolume list -s /
ID 259 gen 985 top level 258 path @/.snapshots/1/snapshot # ça, c'est le sous-volume par défaut
ID 286 gen 171 top level 258 path @/.snapshots/2/snapshot # le snapshot que j'ai créé
#
Je voudrais transférer ce cliché (n°2) sur un disque dur externe, histoire d'être un peu plus rassuré. Et aussi histoire de pouvoir ensuite le supprimer de mon disque dur interne si jamais je manque de place.
J'ai donc créé une partition en Btrfs sur mon disque dur externe, que j'ai montée sur /mnt
. Sur cette partition, rien :
~# btrfs subvolume list /mnt
~# btrfs subvolume get-default /mnt/
ID 5 (FS_TREE)
~#
Bien, allons-y.
Mon premier transfert
C'est hyper simple.
~# # Je me crée un petit dossier de destination sur mon disque dur externe
~# mkdir -p /mnt/2
~#
~# # La commande suivante est magnifique
~# btrfs send /.snapshots/2/snapshot | btrfs receive /mnt/2
At subvol /.snapshots/2/snapshot/
At subvol snapshot
~#
~# # Le sous-volume apparaît bien de l'autre côté
~# btrfs subvolume list /mnt
ID 262 gen 80 top level 257 path 2/snapshot
~# tree -L 2 /mnt/2
/mnt/2
└── snapshot
├── bin
├── boot
├── dev
├── etc
├── lib
├── lib64
├── mnt
├── proc
├── root
├── run
├── sbin
├── selinux
├── sys
├── usr
└── var
~#
btrfs send
crée un flux d'instructions pour reconstruire l'instantané.btrfs receive
reçoit ce flux et l'exécute sur /mnt
.
C'est beau.
Je peux aussi copier le fichier /.snapshots/2/info.xml
, au cas où je veuille plus tard utiliser snapper pour faire une restauration :
~# cp /.snapshots/2/info.xml /mnt/2
~#
L'incrémental
L'option -p
: définir un parent
Le temps a passé. J'ai pris un deuxième instantané. Il porte le numéro 3 :
~# btrfs subvolume list -s /
ID 259 gen 985 top level 258 path @/.snapshots/1/snapshot
ID 286 gen 171 top level 258 path @/.snapshots/2/snapshot
ID 293 gen 377 top level 258 path @/.snapshots/3/snapshot # c'est lui !
~#
Je veux faire la même chose mais j'ai quand même regardé le manuel alors je fais un peu différemment :
~# # Je crée un petit dossier de destination sur mon disque dur externe
~# mkdir -p /mnt/3
~#
~# # Utilisons l'option -p
~# btrfs send -p /.snapshots/2/snapshot /.snapshots/3/snapshot/ | btrfs receive /mnt/3
At subvol /.snapshots/3/snapshot/
At subvol snapshot
~#
C'est beaucoup plus rapide ! Et puis j'ai l'accès aux deux snapshots en entier, comme pour les originaux :
~# btrfs subvolume list /mnt
ID 262 gen 80 top level 257 path 2/snapshot
ID 265 gen 80 top level 257 path 3/snapshot
~# tree -L 3 /mnt
├── 2
│ └── snapshot
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── lib
│ ├── lib64
│ ├── mnt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── selinux
│ ├── sys
│ ├── usr
│ └── var
└── 3
└── snapshot
├── bin
├── boot
├── dev
├── etc
├── lib
├── lib64
├── mnt
├── proc
├── root
├── run
├── sbin
├── selinux
├── sys
├── usr
└── var
~#
En fait, l'option -p
demande à btrfs send
de supposer que le sous-volume indiqué (/.snapshots/2/snapshot
) est le sous-volume parent du sous-volume à envoyer (/.snapshots/3/snapshot
). Ainsi, btrfs send
n'enverra que les instructions pour transformer le sous-volume parent (/.snapshots/2/snapshot
) en le sous-volume voulu (/.snapshots/3/snapshot
).
De son côté, btrfs receive
, à la réception du flux, fera un snapshot du sous-volume qui correspond à celui indiqué comme parent (/mnt/3/snapshot
= /.snapshots/3/snapshot
) puis le remplira petit à petit en suivant les instructions du flux.
L'option -c
: définir des clones
Il existe une autre option que le -p
pour le transfert incrémental : -c
.
-c
permet comme -p
de spécifier un sous-volume qui partage des données avec le sous-volume à envoyer mais contrairement à -p
:
- l'option peut être répétée (on peut spécifier plusieurs sous-volumes) ;
- le sous-volume indiqué ne sera pas forcément utilisé comme parent à la réception.
L'option -c
paraît donc utile pour des données afin de minimiser encore davantage les données à émettre par le send
, par exemple quand il y a des cp --reflinks
qui ont été faits entre les sous-volumes.
Exemple :
~# # Création d'un sous-volume de test
~# btrfs subvolume create /test
Create subvolume '//test'
~# cd /test
~#
~# # On crée un gros fichier 'a' (160 Mio) et on prend un cliché ('.1')
~# dd if=/dev/urandom of=a bs=4k count=40k
40960+0 enregistrements lus
40960+0 enregistrements écrits
167772160 bytes (168 MB, 160 MiB) copied, 0,799502 s, 210 MB/s
~# btrfs subvolume snapshot -r . .1
Create a readonly snapshot of '.' in './.1'
~#
~# # On supprime 'a', on crée 'b' (80 Mio) et on prend un cliché ('.2')
~# rm a
~# dd if=/dev/urandom of=b bs=4k count=20k
20480+0 enregistrements lus
20480+0 enregistrements écrits
83886080 bytes (84 MB, 80 MiB) copied, 0,400634 s, 209 MB/s
~# btrfs subvolume snapshot -r . .2
Create a readonly snapshot of '.' in './.2'
~#
~# # On restaure 'a' depuis '.1', on crée 'c' (40 Mio) et on prend un cliché ('.3')
~# cp --reflink .1/a . # on restaure a
~# dd if=/dev/urandom of=c bs=4k count=10k
10240+0 enregistrements lus
10240+0 enregistrements écrits
41943040 bytes (42 MB, 40 MiB) copied, 0,202652 s, 207 MB/s
~# btrfs subvolume snapshot -r . .3
Create a readonly snapshot of '.' in './.3'
~#
~# # On a donc trois snapshots : /test/.{1..3}
~# # Regardons un peu ce qu'il me coûterait d'envoyer '.3'…
~#
~# # … si c'était un premier transfert
~# btrfs send .3 | wc -c
At subvol .3
293787573 # 280 Mio : 'a' (160 Mio) + 'b' (80 Mio) + 'c' (40 Mio). Logique.
~#
~# # … si c'était un transfert incrémental par rapport à la version la plus récente
~# btrfs send -p .2 .3 | wc -c
At subvol .3
209848255 # 200 Mio : 'a' + 'c'. Et oui, '.2' ne contient pas 'a'…
~#
~# # … si c'était un transfert incrémental par rapport à la version la plus ancienne
~# btrfs send -p .1 .3 | wc -c
At subvol .3
125909800 # 120 Mio : 'b' + 'c'. Finalement, c'est plus avantageux d'utiliser '.1' que '.2'.
~#
~# # … si c'était un transfert incrémental par rapport aux deux versions
~# btrfs send -c .1 -c .2 .3 | wc -c
At subvol .3
41970437 # 40 Mio : seulement 'c' ! On a déjà 'a' de '.1' et 'b' de '.2', pas besoin de les envoyer !
~#
Une affaire de famille
Au final, on ne peut pas faire un transfert incrémental sans avoir déterminé qui jouera le rôle de parent côté réception.
L'option -p
permet de le définir directement. Avec l'option -c
et si -p
n'est pas utilisé, btrfs send
en déterminera un tout seul à partir des sous-volumes clones.
Donc, du point de vue du système de fichiers source, .1
, .2
et .3
sont frères, ce sont des instantanés du même sous-volume test
:
~# btrfs subvolume list -tuq / | awk 'NR <= 2 || /test/'
ID gen top level parent_uuid uuid path
-- --- --------- ----------- ---- ----
6037 446781 5 - 7a876f9d-d6a9-1e46-ac1e-d9efae8128a3 test
6038 446779 6037 7a876f9d-d6a9-1e46-ac1e-d9efae8128a3 ca43a0a5-11bf-3d43-b524-79be6fc08260 test/.1
6039 446780 6037 7a876f9d-d6a9-1e46-ac1e-d9efae8128a3 3329c9b4-6a95-ce4a-a059-2c2872ba71e8 test/.2
6040 446781 6037 7a876f9d-d6a9-1e46-ac1e-d9efae8128a3 92eda75e-afcd-654f-a42c-9ea6539a1ebe test/.3
~#
Mais du point de vue du système de fichiers destination, .1
est le parent de .2
, lui-même le parent de .3
:
~# btrfs subvolume list -tuq /mnt
ID gen top level parent_uuid uuid path
-- --- --------- ----------- ---- ----
295 425 5 - ec91e7bb-aae7-de42-b3dd-9e12c1fefc68 .1
296 428 5 ec91e7bb-aae7-de42-b3dd-9e12c1fefc68 39cadd5a-384b-534a-b256-e2b8c62959dd .2
297 429 5 39cadd5a-384b-534a-b256-e2b8c62959dd abb14bb5-ceb8-db4c-badc-3e171cac4328 .3
~#
La suite
La restauration
Ça s'en va et ça revient…
Savoir faire des sauvegardes, c'est bien. Savoir les restaurer… c'est bien aussi.
Ça tombe bien, on a vu plus compliqué :
~# mkdir -p /.snapshots/2
~# btrfs send /mnt/2/snapshot | btrfs receive /.snapshots/2
~# cp /mnt/2/info.xml /.snapshots/2/ # pour snapper
~#
Bref, ensuite il suffit de faire une restauration classique, avec snapper ou directement avec les commandes btrfsprogs.
Des soucis avec GRUB ?
Pour mettre à jour le menu du chargeur d'amorçage avec un GRUB vanilla, je vous laisse regarder sur vos wikis préférés. Parce que je ne sais pas le faire.
Pour le GRUB patché d'openSUSE, il n'y a rien à toucher normalement.
À la rigueur, si vous ne faites pas un rollback de suite, vous pourrez avoir envie de mettre à jour le fichier /.snapshots/grub-snapshot.cfg
afin de voir le snapshot reçu dans les menus. Il y a un script pour cela, l'appel est le suivant :
~# /usr/lib/snapper/plugins/grub --refresh
~#
Autrement, il n'y a donc rien à faire… en théorie. Car en pratique, il y a un bug gênant (boo#1049994) qui fait que :
- le menu du l'instantané reçu ne s'affiche pas dans le chargeur d'amorçage d'openSUSE ;
- en cas de restauration de ce snapshot, les autres instantanés bootables ne s'affichent plus.
Les deux soucis ont la même cause : une gestion problématique du sous-volume @/.snapshots
. Un workaround :
1) Ajouter un dossier .snapshots
au sous-volume reçu :
~# btrfs property set -t s /.snapshots/<id>/snapshot ro false
~# mkdir -p /.snapshots/<id>/snapshot/.snapshots
~# btrfs property set -t s /.snapshots/<id>/snapshot ro true
~#
2) Dans GRUB, avant de tenter d'entrer dans le menu de l'instantané <id>
, passer en ligne de commande et faire :
grub> btrfs-mount-subvol ($root) /.snapshots @/.snapshots
ou bien changer /etc/grub.d/80_suse_btrfs_snapshot
:
--- 80_suse_btrfs_snapshot (avant)
+++ 80_suse_btrfs_snapshot (après)
@@ -3,6 +3,8 @@
if [ "x${SUSE_BTRFS_SNAPSHOT_BOOTING}" = "xtrue" ] &&
[ "x${GRUB_FS}" = "xbtrfs" ] ; then
cat << EOF
+# Explicitely mount @/.snapshots (workaround boo#1049994)
+btrfs-mount-subvol (\$root) /.snapshots @/.snapshots
if [ -f "/.snapshots/grub-snapshot.cfg" ]; then
source "/.snapshots/grub-snapshot.cfg"
fi
et régénérer GRUB :
~# update-bootloader --refresh
~#
Vu la vitesse a laquelle le bug a été analysé, j'ai bon espoir qu'il soit corrigé d'ici la fin de l'année.
Interrompre et reprendre un transfert
Les commandes btrfs send
et btrfs receive
ne gèrent pas l'interruption d'un transfert. C'est-à-dire qu'il n'est pas possible de reprendre l'envoi d'un snapshot partiellement transféré, il faut tout recommencer. Cela peut être embêtant dans le cas d'un transfert très long ou envoyé par le réseau.
Cependant, on peut contourner le problème simplement en mettant la sortie de btrfs send
dans un fichier que l'on transmettra avec un outil supportant la reprise de transfert, par exemple rsync
.
~# btrfs send /.snapshots/77/snapshot -f /tmp/snapshot.img
At subvol /.snapshots/77/snapshot
~# rsync /tmp/snapshot.img /mnt/
^C
~# rsync --append-verify /tmp/snapshot.img /mnt
~# btrfs receive /mnt/77 -f /mnt/snapshot.img
At subvol /.snapshots/77/snapshot
~# rm /{tmp,mnt}/snapshot.img
Cela demande toutefois de la place sur les systèmes de fichiers source et destination…
Il existe aussi un script Python, buttersink, qui est censé gérer la reprise de transfert, mais a priori uniquement vers un stockage Amazon S3. De plus, il nécessite btrfsprogs 4.11 ou plus récent (issue#36, bko#195597).
Comparer des instantanés
Vu que btrfs send
est capable d'envoyer ce qui diffère entre deux instantanés, il peut potentiellement devenir un bel outil de comparaison de snapshots… à condition de savoir interpréter son flux de sortie.
Il existe un script Python, btrfs-snapshots-diff, qui est capable de le décoder et de l'afficher sous une forme à peu près lisible :
~# btrfs-snapshots-diff.py -p /test/.1 -c /test/.2
At subvol /test/.2
Found a valid Btrfs stream header, version 1
.2
snapshot: uuid=3329c9b46a95ce4aa0592c2872ba71e8, ctrasid=446780, clone_uuid=ca43a0a511bf3d43b52479be6fc08260, clone_ctransid=446779
__sub_root__
times a=2017/09/03 21:03:00 m=2017/09/03 21:03:32 c=2017/09/03 21:03:32
times a=2017/09/03 21:03:00 m=2017/09/03 21:03:32 c=2017/09/03 21:03:32
times a=2017/09/03 21:03:00 m=2017/09/03 21:03:32 c=2017/09/03 21:03:32
a
unlink
o258-446780-0
mkfile
rename to "b"
b
renamed from "o258-446780-0"
update extents 0 -> 83886080
truncate 83886080
owner 0:0
mode 644
times a=2017/09/03 21:03:32 m=2017/09/03 21:03:33 c=2017/09/03 21:03:33
~#
Autrement, snapper peut également comparer des instantanés mais je ne crois pas qu'il utilise btrfs send
.
That's all folks!
Liens
- Sur le wiki Btrfs :
- Sur le wiki Debian Facile, un tuto très accessible je trouve : La sauvegarde de sous-volumes Btrfs.
- Sur le blog de Marc Merlin, Btrfs Tips: Doing Fast Incremental Backups With Btrfs Send and Receive : un script pour faire des sauvegardes incrémentales et les envoyer vers un disque distant via SSH.
- Sur la liste de diffusion linux-btrfs, un fil sur les options
-p
et-c
debtrfs send
.
# buttersink est ssh compatible désormais
Posté par ZeDuke . Évalué à 3.
Par contre il manque encore la reprise sur échec.Mais c'est une feature request déjà proposée. Si j'ai le temps ces jours-ci je m'y attèle. Je ne sais pas si une nouvelle dépendance (rsync) sera acceptée.
# Question
Posté par .Nicolas. . Évalué à 2.
Je lis avec beaucoup d’intérêt ta série de journaux. Merci !
J’ai une question et une suggestion.
1/ Sais-tu si le format du flux obtenu par
btrfs send
est stable au changement de version de l’outil et/ou du noyau ? Je n’ai trouvé aucune information là-dessus. Tout ce qu’on a sur le wiki btrfs c’est que le format sur disque de btrfs n’est plus sensé bouger au point de créer des incompatibilités. Pourquoi j’ai besoin de savoir ? C’est que je suis en train de jouer avec le stockage cloud pour mes sauvegarde, et qu’on n’a pas la main sur le système de fichier distant : du coup je prépare un fichier .btrfs tel qu’obtenu par l’outilbtrfs send
, je le compresse, et j’envoie le tout. Mais en l’état actuel des choses, je n’ai pas la garantie que ma sauvegarde soit valable au changement de version des outils btrfs. Ça demande de faire attention à la version utilisée.Ton lien intitulé “notes de conception sur send/receive” apporte au moins un élément de réponse : je sais maintenant que le format est amené à changer. Reste à savoir dans quelle proportion et si un jour il est prévu une certaine stabilisation.
2/ Autre fonctionnalité très intéressante de btrfs, pas forcément pertinente pour ce journal, mais je profite pour le mentionner : la possibilité de mettre un système de fichier donné sur un device en lecture seule et un device en écriture incrémentale. Applications : un livecd ou une raspberry.
[^] # Re: Question
Posté par AR7 (site web personnel) . Évalué à 2. Dernière modification le 04 septembre 2017 à 08:00.
Non, je n'en sais pas plus que toi maintenant… Le wiki dit effectivement qu'un format v2 est prévu mais il n'y a quasiment aucune information sur lui. Je n'ai pas l'impression que ce soit une priorité pour l'instant.
Le seed device c'est ça ? Oui ça a l'air intéressant, je n'ai jamais testé mais je prends note de l'intérêt 😉
J'avais vu passer un mail sur la liste de diffusion opensuse-factory par rapport à son utilité pour les live CD. Ça pourrait être une alternative à OverlayFS.
J'ai vu cependant que la nouvelle version de KIWI (l'outil pour générer des images de distributions utilisé par openSUSE dans le Build Service) ne prenait plus en charge cette fonctionnalité.
[^] # Re: Question
Posté par Psychofox (Mastodon) . Évalué à 2. Dernière modification le 04 septembre 2017 à 08:15.
Si c'est comme ZFS la compatiblité future n'est pas garantie. Néanmoins sous ZFS il est possible d'upgrader incrémentalement les version du pool ZFS. En pratique généralement on essaie de suivre régulièrement les montées en versions. Raison pour laquelle c'est souvent plus pratique avec ce genre de systèmes de maitriser toute la chaine et de recevoir sur une machine faisant tourner le même filesystem et pas sous forme de fichier.
Il existe des stockages cloud supportant le zfs receive (rsync.net) auquel cas tu reçois une erreur si ton stream zfs est incompatible. As-tu regardé s'il existe le même genre de choses pour btrfs ?
# J’ai regardé que les images.
Posté par Cheuteumi . Évalué à 3.
Ça m’a fait rire.
Non en vrai, vraiment sympa ces articles, merci :)
# Merci et passage de la série d'article dans le wiki ?
Posté par showtime . Évalué à 3.
Un grand merci pour cette superbe série.
Il me semble qu'un travail aussi détaillé, complet, sourcé et qualitatif devrait figurer dans le wiki. Qu'en pensez-vous ?
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.