ZFS partie 2

ZFS partie 2

Photo by Gary Meulemans on Unsplash

Maintenant que ZFS n’a plus de secret pour vous, on va pouvoir mettre les mains dans le cambouis !

Cet article s’inscrit dans la continuité du précédent billet sur la terminologie ZFS. N’hésitez pas à vous y référer pour avoir plus de détails sur les notions abordées.

Installation ZFS

Je ne m’étale pas sur l’installation classique de l’Ubuntu 20.04 LTS à grands coups de PXE, Preseed et d’Ansible pour me focaliser sur la partie ZFS.

La commande lsmod | grep zfs permet de savoir si le module kernel ZFS est déjà installé. Dans le cas contraire, il suffit de faire:

 1$ sudo apt install zfsutils-linux
 2$ lsmod | grep zfs
 3zfs                  4034560  6
 4zunicode              331776  1 zfs
 5zlua                  147456  1 zfs
 6zavl                   16384  1 zfs
 7icp                   303104  1 zfs
 8zcommon                90112  2 zfs,icp
 9znvpair                81920  2 zfs,zcommon
10spl                   126976  5 zfs,icp,znvpair,zcommon,zavl

Je vais commencer par une étape facultative mais qui facilite grandement la vie au quotidien. Elle consiste à utiliser des alias pour désigner les disques en fonction du numéro de logement indiqué sur le serveur. En cas de panne, cela permet d’identifier plus facilement le disque à remplacer.

Sur le Dell PowerEdge R740XD, les disques sont répartis selon le diagramme suivant:

logements disques Dell R740XD

