Quelle est la relation entre LVM et une matriochka ?

Bonne journée.
Je souhaite partager avec la communauté mon expérience pratique dans la construction d'un système de stockage de données pour KVM en utilisant md RAID + LVM.

Le programme comprendra :

  • Montage md RAID 1 à partir de NVMe SSD.
  • Montage md RAID 6 à partir de SSD SATA et de disques classiques.
  • Particularités du fonctionnement de TRIM/DISCARD sur RAID 1/6 SSD.
  • Création d'un tableau md RAID 1/6 bootable à partir d'un ensemble de disques.
  • Installation du système sur NVMe RAID 1 sans support NVMe dans le BIOS.
  • Utilisation de LVM cache et LVM thin.
  • Utilisation des instantanés BTRFS et send/receive pour la sauvegarde.
  • Utilisation des snapshots thin LVM et thin_delta pour la sauvegarde de style BTRFS.

Si intéressé, je vous invite à continuer.

Déclaration

L'auteur ne prend aucune responsabilité pour les conséquences de l'utilisation ou de la non-utilisation des matériaux/exemples/code/conseils/données de cet article. En lisant ou en utilisant ce matériel d'une manière ou d'une autre, vous assumez la responsabilité de toutes les conséquences de ces actions. Les conséquences possibles comprennent :

  • Des NVMe SSD grillés jusqu'à une croûte croustillante.
  • Usure complète de la capacité d'écriture et défaillance des SSD.
  • Perte totale de toutes les données sur tous les disques, y compris les sauvegardes.
  • Matériel informatique défectueux.
  • Temps, nerfs et argent perdus.
  • Toute autre conséquence non mentionnée ci-dessus.

Matériel

S'étaient disponibles :

Une carte mère datant d'environ 2013 avec un chipset Z87 associé à un Intel Core i7 / Haswell.

  • Processeur 4 cœurs, 8 threads
  • 32 Go de RAM DDR3
  • 1 x 16 ou 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 x connecteurs SATA 3 à 6 Gb/s

Adaptateur SAS LSI SAS9211-8I reflashé en mode IT / HBA. Le firmware avec support RAID a intentionnellement été remplacé par le firmware HBA afin que :

  1. Cet adaptateur puisse être retiré et remplacé à tout moment par n'importe quel autre aux alentours.
  2. TRIM/Discard fonctionnait normalement sur les disques, car dans le firmware RAID, ces commandes ne sont pas du tout supportées, et l'HBA se moque en gros des commandes qui sont envoyées sur le bus.

Disques durs — 8 HGST Travelstar 7K1000 de 1 To en format 2,5, comme pour les ordinateurs portables. Ces disques avaient déjà été dans un tableau RAID 6. Ils trouveront également leur place dans le nouveau système. Pour le stockage de sauvegardes locales.

En supplément, ont été ajoutés :

6 SATA SSDs modèle Samsung 860 QVO 2 To. Ces SSD nécessitaient un grand volume, la présence d'un cache SLC, de la fiabilité souhaitée et un prix abordable. Une prise en charge obligatoire de discard/zero qui est vérifiée par une ligne dans dmesg :

noyau : ata1.00 : Activation de discard_zeroes_data

2 unités NVMe SSD modèle Samsung SSD 970 EVO 500 Go.

Pour ces SSD, la vitesse de lecture/écriture aléatoire et la durabilité en fonction de vos besoins sont importantes. Un radiateur est indispensable. Absolument indispensable. Sinon, vous risquez de les brûler à une chaleur excessive lors de la première synchronisation RAID.

Adaptateur StarTech PEX8M2E2 pour 2 x NVMe SSD à installer dans un slot PCIe 3.0 8x. Encore une fois, c'est juste un HBA, mais pour NVMe. Il se distingue des adaptateurs bon marché par l'absence d'exigence de prise en charge de la bifurcation PCIe par la carte mère grâce à un commutateur PCIe intégré. Il fonctionnera même dans le système le plus ancien avec PCIe, même si c'est un slot PCIe 1.0 x1. Naturellement, à la vitesse correspondante. Pas de RAID là-dedans. Aucuns BIOS intégré à bord. Donc, votre système ne saura pas magiquement démarrer à partir de NVMe, encore moins créer un RAID NVMe grâce à cet appareil.

Ce composant a été déterminé uniquement par la présence d'un seul PCIe 3.0 8x libre dans le système, et avec 2 slots libres, il peut facilement être remplacé par deux PEX4M2E1 bon marché ou équivalents, que vous pouvez acheter partout à partir de 600 roubles.

Le refus de tout type de RAID matériel ou intégré dans le chipset/BIOS a été fait de manière consciente, dans le but de pouvoir remplacer complètement le système, à l'exception des SSD/HDD, tout en préservant toutes les données. Idéalement, pour pouvoir même conserver le système d'exploitation installé lors du passage à un matériel complètement nouveau/différent. Tant que des ports SATA et PCIe sont disponibles. C'est comme un CD live ou une clé USB de démarrage, mais très rapide et un peu encombrante.

HumourVous savez comment ça fonctionne parfois, il est parfois urgent de prendre tout le système avec vous. Et vous ne voulez pas perdre de données. Pour cela, tous les supports mentionnés se trouvent commodément dans des glissières dans des emplacements de 5.25 dans un boîtier standard.

Eh bien, et bien sûr, pour expérimenter différentes méthodes de mise en cache SSD sous Linux.

Les RAID matériels, c'est ennuyeux. On les active, ça fonctionne ou non. Avec mdadm, il y a toujours des options.

Logiciel

Auparavant, Debian 8 Jessie, qui est proche de la fin de vie, était installée sur le matériel. Un RAID 6 a été créé avec les HDD mentionnés précédemment en combinaison avec LVM. Il était utilisé machines virtuelles dans kvm/libvirt.

Comme l'auteur a une expérience pertinente dans la création de clés USB SATA/NVMe bootables, et afin de ne pas rompre le modèle apt habituel, le système cible choisi est Ubuntu 18.04, qui s'est déjà suffisamment stabilisé, mais qui bénéficie encore de 3 ans de support à l'avenir.

Le système mentionné contient tous les pilotes matériels nécessaires dès la sortie de la boîte. Aucun logiciel ni pilote tiers ne sera requis.

Préparation à l'installation

Pour installer le système, nous aurons besoin de l'image de bureau d'Ubuntu. La version serveur a un installateur assez ennuyeux qui fait preuve d'une autonomie excessive en insérant systématiquement une partition système UEFI sur l'un des disques, ruinant ainsi toute l'esthétique. Par conséquent, elle ne peut être installée qu'en mode UEFI. Aucune autre option n'est proposée.

Cela ne nous convient pas.

Pourquoi ?Malheureusement, le démarrage UEFI est extrêmement mal compatible avec le RAID logiciel bootable, car personne ne propose de redondance pour la partition ESP UEFI. Il existe des recettes en ligne qui suggèrent de placer la partition ESP sur une clé USB, mais cela constitue un point de défaillance. Il y a des recettes utilisant un RAID 1 logiciel mdadm avec des métadonnées de version 0.9 qui ne gênent pas le BIOS UEFI à voir cette partition, mais cela ne dure que jusqu'à ce que le BIOS ou un autre OS sur le matériel écrive quelque chose dans l'ESP sans synchroniser sur d'autres miroirs.

De plus, le démarrage UEFI dépend de la NVRAM, qui ne se déplacera pas avec les disques vers un nouveau système, car elle fait partie de la carte mère.

Donc, nous ne réinventons pas la roue. Nous avons déjà une vieille roue prête, testée depuis des années, maintenant appelée boot Legacy/BIOS, fièrement nommée CSM sur les systèmes compatibles UEFI. Nous allons simplement la sortir de l'étagère, lubrifier, regonfler les pneus et la dépoussiérer avec un chiffon humide.

La version Desktop d'Ubuntu n'arrive pas non plus à s'installer correctement avec un chargeur d'amorçage Legacy, mais ici, comme on dit, au moins il y a des options.

Alors, assemblons le matériel et chargeons le système depuis la clé USB bootable Ubuntu Live. Nous devrons télécharger des paquets, alors configurons le réseau, selon ce qui fonctionne déjà. S'il ne fonctionne pas, les paquets nécessaires peuvent être chargés sur la clé USB à l'avance.

Entrons dans l'environnement de bureau, ouvrons un émulateur de terminal, et c'est parti :

#sudo bash

Comment...?La ligne ci-dessus est un déclencheur canonique des débats sur sudo. Avec bdeDe plus grandes possibilités s'accompagnent d'une plus grande responsabilité.deLa question est de savoir si vous êtes capable de l'assumer. Beaucoup considèrent que l'utilisation de sudo de cette manière est, au moins, imprudente. Cependant :

