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:
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…