Introduction

L'objectif de cet article est de présenter une méthode pour passer d'un système physique à un système virtuel. La méthode utilisée n'a pas la prétention de faire référence, c'est juste un moyen que j'ai trouvé pour parvenir à mes fins.

Passer d'un système MS-Windows XP physique à une machine virtuelle s'est révélé plein de surprises et pas forcément si simple que ça. Voilà pourquoi j'ai préféré concentrer la documentation dans ce document.

Par ailleurs, je suis sûr que les informations qui y sont présentes pourront être utiles à quelqu'un.

Étape zéro: l'état des lieux

Nous avons une installation physique organisée de la manière suivante:

  • Une machine avec un seul disque dur (/dev/sda)
  • Ce disque dur dispose de plusieurs partitions organisée de la manière suivante:
    • Partition 1 (25Go) NTFS Racine MS Windows XP (/dev/sda1)
    • Partition 2 (10Go) ext4 / GNU/Linux (/dev/sda2)
    • Partition 3 (4Go) swap GNU/Linux (/dev/sda3)
    • Partition 4 (50Go) ext4 /opt GNU/Linux (/dev/sda4)

Le bootloader de cette machine est GRUB2. Cette machine est, à l'origine, une machine sous MS Windows XP Pro dont nous avons réduit la partition initiale (qui occupait tout le disque) grâce à un système d'exploitation GNU/Linux sous forme de LiveCD.

Une fois la place libérée, nous avons utilisé l'espace disponible pour installer notre nouveau système d'exploitation.

Nous voulons maintenant prendre le système d'exploitation installé sur la première partition (et tous les logiciels/paramétrages qui vont avec) et le virtualiser. Le fichier d'image disque sera situé dans /opt/ qui est une partition suffisamment dimensionnée pour accueillir le volume de données.

Pour atteindre cet objectif, nous allons utiliser VirtualBox et quelques outils systèmes supplémentaires.

Etape 1: Importer les données

Pour commencer, il nous faut un moyen de récupérer les données de la première partition et de les rendre exploitables pour VirtualBox. En d'autres termes, nous devons fabriquer un fichier VDI qui contiendra le système de fichiers présents dans notre partition n°1.

La commande '''VBoxManage convertfromraw''' permet de faire ce genre d'opération. Toutefois, un détail important ne vous a sans doute pas échappé ! En effet, un fichier VDI est une image d'un disque et non d'une partition. Pour être exploitable, il faut donc cloner un disque avec sa table de partition et tout ce dont il a besoin pour démarrer.

Si vous avez installé GRUB2, il vous faudra donc récupérer également la partition /boot qui contient les binaires et les fichiers de conf de GRUB2. Dans le cas contraire, vous aurez droit à un plantage de GRUB (grub rescue).

Notre problème est donc de bien faire attention à récupérer notre disque et non notre partition. Mais, dans notre situation, notre machine ne dispose que d'un seul disque dur physique avec un espace assez contraint. Nous allons donc devoir ruser et récupérer un début d'image de disque contenant notre table de partition et notre partition MS Windows XP complète. Nous récupérerons une image disque VDI non bootable dans un premier temps (GRUB2 tentera d'accéder à des fichiers non présents dans l'image car situés trop loin sur le disque physique).

Pour commencer, vous devez connaître le nombre d'octets de votre partition MS Windows XP. Pour cela, nous allons utiliser fdisk (sous root) :

  # fdisk -s /dev/sda1
  25602016

Pour obtenir notre taille en octets, il suffit de multiplier par 1000. Toutefois, nous devons également récupérer notre table de partition au début. Le nombre d'octets que nous allons devoir récupérer est donc un peu plus important que prévu. Dans le doute, autant en mettre un peu plus. Nous utiliserons donc la valeur de 25702016000.

Pour importer tout ça, nous allons utiliser la commande suivante:

cat /dev/sda | VBoxManage convertfromraw stdin /opt/VM/WinXP.vdi 25712016000

La commande importe les données du disque /dev/sda depuis le début et transforme le tout en image disque au format VDI (celui de VirtualBox). L'import s'arrêtera au bout de 25712016000 octets ce qui nous permettra de récupérer proprement:

  • la table de partition,
  • la partition MS Windows XP
  • probablement un bout de la deuxième partition (dont nous n'avons que faire).

Etape 2: Objectif: Booter sous Windows XP

Si vous essayez de créer une machine virtuelle en lui attachant cette image disque et que vous bootez dessus, vous serez vite arrêté par GRUB qui passera en mode rescue: il manque en effet tous les fichiers de la deuxième partition (/dev/sda2) qui sont utiles à GRUB pour se lancer.

Comment faire pour booter sur Windows XP ?

J'ai utilisé la démarche suivante:

  • Créer une machine virtuelle vierge.
  • Lui installer un système GNU/Linux Debian (en fait peu importe la distribution) avec peu d'espace (4Go ça suffit).
  • Ajouter notre image disque initiale (celle qui contient Ms Windows XP) en tant que disque secondaire de la machine virtuelle.
  • Booter notre machine virtuelle normalement (à partir de maintenant, tout se passe sur cette machine virtuelle qui dispose de deux disque: /dev/sda et /dev/sdb alias notre image de Windows XP)
  • Installer testdisk dans le système GNU/Linux Debian de la machine virtuelle. Testdisk permet de réparer la table de partition qui est défectueuse (normal, on n'a pas tout importé). Il va notamment supprimer l'entrée qui pose problème (/dev/sdb2 qui n'existe pas physiquement).
  • Lancer grub-update afin de prendre en compte la partition /dev/sdb1 (WinXP) dans le processus de démarrage de notre machine virtuelle.
  • Lancer grub-install /dev/sdb pour écraser notre grub originel.
  • À partir de ce stade, on peut booter sur Windows XP (qui en profite pour mettre à jour plein de trucs).

Toutefois, notre objectif est d'avoir une machine virtuelle qui ne contient que MS Windows XP. Il faudrait donc supprimer la partition qui permet de lancer Debian. Mais cette suppression va poser des problèmes à GRUB qui a besoin de fichiers situés dans la partition Debian pour pouvoir s'exécuter correctement.

Pour contourner le problème, il faudrait donc installer un bootloader capable de booter sur MS-windows XP. Il en existe pléthore mais mon choix s'est rabattu sur celui de ms-sys. Ce dernier permet d'installer un bootloader (non libre) simpliste permettant de démarrer MS-Windows XP. Comme le bootloader n'est pas libre, il n'est pas packagé dans Debian. Il faut donc le récupérer et le compiler. C'est ce travail que nous allons réalisé dans notre Debian virtuelle (qui a accès à tous les disques).

  • Éteindre la machine virtuelle.
  • Reconfigurer ses disques en inversant les disques: grub va quand même pouvoir gérer ça comme un grand (grâce aux UUID).
  • Booter sous GNU/Linux (maintenant /dev/sdb1).
  • Installer build-essential et gettext: '''aptitude install build-essential gettext'''
  • Récupérer le logiciel ms-sys et le compiler (un simple make suffit).
  • Aller dans le répertoire bin après la compilation.
  • Lancer la commande: '''./ms-sys -m /dev/sda1'''
  • Arrêter la machine virtuelle.
  • Déconnecter le deuxième disque (celui où GNU/Linux est installé).
  • Windows XP doit maintenant booter tout seul comme un grand.

Conclusion

Grâce à ces quelques outils, on peut récupérer un système MS-Windows valide, même sans le mot de passe administrateur de ce système d'exploitation. Bien entendu, il restera des choses à faire comme installer les extensions VirtualBox et pour lesquelles il faut justement un compte d'administrateur. Mais ceci est une autre histoire...

Je suis sûr qu'il existe d'autres techniques mais celle qui est présentée permet de se débrouiller avec une seule machine disposant d'une seul disque.

Posted jeu. 27 juin 2013 19:30:00 Tags:

Introduction

Certes, il y a longtemps que je n'ai pas écrit d'article sur ce blog mais pour celui-ci, je vais augmenter sensiblement le volume de rédaction. Cette fois, je vous propose d'étudier ce que feu l'entreprise SUN appelait la Rolls des systèmes de fichier: ZFS. Les personnes qui manipulent des commandes de création de système de fichiers toute la journée sont rompues aux exercices périlleux d'augmentation de volume, de création d'espace, d'allocation de quota, de sauvegardes, de déplacements en tout genre. Ils sont des experts d'une floppée d'outils et connaissent un maximum de types de système de fichiers.

Pour toutes ces personnes, ZFS propose de créer le système de fichier ultime qui offre toutes les fonctionnalités modernes permettant de gagner un maximum d'efficacité pour les administrateurs système. C'est une promesse alléchante et, le plus simple, est de le vérifier par nous même. En terme de références, j'ai utilisé le site officiel d'Oracle (qui a acheté SUN il y a quelques années) qui propose un guide d'administrateur de ZFS. J'ai également utilisé le site de ZFSOnLinux, une implémentation de ZFS pour le noyau Linux et notamment son bugtracker.

Les exemples qui suivent sont tirés de mon expérience personnelle sur un système CentOS 6.3 (qui est franchement moins bien qu'une Debian mais on ne se refait pas !).

Nous allons commencer par "installer" ZFS sur notre système d'exploitation et commencer par un exemple simple avant d'aborder les deux concepts importants de ZFS: les "pools" et les "filesystems".

Installation de ZFSonLinux

Un des problèmes majeurs de ZFS c'est que la licence sous laquelle il est publié (la CDDL) est incompatible avec la GPLv2 du noyau Linux. Ainsi, il est impossible de récupérer le code source de ZFS pour l'inclure dans le code source officiel du noyau Linux. Donc tous les systèmes GNU/Linux doivent ruser pour installer ZFS... Alors que les systèmes BSD n'ont pas ce problème. Heureusement, il existe plusieurs implémentations indépendantes de ZFS pour Linux. La première est basée sur un module (donc en espace kernel). Elle se nomme ZFSOnLinux. La seconde fonctionne en espace utilisateur via fuse et se nomme zfs-fuse.

Toutefois, il est important de savoir que ces deux implémentations ne sont pas au même niveau. En effet, ZFSOnLinux semble toujours bénéficier d'un développement actif si l'on en croit son bugtracker. C'est donc le système que j'ai retenu et expérimenté. ZFSOnLinux offre, qui plus est, des modules noyaux empaquetés pour les différentes distributions, dont les distributions à base de RPM.

Néanmoins, sur ma CentOs 6.3, je n'ai pas pu profiter des paquets DKMS car la version de DKMS n'est pas suffisamment à jour pour faire en sorte d'utiliser les modules DKMS livrés par ZFSOnLinux. Pour rappel, DKMS est un système permettant de recompiler à la volée les sources des modules lors de l'installation d'un nouveau noyau. C'est un système très pratique pour tous les modules qui ne peuvent être livrés que sous forme de sources. Il nous faudra, malheureusement, nous en passer et se taper les étapes suivantes, à chaque changement du noyau ! Je suis presque certain que je n'aurai pas eu le même problème avec la distribution Debian. Néanmoins, parfois, on ne choisit pas le système sur lequel on travaille. De plus, s'il est possible d'installer ZFSOnLinux sur CentOS, il sera également possible de l'installer sous RedHat et ses RHEL, donnant ainsi un accès plus facile à ZFS pour les entreprises et structures qui utilisent ces systèmes et le support qui va avec.

Voici, sous forme scriptées les commandes permettant d'installer tous les paquets utiles pour ZFSOnLinux. J'ai utilisé la version ReleaseCandidate 14 de la version 0.6.0 des sources. (Note: A l'heure de la publication finale de cet article, la version 0.6.1 est sortie.)

curl -O http://archive.zfsonlinux.org/downloads/zfsonlinux/spl/spl-modules-0.6.0-rc14.src.rpm
rpmbuild --rebuild spl-modules-0.6.0-rc14.src.rpm
yum install rpmbuild/RPMS/x86_64/spl-modules-0.6.0-rc14*.rpm
yum install rpmbuild/RPMS/x86_64/spl-modules-devel-0.6.0-rc14*.rpm

curl -O http://archive.zfsonlinux.org/downloads/zfsonlinux/spl/spl-0.6.0-rc14.src.rpm
rpmbuild --rebuild spl-0.6.0-rc14.src.rpm
yum install rpmbuild/RPMS/x86_64/spl-0.6.0-rc14*.rpm
yum install rpmbuild/RPMS/x86_64/spl-0.6.0-rc14*.rpm

curl -O http://archive.zfsonlinux.org/downloads/zfsonlinux/zfs/zfs-modules-0.6.0-rc14.src.rpm
rpmbuild --rebuild zfs-modules-0.6.0-rc14.src.rpm
yum install rpmbuild/RPMS/x86_64/zfs-modules-0.6.0-rc14*.rpm

curl -O http://archive.zfsonlinux.org/downloads/zfsonlinux/zfs/zfs-0.6.0-rc14.src.rpm
rpmbuild --rebuild zfs-0.6.0-rc14.src.rpm
yum install rpmbuild/RPMS/x86_64/zfs-0.6.0-rc14*.rpm

A la fin, nous obtenons des commandes ZFS opérationnelles ainsi qu'un module pour le noyau:

# lsmod | grep zfs
zfs                  1140074  3 
zcommon                44356  1 zfs
znvpair                79994  2 zfs,zcommon
zavl                    6925  1 zfs
zunicode              323120  1 zfs
spl                   260979  5 zfs,zcommon,znvpair,zavl,zunicode

Il est temps maintenant de faire joujou avec notre nouvel outil... avec le compte root, ce sera plus facile pour la suite !

Découvrir ZFS par la pratique: un petit exemple

Nous allons créer un pool ZFS et découvrir tout de suite après le concept de filesystem.

Un pool ZFS est un ensemble de disques, de partitions, de slices (morceau de disques) ou carrément de fichiers. Tous ces périphériques de blocs sont agrégés ensemble suivant certaines modalités pour offrir certaines fonctionnalités. On peut par exemple, prendre les 10 disques durs d'une machine et faire un pool ZFS avec 8 d'entre eux. On peut choisir de mobiliser 4 disques pour faire le mirroir de 4 autres disques. Ce qui est intéressant dans le concept de pool, c'est la grande variété des "périphériques" de stockage qui peuvent être utilisés. On peut par exemple mixer des partitions de disque avec des disques entiers. L'objectif est de grouper tout ce joyeux monde pour offrir un pool dans lequel on va pouvoir mettre des filesystems.

Voyons maintenant comment créer un pool. Nous allons fabriquer un pool à partir de deux disques durs vides de 1Go (l'exemple est lancé sur une machine virtuelle pour faciliter la gestion du matériel: je ne vais pas acheter une baie de disques spécialement pour découvrir ZFS !). Ces disques dur sont des disques SATA (virtuels) et sont nommés /dev/sdb et /dev/sdc. Nous allons, de plus, effectuer une configuration en mirroir: le contenu des disques sera en permanence synchronisé.

La première des choses à laquelle il faut faire attention, c'est que lorsque vous utilisez directement des disques pour votre pool, il faut les formatter en utilisant une table de partition en GPT. L'utilitaire fdisk (le vénérable) ne sait pas gérer ce genre de tables. Il vous faudra utiliser GNU parted pour le faire:

# parted /dev/sdb
mklabel gpt
quit
# #Refaire la même chose pour /dev/sdc !

Ensuite nous pouvons créer notre pool en une seule commande:

zpool create tank mirror sdb sdc

Nous avons nommé notre pool tank, en miroir des disques /dev/sdb et /dev/sdc. Comme vous pouvez le constater, ZFS est vraiment dans la concision, il n'y a pas besoin d'indiquer le chemin complet du disque (/dev/). La commande suivante permet de lister les pools ZFS connus sur la machine:

# zpool list
NAME   SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
tank  1008M   158K  1008M     0%  1.00x  ONLINE  -

On voit que nous avons un pool nommé tank dont la taille fait 1Go (mirroir de 2 disques de 1Go). On peut également voir que la commande zpool gère tous les aspects de gestion des pools ZFS. Vous n'avez qu'une seule commande pour tout faire, un peu dans le même concept que git.

Voyons maintenant le concept de filesystem ZFS... La commande zfs gère toute la partie filesystem. Classiquement un filesystem est un espace de rangement qui est créé une bonne fois pour toute sur une partition de disque dur. Dans ZFS, la notion de filesystem est différente. Un filesystem est tellement simple à créer qu'on peut le considérer presque comme un répertoire. Il y a d'ailleurs une notion de hiérarchie dans les filesystems.

Dans notre exemple de création de pool, ZFS créé par défaut un filesystem qui a le même nom que le pool. C'est le filesystem racine. On peut lui en ajouter autant qu'on veut. Ces derniers se partageront l'espace disponible sur le pool. La recommandation officielle d'Oracle sur le sujet est de créer autant de filesystems que de besoin. Par exemple, il est recommandé d'en créer un par utilisateur de la machine ! On voit bien qu'on est très proche d'un concept de répertoire. Voyons maintenant comment ça se passe dans la pratique:

 # zfs list
 NAME          USED  AVAIL  REFER  MOUNTPOINT
 tank          156K   976M    31K  /tank
 # mount
 /dev/sda1 on / type ext4 (rw)
 proc on /proc type proc (rw)
 sysfs on /sys type sysfs (rw)
 devpts on /dev/pts type devpts (rw,gid=5,mode=620)
 tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
 none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
 sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
 tank on /tank type zfs (rw,xattr)

Le premier filesystem (le root) se nomme comme le pool et il est monté dans /tank. Le point de montage est /tank et on voit, avec la commande mount, qu'il est bien monté automatiquement, même s'il n'y a rien dans /etc/fstab. Le point de montage est défini dans les propriétés du filesystem. Il n'y a donc pas besoin de passer par /etc/fstab pour gérer des filesystems ZFS. ZFS propose ses propres commandes pour monter et démonter des filesystems zfs. Pour démonter notre filesystem tank, un simple zfs unmount tank suffit. De même, un simple zfs mount tank permettra de le remonter:

 # zfs unmount tank
 # zfs mount tank

Nous allons maintenant créer un nouveau filesystem nommé backup et situé dans le filesystem tank:

 # zfs create tank/backup
 # zfs list
 NAME          USED  AVAIL  REFER  MOUNTPOINT
 tank          158K   976M    31K  /tank
 tank/backup    32K   976M    32K  /tank/backup

On voit alors que l'espace est bien partagé: tank/backup utilise 32Ko dans le pool tank.

A la découverte des filesystems ZFS

Nous savons créer des filesystem ZFS mais il est temps d'aller un peu plus loin dans la pratique. Nous avons pu constater également que tout passe par la commande zfs qui utilisera des options spécifiques selon vos besoins.

Le premier élément à connaître sur les filesystems ZFS est la notion de paramètres. ZFS conserve sa configuration comme un grand. Des métadonnées sont intégrées directement dans le système de fichier et non dans des fichiers de configuration externes. Chaque filesystem a donc plusieurs paramètres stockés qui permettent de définir des options. Je ne vais pas présenter tous les paramètres, juste ceux qui sont le plus utiles. Le premier d'entre eux sera le point de montage. Jusqu'à présent nous avons utilisé le point de montage par défaut qui est fonction du nom du filesystem ZFS (cf le dernier résultat de zfs list ci-dessus). Mais imaginons que nous souhaitons renommer tout ça avec une méthode de rangement différente. Pour le faire, il faudra seulement changer le paramètre mountpoint de chacun de nos filesystem. Le changement de paramètre se fait avec la commande set de zfs:

# zfs set mountpoint=/media/backup tank/backup
# zfs list
NAME          USED  AVAIL  REFER  MOUNTPOINT
tank          158K   976M    31K  /tank
tank/backup    32K   976M    32K  /media/backup
# mount
/dev/sda1 on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
tank on /tank type zfs (rw,xattr)

On voit qu'on a changé le mountpoint. Comme tank/backup était déjà monté, il a été automatiquement démonté. Un simple zfs mount tank/backup permet de le remonter au bon endroit. Cet exemple illustre également que le point de montage est complètement indépendant du nom du filesystem ainsi que de la hiérarchie. Pour obtenir la valeur d'un paramètre, on utilise la commande get de zfs:

# zfs get mountpoint tank
NAME  PROPERTY    VALUE       SOURCE
tank  mountpoint  /tank       local

Pour voir tous les paramètres d'un file system, il suffit de le demander:

# zfs get all tank
NAME  PROPERTY              VALUE                    SOURCE
tank  type                  filesystem               -
tank  creation              mer. mars 13 16:44 2013  -
...

On voit également que le nom du filesystem n'est pas un paramètre. Pour renommer un filesystem ZFS, y compris pour modifier sa hiérarchie, on peut utiliser la commande rename de zfs:

# zfs rename tank/backup tank/sauvegarde
# zfs list
NAME              USED  AVAIL  REFER  MOUNTPOINT
tank              159K   976M    31K  /tank
tank/sauvegarde    32K   976M    32K  /media/backup

Lorsqu'on affiche un ou plusieurs paramètres d'un filesystem, on voit qu'il existe une colonne SOURCE dont le contenu varie suivant le paramètre. Cette source indique comment la valeur du paramètre a été créée:

  • Une SOURCE à local indique qu'on a modifié manuellement le paramètre.
  • Une SOURCE à default indique que le paramètre a une valeur par défaut.
  • Une SOURCE à inherited indique que le paramètre a une valeur issue d'un filesystem parent.

Bien entendu, certains paramètres sont modifiables (ex: compression) alors que d'autres sont en lecture-seule (taux de compression). Bien entendu, zfs get permet grâce aux options -H et -o de récupérer une seule valeur ou une liste de valeur pour les paramètres que vous avez demandé pour pouvoir effectuer des traitements par script. C'est vraiment bien pensé et très efficace.

Une autre notion supplémentaire à connaître, c'est que comme les filesystems se partagent le même espace, quelques problèmes peuvent survenir. Ainsi, il est possible qu'un filesystem sature le pool. Pour se prémunir de tout ça, on peut utiliser un système de quota: dire que par exemple le filesystem tank/backup ne peut utiliser que 10Go. C'est une fonctionnalité intéressante, car elle permet de redimensionner un "espace disque" à chaud sans trop d'incidence sur le reste et ce, sans avoir à démonter le dit filesystem. C'est particulièrement pratique sur les systèmes en production qui ne peuvent souffrir le moindre arrêt !

Fixons un quota de 500Mo pour le filesystem tank/sauvegarde:

# zfs set quota=500Mb tank/sauvegarde
# zfs list
NAME              USED  AVAIL  REFER  MOUNTPOINT
tank              326K   976M    31K  /tank
tank/sauvegarde    32K   500M    32K  /media/backup

On voit qu'on ne pourra par aller au delà de 500M pour tank/sauvegarde.

Dans le même registre, il est possible de réserver un espace pour un filesystem. On peut donc garantir qu'un filesystem aura la place indiquée, au détriment des autres filesystems bien entendu. En effet, le quota est intéressant mais il n'empêchera pas la saturation du pool si un seul filesystem n'a pas de quota. La réservation permet de se prémunir de se genre d'incidents. On peut donc définir une réservation d'espace pour un filesystem donné et laisser les autres vivre leur vie, sans plus d'administration que ça en étant certain que notre filesystem réservé aura toujours la place définie au départ, même si le pool est saturé.

Fixons une réservation de 500Mo pour le filesystem tank/sauvegarde:

# zfs set reservation=500Mb tank/sauvegarde
# zfs list
NAME              USED  AVAIL  REFER  MOUNTPOINT
tank              500M   476M    31K  /tank
tank/sauvegarde    32K   500M    32K  /media/backup

On constate maintenant que c'est l'espace disponible du filesystem parent qui est diminué.

Pour terminer sur les filesystems ZFS, sachez qu'il y a un très grand nombre de paramètres sur lesquels on peut jouer. On peut également faciliter la gestion d'un grand nombre de filesystems en utilisant l'héritage du parent. Je vous invite à lire le chapitre 6 du guide d'administration de Solaris sur ZFS pour plus d'informations sur les filesystems ZFS.

Revenons aux pools

Maintenant que nous savons gérer les filesystems en détails, revenons à l'étude des pools. La constitution des pools est effectivement une étape importante. Suivant les options que nous aurons mises en place, le pool sera redondant, sécurisé et résistant aux pannes matérielles ou non ! Ses performances seront également mises à l'épreuve en cas de mauvaise configuration. Il faut donc faire un tour sur ce sujet avant d'aller plus loin.

Comme je l'avais déjà évoqué auparavant, il nous faut des supports pour notre (ou nos) pool(s). Cela peut être des suports physiques comme un disque dur ou une partition. Mais cela peut également être des fichiers (un peu à la méthode loopback), même si ce mode de fonctionnement n'est pas recommandé pour la production.

Lorsqu'on utilise un disque dur entier, ZFS le formatte avec une seule partition et d'un label EFI. Voilà pourquoi il faut formatter le disque avec une table de partition au format GPT:

# parted /dev/sdb
print                                                            
Modèle: ATA VBOX HARDDISK (scsi)
Disque /dev/sdb : 1074MB
Taille des secteurs (logiques/physiques): 512B/512B
Table de partitions : gpt

Numéro  Début   Fin     Taille  Système de fichiers  Nom  Fanions
 1      1049kB  1064MB  1063MB                       zfs
 9      1064MB  1073MB  8389kB

Parlons maintenant un peu des modes de création. On peut créer un pool de manière assez simple:

# zpool create tank sdb sdc

Cette commande va créer un pool s'appuyant sur les disques /dev/sdb et /dev/sdc. ZFS utilisera tout l'espace disponible de manière répartie (en parallèle) pour minimiser les temps d'écritures.

On peut également créer une configuration en mirroir: je vous invite à relire le point précédent sur la création d'un pool en mirroir simple. Sur ce point, on peut cumuler l'écriture répartie en écrivant sur deux mirroirs plutôt que sur deux disques:

# zpool creation tank mirror sdb sdc mirror sdd sde

Cette configuration ressemble à la suivante:

                                  * Pool
                                  |
                                  |
                         ---------+---------
                         |                 |
               mirroir 1 *                 * mirroir 2
                         |                 |
                      ---+---           ---+---
                      |     |           |     |
                  sdb *     * sdc   sdd *     * sde      

On peut également créer une configuration en RAID-Z. Il existe d'ailleurs plusieurs niveau de RAID-Z. Commençons par étudier le premier (raidz tout court). Pour faire simple, disons que raidz correspond à du RAID-5 avec une meilleure tolérance de panne. Pour rappel (je vous conseille de lire cette référence), le RAID-5 est un système d'écriture sur plusieurs disques (au moins 3): on écrit les données sur deux disques différents puis on écrit un bit de parité sur le disque sur lequel on n'a pas écrit. L'intérêt est que ce bit de parité va nous permettre de récupérer les données à partir d'un seul morceau de la donnée (peu importe où elle se trouve). Donc en toute logique, si un seul des trois disques crame, je peux reconstituer la donnée.

Néanmoins, RAID-5 a tout de même une faille: si j'ai une panne de courant alors que la parité n'a pas été écrite, ma donnée est accessible mais elle est en total décorrélation avec ma parité. Pire encore, c'est silencieux: je n'ai pas de mécanisme pour m'en rendre compte (sauf à utiliser des logiciels spécialement conçus pour ça). Lors de ma reconstruction de grappe RAID, les données regénérées au point qui pose problème seront incohérentes et, dans tous les cas, pas comme on pouvait les attendre. Ça peut avoir des conséquences catastrophiques pour le système de fichiers qui se trouve au dessus.

Le principe du RAID-Z est assez complexe: l'écriture se fait avec des tailles de grappe variable. Une donnée va peut-être s'éclater en trois blocs+1 bloc de parité, une autre, dans 5 blocs et 1 autre de parité. Toute l'écriture se fait en continu. De plus, ZFS utilisant le Copy-On-Write, il n'y a plus de possibilité d'écrire les blocs de données sans que la parité soit elle-même écrite.

Les autres niveaux de RAID-Z utilisent plus d'éléments de parité. En conséquence, on peut perdre plus de disques puisque la parité est répliquée sur, respectivement 2 ensembles pour le RAID-Z2 et 3 ensembles pour RAID-Z3.

Voici comment créer un pool qui utilisera raid-z. Il vous faut au moins 3 disques:

# zpool create lepool raidz sdd sde sdf sdg

Il est également possible d'ajouter des périphériques dédiés à certaines opérations. Par exemple pour les logs ou pour le cache. Voici un exemple de configuration en raid-z avec un disque réservé pour les logs et un autre réservé pour le cache:

# zpool create lepool raidz sdd sde sdf log sdg cache sdh

Après cette parenthèse sur la manière de créer des pools, voyons d'autres commandes utiles. Pour commencer, vous pouvez utiliser la commande list qui permet de vous afficher un résumé de l'état de vos pools:

# zpool list
NAME     SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
lepool  2,95G   274K  2,95G     0%  1.00x  ONLINE  -
tank    1008M   342M   666M    33%  1.68x  ONLINE  -

La commande zpool status donne plus de détails quand à l'état de santé de votre pool, qu'il soit ultra-simple (un seul disque utilisé complètement) ou complexe (genre un mirroir de mirroirs de raidz):

# zpool status lepool
  pool: lepool
state: ONLINE
  scan: none requested
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  raidz1-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0
    sdf     ONLINE       0     0     0

errors: No known data errors

Bien entendu, en cas de problème vous pouvez détruire votre pool avec la commande zpool destroy:

# zpool destroy lepool

Gestion des zpools

Pour l'instant, nous savons créer et détruire des pools mais d'autres opérations sont bien utiles. Par exemple, nous souhaitons parfois agrandir un pool, parfois réduire sa taille. En cas de problème, il faut pouvoir agir et remplacer un disque et demander une reconstruction. C'est ce que nous allons voir dans la suite de cette partie.

Il faut d'abord distinguer deux choses: ajouter un périphérique ou attacher un périphérique. Le terme d'ajout ne convient qu'au niveau des périphériques virtuels. Pour comprendre ce concept, voici ce qui se passe lorsque je créé un mirroir de deux mirroirs (l'option -n de zpool create permet de simuler ce qui va se passer):

# zpool create -n lepool mirror sdd sde mirror sdf sdg
would create 'lepool' with the following layout:

  lepool
    mirror
      sdd
      sde
    mirror
      sdf
      sdg

On voit bien qu'on a deux mirroirs, avec chacun deux disques. Ces mirroirs sont des périphériques virtuels. Ils n'existent pas vraiment en tant que tel. Seul ZFS sait les gérer. Lorsqu'on parle d'ajout, on ne discute que de périphériques virtuels. Reprenons notre exemple précédent et essayons d'ajouter (via la commande zpool add) un disque au pool lepool:

# zpool create lepool mirror sdd sde
# zpool add lepool sdf
invalid vdev specification
use '-f' to override the following errors:
mismatched replication level: pool uses mirror and new vdev is disk

On voit bien que la commande ne fonctionne pas car /dev/sdf est un vrai disque, pas un périphérique virtuel. A l'inverse, on peut ajouter un autre périphérique virtuel comme un miroir:

# zpool add -n lepool mirror sdf sdg
would update 'lepool' to the following configuration:
      lepool
        mirror
          sdd
      sde
    mirror
      sdf
      sdg

Ou encore un périphérique virtuel de cache ou de log (car le cache et le log sont des périphériques virtuels, gérés uniquement par ZFS):

# zpool add lepool log sdg cache sdf
# zpool status lepool
 pool: lepool
state: ONLINE
 scan: none requested
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0
  logs
    sdg     ONLINE       0     0     0
      cache
        sdf     ONLINE       0     0     0

Voyons maintenant la différence avec l'attachement. Dans certaines situations (pas toutes), on peut vouloir rajouter de l'espace disque à un pool. On peut souhaiter faire du mirroir sur trois disques et non deux. Il faudra dans ce cas utiliser la commande zfs attach qui travaille au niveau des disques et non des périphériques virtuels. Cela ne marche que pour ce qui n'est pas du raidz donc pour les mirroirs et les grappes de disques non redondantes.

# zpool attach lepool sde sdf
# zpool status lepool
  pool: lepool
 state: ONLINE
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
  scan: resilver in progress since Tue Mar 19 16:47:00 2013
        25,2M scanned out of 455M at 8,41M/s, 0h0m to go
        25,1M resilvered, 5,55% done
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0
        sdf     ONLINE       0     0     0  (resilvering)

On voit que l'attachement se fait au niveau des disques et non des périphériques virtuels. Ainsi, la commande précédente permet d'attacher le disque /dev/sdf au disque /dev/sde. Comme ce dernier fait déjà partie d'un mirroir, zpool créé un mirroir à trois entrées et synchronise automatiquement le contenu des disques ce qui peut représenter un peu de temps. La commande zpool status lancée depuis une autre console permet de montrer ce qui se passe tant que la synchronisation du mirroir n'est pas totale entre les disques. Il y a du "resilvering": ZFS reconstruit le disque /dev/sdf avec le contenu du mirroir.

Bien entendu, s'il y a une différence entre ajouter et attacher, il y a une différence avec les commandes de suppression et de détachement. zpool detach agit sur des périphériques physiques:

# zpool detach lepool sdf
# zpool status lepool
  pool: lepool
 state: ONLINE
  scan: resilvered 97,5K in 0h0m with 0 errors on Tue Mar 19 16:49:20 2013
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0

errors: No known data errors

Alors que zpool remove agit sur les périphériques virtuels (et uniquement les logs, les spares et les caches):

# zpool status lepool
# zpool status lepool
 pool: lepool
state: ONLINE
 scan: none requested
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0
  logs
    sdg     ONLINE       0     0     0
      cache
        sdf     ONLINE       0     0     0
 # zpool remove lepool sdf
 # zpool remove lepool sdg
 # zpool status lepool
  pool: lepool
 state: ONLINE
  scan: resilvered 97,5K in 0h0m with 0 errors on Tue Mar 19 16:49:20 2013
config:

NAME        STATE     READ WRITE CKSUM
lepool      ONLINE       0     0     0
  mirror-0  ONLINE       0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0

errors: No known data errors

Maintenant, voyons comment allumer et éteindre des périphériques de pool. Les commandes online et offline permettent, respectivement, de mettre en mode opérationnel et de désactiver des périphériques de base. Je ne vais pas m'apesantir sur ce sujet. Sachez qu'il est parfois indispensable de désactiver certains périphériques dans le pool pour des opérations de maintenance. Nous allons le mettre en pratique assez rapidement avec également la présentation de la commande qui permet de remplacer un disque par un autre. Lorsqu'une panne se présente, l'état du pool change. Illustrons-le avec une panne sur un des disques de notre mirroir. En pratique, il suffit de supprimer le disque virtuel contenant les données ZFS (/dev/sdd par exemple) en le recréant avec une commande du type (pour VirtualBox):'''VBoxManage createhd --filename zfs_pool_5.vdi --size 1024 --variant fixed'''. Après avoir réattaché le disque à la machine virtuelle et redémarrer la dite machine, ZFS va s'apercevoir qu'il y a un problème:

# zpool status lepool
  pool: lepool
 state: DEGRADED
status: One or more devices could not be used because the label is missing or
    invalid.  Sufficient replicas exist for the pool to continue
    functioning in a degraded state.
action: Replace the device using 'zpool replace'.
   see: http://zfsonlinux.org/msg/ZFS-8000-4J
  scan: none requested
config:

    NAME        STATE     READ WRITE CKSUM
    lepool      ONLINE       0     0     0
      mirror-0  ONLINE       0     0     0
    sdd     UNAVAIL      0     0     0
        sde     ONLINE       0     0     0

errors: No known data errors

Effectivement sdd n'est pas disponible. Il faut juste le remplacer. Pour cela on peut imaginer de le remplacer avec lui-même puisque le disque est disponible. Nous allons le réaliser avec la commande zpool replace:

# zpool replace lepool sdd

On aurait également pu le remplacer avec un autre disque disponible (genre /dev/sdf ou /dev/sdg):

# zpool replace lepool sdd sdf

Enfin, il y a un domaine que nous n'avons pas encore exploré: les périphériques virtuels de spare. Il s'agit de disques mis en attente "au cas où". En cas de panne, ces périphériques physiques sont automatiquement (ou non selon la configuration du pool) utilisés comme secours en cas de panne sur un disque. Nous allons revenir à une configuration en raidz et nous allons ajouter un disque de "spare":

# zpool destroy lepool
# zpool create lepool raidz sdd sde sdf
# zpool add lepool spare sdg

Une fois ces créations terminées, nous allons copier des données dans cet espace en raidz. Enfin, nous allons supprimer un disque en le reformattant. Pour cela, j'efface le disque virtuel (au niveau supérieur donc) et je le recréé avec une commande du type '''VBoxManage createhd --filename zfs_pool_5.vdi --size 1024 --variant fixed''' puis je le réattache à la machine virtuelle. De cette manière, le disque /dev/sdf est maintenant vide de données. Donc ZFS va s'apercevoir qu'il y a un problème:

# zpool status lepool
  pool: lepool
 state: DEGRADED
status: One or more devices could not be used because the label is missing or
        invalid.  Sufficient replicas exist for the pool to continue
    functioning in a degraded state.
action: Replace the device using 'zpool replace'.
   see: http://zfsonlinux.org/msg/ZFS-8000-4J
  scan: none requested
config:

NAME        STATE     READ WRITE CKSUM
lepool      DEGRADED     0     0     0
  raidz1-0  DEGRADED     0     0     0
        sdd     ONLINE       0     0     0
        sde     ONLINE       0     0     0
        sdf     UNAVAIL      0     0     0
      spares
        sdg       AVAIL

On voit bien qu'il y a un problème. Pour le corriger, on peut utiliser la commande zpool replace:

# zpool replace lepool sdf sdg

Attention, il y avait un bug dans ZFSonLinux sur la gestion des hot spares qui vient d'être corrigé récemment. Il est fort possible que si vous utilisez la version 0.6.0-rc14 de ZFSOnLinux, vous ayez des problèmes avec les hot spares. Dans la pratique, les hotspares n'ont qu'un intérêt pour réserver des disques en cas de pannes et pour les activer rapidement. On peut très bien faire sans hot spare en réservant un disque sans l'utiliser dans un pool. Néanmoins, lorsqu'on affecte un disque de spare pour un pool, on peut le voir avec la commande zpool status...

Voilà, avec ces exemples, nous devrions avoir fait le tour des quelques fonctionnalités de gestion des pools ZFS. Bien entendu, il manque pléthore de commandes qui permettent d'aller plus loin (zpool history, zpool set et get) mais je vous laisse lire la doc sur le sujet.

De la déduplication

Un truc qui est bien dans ZFS, c'est l'ajout de nouvelle fonctionnalités au fil du temps. A partir de zpool v21, la déduplication a fait son apparition. Pour faire simple la déduplication est une compression de bas niveau qui consiste à isoler les blocs identiques dans le pool. De cette manière, on réduit la taille occupée à celle des blocs non identiques. Dans certains cas, cela permet de récupérer, à moindre coût, de l'espace disque. Le revers de la médaille, c'est que cette opération qui est effectuée au niveau du pool consomme des ressources CPU et de la RAM.

C'est vraiment cette dernière qui est en jeu lorsque vous souhaitez garder une certaine vélocité à votre système. En effet, la déduplication est une technique basée sur des empreintes de blocs. Ainsi, à chaque bloc correspond un hash. La table qui référence les hash se doit donc d'être en RAM si l'on veut obtenir des performances en écriture dignes de ce nom. Oracle recommande 1Go pour entre 1 et 2To dédupliqués... Voyons comment mettre en oeuvre la déduplication dans ZFS.

Si vous avez suivi, l'opération devrait normalement se dérouler pour le pool mais en fait, c'est dans le filesystem que nous allons explicitement demander la déduplication:

# zfs set dedup=on tank/clone

Après quelques opérations, vous pourrez voir les performances de déduplication, non pas dans le filesystem, mais dans le pool:

# zpool list
NAME     SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
tank    1008M   342M   666M    33%  1.68x  ONLINE  -

On voit que le taux de déduplication du pool tank est de 1.68 ce qui est très faible (et donc non rentable). Le paramètre de déduplication fait partie des quelques incohérences de ZFS. Pour relativiser, la mise en place de la déduplication est ultra simple. Tant pis si ça perturbe légèrement l'ordre logique des commandes pour la mettre en oeuvre.

Dans certaines situations la déduplication permet d'obtenir des gains significatifs en terme de taille de fichiers. Si vous avez des données qui sont souvent identiques (genre images de VM, fichiers copiés de nombreuses fois dans des répertoires différents), vous pouvez atteindre des taux de 1 pour 10 par exemple. Pour le savoir, le plus simple reste de procéder par échantillons et de vérifier à l'aide de la commande zpool list le taux de déduplication du pool. Un taux inférieur à 2 n'a pas vraiment d'intérêt: vous devriez plutôt utiliser d'autres techniques (compression externe en amont). En effet, à moins de 2, le coût technique de la déduplication sur les performances de lecture/écriture ne vaut pas l'enjeu de la consommation d'espace sur des systèmes en fonctionnement (si c'est pour du stockage "lent", on peut bien entendu revoir ce chiffre).

Instantanés

Les pools n'ont plus (trop) de secrets pour nous, passons maintenant à une fonctionnalité importante de ZFS: les instantanés. Pour faire simple, un instantané est une prise de vue à un instant donné du contenu d'un filesystem ZFS. Dans sa manière de fonctionner, ZFS permet à un instantané d'être créé très rapidement. Au fur et à mesure de l'évolution du contenu du filesystem dans le temps, l'instantané prendra de la place en conservant seulement les éléments dont il a besoin. Ainsi, à la création de l'instantané, aucun espace supplémentaire n'est créé. Si on change par la suite des fichiers dans le filesystem, l'instantané contiendra uniquement l'original des fichiers qui auront changé. Bien entendu, un instantané est en lecture-seule.

Il n'y a pas vraiment de limites au nombre d'instantanés par filesystem. Les données de l'instantané sont stockées directement dans le filesystem. Il n'y a pas besoin de prévoir de place pour lui sauf si on souhaite changer une grande proportion du contenu du filesystem.

Nous allons créer notre snapshot qu'on nommera snap_1:

# zfs snapshot tank/sauvegarde@snap_1
# zfs list
NAME              USED  AVAIL  REFER  MOUNTPOINT
tank              500M   476M    31K  /tank
tank/sauvegarde   334M   166M   334M  /media/backup
# zfs list -t all
NAME                     USED  AVAIL  REFER  MOUNTPOINT
tank                     500M   476M    31K  /tank
tank/sauvegarde          334M   166M   334M  /media/backup
tank/sauvegarde@snap_1      0      -   334M  -

On voit que les snapshots sont des objets un peu particuliers. En effet, d'abord, ils n'apparaissent pas comme des filesystems (cf zfs list). Il faut demander à zfs list de lister tous les types de filesystems. Ensuite, on voit bien qu'à la création, le snapshot n'occupe aucune place. Si je m'amuse à changer des fichiers ou à en supprimer, voici ce que j'obtiens:

# zfs list -t all
NAME                     USED  AVAIL  REFER  MOUNTPOINT
tank                     500M   476M    31K  /tank
tank/sauvegarde          335M   165M   213M  /media/backup
tank/sauvegarde@snap_1   122M      -   334M  -

On pourrait craindre que le snapshot soit un truc compliqué qui fait des stockages au niveau binaire en arrière plan. Mais en fait, ce n'est pas le cas ! Les fichiers propres à un snapshot sont exposés en espace utilisateur, accessibles directement en lecture dans le répertoire .zfs/snapshot du filesystem ZFS. Ainsi, pour notre exemple, on trouvera les fichiers figés du snapshot snap_1 dans /tank/sauvegarde/.zfs/snapshot/snap_1. C'est assez efficace si on a besoin de reprendre juste un ou plusieurs fichiers de ce snapshot.

Bien entendu, l'un des intérêts des instantanés, c'est de pouvoir revenir en arrière ! C'est l'objet de la commande rollback de zfs:

# zfs rollback tank/sauvegarde@snap_1

Toutefois, il faudra bien faire attention: rollback ne permet que de revenir au précédent snapshot (le plus récent). Si on souhaite revenir plus en amont, il faut supprimer les snapshots fils. Par exemple, admettons que je fasse des snapshots de backup; un par jour. Voici ce que j'obtiens (les chiffres sont faux, c'est pour l'exemple):

# zfs list -t all
NAME                     USED  AVAIL  REFER  MOUNTPOINT
tank                     500M   476M    31K  /tank
tank/sauvegarde          335M   165M   213M  /media/backup
tank/sauvegarde@lundi    122M      -   334M  -
tank/sauvegarde@mardi    122M      -   334M  -
tank/sauvegarde@mercredi 122M      -   334M  -
tank/sauvegarde@jeudi    122M      -   334M  -
tank/sauvegarde@vendredi 122M      -   334M  -

On voit d'abord que la liste des snapshots s'affiche d'abord les snapshots anciens. Si je souhaite revenir à l'état de mardi alors que je suis vendredi, je dois supprimer vendredi, jeudi et mercredi. On peut le faire grâce à la commande zfs destroy. Plus simplement et de manière plus concise, on peut utiliser l'option -r de zfs rollback pour effacer automatiquement les snapshots concernés.

Mais alors, comment faire si je souhaite revenir à mardi mais garder mercredi, jeudi et vendredi ? La réponse est au point qui suit.

La guerre des clones

Le titre est très facile pour ce sujet. En effet, un snapshot n'est pas modifiable. On ne peut donc pas écrire dedans pour y modifier son contenu. Ce contenu n'est pas vivant non plus: ZFS s'arrange pour que le contenu de l'instantané soit toujours disponible dans le filesystem (même si ce dernier n'est pas directement accessible dans le filesystem vivant). De plus, on a vu qu'on ne pouvait revenir en arrière que d'un cran avec les snapshots. Comment faire pour récupérer un instantané et le rendre vivant (modifiable) ?

Une réponse à la question peut être de récupérer le contenu du snapshot et de le mettre dans un nouveau filesystem. Mais cette opération consomme de l'espace: je dois créer un filesystem de la taille de mon espace instantané. ZFS propose à la place un mécanisme qui ne consomme pas d'espace supplémentaire: le clone.

Un clone est une copie vivante d'un snapshot. C'est un filesystem vivant donc modifiable complètement (écriture et modification des paramètres) qui est construit à partir du snapshot. Donc à l'instant t=0, le clone n'occupe aucun espace supplémentaire. Néanmoins, un clone est toujours lié à l'instantané qui a servi à le réaliser. C'est l'inconvénient nécessaire pour permettre d'optimiser l'occupation de l'espace disque.

Voyons comment créer un clone:

# zfs list -t all 
NAME                          USED  AVAIL  REFER  MOUNTPOINT
tank                          269M   707M    31K  /tank
tank/sauvegarde               269M   707M   151M  /media/backup
tank/sauvegarde@snap_dragon    56K      -   213M  -
tank/sauvegarde@snap_2         56K      -   269M  -
tank/sauvegarde@snap_3        102K      -   151M  -
# zfs clone tank/sauvegarde@snap_2 tank/clone
# zfs list -t all
NAME                          USED  AVAIL  REFER  MOUNTPOINT
tank                          269M   707M    32K  /tank
tank/clone                      1K   707M   269M  /tank/clone
tank/sauvegarde               269M   707M   151M  /media/backup
tank/sauvegarde@snap_dragon    56K      -   213M  -
tank/sauvegarde@snap_2         56K      -   269M  -
tank/sauvegarde@snap_3        102K      -   151M  -

On voit bien que l'espace utilisé sur le zfs racine tank n'a pas changé. En revanche, nous avons un nouveau filesystem nommé tank/clone. Celui-ci contient le contenu de l'instantané snap_2 de tank/sauvegarde. Une fois le clone créé, nous pouvons l'utiliser pour y copier, y modifier des fichiers. Tant que le snapshot d'origine sera disponible, le clone sera utilisable. D'ailleurs, je ne peux pas supprimer le snapshot snap_2 car ZFS voit bien qu'il est utilisé par le clone tank/clone:

# zfs destroy tank/sauvegarde@snap_2
cannot destroy 'tank/sauvegarde@snap_2': snapshot has dependent clones
use '-R' to destroy the following datasets:
tank/clone

Mais en règle générale, si l'on désire réaliser un clone c'est pour effectuer des modifications sur un instantané puis utiliser ce contenu modifié de manière indépendante, en pouvant supprimer l'instantané. Cette opération est possible grâce à la commande zfs promote. Bien entendu cela se fera au détriment de l'existence de l'instantané. Voyons comment faire avec notre clone:

# zfs promote tank/clone
# zfs list -t all -o space,type
NAME                    AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD  TYPE
tank                     706M   270M         0     31K              0       270M  filesystem
tank/clone               706M   270M     67,1M    203M              0          0  filesystem
tank/clone@snap_2           -  67,1M         -       -              -          -  snapshot
tank/sauvegarde          706M   206K      102K    104K              0          0  filesystem
tank/sauvegarde@snap_3      -   102K         -       -              -          -  snapshot

On voit d'abord que notre clone tank/clone est devenu un vrai filesystem. Lors de la promotion, l'instantané snap_2 qui était utilisé a été rattaché au nouveau filesystem (tank/clone). Il a été supprimé du filesystem tank/sauvegarde. Néanmoins, il reste lié à tank/sauvegarde et tank/sauvegarde@snap_3:

# zfs destroy tank/clone@snap_2
cannot destroy 'tank/clone@snap_2': snapshot has dependent clones
use '-R' to destroy the following datasets:
tank/sauvegarde@snap_3
tank/sauvegarde

Toutes ces opérations de création d'instantanés ou de clones prennent peu de temps. Elles sont quasiment immédiates. La bonne technique pour revenir en arrière à moindre risque avec un instantané, c'est d'en faire un clone, d'y faire quelques tests puis, après validation, de le promouvoir en filesystem. Tout ceci est intéressant mais, tout ça se passe uniquement sur une seule machine. Que faire si je veux externaliser un snapshot (une opération de sauvegarde assez basique et courante somme toute).

Balancer des instantanés et les recevoir

En plus de toutes les fonctionnalités vues auparavant, on peut encore aller plus loin avec les instantanés. En effet, l'intérêt d'un snapshot, c'est de pouvoir l'envoyer ailleurs pour en faire une copie (vivante ou non) sur une autre machine. C'est également un excellent moyen de sauvegarde. Là encore, zfs propose une solution KISS grâce aux commandes zfs send et zfs receive. zfs send permet de convertir un instantané en flux sur la sortie standard. zfs receive permet de convertir un flux sur l'entrée standard en filesystem ou en snapshot. L'intérêt de cette méthode c'est qu'elle respecte les concepts propres à UNIX et qu'elle est utilisable avec tout ce qui peut se greffer entre la sortie standard et une entrée standard.

Voici un petit exemple:

# zfs list
NAME         USED  AVAIL  REFER  MOUNTPOINT
tank         407M   574M    31K  /tank
tank/clone   405M   574M   405M  /media/clone
# zfs snapshot tank/clone@instant
# zfs send tank/clone@instant | zfs receive tank/bob
# zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
tank                 813M   566M    32K  /tank
tank/bob             405M   566M   405M  /tank/bob
tank/bob@instant        0      -   405M  -
tank/clone           405M   566M   405M  /media/clone
tank/clone@instant      0      -   405M  -

On voit que maintenant nous avons créé un nouveau filesystem nommé tank/bob qui contient le contenu de tank/clone. De la même manière, on aurait pu également réaliser cette opération d'envoi sur un autre pool (en imaginant que notre machine en soit équipée). On aurait pu lancer la commande zfs send tank/clone@instant | zfs receive char/bob pour recréer le contenu de tank/clone@instant dans le filesystem bob du zpool char.

On peut également balancer tout ça sur une autre machine sur un flux sécurisé en utilisant tout simplement ssh (il faut bien sûr que la machine d'en face soit équipée de ZFS !):

# zfs send tank/clone@instant | ssh autre_serveur zfs receive char/bob

La force des logiciels KISS est justement de permettre ce nouvel usage avec peu d'effort pour l'administrateur.

Bon, vous allez me dire que l'étape de la prise de l'instantané est peu être de trop: après tout, on aurait très bien pu faire une copie d'un endroit à l'autre avec des outils comme cp/tar/cpio. Oui mais l'intérêt du snapshot, c'est qu'il fige le système de fichiers. On sera donc sûr de récupérer l'intégralité des fichiers qui se trouvaient présents au moment du snapshot même si, dans l'intervalle du send/receive, il y a eu des suppressions ou des ajouts. Ensuite, on pourrait dire que l'instantané qui est recréé sur le filesystem destinataire est de trop. Mais après tout, autant le laisser: il n'occupe aucun espace !

Nous devons toutefois être attentifs à un certains nombres d'éléments lors de la réception d'un flux:

  • D'abord, comme on vient de le voir, l'instantané et le filesystem sont reçus.
  • Ensuite, le filesystem ainsi que son éventuelle hiérarchie descendante sont démontés lors de la réception. Le filesystem n'est donc plus accessible.

Nous avons vu qu'on pouvait envoyer des snapshots un peu n'importe où. C'est bien mais pas suffisant ! En effet, le système de prise d'instantanés de ZFS étant finalement assez léger en volume et en temps, il est effectivement recommandé d'en abuser. Il est donc probable que nous aurons sur nos machines plusieurs instantanés d'un même filesystem. De plus, nous ne devons pas oublier que l'envoi d'un snapshot est, en revanche, assez volumineux. En effet, c'est tout le contenu du snapshot qui est envoyé dans le flux. Sur un filesystem qui consomme 500Mo, si vous avez un snapshot récent qui n'occupe que 30Mo, l'envoi du flux occupera environ 500Mo (à plus ou moins 30Mo). Le plus simple est de se fier à la colonne REFER de la commande zfs list pour savoir quelle sera la taille totale du snapshot.

Or, si on synchronise régulièrement des filesystem d'une machine à une autre, on ne souhaite pas forcément tout renvoyer à chaque fois. De plus, si le filesystem dispose déjà d'un snapshot, on ne pourra pas lui renvoyer un autre snapshot:

# zfs send tank/clone@instant_1 | zfs receive -vF tank/bob
cannot receive new filesystem stream: destination has snapshots (eg. tank/bob@instant)
must destroy them to overwrite it

En lieu et place de ce mécanisme un peu pénible, on peut utiliser des envois incrémentaux: zfs send envoie alors uniquement la différence entre deux snapshots. Il faut bien entendu que le système qui recoit ce flux dispose des éléments minimums pour le faire c'est à dire du snapshot initial. A noter que la différence entre deux snapshots est libre: on peut la réaliser indépendamment entre deux instantanés qui peuvent avoir de nombreux autres incréments entre eux. Il faut simplement que le snapshot de départ soit plus vieux que le snapshot de fin.

# zfs send -i tank/clone@instant tank/clone@instant_2 | zfs receive -v tank/bob
receiving incremental stream of tank/clone@instant_2 into tank/bob@instant_2
received 56,5MB stream in 9 seconds (6,28MB/sec)

C'est bien l'option -i qui permet de gérer la différence. On voit que nous sommes passés directement du snapshot instant au snapshot instant_2 en sautant le snapshot instant_1. C'est un mécanisme plutôt ingénieux qui permet de réduire fortement le volume à transférer.

Si vous lisez la page du guide d'administration de Solaris sur ZFS, dans la partie sur les envois/réception des instantanés, vous constaterez qu'il y a d'autres options pour recevoir et envoyer des snapshots. Pour ma part, je vais vous présenter une option qui n'est pas documentée chez Oracle (en fait je ne sais pas si elle implémentée ou non dans le ZFS officiel). Il s'agit de l'option -D de zfs send. Elle est implémentée dans ZFSonLinux et elle permet d'envoyer uniquement des informations dédupliquées. En pratique, cela vous permet de gagner de la place lors de tous vos envois et c'est un vrai bonheur. Voici quelques éléments pour vous en convaincre:

# zfs send -i tank/clone@instant tank/clone@instant_1 > /opt/bordel.bin
# ls -lh /opt/bordel.bin
-rw-r--r-- 1 root root 225M 15 mars  13:46 /opt/bordel.bin
# zfs send -D -i tank/clone@instant tank/clone@instant_1 > /opt/bordel.bin
# ls -lh /opt/bordel.bin
-rw-r--r-- 1 root root 57M 15 mars  13:47 /opt/bordel.bin

On voit bien l'intérêt d'utiliser la déduplication lors de l'envoi de données, qui plus est, pour les différentiels entre snapshots, surtout lorsque l'envoi de données se fait entre deux machines avec un lien réseau pas forcément vaillant. Bien entendu, cela dépend des données que nous avons sur nos machines: certaines se prêtent plus à la déduplication que d'autres. Dans tous les cas, on peut voir que ZFS peut nous permettre de répliquer le contenu de deux machines distantes en utilisant le moins de ressources réseau possible.

D'autres choses en cours

Dans cette partie, je vais faire rapidement référence aux fonctionnalités de ZFS qui ne sont pas encore implémentées (ou pas correctement) dans ZFSonLinux. D'abord, et malheureusement, ZFSonLinux ne propose pas de chiffrement à la volée. Celle-ci est implémentée dans la version 30 de zfs et son contenu est opensource depuis peu. On pourra espérer une implémentation de la partie chiffrement dans une prochaine version de ZFSonLinux. Mais tout ceci ne doit pas trop vous géner: il y a pléthores d'autres méthodes de chiffrement. Plus on chiffre tôt, plus c'est sécurisé. Si ça l'est dès le système de fichiers, c'est donc assez tôt... Mais c'est pour plus tard pour les systèmes GNU/Linux.

Par exemple, il est tout à fait possible d'utiliser dm-crypt pour chiffrer le contenu d'un ou de plusieurs disques et de mettre un pool ZFS au dessus: au final le contenu du pool sera chiffré sur le disque. Il sera impossible de récupérer les données et de disposer d'un pool ZFS et d'un filesystem ZFS sans disposer de la clef de chiffrement utilisée par dm-crypt (via cryptsetup). Attention, ce chiffrement est bien relatif et ne sert qu'à contrer un seul cas de figure: le vol physique de disque dur. Un attaquant qui accède au système en fonctionnement verra tout en clair !

Ensuite, ZFS dispose d'un système de délégation pour autoriser tel ou tel utilisateur ou groupe d'utilisateur de faire telle ou telle action. Par exemple, on peut choisir que l'utilisateur nommé jean.pierre a le droit de créer des filesystems, de faire des instantanés mais n'a pas le droit de détruire des filesystems. Cela se ferait grâce à une commande du genre:

# zfs allow jean.pierre create,mount,snapshot tank

ZFSOnLinux implémente la commande allow mais le fait que le module ne soit pas en espace utilisateur fait que très vite, on se heurte à des problèmes de droits où il faut être root !

Au niveau des choses un peu bizarres dans ZFS on peut ainsi observer le concept de volume. Un volume ZFS est un espace disque d'une taille déterminée qui peut être accédé en tant que périphérique de blocs. On peut l'utiliser pour "trouer" un pool ZFS. Avec ce volume ZFS, on peut par exemple déterminer un espace de swap sur un système 100% occupé par ZFS. Ceci est mieux implémenté dans Solaris et ses spécificités. Pas sûr que les volumes soient implémentés un jour dans ZFSOnLinux.

Conclusion

Je dois avouer que j'ai été assez bluffé par ZFS et ses fonctionnalités. Les concepts inhérents à ZFS comme les pools et les filesystems sont très efficaces. Ils permettent une gestion simple et performante d'un ensemble de données et viennent un peu bouleverser la vision traditionnelle du système de fichier classique, solidement arrimé aux limites de sa partition avec de nombreux points de bloquage.

Les outils d'administration sont très bien pensés: il n'y a que deux utilitaires: un pour gérer les pools, l'autre pour les filesystems (plus un autre pour débugger, il est vrai). L'ergonomie est donc mieux foutue de ce côté, notamment pour les administrateurs qui utilisent occasionnellement ZFS (pas besoin de retenir la commande pour purger le système, la commande pour lister les volumes, celle pour agrandir l'espace alloué, etc.). C'est donc un vrai bonheur d'implémentation.

De mon analyse personnelle et de certains commentaires sur la mailing-list de ZFSOnLinux, ZFSOnLinux me paraît prêt pour la production même si le numéro de version est encore inférieur à 1.0. Les nombreuses fonctionnalités implémentées permettent de jouir d'un système fiable et bien pensé. Les snapshots et les clones permettent de faire des sauvegardes en quelques commandes qu'on peut parfaitement intégrer dans des scripts. Les commandes send et receive permettent de réaliser l'indispensable externalisation de données entre deux machines ZFS. Pas étonnant que certaines boîtes basent leur solution de stockage et de sauvegarde sur ZFS. L'exemple le plus notable est celui de Nexenta et de ses produits de stockage qui se basent sur ZFS. Ainsi, Nexenstor est un système d'exploitation "customisé" qui transforme un serveur en NAS dont le stockage sera géré par ZFS.

Si vous êtes intéressé à titre professionnel par ZFS (entendre, vous avez besoin d'un contrat de support), le plus simple est d'utiliser Oracle Solaris et de prendre un contrat de support. D'autres implémentations existent. Les systèmes BSD (FreeBSD, OpenBSD, etc.) ainsi que les clones libres de Solaris (illumos) implémentent ZFS directement dans le noyau. Vous avez donc le choix quant à la plate-forme qui va opérer avec ZFS.

En conclusion, je rappellerai que ZFS a le gros inconvénient d'être une propriété d'Oracle. Ce dernier peut à tout moment décider de fermer le code (la publication sous licence CDDL est réalisée quelques mois après la livraison du code dans Solaris), ou encore de mettre fin au projet (ce ne serait pas la première fois). Il n'y a pas vraiment moyen de se protéger contre ça. Du côté du monde Linux, BTRFS est censé devenir le système de fichiers ultime. C'est sans doute un voeux pieu et je suis persuadé qu'à terme, cela sera le cas. Mais pour l'instant, BTRFS n'a pas encore le niveau de fonctionnalité de ZFS. Même si on retrouve l'équivalent de zfs send (la commande est btrfs send !) à partir du kernel 3.9, la déduplication est encore en projet et BTRFS n'est pas encore certifié pour la mise en production sachant qu'il n'a pas encore le niveau de performances basiques (lecture/écriture) d'ext4. En attendant ce niveau de maturation, il reste ZFS, assez loin devant et que je vous conseille d'adopter...

Posted sam. 29 juin 2013 21:50:35