Lire la vidéo

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc

Pourquoi pas ZFS…?Lorsque nous installons un logiciel sur notre ordinateur, nous empruntons essentiellement notre matériel aux développeurs de ce logiciel.
Lorsque nous faisons confiance à ce logiciel pour sécuriser nos données, nous prenons un crédit équivalent à la valeur de la récupération de ces données, que nous devrons un jour rembourser.

Sous cet angle, ZFS est une Ferrari, tandis que mdadm+lvm ressemble plus à un vélo.

Subjectivement, l'auteur préfère emprunter un vélo à des inconnus qu'une Ferrari. Dans ce cas, le coût n'est pas élevé. Pas besoin de droits. Les règles de circulation sont plus simples. Le stationnement est gratuit. La mobilité est meilleure. On peut toujours ajouter des roues au vélo, et il est même possible de le réparer soi-même.

Pourquoi alors BTRFS…?Pour démarrer le système d'exploitation, nous aurons besoin d'un système de fichiers pris en charge par Legacy/BIOS GRUB par défaut, qui supporte également les snapshots à chaud. Nous l'utiliserons pour la partition /boot. De plus, l'auteur préfère utiliser ce système de fichiers pour / (racine), sans oublier qu'il est possible de créer des partitions séparées sur LVM pour tout autre logiciel et de les monter dans les répertoires nécessaires.

Nous ne stockerons ni images machines virtuelles, ni bases de données sur ce système de fichiers.
Ce système de fichiers sera utilisé uniquement pour créer des snapshots instantanés du système sans l'éteindre, suivis du transfert de ces snapshots sur un disque de sauvegarde à l'aide de send/receive.

De plus, l'auteur préfère en général garder un minimum de logiciels directement sur le matériel et exécuter tout le reste dans des machines virtuelles en utilisant des technologies telles que le passage de GPU et de contrôleurs hôtes PCI-USB dans KVM via IOMMU.

Sur le matériel restent seulement — le stockage de données, la virtualisation et la sauvegarde.

Si vous faites davantage confiance à ZFS, alors, en principe, pour l'application indiquée, ils sont interchangeables.

Néanmoins, l'auteur ignore délibérément les fonctions intégrées de mise en miroir / RAID et de redondance disponibles dans ZFS, BTRFS et LVM.

En tant qu'argument supplémentaire, BTRFS a la propriété de convertir l'écriture aléatoire en séquentielle, ce qui a un impact très positif sur la vitesse de synchronisation des instantanés / sauvegardes sur HDD.

Nous allons rescanner tous les appareils :

#udevadm control --reload-rules && udevadm trigger

Faisons le point :

#lsscsi && nvme list
[0:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disque ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disque ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disque ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disque ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disque ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disque ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disque ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disque ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disque ATA HGST HTS721010A9 A3J0 /dev/sdn
Node SN Modèle Espace de noms Utilisation Format Rév FW
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1 S466NXXXXXXX15L Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
/dev/nvme1n1 S5H7NXXXXXXX48N Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7

Partitionnement des « disques »

NVMe SSD

Nous ne les partitionnerons pas du tout. De toute façon, notre BIOS ne voit pas ces périphériques. Ils iront donc complètement dans le RAID logiciel. Même pas besoin de créer des partitions là. Si vous souhaitez le faire de manière « canonique » ou par « principe » — créez une grande partition, comme sur un HDD.

SATA HDD

Ici, nous n'avons pas besoin d'inventer quoi que ce soit. Nous créerons une seule partition pour tout. Nous créons une partition parce que ces disques sont vus par le BIOS et peuvent même tenter de démarrer à partir d'eux. Nous installerons même GRUB sur ces disques plus tard pour que le système puisse démarrer de manière inattendue.

#cat >hdd.part << EOF
étiquette : dos
étiquette-id : 0x00000000
dispositif : /dev/sdg
unité : secteurs

/dev/sdg1 : start= 2048, size= 1953523120, type=fd, bootable
EOF
#sfdisk /dev/sdg < hdd.part
#sfdisk /dev/sdh < hdd.part
#sfdisk /dev/sdi < hdd.part
#sfdisk /dev/sdj < hdd.part
#sfdisk /dev/sdk < hdd.part
#sfdisk /dev/sdl < hdd.part
#sfdisk /dev/sdm < hdd.part
#sfdisk /dev/sdn < hdd.part

SATA SSD

Ici, c'est encore plus intéressant.

Tout d'abord, nos périphériques font 2 To. C'est dans la limite acceptable pour MBR, que nous utiliserons. Si nécessaire, cela peut être remplacé par GPT. Les disques GPT ont une couche de compatibilité qui permet aux systèmes compatibles MBR de voir les quatre premières partitions si elles se trouvent dans les deux premiers téraoctets. L'essentiel est que la partition de démarrage et la partition bios_grub sur ces disques soient au début. Cela permet même un démarrage Legacy/BIOS à partir de disques GPT.

Mais ce n'est pas notre cas.

Ici, nous allons créer deux partitions. La première sera de 1 Go et utilisée pour RAID 1 /boot.

La seconde sera utilisée pour RAID 6 et occupera tout l'espace libre restant, sauf une petite zone non partitionnée à la fin du périphérique.

Qu'est-ce que cette zone non partitionnée ?Selon les sources en ligne, nos SSD SATA disposent d'un cache SLC extensible dynamiquement allant de 6 à 78 gigaoctets. Nous obtenons 6 gigaoctets « gratuitement » grâce à la différence entre les « gigaoctets » et les « gibioctets » dans la documentation technique du périphérique. Les 72 autres gigaoctets sont alloués à partir de l'espace inutilisé.

Il convient de noter que notre cache est SLC, tandis que l'espace utilisé fonctionne en mode 4 bit MLC. Cela signifie pour nous que pour chaque 4 gigaoctets d'espace libre, nous obtenons seulement 1 gigaoctet de cache SLC.

Nous multiplions 72 gigaoctets par 4 et nous obtenons 288 gigaoctets. C'est l'espace libre que nous ne partitionnerons pas pour permettre aux périphériques d'utiliser pleinement le cache SLC.

Ainsi, nous obtiendrons efficacement jusqu'à 312 Go de cache SLC au total à partir de six périphériques. Parmi tous les périphériques, 2 seront utilisés en RAID pour la redondance.

Une telle quantité de cache nous permettra de rarement faire face à une situation où l'écriture ne se fait pas dans le cache. Cela compense extrêmement bien le plus grand défaut de la mémoire QLC - la vitesse d'écriture très basse lorsque les données sont écrites en contournant le cache. Si vos charges de travail ne correspondent pas à cela, je vous recommande vivement de réfléchir à la durée de vie de vos SSD sous une telle charge, en tenant compte du TBW dans la fiche technique.

#cat >ssd.part << EOF
étiquette : dos
étiquette-id : 0x00000000
appareil : /dev/sda
unité : secteurs

/dev/sda1 : start= 2048, size= 2097152, type=fd, bootable
/dev/sda2 : start= 2099200, size= 3300950016, type=fd
EOF
#sfdisk /dev/sda < ssd.part
#sfdisk /dev/sdb < ssd.part
#sfdisk /dev/sdc < ssd.part
#sfdisk /dev/sdd < ssd.part
#sfdisk /dev/sde < ssd.part
#sfdisk /dev/sdf < ssd.part

Création de tableaux

Pour commencer, nous devons renommer la machine. Cela est nécessaire car le nom de l'hôte fait partie du nom du tableau quelque part à l'intérieur de mdadm et influence quelque chose. Les tableaux peuvent bien sûr être renommés plus tard, mais c'est une démarche superflue.

#mcedit /etc/hostname
#mcedit /etc/hosts
#hostname
vdesk0

NVMe SSD

#mdadm --create --verbose --assume-clean /dev/md0 --level=1 --raid-devices=2 /dev/nvme[0-1]n1

Pourquoi -assume-clean…?Pour ne pas initialiser les tableaux. Pour les niveaux RAID 1 et 6, cela est acceptable. Tout peut fonctionner sans initialisation si c'est un nouveau tableau. De plus, l'initialisation d'un tableau SSD lors de sa création est un gaspillage de ressources TBW. Nous utilisons TRIM/DISCARD où cela est possible sur les tableaux SSD assemblés pour les "initialiser".

Pour les tableaux SSD RAID 1, le DISCARD est pris en charge dès le départ.

Pour les tableaux SSD RAID 6, le DISCARD doit être activé dans les paramètres du module du noyau.

Cela ne doit être fait que si tous les SSD utilisés dans les tableaux de niveaux 4/5/6 de ce système prennent en charge la fonctionnalité discard_zeroes_data. Parfois, on trouve des périphériques étranges qui signalent au noyau qu'ils prennent en charge cette fonctionnalité, mais en réalité, ce n'est pas le cas, ou la fonctionnalité ne fonctionne pas toujours. Actuellement, la prise en charge est pratiquement universelle, cependant, des vieux périphériques et des firmwares erronés peuvent encore exister. Pour cette raison, la prise en charge du DISCARD est désactivée par défaut pour le RAID 6.

Attention, la commande suivante détruira toutes les données sur les périphériques NVMe en "initialisant" le tableau avec des "zéros".

#blkdiscard /dev/md0

Si quelque chose ne va pas, essayez de spécifier l'étape.

#blkdiscard --step 65536 /dev/md0

SATA SSD

#mdadm --create --verbose --assume-clean /dev/md1 --level=1 --raid-devices=6 /dev/sd[a-f]1
#blkdiscard /dev/md1
#mdadm --create --verbose --assume-clean /dev/md2 --chunk-size=512 --level=6 --raid-devices=6 /dev/sd[a-f]2

Pourquoi si grand…?L'augmentation de la taille des chunks a un impact positif sur la vitesse de lecture aléatoire par blocs jusqu'à la taille du chunk inclusivement. Cela se produit parce qu'une opération de taille correspondante ou inférieure peut être complètement effectuée sur un seul périphérique. Ainsi, les IOPS de tous les périphériques s'additionnent. Statistiquement, 99% des opérations d'entrée/sortie ne dépassent pas 512 Ko.

Dans RAID 6, les IOPS d'écriture ont toujours sont moins ou égaux aux IOPS d'un seul disque. En revanche, pour la lecture aléatoire, les IOPS peuvent être plusieurs fois supérieurs à ceux d'un seul disque, et la taille du bloc joue un rôle clé.
L'auteur ne voit pas l'intérêt d'optimiser un paramètre qui est intrinsèquement mauvais pour RAID 6, et préfère optimiser ce dans quoi RAID 6 se montre performant.
Nous compenserons les mauvaises écritures aléatoires de RAID 6 avec un cache NVMe et des astuces avec le thin-provisioning.

Nous n'avons pas encore activé DISCARD pour RAID 6. Donc, nous n'initialiserons pas encore ce volume. Nous le ferons plus tard, après l'installation du système d'exploitation.

SATA HDD

#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1

LVM sur RAID NVMe

Pour plus de rapidité, nous souhaitons placer le système de fichiers racine sur RAID NVMe 1 qui est /dev/md0.
Cependant, ce volume rapide sera également nécessaire pour d'autres besoins, tels que swap, métadonnées et cache LVM-cache ainsi que des métadonnées LVM-thin, donc sur ce volume, nous créerons un VG LVM.

#pvcreate /dev/md0
#vgcreate root /dev/md0

Créons une partition pour le système de fichiers racine.

#lvcreate -L 128G --name root root

Créons une partition de swap de la taille de la mémoire vive.

#lvcreate -L 32G --name swap root

Installation du système d'exploitation

En résumé, nous avons tout ce qu'il faut pour installer le système.

Nous lançons l'assistant d'installation du système depuis l'environnement Ubuntu Live. Installation classique. Juste à l'étape de choix des disques à utiliser, il faut indiquer ce qui suit :

  • /dev/md1, — точка монтирования /boot, ФС — BTRFS
  • /dev/root/root (a.k.a /dev/mapper/root-root), — точка монтирования / (корень), ФС — BTRFS
  • /dev/root/swap (a.k.a /dev/mapper/root-swap), — использовать как раздел подкачки
  • Installer le chargeur de démarrage sur /dev/sda

En choisissant BTRFS pour le système de fichiers racine, l'installateur créera automatiquement deux volumes BTRFS nommés "@" pour / (la racine) et "@home" pour /home.

Lancement de l'installation...

L'installation finira par une boîte de dialogue modale signalant une erreur d'installation du chargeur de démarrage. Malheureusement, il ne sera pas possible de quitter cette boîte de dialogue par des moyens standards et de continuer l'installation. Déconnectez-vous du système et reconnectez-vous pour retrouver un bureau propre d'Ubuntu Live. Ouvrez un terminal et à nouveau :

#sudo bash

Créons un environnement chroot pour poursuivre l'installation :

#mkdir /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@ /dev/mapper/root-root /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@home /dev/mapper/root-root /mnt/chroot/home
#mount -o defaults,space_cache,noatime,nodiratime,discard /dev/md1 /mnt/chroot/boot
#mount --bind /proc /mnt/chroot/proc
#mount --bind /sys /mnt/chroot/sys
#mount --bind /dev /mnt/chroot/dev

Configurons le réseau et le hostname dans le chroot :

#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf

Entrons dans l'environnement chroot :

#chroot /mnt/chroot

Tout d'abord, installons les paquets :

apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm

Vérifions et corrigeons tous les paquets qui se sont mal installés à cause de l'installation inachevée du système :

#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES

Si quelque chose n'a pas fonctionné, vous devrez peut-être d'abord modifier /etc/apt/sources.list

Corrigeons les paramètres pour le module RAID 6 afin d'activer TRIM/DISCARD :

#cat >/etc/modprobe.d/raid456.conf << EOF
options raid456 devices_handle_discard_safely=1
EOF

Ajustons un peu nos volumes :

#cat >/etc/udev/rules.d/60-md.rules << EOF
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/stripe_cache_size", ATTR{md/stripe_cache_size}="32768"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_min", ATTR{md/sync_speed_min}="48000"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_max", ATTR{md/sync_speed_max}="300000"
EOF
#cat >/etc/udev/rules.d/62-hdparm.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/hdparm -B 254 /dev/%k"
EOF
#cat >/etc/udev/rules.d/63-blockdev.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/blockdev --setra 1024 /dev/%k"
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="md*", RUN+="/sbin/blockdev --setra 0 /dev/%k"
EOF

Qu'est-ce que c'était ..?Nous avons créé un ensemble de règles udev qui feront ce qui suit :

  • Définir une taille de cache des blocs adéquate pour RAID 6 pour l'année 2020. La valeur par défaut n'a apparemment pas changé depuis la création de Linux et n'est plus du tout adaptée.
  • Réserver un minimum d'IO pendant les vérifications/synchronisations des matrices. Cela est nécessaire pour que vos matrices ne se bloquent pas dans un état de synchronisation éternelle sous charge.
  • Limiter un maximum d'IO pendant les vérifications/synchronisations des matrices. Cela est nécessaire pour que la synchronisation/la vérification des RAID SSD ne chauffe pas vos disques à blanc. C'est particulièrement vrai pour NVMe. (Vous vous souvenez du radiateur ? Je ne rigolais pas.)
  • Interdire aux disques via APM d'arrêter la rotation du plateau (HDD) et définir un timeout de mise en veille des contrôleurs de disques à 7 heures. Vous pouvez complètement désactiver l'APM si vos disques le supportent (-B 255). Avec la valeur par défaut, les disques s'arrêteront après cinq secondes. Ensuite, le système d'exploitation voudra réinitialiser le cache disque, les disques redémarreront, et c'est reparti pour un tour. Les disques ont un nombre maximum limité de rotations du plateau. Ce cycle simple par défaut peut facilement endommager vos disques en quelques années. Tous les disques ne souffrent pas, mais nos « disques portables », avec des réglages par défaut appropriés, transforment le RAID en une version déformée de mini-MAID.
  • Configurer le readahead sur les disques (rotatifs) à 1 mégaoctet — deux blocs/chunks consécutifs RAID 6.
  • Interdire le readahead sur les matrices elles-mêmes.

Modifions /etc/fstab :

#cat >/etc/fstab << EOF
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
# file-system mount-point type options dump pass
/dev/mapper/root-root / btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@ 0 1
UUID=$(blkid -o value -s UUID /dev/md1) /boot btrfs defaults,space_cache,noatime,nodiratime,discard 0 2
/dev/mapper/root-root /home btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@home 0 2
/dev/mapper/root-swap none swap sw 0 0
EOF

Pourquoi cela.. ?Nous allons chercher la partition /boot par UUID car la nomination des matrices peut théoriquement changer.

Nous allons chercher les autres partitions par les noms LVM en notation /dev/mapper/vg-lv, car elles identifient les partitions de manière suffisamment unique.

Nous n'utilisons pas UUID pour LVM car les UUID des volumes LVM et de leurs snapshots peuvent coïncider.Monter deux fois /dev/mapper/root-root.. ?Oui, exactement. C'est une caractéristique de BTRFS. Ce système de fichiers peut être monté plusieurs fois avec différents subvolumes.

En raison de cette même caractéristique, je recommande de ne jamais créer de snapshots LVM des volumes BTRFS actifs. Vous pourriez avoir une surprise lors du redémarrage.

Régénérons la configuration mdadm :

#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf

Ajustons les paramètres LVM :

#cat >>/etc/lvm/lvmlocal.conf << EOF

activation {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocation {
cache_pool_max_chunks=2097152
}
devices {
global_filter=["r|^/dev/.*_corig$|","r|^/dev/.*_cdata$|","r|^/dev/.*_cmeta$|","r|^/dev/.*gpv$|","r|^/dev/images/.*$|","r|^/dev/mapper/images.*$|","r|^/dev/backup/.*$|","r|^/dev/mapper/backup.*$|"]
issue_discards=1}
}
EOF

Qu'est-ce que c'était ..?Nous avons activé l'extension automatique des pools LVM thin lorsque 90 % de l'espace est occupé de 5 % du volume.

Nous avons augmenté le nombre maximum de blocs cache pour LVM cache.

Nous avons interdit à LVM de rechercher des volumes LVM (PV) sur :

  • des périphériques contenant LVM cache (cdata)
  • des périphériques mis en cache avec LVM cache en contournant le cache (<lv_name>_corig). Le périphérique mis en cache sera tout de même analysé via le cache (juste <lv_name>).
  • des périphériques contenant des métadonnées LVM cache (cmeta)
  • tous les périphériques dans VG nommé images. Ici, nous aurons des images de disques de machines virtuelles, et nous ne voulons pas que LVM sur l'hôte active des volumes appartenant au système d'exploitation invité.
  • tous les périphériques dans VG nommé backup. Ici, nous aurons des sauvegardes d'images de machines virtuelles.
  • tous les périphériques dont le nom se termine par « gpv » (guest physical volume)

Nous avons activé le support DISCARD lors de la libération d'espace libre sur LVM VG. Attention. Cela rendra la suppression de LV sur SSD assez longue. Cela est particulièrement vrai pour le SSD RAID 6. Cependant, nous prévoyons d'utiliser le thin provisioning, donc cela ne nous dérangera pas.

Mettons à jour l'image initramfs :

#update-initramfs -u -k all

Installons et configurons grub :

#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc

Quels disques choisir ?Tous ceux qui sont sd*. Le système doit être capable de démarrer à partir de n'importe quel disque SATA ou SSD fonctionnel.

Pourquoi avoir désactivé os-prober..?Pour trop d'autonomie et des mains espiègles.

Il ne fonctionne pas correctement si un des RAID est en état dégradé. Il tente de rechercher le système d'exploitation sur des partitions qui sont utilisées dans des machines virtuelles fonctionnant sur ce matériel.

S'il vous est nécessaire, vous pouvez le laisser, mais gardez à l'esprit tout ce qui précède. Je recommande de chercher des solutions pour se débarrasser des mains espiègles sur Internet.

Nous avons terminé l'installation initiale. Il est temps de redémarrer sur le système d'exploitation fraîchement installé. N'oubliez pas de retirer le CD/USB Live de démarrage.

#exit
#reboot

Pour le dispositif de démarrage, choisissez l'un des SSD SATA.

LVM sur SSD SATA

À ce stade, nous avons déjà démarré dans le nouveau système d'exploitation, configuré le réseau, apt, ouvert l'émulateur de terminal, et lancé :

#sudo bash

Continuons.

« Initialisons » le tableau de SSD SATA :

#blkdiscard /dev/md2

Si cela ne fonctionne pas, essayons :

#blkdiscard --step 65536 /dev/md2
Créons LVM VG sur SSD SATA :

#pvcreate /dev/md2
#vgcreate data /dev/md2

Pourquoi une autre VG..?En fait, nous avons déjà une VG nommée root. Pourquoi ne pas tout ajouter dans une seule VG ?

Si plusieurs PV sont présents dans VG, tous les PV doivent être présents (en ligne) pour une activation correcte de VG. L'exception est LVM RAID, que nous ne utilisons pas intentionnellement.

Nous souhaitons ardemment que, lors d'une panne (lire perte de données) sur l'un des tableaux RAID 6, le système d'exploitation démarre normalement et nous donne la possibilité de résoudre le problème.

Pour cela, au premier niveau d'abstraction, nous isolerons chaque type de « support » physique dans un VG distinct.

Scientifiquement parlant, différents tableaux RAID se rapportent à différents « domaines de fiabilité ». Il ne faut pas créer un point de défaillance commun en les regroupant dans un même VG.

La présence de LVM au niveau « matériel » nous permettra de découper arbitrairement des portions de différents tableaux RAID tout en les combinant différemment. Par exemple, démarrer simultanément bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, une configuration complexe ZFS avec des caches ou toute autre combinaison complexe pour tout tester et comparer.

Au niveau « matériel », nous n'utiliserons rien d'autre que les bons vieux volumes LVM « épais ». L'exception à cette règle pourrait être une partition pour la sauvegarde.

Je pense qu'à ce stade, beaucoup de lecteurs commencent déjà à suspecter quelque chose au sujet de la matriochka.

LVM sur HDD SATA

#pvcreate /dev/md3
#vgcreate backup /dev/md3

Encore un nouveau VG..?Nous souhaitons ardemment que, lors d'une panne de la matrice de disques que nous utiliserons pour sauvegarder des données, notre système d'exploitation continue de fonctionner normalement, tout en maintenant accès aux données non sauvegardées. Par conséquent, pour éviter les problèmes d'activation de VG, nous créons un VG distinct.

Configuration du cache LVM

Nous allons créer un LV sur NVMe RAID 1 pour l'utiliser comme dispositif de mise en cache.

#lvcreate -L 70871154688B --name cache root

Pourquoi si peu…?En fait, nos SSD NVMe ont également un cache SLC. 4 Go de « gratuit » et 18 Go dynamiques en raison de l'espace libre occupé par le MLC 3-bits. En épuisant ce cache, les SSD NVMe ne deviendront pas beaucoup plus rapides que notre SSD SATA avec cache. C'est pourquoi il n'est pas judicieux de créer une partition de cache LVM beaucoup plus grande que le double de la taille du cache SLC du support NVMe. Pour les supports NVMe utilisés, l'auteur considère raisonnable de faire un cache de 32 à 64 Go.

La taille de la partition indiquée est nécessaire pour organiser 64 Go de cache, pour le stockage des métadonnées du cache et pour une sauvegarde des métadonnées.

Je remarque en outre qu'après un arrêt brutal du système, LVM marquera tout le cache comme sale et synchronisera de nouveau. De plus, cela se répétera à chaque utilisation de lvchange sur cet appareil jusqu'au prochain redémarrage du système. C'est pourquoi je recommande de recréer immédiatement le cache avec le script approprié.

Créons un LV sur un RAID SATA 6 pour l'utiliser comme périphérique de mise en cache.

#lvcreate -L 3298543271936B --name cache data

Pourquoi seulement trois téraoctets..?Pour que, si nécessaire, on puisse utiliser un RAID SATA SSD 6 pour d'autres besoins. La taille de l'espace mis en cache peut être augmentée dynamiquement, à chaud, sans arrêter le fonctionnement du système. Pour cela, il est nécessaire d'arrêter temporairement et de redémarrer le cache, mais l'un des avantages distinctifs de LVM-cache par rapport, par exemple, à bcache, est que cela peut se faire à chaud.

Créons un nouveau VG pour la mise en cache.

#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache

Créons un LV sur le périphérique de mise en cache.

#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache

Ici, nous avons immédiatement occupé tout l'espace libre sur /dev/data/cache afin que toutes les autres partitions nécessaires soient créées directement sur /dev/root/cache. Si quelque chose a été créé à un autre endroit, vous pouvez le déplacer en utilisant pvmove.

Créons et activons le cache :

#lvcreate -y -L 64G -n cache cache /dev/root/cache
#lvcreate -y -L 1G -n cachemeta cache /dev/root/cache
#lvconvert -y --type cache-pool --cachemode writeback --chunksize 64k --poolmetadata cache/cachemeta cache/cache
#lvconvert -y --type cache --cachepool cache/cache cache/cachedata

Pourquoi une telle taille de morceau..?Par des expériences pratiques, l'auteur a pu déterminer que le meilleur résultat est obtenu lorsque la taille du bloc LVM cache correspond à la taille du bloc LVM thin. Et plus la taille est petite, meilleure est la configuration en cas d'écriture aléatoire.

64 Ko est la taille minimale de bloc acceptée pour LVM thin.

Attention au writeback..!Oui. Ce type de cache retarde la synchronisation de l'écriture sur le périphérique mis en cache. Cela signifie qu'en cas de perte de cache, des données peuvent être perdues sur le périphérique mis en cache. Plus tard, l'auteur expliquera quelles mesures, en plus du RAID NVMe 1, peuvent être prises pour compenser ce risque.

Ce type de cache a été choisi délibérément pour compenser la faible performance du RAID 6 en cas d'écriture aléatoire.

Vérifions ce que nous avons obtenu :

#lvs -a -o lv_name,lv_size,devices --units B cache
Appareils de taille LV
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)