Ces numéros de logements correspondent aux identifiants de disques (target) sur la chaine SCSI ([Host:Bus:Target:Lun])

 1$ lsscsi
 2lsscsi
 3[6:0:0:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sda
 4[6:0:1:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdb
 5[6:0:2:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdc
 6[6:0:3:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdd
 7[6:0:4:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sde
 8[6:0:5:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdf
 9[6:0:6:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdg
10[6:0:7:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdh
11[6:0:8:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdi
12[6:0:9:0]    disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdj
13[6:0:10:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdk
14[6:0:11:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdl
15[6:0:12:0]   disk    TOSHIBA  KPM5XVUG480G     B028  /dev/sdm
16[6:0:13:0]   disk    TOSHIBA  KPM5XVUG480G     B028  /dev/sdn
17[6:0:14:0]   disk    TOSHIBA  KPM5XVUG480G     B028  /dev/sdo
18[6:0:16:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdp
19[6:0:17:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdq
20[6:0:18:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sds
21[6:0:19:0]   disk    TOSHIBA  MG08SCA16TEY     EJ07  /dev/sdr

Je vais donc créer un fichier vdev_id.conf pour indiquer la correspondance entre un alias plus parlant (diskxx) et le disque sur la chaine SCSI (et donc son numéro de logement sur le serveur et dans la carte d’administration à distance DELL iDRAC).

 1$ cat /etc/zfs/vdev_id.conf
 2
 3# Front side - SAS-NL 16TB
 4alias disk00   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:0:0
 5alias disk01   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:1:0
 6alias disk02   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:2:0
 7alias disk03   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:3:0
 8alias disk04   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:4:0
 9alias disk05   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:5:0
10alias disk06   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:6:0
11alias disk07   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:7:0
12alias disk08   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:8:0
13alias disk09   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:9:0
14alias disk10   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:10:0
15alias disk11   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:11:0
16
17# Rear side - SSD 447GB MLC
18alias disk12   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:12:0
19alias disk13   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:13:0
20alias disk14   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:14:0
21
22# Inside - SAS-NL 16TB
23alias disk16   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:16:0
24alias disk17   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:17:0
25alias disk18   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:18:0
26alias disk19   /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:19:0

Il suffit maintenant de générer les liens avec la commande: udevadm trigger

 1$ sudo udevadm trigger
 2$ ls -l /dev/disk/by-vdev/
 3total 0
 4lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk00 -> ../../sda
 5lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk01 -> ../../sdb
 6lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk02 -> ../../sdc
 7lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk03 -> ../../sdd
 8lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk04 -> ../../sde
 9lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk05 -> ../../sdf
10lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk06 -> ../../sdg
11lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk07 -> ../../sdh
12lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk08 -> ../../sdi
13lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk09 -> ../../sdj
14lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk10 -> ../../sdk
15lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk11 -> ../../sdl
16lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk12 -> ../../sdm
17lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk13 -> ../../sdn
18lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk14 -> ../../sdo
19lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk16 -> ../../sdp
20lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk17 -> ../../sdq
21lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk18 -> ../../sds
22lrwxrwxrwx 1 root root 9 déc.   6 19:12 disk19 -> ../../sdr

Création du ZPOOL

Si vous avez lu attentivement mon précédent article, vous avez probablement déjà une idée de l’organisation ZFS retenue pour les 16 disques mécaniques et les 3 disques SSD:

  • 2 VDEVs de 8 disques en RAIDz2
  • 2 SSDs en miroir pour le SLOG
  • 1 SSD pour le L2ARC

Cette configuration offre une tolérance à la panne de 2+2 disques (2 par VDEVs) qui statistiquement devrait être acceptable même avec cette taille de disque (temps de reconstruction très long). Par contre comme tous les disques sont identiques, il faut croiser les doigts pour ne pas avoir un défaut de série (certains constructeurs panachent les fabricants de disques pour éviter ce type de problème).

Pour les besoins de cet article, je vais créer le ZPOOL en 3 étapes mais il est bien entendu possible de le faire en une seule commande.

Création du ZPOOL avec 2 VDEVs en RAIDz2:

  • 1er VDEV avec les disques: disk00 à disk07
  • 2nd VDEV avec les disques: disk08 à disk11 et disk16 à disk19
 1$ sudo zpool create -f -o ashift=12 -O xattr=sa -O atime=off -O com.sun:auto-snapshot=false -O compression=on -O mountpoint=none tank raidz2 disk0{0..7} raidz2 disk0{8..9} disk1{0..1} disk1{6..9}
 2
 3$ zpool status
 4  pool: tank
 5 state: ONLINE
 6  scan: none requested
 7config:
 8
 9	NAME        STATE     READ WRITE CKSUM
10	tank        ONLINE       0     0     0
11	  raidz2-0  ONLINE       0     0     0
12	    disk00  ONLINE       0     0     0
13	    disk01  ONLINE       0     0     0
14	    disk02  ONLINE       0     0     0
15	    disk03  ONLINE       0     0     0
16	    disk04  ONLINE       0     0     0
17	    disk05  ONLINE       0     0     0
18	    disk06  ONLINE       0     0     0
19	    disk07  ONLINE       0     0     0
20	  raidz2-1  ONLINE       0     0     0
21	    disk08  ONLINE       0     0     0
22	    disk09  ONLINE       0     0     0
23	    disk10  ONLINE       0     0     0
24	    disk11  ONLINE       0     0     0
25	    disk16  ONLINE       0     0     0
26	    disk17  ONLINE       0     0     0
27	    disk18  ONLINE       0     0     0
28	    disk19  ONLINE       0     0     0
29
30errors: No known data errors

Détails des options:

  • tank : nom usuel des zpool dans ZFS
  • atime=off: améliore les performances en désactivant l’enregistrement des dates d’accès
  • compression=on : par défaut en LZ4 mais il est possible de changer l’algorithme (gzip…)
  • ashift=12 : force l’utilisation des secteurs de 4K sur les disques
  • xattr=sa : active le support des attributs étendus (compatible avec les ACL Posix)

La compression:

Inutile de se poser la question, il faut l’activer par défaut. En plus d’économiser de l’espace disque, elle permet d’augmenter globalement les performances (débit et IOPS) du stockage. Elle peut être définie au niveau ZPOOL et DATASET (par héritage du zpool ou en surchargeant la valeur).

La directive compression=on active la compression avec l’algorithme LZ4 par défaut. Il est possible d’utiliser des algorithmes plus performants (GZIP) en terme de taux de compression mais forcement avec un coût CPU plus élevé.

Pour auditer une configuration existante (tank = nom du zpool):

1$ sudo zfs get compression tank
2NAME  PROPERTY     VALUE     SOURCE
3tank  compression  on        local
4
5$ sudo zpool get feature@lz4_compress tank
6NAME  PROPERTY              VALUE                 SOURCE
7tank  feature@lz4_compress  active                local

ashift:

Cette option permet d’indiquer à ZFS la taille réelle des secteurs des disques. Elle est exprimée en puissance de 2:

ashift taille secteur usage
9 512B ancien disque, certain SSD
12 4KB disque récent
13 8K certain SSD

Les disques modernes continuent d’émuler les secteurs de 512B pour maintenir la compatibilité avec les anciens systèmes de fichiers (voir: Advanced Format). En général ZFS se débrouille bien pour détecter la bonne taille mais il est possible de le définir au moment de la création des VDEVs (non modifiable après).

Sur une configuration existante, vous pouvez verifier que le “sector size physical” (4KB dans l’exemple) des disques correspond bien à la valeur ashift utilisée (ashift=12):

 1$ sudo sfdisk  /dev/sda    // disque mécanique
 2Disk /dev/sda: 14,57 TiB, 16000900661248 bytes, 31251759104 sectors
 3Disk model: MG08SCA16TEY
 4Units: sectors of 1 * 512 = 512 bytes
 5Sector size (logical/physical): 512 bytes / 4096 bytes
 6I/O size (minimum/optimal): 4096 bytes / 4096 bytes
 7
 8$ sudo sfdisk  /dev/sdm     //disque SSD
 9Disk /dev/sdm: 447,13 GiB, 480103981056 bytes, 937703088 sectors
10Disk model: KPM5XVUG480G
11Units: sectors of 1 * 512 = 512 bytes
12Sector size (logical/physical): 512 bytes / 4096 bytes
13I/O size (minimum/optimal): 4096 bytes / 4096 bytes
14
15$ sudo zdb | grep -e ashift
16            ashift: 12

Une erreur d’alignement des blocks ZFS avec la taille des secteurs des disques entraine une dégradation importante des performances.

Ajout du SLOG:

Nous avons vu précédemment que pour augmenter les performances de ZFS sur les écritures synchrones (avec NFS par exemple), il peut être intéressant d’ajouter un périphérique rapide de type SSD ou PMEM.

Pour des raisons de sécurité, le SLOG est monté en miroir sur les disques SSD arrière 12 et 13:

1$ sudo zpool add -f tank log mirror disk1{2..3}
2
3$ sudo zpool status | grep -A 4 "logs"
4logs
5  mirror-2  ONLINE       0     0     0
6    disk12  ONLINE       0     0     0
7    disk13  ONLINE       0     0     0

Ajout du L2ARC:

Je suis pratiquement convaincu que ce cache en lecture n’est pas nécéssaire dans mon cas d’usage. Il est même potentiellement contre-productif en consommant de la RAM qui pourrait être bénéfique pour l’ARC. Je vais mettre en place un tableau de bord Graphana pour mesure les hits sur le cache L2ARC et aviser après 1 mois d’utilisation.

Le L2ARC n’est pas critique pour le fonctionnement de ZFS, je vais me contenter du dernier SSD (disk14) en face arrière:

1$ sudo zpool add -f  tank cache disk14
2$ sudo zpool status | grep -A 2 "cache"
3	cache
4	  disk14    ONLINE       0     0     0

Création du DATASET:

ZFS stocke les données dans des blocks logiques appelés records avant de les écrire dans un ou plusieurs blocks matériels (voir ashift plus haut). Dans l’ideal, il faut tenter de faire correspondre la taille des records (RECORDSIZE) avec le type des données qui vont être stockées dans le DATASET

Quelques exemples:

  • MariaDB utilise des pages de 16KB pour son moteur innodb
  • PostgreSQL utilise des pages de 8KB
  • KVM avec le format qcow2 utilise un cluster_size de 64KB

En dehors de ces cas d’usages particuliers (il en existe probablement d’autres), la recommandation consiste à utiliser la plus grande taille possible (128KB par défaut, configurable de 512B à 16MB et exprimée en puissance de 2). Cette valeur est modifiable à chaud mais avec effet uniquement sur les nouveaux fichiers. De ce que je comprends, le moteur de compression de ZFS intervient entre les records et l’écriture sur les secteurs des disques. En toute logique, plus le RECORDSIZE est élevé et plus le ratio de compression peut être efficace. En contre-partie, la ré-écriture partielle d’un gros record va nécessiter une relecture complète des données depuis le cache ARC ou pire depuis les disques. Un RECORSIZE trop grand risque alors de pénaliser les performances.

Dans le doute, je vais partir sur un RECORDSIZE de 1MB qui est la valeur recommandée dans la documentation OpenZFS pour un usage en serveur de fichiers.

 1$ sudo zfs create tank/nfs  
 2$ sudo zfs set recordsize=1M tank/nfs
 3$ zfs set quota=128T tank/nfs
 4
 5$ zfs list
 6NAME       USED  AVAIL     REFER  MOUNTPOINT
 7tank      1,45M   160T      205K  none
 8tank/nfs   205K   128T      205K  none
 9
10$ sudo zfs set mountpoint=/srv/nfs tank/nfs
11
12$ $ df -h
13Filesystem              Size  Used Avail Use% Mounted on
14tank/nfs                128T  1,0M  128T   1% /srv/nfs
15
16$ sudo zfs set com.sun:auto-snapshot=true tank/nfs

Par défaut, le DATASET peut consommer l’intégralité du ZPOOL. Je fixe un quota de 128TB (80%) par mesure de précaution.

Les 2 dernières commandes ZFS permettent respectivement de monter le dataset (ZFS n’utilise pas le traditionnel fichier /etc/fstab) sur le point de montage /srv/nfs et à activer une propriété pour la gestion automatique des snapshots

Gestion des snapshots:

Sans dispenser de mettre en place une vrai solution de sauvegarde (un snapshot n’est pas une sauvegarde on est d’accord hein !), les snasphots ZFS peuvent proposer un 1er rempart aux CryptoLockers sur un partage réseau. L’utilisateur dispose également d’une solution rapide pour récupérer des données effacées par mégarde. En effet, ZFS va proposer un dossier caché (.zfs/) à la racine du point de montage qui va contenir l’état du système de fichiers au moment des différents snapshots.

Pour faciliter la gestion de la rétention, je vais utiliser le projet zfs-auto-snapshot même si il ne semble plus très actif.

1$ cd /tmp
2$ wget https://github.com/zfsonlinux/zfs-auto-snapshot/archive/master.zip
3$ unzip master.zip
4$ cd zfs-auto-snapshot-master
5$ sudo make install

Cette configuration propose :

  • 1 snapshot par heure avec une rétention de 24 heures
  • 1 snapshot par jour avec une rétention de 31 jours
  • 1 snapshot par semaine avec une rétention sur 8 semaines
  • 1 snapshot par mois avec une rétention sur 12 mois

Conclusion:

Je vais m’arrêter là pour conserver un article pas trop indigeste (du moins je l’espère). Je vous propose d’aborder la configuration NFS (Ganesha), le monitoring (Graphana), le tuning et les benchmarks dans le prochain épisode…

Références: