Journal [Btrfs et openSUSE] Épisode 4 : le transfert de sous-volume

Posté par . Licence CC by-sa
26
3
sept.
2017

Sommaire

Btrfs is hard

« 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.

multifonction

À 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.

manifaik

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

clone

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

mommy

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.

beurre

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

pause

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

  • # buttersink est ssh compatible désormais

    Posté par . Évalué à 3 (+3/-0).

    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 . Évalué à 2 (+2/-0).

    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’outil btrfs 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 . Évalué à 2 (+1/-0). Dernière modification le 04/09/17 à 08:00.

      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 ?

      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.

      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.

      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 . Évalué à 2 (+1/-1). Dernière modification le 04/09/17 à 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 . Évalué à 3 (+2/-0).

    Ç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 . Évalué à 3 (+3/-0).

    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 ?

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.