Sur /dev/data/cache, seul [cachedata_corig] doit être présent. Si quelque chose ne va pas, utilisez pvmove.

Pour désactiver le cache si nécessaire, vous pouvez le faire avec une seule commande :

#lvconvert -y --uncache cache/cachedata

Cela se fait en ligne. LVM synchronise simplement le cache sur le disque, le supprime et renomme cachedata_corig en cachedata.

Configuration de LVM thin

Estimons approximativement combien d'espace nous aurons besoin pour les métadonnées LVM thin :

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - taille estimée de la zone de métadonnées de 3385794560 octets pour "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

Arrondissons à 4 Go : 4294967296B

Multiplions par deux et ajoutons 4194304B pour les métadonnées LVM PV : 8594128896B
Créons une partition distincte sur NVMe RAID 1 pour y placer les métadonnées LVM thin et leur sauvegarde :

#lvcreate -L 8594128896B --name images root

Pourquoi..?Une question peut se poser ici, pourquoi placer les métadonnées LVM thin séparément, si elles seront quand même mises en cache sur NVMe et fonctionneront rapidement.

Bien que la vitesse soit importante, ce n'est pas la raison principale. Tout est une question de cache, car c'est un point de défaillance. Il peut lui arriver quelque chose et, si les métadonnées LVM thin sont mises en cache, cela entraînera une perte totale de tout. Sans des métadonnées intactes, il sera pratiquement impossible de reconstituer les volumes thin.

