Expérience avec l'intégrité des systèmes de fichiers
Introduction
Pendant longtemps, je me suis contenté de la très grande fiabilité d'ext4, et de sa très grande performance. Je me bornais à utiliser des solutions de comparaison de données régulières (md5sum, integrity, dump trié de base de données, etc), aussi des sauvegardes hors site et à compter sur les CRC lors du transport, sur l'ECC des blocs de disques (voire aussi de la RAM) et le RAID.
Toutefois, il y a des inconvénients
- nécessité d'une très longue procédure de qualification du nouveau matériel (RAM, disques, etc)
- détection tardive des problèmes (jamais arrivé pour moi)
- pour certains types d'applications (bases de données non conçues pour ou variant trop, fichiers de dump spécifiques), pas très pratique de détecter des corruptions, ou de corriger (un grand journal des transactions à rejouer peut être utilisé)
Donc, je me suis orienté vers les périphériques de type bloc, voire les filesystems qui proposent de la détection de corruption.
Critères
Voici mes critères:
- respecter le plus possible la philosophie UNIX, soit un outil pour une tâche
- respecter un modèle en couche (layering), plus facile à gérer et débugger, en général
- ne pas dégrader (trop) la performance
- ne pas nécessiter d'outils non libres
- longévité
Expériences
J'ai fait quelques tests moi-même, et j'ai également mis des références (voir section).
Les solutions intégrées: zfs, btrfs
Ces deux solutions pêchent par leur absence de layering et leur volonté de réinventer la roue (volume manager intégré notamment).
zfs pêche particulièrement par sa complexité, sa performance très dégradée et son usage de RAM et de CPU complètement incroyable. Il y a aussi des risques sur la longévité vu qu'il s'agit d'un projet tiers. Je trouve intéressant le scan mensuel automatique qui mentionne s'il y a eu des différences et sur quel périphérique. Quelques soucis si l'on utilise les noms usuels de périphériques bruts (/dev/sdX): zfs semble facilement se mélanger les pinceaux.
btrfs n'a apparemment aucun problème de performance, mais il partage en partie les problèmes de complexité de zfs. Il est encore en développement. Un avantage: sa possibilité de convertir "on-line" des fs ext4, très appréciable!
Il ne me semble pas, mais j'ai peut-être tort, qu'on puisse facilement intégrer cela à du chiffrement.
Les solutions selon le modèle UNIX (couches)
ext4 peut faire de la détection de corruption de méta-données, mais pas encore de donnée.
On peut donc utiliser une approche en couche en créant un système de fichiers ext4 dans:
- un volume logique (LVM) avec option dm-integrity (et dm-raid en option): ici LVM en fait beaucoup
- un système complètement en couches avec volumes logiques, intégrité et optionnellement RAID (md)
Il y a une grosse différence entre les deux approches, en plus que la 1ère n'est pas complètement en couches, LVM essayant d'en faire trop à mon avis.
Dans les deux cas, la création prend un certain temps (si l'on souhaite pré-créer les méta-données d'intégrité, ce qui est très fortement recommandé).
Les deux ont des choix identiques de modes d'intégrité (SHA ou CRC, en fonction de ce qu'on veut protéger: pirate ou accidents -- SHA est surtout utile en cas de combinaison avec dm-crypt mais prendra plus de CPU).
|
LVM+dm-integrity+dm-raid |
couches |
configuration |
tout se fait avec une seule commande LVM, il suffit d'avoir deux périphériques bruts de taille identique |
on ajoute une couche d'intégrité à chaque périphérique brut, puis on crée un array RAID (p.ex. RAID1) avec mdadm, puis on donne cet array comme physical volume à LVM, puis on fait ce qu'on veut avec ce LVM |
complexité |
clairement plus complexe, difficile d'avoir d'une vue d'ensemble; seul avantage: l'indicateur lvs -a de progression de la création de l'intégrité |
KISS |
intégrité |
vu la complexité, difficile de voir exactement ce qui se passe |
j'ai pu assez facilement créer des erreurs à la main sur les périphériques bruts: elles sont détectées par la couche integrity et le RAID1 va automatiquement lire de l'autre integrity et réécrire le(s) bloc(s) sans souci -- un log kernel est créé et logcheck le montre |
performance |
très bonne |
très bonne |
snapshots |
IMPOSSIBLES |
sans souci |
boot |
IMPOSSIBLE si /boot pas séparé et bricolage initrd nécessaire: dès lors que /boot est dans un VG qui contient du dm-integrity, c'est fini! (grub2 plante) |
il faut modifier l'initrd pour activer les integrity-devices, ensuite tout fonctionnement automatiquement: RAID, LVM, etc; /boot doit être séparé également |
commentaires |
on peut faire des checkarray, mais c'est plus compliqué |
ne pas oublier de mettre le swap sur un autre array RAID, de manière à simplifier le checkarray automatique mensuel; on peut aussi lire chaque integrity device séparément pour détecter des erreurs mais alors elles ne seront pas corrigées |
Pour mon utilisation, après plusieurs tests j'ai pris l'approche en couche en RAID1. Cela coche toutes les cases, c'est une approche en couche, c'est facile à tester, valider, débugger (chaque couche ses outils), la performance est excellente (en mode bitmap, je n'ai pas testé le mode journal qui est moins performant, mais en théorie pourrait mieux gérer les coupures de courant intempestives, mais qui est moins adapté pour NVMe semble-t-il).
Les options dm-integrity que je n'ai pas utilisées:
- avec le mode dm-integrity CRC, on pourrait en théorie avec des disques compatibles stocker les 4 octets de CRC par secteur dans les 8 octets de stockage étendu SCSI T10, directement sur le disque, sans que la capacité du disque ne se réduise (méta-données dm-integrity) -- autre avantage: la détection est end-to-end
- avec des disques modernes, on peut les reformatter (bas niveau) pour stocker les SHA: en théorie cela marche même avec des NVMe (mais ça dure des jours)
- le mode journal (voir ci-dessus)
- combinaison avec chiffrement dm-crypt (je l'utilise toutefois pour certaines sauvegardes sur disque-dur amenées hors site)
Au niveau performance, on obtient sans souci quelque chose de proche de la performance native du disque, avec une charge CPU modérée.
Pour la petite histoire, un montage glusterfs par réseau (juste un switch intermédiaire) d'un fs zfs donnait environ 4 MByte/s, alors qu'avec ce qui précéde -- l'approche en couche -- on obtient 80 MByte/s (Ethernet 1 Gbit/s) ... et le client c'est un apu2, donc je suis sûr qu'on atteindrait facilement les 100 Mbyte/s dans un cas "usuel", soit la saturation du débit Ethernet. A voir ce qui se passe en 10GE. En local on approche le GByte/s.
Enfin, si l'on trouve la performance du RAID1, on pourrait faire un RAID10 (p.ex. à 4 disques). Dans mon cas, vu qu'il s'agit de NVMe, la performance est tellement incroyable de base que je n'ai pas trouvé l'intérêt du RAID10 (2 NVMe de 2 TB ...). Si le coût est prohibitif, un RAID5, voire RAID60 pourrait être intéressant et là on serait similaire aux modes les plus performants de zfs, probablement sans autant de perte de performance.
Exemple de configuration en couche (ext4 sur LVM sur (md) RAID1 sur 2 integrities, chacun sur 1 périphérique brut
Ici, on migre d'un système existant, donc on crée d'abord des arrays RAID1 dégradés, et une fois les données copiées on intègre l'ancien disque comme miroir.
apt-get install cryptsetup-bin mdadm lvm2
# partitionnement
# nvme0n1
# p1 boot
# p2 UEFI
# p3 big (futur LVM vg sur R1 sur integrity)
# p4 small (futur swap sur R1 sur integrity)
integritysetup format --integrity crc32 --integrity-bitmap-mode /dev/nvme0n1p3
integritysetup format --integrity crc32 --integrity-bitmap-mode /dev/nvme0n1p4
# création des integrity devices (2 arrays R1 en mode dégradé pour le moment)
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme0n1p4 integ_vg1_0
mdadm --create --verbose --level=1 --raid-devices=2 /dev/md0 /dev/mapper/integ_vg1_0 missing
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme0n1p3 integ_swap_0
mdadm --create --verbose --level=1 --raid-devices=2 /dev/md1 /dev/mapper/integ_swap_0 missing
mkswap /dev/md1 # UUID in /etc/fstab
pvcreate /dev/md0
vgcreate vg0 /dev/md0
lvcreate -L20G -n root /dev/vg0
mkdir /mnt/root
mount /dev/vg0/root /mnt/root
lvcreate -L500G -n docker /dev/vg0
lvcreate -L100G -n scratch /dev/vg0
for i in root docker scratch; do mkfs.ext4 /dev/vg0/$i && tune2fs -c 0 -i 0 /dev/vg0/$i; mkdir /mnt/$i; mount /dev/vg0/$i /mnt/$i; done
# boot preparation
echo dm_integrity >> /etc/initramfs-tools/modules
# RT#1434
cat > /etc/initramfs-tools/hooks/integrity <<'EOF'
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
# Begin real processing below this line
force_load dm_integrity
copy_exec /usr/sbin/integritysetup /usr/sbin
#copy_file text /etc/udev/rules.d/99-integrity.rules
EOF
chmod a+rx /etc/initramfs-tools/hooks/integrity
# another way would be through standard naming and udev.d
cat > /etc/initramfs-tools/scripts/local-premount/integrity <<'EOF'
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /scripts/functions
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme0n1p4 integ_vg1_0
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme0n1p3 integ_swap_0
EOF
chmod a+rx /etc/initramfs-tools/scripts/local-premount/integrity
update-initramfs -u -k all
# put the running system in freeze mode (stop all services that may
# change files: cron, databases, web servers, logins, etc)
cp -ax /. /mnt/root
cp -a /var/lib/docker/. /mnt/docker
cp -a /scratch/. /mnt/scratch
# clean buffers (probably 1x RAM is ok)
dd if=/dev/mapper/vg0-scratch of=/dev/null
# compare data
find / -mount -type f -print0 | xargs -0 md5sum | (cd /mnt/root && md5sum -c | egrep -v ': OK$')
find /var/lib/docker -mount -type f -print0 | xargs -0 md5sum | (cd /mnt/docker && md5sum -c | egrep -v ': OK$')
find /scratch -mount -type f -print0 | xargs -0 md5sum | (cd /mnt/scratch && md5sum -c | egrep -v ': OK$')
# temp copy EFI
dd if=/dev/nvme0n1p2 of=nvme1n1p1
# replace to vg0
vi /mnt/root/etc/fstab
# reboot, edit with root on /dev/vg0/root and see what happens
echo 'RESUME=/dev/md1' > /etc/initramfs-tools/conf.d/resume
update-initramfs -u -k all
# a lot of warnings about vg1
update-grub
grub-install /dev/nvme1n1
grub-install /dev/nvme0n1
reboot # ok
# repartition nvme1n1
# copy /boot and /boot/efi in case
for i in 1 2; do dd if=/dev/nvme0n1p$i of=/dev/nvme1n1p$i; done
# add to both R1 array
integritysetup format --integrity crc32 --integrity-bitmap-mode /dev/nvme1n1p3
integritysetup format --integrity crc32 --integrity-bitmap-mode /dev/nvme1n1p4
cat >> /etc/initramfs-tools/scripts/local-premount/integrity <<'EOF'
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme1n1p3 integ_swap_1
integritysetup --integrity crc32 --integrity-bitmap-mode --allow-discards open /dev/nvme1n1p4 integ_vg1_1
EOF
# and run the above two commands
mdadm /dev/md0 --add /dev/mapper/integrity_dev_1
watch cat /proc/mdstat
mdadm /dev/md0 --add /dev/mapper/integ_vg1_1
update-initramfs -u -k all
# less warnings, but still some
update-grub
# copy boot and UEFI to second disk
for i in 1 2; do dd if=/dev/nvme0n1p$i of=/dev/nvme1n1p$i; done
reboot
watch cat /proc/mdstat
# NOTES
# BUGS
# - the above for i in 1 2 might be required at each kernel upgrade (noted in /etc/motd)
# TODO
Conclusion
Il est possible d'ajouter une couche de détection de corruption, supplémentaires aux ECC des disques, pour détecter des corruptions silencieuses après-coup, voire corriger les problèmes en combinant avec du RAID > 0.
La performance reste excellente avec les SSD et se dégrade légèrement avec les HDD.
C'est bien plus simple que de gérer des hachages ou CRC soi-même (md5sum, integrit, etc)
Il ne reste plus qu'à relire périodiquement l'ensemble des données de tous les disques (checkarray en md RAID): cela aura l'avantage avec les disques (HDD ou SDD) d'activer l'algorithme de détection/correction, qui va
réécrire les secteurs en problème (silencieusement en général, lorsque la correction n'est pas possible, c'est le RAID qui réécrira tant qu'il reste une copie valide).
Enfin, ne pas oublier de tester la qualité des disques au fur et à mesure de l'exploitation pour prévoir les soucis:
- temps d'accès (voir statistiques munin par exemple)
- débit en lecture (idem et aussi hdparm -t)
- évolution de la température (18-40C)
- outils SMART qui peuvent parfois détecter des problèmes AVANT qu'ils ne surviennent
Références
- RT#1459,1434,1435,1468 (interne)
--
MarcSCHAEFER - 05 Oct 2024