En déplaçant les métadonnées sur un volume séparé, non mis en cache mais rapide, nous garantissons la sécurité des métadonnées en cas de perte ou de corruption du cache. Dans ce cas, tous les dommages causés par la perte du cache seront localisés à l'intérieur des volumes thin, ce qui simplifiera considérablement la procédure de récupération. Il est très probable que ces dommages pourront être restaurés à l'aide des journaux du système de fichiers.

De plus, si un instantané du volume thin a été pris précédemment, et que, par la suite, le cache a été complètement synchronisé au moins une fois, alors, en raison des caractéristiques internes de LVM thin, l'intégrité de l'instantané sera garantie en cas de perte du cache.

Créons un nouveau VG qui sera responsable du thin-provisioning :

#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata

Créons un pool :

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Pourquoi -Z yEn plus de l'objectif principal de ce mode, c'est-à-dire d'empêcher les données d'une machine virtuelle de fuir vers une autre machine virtuelle lors de la redistribution de l'espace, le zeroing est également utilisé pour augmenter la vitesse d'écriture aléatoire avec des blocs de moins de 64k. Toute écriture de moins de 64k dans une zone précédemment non allouée du volume thin sera transformée en blocs alignés à 64K sur la frontière du cache. Cela permettra d'exécuter l'opération entièrement via le cache tout en évitant le dispositif mis en cache.

Déplaçons le LV vers les PV correspondants :

#pvmove -n images/thin-pool_tdata /dev/root/images /dev/cache/cachedata
#pvmove -n images/lvol0_pmspare /dev/cache/cachedata /dev/root/images
#pvmove -n images/thin-pool_tmeta /dev/cache/cachedata /dev/root/images

Vérifions :

#lvs -a -o lv_name,lv_size,devices --units B images
Appareils de taille LV
[lvol0_pmspare] 4294967296B /dev/root/images(0)
thin-pool 274877906944B thin-pool_tdata(0)
[thin-pool_tdata] 274877906944B /dev/cache/cachedata(0)
[thin-pool_tmeta] 4294967296B /dev/root/images(1024)

Créons un volume thin pour les tests :

#lvcreate -V 64G --thin-pool thin-pool --name test images

Installons des paquets pour les tests et l'observation :

#apt-get install sysstat fio

Voici comment observer le comportement de notre configuration de stockage en temps réel :

#watch 'lvs --rows --reportformat basic --quiet -ocache_dirty_blocks,cache_settings cache/cachedata && (lvdisplay cache/cachedata | grep Cache) && (sar -p -d 2 1 | grep -E "sd|nvme|DEV|md1|md2|md3|md0" | grep -v Average | sort)'

Voici comment tester notre configuration :

#fio --loops=1 --size=64G --runtime=4 --filename=/dev/images/test --stonewall --ioengine=libaio --direct=1
--name=4kQD32read --bs=4k --iodepth=32 --rw=randread
--name=8kQD32read --bs=8k --iodepth=32 --rw=randread
--name=16kQD32read --bs=16k --iodepth=32 --rw=randread
--name=32KQD32read --bs=32k --iodepth=32 --rw=randread
--name=64KQD32read --bs=64k --iodepth=32 --rw=randread
--name=128KQD32read --bs=128k --iodepth=32 --rw=randread
--name=256KQD32read --bs=256k --iodepth=32 --rw=randread
--name=512KQD32read --bs=512k --iodepth=32 --rw=randread
--name=4Kread --bs=4k --rw=read
--name=8Kread --bs=8k --rw=read
--name=16Kread --bs=16k --rw=read
--name=32Kread --bs=32k --rw=read
--name=64Kread --bs=64k --rw=read
--name=128Kread --bs=128k --rw=read
--name=256Kread --bs=256k --rw=read
--name=512Kread --bs=512k --rw=read
--name=Seqread --bs=1m --rw=read
--name=Longread --bs=8m --rw=read
--name=Longwrite --bs=8m --rw=write
--name=Seqwrite --bs=1m --rw=write
--name=512Kwrite --bs=512k --rw=write
--name=256write --bs=256k --rw=write
--name=128write --bs=128k --rw=write
--name=64write --bs=64k --rw=write
--name=32write --bs=32k --rw=write
--name=16write --bs=16k --rw=write
--name=8write --bs=8k --rw=write
--name=4write --bs=4k --rw=write
--name=512KQD32write --bs=512k --iodepth=32 --rw=randwrite
--name=256KQD32write --bs=256k --iodepth=32 --rw=randwrite
--name=128KQD32write --bs=128k --iodepth=32 --rw=randwrite
--name=64KQD32write --bs=64k --iodepth=32 --rw=randwrite
--name=32KQD32write --bs=32k --iodepth=32 --rw=randwrite
--name=16KQD32write --bs=16k --iodepth=32 --rw=randwrite
--name=8KQD32write --bs=8k --iodepth=32 --rw=randwrite
--name=4kQD32write --bs=4k --iodepth=32 --rw=randwrite
| grep -E 'read|write|test' | grep -v ioengine

Attention ! Ressource !Ce code exécutera 36 tests différents, chacun devant durer 4 secondes. La moitié des tests concernent l'écriture. En 4 secondes sur NVMe, il est possible d'écrire beaucoup. Jusqu'à 3 Go par seconde. Ainsi, chaque exécution des tests d'écriture peut consommer jusqu'à 216 Go de ressources SSD.

Lecture et écriture en mélange?Oui. Il est judicieux d'exécuter les tests de lecture et d'écriture séparément. De plus, il est important de s'assurer que tous les caches sont synchronisés afin qu'un écrit précédent n'affecte pas la lecture.

Les résultats varieront considérablement lors du premier lancement et des suivants, à mesure que le cache et le volume fin seront remplis, et en fonction du fait que le système a pu synchroniser les caches remplis lors du dernier lancement.

En outre, je recommande de mesurer la vitesse sur un volume fin déjà rempli, à partir duquel un instantané vient juste d'être créé. L'auteur a eu l'occasion d'observer comment un écriture aléatoire s'accélère brusquement immédiatement après la création du premier instantané, surtout lorsque le cache n'est pas encore complètement rempli. Cela se produit grâce à la sémantique copy-on-write de l'écriture, à l'alignement des blocs entre le cache et le volume fin, et au fait qu'une écriture aléatoire sur RAID 6 se transforme en lecture aléatoire avec RAID 6, suivie d'une écriture dans le cache. Dans notre configuration, la lecture aléatoire avec RAID 6 est jusqu'à 6 fois (le nombre de SSD SATA dans le groupe) plus rapide que l'écriture. Comme les blocs pour CoW sont réservés séquentiellement à partir du pool fin, l'écriture se transforme en grande partie également en séquentielle.

Ces deux caractéristiques peuvent être avantageusement exploitées.

Instantanés de cache « cohérents »

Pour réduire le risque de perte de données en cas de corruption/perte du cache, l'auteur propose d'introduire une pratique de rotation des instantanés garantissant leur intégrité dans ce cas.

D'abord, grâce au fait que les métadonnées des volumes fins se trouvent sur un dispositif non mis en cache, les métadonnées seront intègres et les éventuelles pertes seront isolées à l'intérieur des blocs de données.

Le prochain cycle de rotation des instantanés garantit l'intégrité des données à l'intérieur des instantanés en cas de perte du cache :

  1. Pour chaque volume fin portant le nom , créons un instantané nommé .cached
  2. Fixons le seuil de migration à une valeur raisonnablement élevée : #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. Dans la boucle, vérifions le nombre de blocs sales dans le cache : #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' jusqu'à ce que nous ayons zéro. S'il n'y a pas de zéro pendant trop longtemps, il peut être créé temporairement en passant le cache en mode writethrough. Cependant, compte tenu des caractéristiques de vitesse de nos arrays SATA et NVMe SSD, ainsi que de leur endurance TBW, vous pourrez soit saisir le moment assez rapidement sans changer le mode de cache, soit votre matériel épuisera complètement ses ressources en quelques jours. En raison des limitations de ressources, le système ne peut en principe pas rester sous une charge d'écriture de 100 % en permanence. Nos NVMe SSD épuiseront complètement leurs ressources sous 100 % de charge d'écriture après 3 à 4 jours. Les SSD SATA durent environ deux fois plus longtemps. Donc, nous allons considérer que la majeure partie de la charge est orientée vers la lecture, et que pour l'écriture, nous avons des pics d'activité très élevés relativement brefs, combinés à une charge moyenne faible.
  4. Dès que nous avons capté (ou créé) un zéro, nous renommant <nom>.cached en <nom>.committed. L'ancien <nom>.committed est alors supprimé.
  5. Optionnellement, si le cache est rempli à 100 %, il peut être régénéré par un script, le vidant ainsi. Avec un cache semi-vide, le système fonctionne beaucoup plus rapidement en écriture.
  6. Nous établissons le seuil de migration à zéro : #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Cela interdirait temporairement la synchronisation du cache vers le support principal.
  7. Nous attendons qu'il y ait suffisamment de modifications accumulées dans le cache #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' ou que le minuteur se déclenche.
  8. Nous répétons à nouveau.

Pourquoi se compliquer la vie avec le seuil de migration… ?La réalité est que dans la pratique «l'écriture aléatoire» n'est pas vraiment aléatoire. Si nous écrivons quelque chose dans un secteur de 4 Ko, il y a de fortes chances que dans les quelques minutes suivantes, une écriture soit faite dans ce secteur ou dans l'un des secteurs voisins (+- 32 Ko).

En plaçant le seuil de migration à zéro, nous retardons la synchronisation de l'écriture sur le SSD SATA et agrégeons plusieurs modifications d'un bloc de 64 Ko dans le cache. Cela permet d'économiser considérablement les ressources du SSD SATA.

Et le code alors… ?Malheureusement, l'auteur se considère comme pas assez compétent en matière de développement de scripts bash car il est 100 % autodidacte et pratique un développement «driver par Google», il estime donc que le terrible code qui sort de ses mains ne devrait pas être utilisé par d'autres.

Je pense que des professionnels dans ce domaine pourront reproduire la logique décrite ci-dessus si nécessaire, et peut-être même la mettre en forme dans un service systemd, comme l'auteur a essayé de le faire.

Ce schéma simple de rotation des snapshots nous permettra non seulement de maintenir en permanence un snapshot entièrement synchronisé sur un SSD SATA, mais aussi d'utiliser l'outil thin_delta pour identifier les blocs modifiés depuis sa création, ce qui facilite grandement la localisation des dommages sur les volumes principaux et la récupération.

TRIM/DISCARD dans libvirt/KVM

Puisque le stockage de données sera utilisé pour KVM sous la gestion de libvirt, il serait souhaitable d'apprendre à nos VM non seulement à occuper de l'espace libre, mais aussi à libérer celui qui n'est plus nécessaire.

Cela se fait par l'émulation du support TRIM/DISCARD sur les disques virtuels. Pour cela, il faut changer le type de contrôleur en virtio-scsi et modifier le fichier XML.

#virsh edit vmname
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writethrough' io='threads' discard='unmap'/>
<source dev='/dev/images/vmname'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>


</disk>

<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>


</controller>

Les DISCARDs de systèmes d'exploitation invités sont correctement traités par LVM, et les blocs sont libérés correctement tant dans le cache que dans le pool thin. Dans notre cas, cela se produit principalement de manière différée, lors de la suppression d'un snapshot.

Sauvegarde BTRFS

Utilisez des scripts prêts à l'emploi avec la plus grande prudence et à vos propres risques. L'auteur a écrit ce code pour lui-même et uniquement pour lui-même. Je suis sûr que de nombreux utilisateurs expérimentés de Linux ont des solutions similaires, et il n'est pas nécessaire de copier celles des autres.

Créons un volume sur le périphérique de sauvegarde :

#lvcreate -L 256G --name backup backup

Formatez en BTRFS :

#mkfs.btrfs /dev/backup/backup

Créons des points de montage et montons les sous-volumes du FS :

#mkdir /backup
#mkdir /backup/btrfs
#mkdir /backup/btrfs/root
#mkdir /backup/btrfs/back
#ln -s /boot /backup/btrfs
# cat >>/etc/fstab << EOF

/dev/mapper/root-root /backup/btrfs/root btrfs defaults,space_cache,noatime,nodiratime 0 2
/dev/mapper/backup-backup /backup/btrfs/back btrfs defaults,space_cache,noatime,nodiratime 0 2
EOF
#mount -a
#update-initramfs -u
#update-grub

Créons des répertoires pour les sauvegardes :

#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot

Créons un répertoire pour les scripts de sauvegarde :

#mkdir /root/btrfs-backup

Copions le script :

Beaucoup de code bash inquiétant. Utilisez à vos risques et périls. Ne pas écrire de lettres de colère à l'auteur…#cat >/root/btrfs-backup/btrfs-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".@base"
PEND_SUFFIX=".@pend"
SNAP_SUFFIX=".@snap"
MOUNTS="/backup/btrfs/"
BACKUPS="/backup/btrfs/back/remote/"

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Attente du verrou..."
wait_lock || terminate "Échec de l'obtention du verrou. Sortie..."
echo "Verrou obtenu..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function backup()
{
SOURCE_PATH="$MOUNTS$1"
TARGET_PATH="$BACKUPS$1"
SOURCE_BASE_PATH="$MOUNTS$1$BASE_SUFFIX"
TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
TARGET_BASE_DIR="$(dirname $TARGET_BASE_PATH)"
SOURCE_PEND_PATH="$MOUNTS$1$PEND_SUFFIX"
TARGET_PEND_PATH="$BACKUPS$1$PEND_SUFFIX"
if [ -d "$SOURCE_BASE_PATH" ]
alors
echo "$SOURCE_BASE_PATH trouvé"
sinon
echo "$SOURCE_BASE_PATH Fichier introuvable, création d'un instantané de $SOURCE_PATH vers $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ]
alors
echo "$TARGET_BASE_PATH trouvé, hors synchronisation avec la source... suppression..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ]
alors
echo "$TARGET_BASE_PATH trouvé"
sinon
echo "$TARGET_BASE_PATH non trouvé. Synchronisation vers $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
if [ -d "$SOURCE_PEND_PATH" ]
alors
echo "$SOURCE_PEND_PATH trouvé, suppression..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
if [ -d "$TARGET_PEND_PATH" ]
alors
echo "$TARGET_PEND_PATH trouvé, suppression..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Envoi de $SOURCE_PEND_PATH vers $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolume snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolume delete -c $SOURCE_BASE_PATH
sync
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}

function list()
{
LIST_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
LIST_TARGET_BASE_DIR="$(dirname $LIST_TARGET_BASE_PATH)"
LIST_TARGET_BASE_NAME="$(basename -s .$BASE_SUFFIX $LIST_TARGET_BASE_PATH)"
find "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolume delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}

function removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")

}

(
COMMAND="$1"
décalage

case "$COMMAND" in
"--help")
echo "Aide"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1"
;;
"list")
list "$1"
;;
"remove")
wait_lock_or_terminate
remove "$1" "$2"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2"
;;
*)
echo "Aucun.."
;;
esac
) 98>$LOCK_FILE

EOF

Que fait-il au juste..?Contient un ensemble de commandes simples pour créer des instantanés BTRFS et les copier vers un autre système de fichiers via BTRFS send/receive.

Le premier lancement peut prendre un certain temps, car toutes les données seront copiées à l'origine. Les exécutions suivantes seront très rapides, car seules les modifications seront copiées.

Un autre script que nous allons ajouter au cron:

Encore un peu de code bash#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENTION="-60 jours"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF

Que fait-il..?Crée et synchronise des instantanés incrémentaux des volumes BTRFS énumérés sur la FS de sauvegarde. Ensuite, il supprime tous les instantanés créés il y a 60 jours. Après exécution, des instantanés datés des volumes énumérés apparaîtront dans les sous-répertoires /backup/btrfs/back/remote.

Nous allons donner les droits d'exécution au code:

#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh

Vérifions et ajoutons au cron:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup
#cat /var/log/syslog | grep btrfs-backup
#crontab -e
0 2 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup

Sauvegarde LVM thin

Créons un pool thin sur le dispositif de sauvegarde:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool

Installons ddrescue, car les scripts utiliseront cet outil:

#apt-get install gddrescue

Créons un répertoire pour les scripts :

#mkdir /root/lvm-thin-backup

Copions les scripts :

Beaucoup de bash à l'intérieur...#cat >/root/lvm-thin-backup/lvm-thin-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".base"
PEND_SUFFIX=".pend"
SNAP_SUFFIX=".snap"
BACKUPS="backup"
BACKUPS_POOL="thin-pool"

export LVM_SUPPRESS_FD_WARNINGS=1

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Attente du verrou..."
wait_lock || terminate "Échec de l'obtention du verrou. Sortie..."
echo "Verrou obtenu..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function read_thin_id {
lvs --rows --reportformat basic --quiet -othin_id "$1/$2" | awk '{print $2}'
}

function read_pool_lv {
lvs --rows --reportformat basic --quiet -opool_lv "$1/$2" | awk '{print $2}'
}

function read_lv_dm_path {
lvs --rows --reportformat basic --quiet -olv_dm_path "$1/$2" | awk '{print $2}'
}

function read_lv_active {
lvs --rows --reportformat basic --quiet -olv_active "$1/$2" | awk '{print $2}'
}

function read_lv_chunk_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -ochunk_size "$1/$2" | awk '{print $2}'
}

function read_lv_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -olv_size "$1/$2" | awk '{print $2}'
}

function activate_volume {
lvchange -ay -Ky "$1/$2"
}

function deactivate_volume {
lvchange -an "$1/$2"
}

function read_thin_metadata_snap {
dmsetup status "$1" | awk '{print $7}'
}

function thindiff()
{
DIFF_VG="$1"
DIFF_SOURCE="$2"
DIFF_TARGET="$3"
DIFF_SOURCE_POOL=$(read_pool_lv $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_POOL=$(read_pool_lv $DIFF_VG $DIFF_TARGET)

if [ "$DIFF_SOURCE_POOL" == "" ]
alors
(>&2 echo "Le LV source n'est pas thin.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ]
alors
(>&2 echo "Le LV cible n'est pas thin.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ]
alors
(>&2 echo "Les LVs source et cible appartiennent à des pools thin différents.")
exit 1
fi

DIFF_POOL_PATH=$(read_lv_dm_path $DIFF_VG $DIFF_SOURCE_POOL)
DIFF_SOURCE_ID=$(read_thin_id $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_ID=$(read_thin_id $DIFF_VG $DIFF_TARGET)
DIFF_POOL_PATH_TPOOL="$DIFF_POOL_PATH-tpool"
DIFF_POOL_PATH_TMETA="$DIFF_POOL_PATH"_tmeta
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" != "-" ]
alors
(>&2 echo "L'instantané des métadonnées du pool thin existe déjà. Supposant qu'il est obsolète. Libération de l'instantané des métadonnées dans 5 secondes.")
sleep 5
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
fi

dmsetup message $DIFF_POOL_PATH_TPOOL 0 reserve_metadata_snap
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" == "-" ]
alors
(>&2 echo "Échec de la création de l'instantané des métadonnées du pool thin.")
exit 1
fi

#We keep output in variable because metadata snapshot need to be released early.
DIFF_DATA=$(thin_delta -m$DIFF_POOL_METADATA_SNAP --snap1 $DIFF_SOURCE_ID --snap2 $DIFF_TARGET_ID $DIFF_POOL_PATH_TMETA)

dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap

echo $"$DIFF_DATA" | grep -E 'different|left_only|right_only' | sed 's/</"/g' | sed 's/ /"/g' | awk -F'"' '{print $6 "t" $8 "t" $11}' | sed 's/different/copy/g' | sed 's/left_only/copy/g' | sed 's/right_only/discard/g'

}

function thinsync()
{
SYNC_VG="$1"
SYNC_PEND="$2"
SYNC_BASE="$3"
SYNC_TARGET="$4"
SYNC_PEND_POOL=$(read_pool_lv $SYNC_VG $SYNC_PEND)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SYNC_VG $SYNC_PEND_POOL)
SYNC_PEND_PATH=$(read_lv_dm_path $SYNC_VG $SYNC_PEND)

activate_volume $SYNC_VG $SYNC_PEND

while read -r SYNC_ACTION SYNC_OFFSET SYNC_LENGTH ; do
SYNC_OFFSET_BYTES=$((SYNC_OFFSET * SYNC_BLOCK_SIZE))
SYNC_LENGTH_BYTES=$((SYNC_LENGTH * SYNC_BLOCK_SIZE))
if [ "$SYNC_ACTION" == "copy" ]
alors
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SYNC_PEND_PATH" "$SYNC_TARGET"
fi

if [ "$SYNC_ACTION" == "discard" ]
alors
blkdiscard -o $SYNC_OFFSET_BYTES -l $SYNC_LENGTH_BYTES "$SYNC_TARGET"
fi
done < <(thindiff "$SYNC_VG" "$SYNC_PEND" "$SYNC_BASE")
}

function discard_volume()
{
DISCARD_VG="$1"
DISCARD_LV="$2"
DISCARD_LV_PATH=$(read_lv_dm_path "$DISCARD_VG" "$DISCARD_LV")
if [ "$DISCARD_LV_PATH" != "" ]
alors
echo "$DISCARD_LV_PATH trouvé"
sinon
echo "$DISCARD_LV n'est pas trouvé dans $DISCARD_VG"
exit 1
fi
DISCARD_LV_POOL=$(read_pool_lv $DISCARD_VG $DISCARD_LV)
DISCARD_LV_SIZE=$(read_lv_size "$DISCARD_VG" "$DISCARD_LV")
lvremove -y --quiet "$DISCARD_LV_PATH" || exit 1
lvcreate --thin-pool "$DISCARD_LV_POOL" -V "$DISCARD_LV_SIZE"B --name "$DISCARD_LV" "$DISCARD_VG" || exit 1
}

function backup()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
SOURCE_PEND_LV="$SOURCE_LV$PEND_SUFFIX"
TARGET_PEND_LV="$TARGET_LV$PEND_SUFFIX"
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ]
alors
echo "$SOURCE_BASE_LV_PATH trouvé"
sinon
echo "Base source non trouvée, création d'un snapshot de $SOURCE_VG/$SOURCE_LV vers $SOURCE_VG/$SOURCE_BASE_LV"
lvcreate --quiet --snapshot --name "$SOURCE_BASE_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo "Abandon de $SOURCE_BASE_LV_PATH car nous devons initialiser."
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
sync
if [ "$TARGET_BASE_LV_PATH" != "" ]
alors
echo "$TARGET_BASE_LV_PATH trouvé en désynchronisation avec la source... suppression..."
lvremove -y --quiet $TARGET_BASE_LV_PATH || exit 1
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
sync
fi
fi
SOURCE_BASE_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$TARGET_BASE_LV_PATH" != "" ]
alors
echo "$TARGET_BASE_LV_PATH trouvé"
sinon
echo "$TARGET_VG/$TARGET_LV non trouvé. Création d'un volume vide."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Doit redémarrer. Abandon de la source à $SOURCE_BASE_LV_PATH"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
TARGET_BASE_POOL=$(read_pool_lv $TARGET_VG $TARGET_BASE_LV)
TARGET_BASE_CHUNK_SIZE=$(read_lv_chunk_size $TARGET_VG $TARGET_BASE_POOL)
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
echo "Abandon de la cible à $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ]
alors
echo "$SOURCE_PEND_LV_PATH trouvé, suppression..."
lvremove -y --quiet "$SOURCE_PEND_LV_PATH" || exit 1
sync
fi
lvcreate --quiet --snapshot --name "$SOURCE_PEND_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
sync
if [ "$TARGET_PEND_LV_PATH" != "" ]
alors
echo "$TARGET_PEND_LV_PATH trouvé, suppression..."
lvremove -y --quiet $TARGET_PEND_LV_PATH
sync
fi
lvcreate --quiet --snapshot --name "$TARGET_PEND_LV" "$TARGET_VG/$TARGET_BASE_LV" || exit 1
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
SOURCE_PEND_LV_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_PEND_LV")
lvresize -L "$SOURCE_PEND_LV_SIZE"B "$TARGET_PEND_LV_PATH"
activate_volume "$TARGET_VG" "$TARGET_PEND_LV"
echo "Synchronisation de $SOURCE_PEND_LV_PATH vers $TARGET_PEND_LV_PATH"
thinsync "$SOURCE_VG" "$SOURCE_PEND_LV" "$SOURCE_BASE_LV" "$TARGET_PEND_LV_PATH" || exit 1
sync

TARGET_DATE_SUFFIX=$(suffix)
lvcreate --quiet --snapshot --name "$TARGET_LV$TARGET_DATE_SUFFIX" "$TARGET_VG/$TARGET_PEND_LV" || exit 1
sync
lvremove --quiet -y "$SOURCE_BASE_LV_PATH" || exit 1
sync
lvremove --quiet -y "$TARGET_BASE_LV_PATH" || exit 1
sync
lvrename -y "$SOURCE_VG/$SOURCE_PEND_LV" "$SOURCE_BASE_LV" || exit 1
lvrename -y "$TARGET_VG/$TARGET_PEND_LV" "$TARGET_BASE_LV" || exit 1
sync
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function verify()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ]
alors
echo "$SOURCE_BASE_LV_PATH trouvé"
sinon
echo "$SOURCE_BASE_LV_PATH non trouvé"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
alors
echo "$TARGET_BASE_LV_PATH trouvé"
sinon
echo "$TARGET_BASE_LV_PATH non trouvé"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparaison de "$SOURCE_BASE_LV_PATH" avec "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Fait...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function resync()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ]
alors
echo "$SOURCE_BASE_LV_PATH trouvé"
sinon
echo "$SOURCE_BASE_LV_PATH non trouvé"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ]
alors
echo "$TARGET_BASE_LV_PATH trouvé"
sinon
echo "$TARGET_BASE_LV_PATH non trouvé"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)

echo Synchronisation de "$SOURCE_BASE_LV_PATH" vers "$TARGET_BASE_LV_PATH"

CMP_OFFSET=0
while [[ "$CMP_OFFSET" != "" ]] ; do
CMP_MISMATCH=$(cmp -i "$CMP_OFFSET" "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH" | grep differ | awk '{print $5}' | sed 's\/\,//g' )
if [[ "$CMP_MISMATCH" != "" ]] ; then
CMP_OFFSET=$(( CMP_MISMATCH + CMP_OFFSET ))
SYNC_OFFSET_BYTES=$(( ( CMP_OFFSET / SYNC_BLOCK_SIZE ) * SYNC_BLOCK_SIZE ))
SYNC_LENGTH_BYTES=$(( SYNC_BLOCK_SIZE ))
echo "Synchronisation de $SYNC_LENGTH_BYTES octets à $SYNC_OFFSET_BYTES de $SOURCE_BASE_LV_PATH vers $TARGET_BASE_LV_PATH"
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
sinon
CMP_OFFSET=""
fi
done
echo Fait...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function list()
{
LIST_SOURCE_VG="$1"
LIST_SOURCE_LV="$2"
LIST_TARGET_VG="$BACKUPS"
LIST_TARGET_LV="$LIST_SOURCE_VG-$LIST_SOURCE_LV"
LIST_TARGET_BASE_LV="$LIST_TARGET_LV$SNAP_SUFFIX"
lvs -olv_name | grep "$LIST_TARGET_BASE_LV.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}

function removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
done < <(list "$1" "$2" | grep "$FILTER")

}

(
COMMAND="$1"
décalage

case "$COMMAND" in
"--help")
echo "Aide"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
wait_lock_or_terminate
verify "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
remove "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "Aucun.."
;;
esac
) 98>$LOCK_FILE

EOF

Que fait-il…?Contient un ensemble de commandes pour manipuler les snapshots fins et synchroniser la différence entre deux snapshots fins, obtenue via thin_delta, vers un autre périphérique de bloc en utilisant ddrescue et blkdiscard.

Un autre script à ajouter au cron :

Encore un peu de bash#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 days"

$BACKUP_SCRIPT backup images linux-dev
$BACKUP_SCRIPT backup images win8
$BACKUP_SCRIPT backup images win8-data
#etc

$BACKUP_SCRIPT removeall images linux-dev "$RETENTION"
$BACKUP_SCRIPT removeall images win8 "$RETENTION"
$BACKUP_SCRIPT removeall images win8-data "$RETENTION"
#etc

EOF

Que fait-il…?Utilise le script précédent pour créer et synchroniser des sauvegardes des volumes fins énumérés. Le script laissera des snapshots inactifs des volumes mentionnés, nécessaires pour suivre les modifications depuis la dernière synchronisation.

Ce script doit être modifié pour indiquer la liste des volumes fins pour lesquels des sauvegardes sont nécessaires. Les noms donnés sont juste des exemples. Si vous le souhaitez, vous pouvez écrire un script qui synchronisera tous les volumes.

Attribuons les droits :

#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh

Vérifions et ajoutons au cron:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup
#cat /var/log/syslog | grep lvm-thin-backup
#crontab -e
0 3 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup

Le premier lancement sera long, car les volumes fins seront entièrement synchronisés en copiant tout l'espace utilisé. Grâce aux métadonnées LVM thin, nous savons quels blocs sont réellement utilisés, donc seules les blocs effectivement utilisés des volumes fins seront copiés.

Les exécutions suivantes copieront les données de manière incrémentielle grâce au suivi des modifications via les métadonnées LVM thin.

durant le « démarrage » du conteneur, il montre que l'environnement d'exécution du conteneur dépend fortement du montage lié (Seule le début d'une longue sortie est affiché).

#time /root/btrfs-backup/cron-daily.sh
réel 0m2,967s
utilisateur 0m0,225s
système 0m0,353s

#time /root/lvm-thin-backup/cron-daily.sh
réel 1m2,710s
utilisateur 0m12,721s
système 0m6,671s

#ls -al /backup/btrfs/back/remote/*
/backup/btrfs/back/remote/boot:
total 0
drwxr-xr-x 1 root root 1260 mars 26 09:11 .
drwxr-xr-x 1 root root 16 mars 6 09:30 ..
drwxr-xr-x 1 root root 322 mars 26 02:00 .@base
drwxr-xr-x 1 root root 516 mars 6 09:39 .@snap.2020-03-06-09-39-37
drwxr-xr-x 1 root root 516 mars 6 09:39 .@snap.2020-03-06-09-39-57
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 mars 26 09:11 .
drwxr-xr-x 1 root root 16 mars 6 09:30 ..
drwxr-xr-x 1 root root 240 mars 26 09:11 @.@base
drwxr-xr-x 1 root root 22 mars 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 mars 6 09:39 @home.@snap.2020-03-06-09-39-35
drwxr-xr-x 1 root root 22 mars 6 09:39 @home.@snap.2020-03-06-09-39-57
...
drwxr-xr-x 1 root root 240 mars 6 09:39 @.@snap.2020-03-06-09-39-26
drwxr-xr-x 1 root root 240 mars 6 09:39 @.@snap.2020-03-06-09-39-56
...

#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV Taille
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV Taille
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t

Quel rapport avec les poupées russes ?

Probablement le fait que les volumes logiques LVM LV peuvent être des volumes physiques LVM PV pour d'autres VG. LVM peut être récursif, comme des poupées russes. Cela donne à LVM une flexibilité exceptionnelle.

P.S.

Dans le prochain article, nous essaierons d'utiliser plusieurs systèmes de stockage mobiles similaires / KVM comme base pour créer un cluster de stockage / vm géo-réparti avec redondance sur plusieurs continents via des ordinateurs de bureau domestiques, Internet domestique et des réseaux P2P.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster