Petite matinée de ménage sur le serveur de la maison (un Rocky Linux 10 sur une vieille carte mère ASRock). Au programme : une partition /boot devenue trop petite, des disques qui dormaient dans la machine et qu’il était temps de mettre en miroir, et des alertes Prometheus pour ne plus découvrir un disque mort par hasard. Retour d’expérience, commandes comprises.

Le problème : un /boot de 1 Go qui ne suffit plus

L’installeur de Rocky crée par défaut un /boot de 1 Go. Ça paraissait large… jusqu’à ce que je regarde ce qu’il contient vraiment :

$ ls -la --block-size=M /boot
152M initramfs-6.12.0-211.67.1.el10_2.x86_64.img
 39M initramfs-6.12.0-211.67.1.el10_2.x86_64kdump.img
 16M vmlinuz-6.12.0-211.67.1.el10_2.x86_64
159M initramfs-0-rescue-….img
 16M vmlinuz-0-rescue-…

Environ 217 Mo par noyau, plus 175 Mo pour l’image de secours. Avec installonly_limit=3 dans dnf.conf, on arrive à ~825 Mo sur 960. Et le piège : pendant une mise à jour, dnf installe le nouveau noyau avant de supprimer le plus ancien. Il y a donc brièvement quatre noyaux, et ça ne passe plus.

Pourquoi un initramfs de 152 Mo ? lsinitrd donne la réponse : une centaine de Mo de firmwares NVIDIA (GSP) tirés par le pilote nouveau, parce qu’il y a une GeForce GTX 1060 dans la machine. Je garde nouveau, donc la solution, c’est un /boot plus grand.

$ lsinitrd /boot/initramfs-$(uname -r).img | sort -k5 -n | tail -3
… 13371848 usr/lib/firmware/nvidia/tu102/gsp/gsp-570.144.bin.xz
… 25639648 usr/lib/firmware/nvidia/ga102/gsp/gsp-535.113.01.bin.xz
… 51324396 usr/lib/firmware/nvidia/ga102/gsp/gsp-570.144.bin.xz

Pourquoi ne pas simplement agrandir la partition ?

Parce que la partition qui suit /boot est le volume physique LVM du système, collé juste derrière. Et XFS ne sait pas rétrécir. Agrandir sur place voudrait dire déplacer tout le LVM (pvmove vers une nouvelle partition, supprimer l’ancienne, agrandir /boot dans le trou). Faisable à chaud, mais c’est beaucoup de manipulations sur le disque système d’une machine qui venait justement de rester plantée au redémarrage deux jours plus tôt.

Il restait plus de 300 Go libres en fin de disque. Plan plus simple : créer une nouvelle partition /boot de 2 Go dans cet espace libre, y copier l’ancien /boot, et réinstaller GRUB pour qu’il la prenne en compte. L’ancienne partition reste intacte et sert de filet de sécurité.

Migrer /boot vers une nouvelle partition (BIOS + GPT)

Le point important, c’est le mode de démarrage. Ici c’est du BIOS « legacy » sur une table GPT : GRUB loge son étage principal (core.img) dans la petite partition bios_grub de 1 Mo, et ce core.img contient en dur l’emplacement de /boot/grub2. Si on déplace /boot sans relancer grub2-install, GRUB continue de lire l’ancienne partition. En UEFI, ce serait le grub.cfg de l’ESP qu’il faudrait mettre à jour à la place.

$ parted -s /dev/sde unit MiB print free
Number  Start    End        Size       File system  Name  Flags
 1      1.00MiB  2.00MiB    1.00MiB                       bios_grub
 2      2.00MiB  1026MiB    1024MiB    xfs                bls_boot
 3      1026MiB  160406MiB  159380MiB                     lvm
        160406MiB  476940MiB  316534MiB  Free Space

À faire avec un accès à la console (écran/clavier ou IPMI), au cas où le premier redémarrage se passe mal.

1. Créer et formater la nouvelle partition

parted -s /dev/sde mkpart boot2 xfs 160406MiB 162454MiB   # 2 GiB -> sde4
mkfs.xfs -L boot2 /dev/sde4

2. Copier le contenu de /boot

mkdir -p /mnt/newboot
mount /dev/sde4 /mnt/newboot
cp -a /boot/. /mnt/newboot/      # -a conserve droits, liens et contextes SELinux
restorecon -R /mnt/newboot

3. Pointer /etc/fstab sur la nouvelle partition

cp -a /etc/fstab /etc/fstab.bak
blkid -s UUID -o value /dev/sde4          # nouvel UUID
# remplacer l'UUID de la ligne /boot par celui-ci, puis :
systemctl daemon-reload
umount /mnt/newboot
umount /boot && mount /boot
findmnt /boot                             # doit afficher /dev/sde4

4. Réinstaller GRUB et régénérer sa configuration

grub2-install /dev/sde                    # core.img pointe maintenant vers sde4
grub2-mkconfig -o /boot/grub2/grub.cfg

Les entrées de démarrage BLS (/boot/loader/entries/*.conf) donnent des chemins relatifs à la partition /boot. Elles fonctionnent donc telles quelles, et grubenv (le noyau par défaut) a été copié avec le reste.

5. Redémarrer et vérifier

reboot
findmnt /boot            # /dev/sde4
df -h /boot              # ~2 Go
grubby --default-kernel  # toujours le bon noyau

Retour arrière : l’ancienne partition sde2 n’a pas été touchée. Si GRUB tombe sur son invite de secours, on peut la réutiliser à la main :

grub rescue> set prefix=(hd0,gpt2)/grub2
grub rescue> set root=(hd0,gpt2)
grub rescue> insmod normal
grub rescue> normal

Ensuite, on remet l’ancien UUID dans /etc/fstab et on relance grub2-install /dev/sde. Une fois la nouvelle partition validée sur plusieurs redémarrages et mises à jour du noyau, l’ancienne n’est plus qu’un giga perdu, ce qui n’a rien de grave.

Au passage : mettre /data en miroir… quand il manque 4 Mo

La machine avait aussi des disques qui ne servaient à rien. J’en profite pour mettre en miroir /data (931 Go, bases de données, dépôts, sauvegardes) avec LVM RAID1. Sur le papier, c’est une seule commande, sans interruption :

lvconvert --type raid1 -m1 data/lvol0 /dev/sdd

En pratique, elle échoue. Un LV RAID1 a besoin, sur chaque disque, d’une petite zone de métadonnées (rmeta, une extent de 4 Mo) en plus de la copie des données. Or le LV d’origine occupait toutes les extents du premier disque (238 467 sur 238 467), et le second disque est exactement de la même taille. Il manque donc 4 Mo de chaque côté. Comme XFS ne rétrécit pas, impossible d’en libérer.

La solution consiste à faire le chemin dans l’autre sens :

  • créer sur le nouveau disque un LV une extent plus petit, le formater, et y copier /data à chaud (rsync -aHAXS --numeric-ids, qui conserve liens durs, ACL, xattrs/SELinux et fichiers creux) ;
  • arrêter les services qui utilisent /data (MariaDB, PostgreSQL, Forgejo…), faire un dernier rsync --delete, renommer les LV pour que le nouveau prenne le nom de l’ancien (le fstab n’a pas besoin de changer), puis redémarrer les services. Comptez un quart d’heure d’interruption ;
  • garder l’ancien LV un jour comme filet de sécurité, puis le supprimer et lancer lvconvert --type raid1 -m1 vers l’ancien disque, qui a maintenant la place pour sa zone rmeta. La synchronisation se fait en arrière-plan.

Leçon retenue : quand on crée un LV qu’on voudra peut-être mettre en miroir un jour, laisser quelques extents libres (-l 99%FREE plutôt que 100%FREE) évite tout ce détour.

Pour deux autres vieux disques identiques, sans données, c’est beaucoup plus simple. On crée directement un volume miroir et LVM réserve lui-même la place des métadonnées :

vgcreate bulk /dev/sda /dev/sdb
lvcreate --type raid1 -m1 -l 100%FREE -n lvol0 bulk
mkfs.xfs /dev/bulk/lvol0
# dans /etc/fstab, nofail : un vieux disque qui lâche ne doit pas bloquer le démarrage
/dev/bulk/lvol0  /bulk  xfs  defaults,nofail  0 0

Surveiller les miroirs avec Prometheus

Un miroir dont personne ne remarque qu’il a perdu un disque n’apporte pas grand-chose. Par défaut, LVM se contente d’écrire un avertissement dans les journaux, et node_exporter ne sait rien du RAID LVM (son collecteur mdadm ne couvre que md). J’ai donc ajouté un petit script, lancé toutes les deux minutes par un timer systemd, qui écrit l’état des LV RAID dans le répertoire du collecteur textfile de node_exporter (--collector.textfile.directory) :

lvs --noheadings --nosuffix --separator ';' \
    -o vg_name,lv_name,lv_health_status,raid_sync_action,copy_percent,raid_mismatch_count \
    --select 'segtype=~raid'

On obtient des métriques du genre lvm_raid_healthy (0 dès que lv_health_status n’est plus vide, par exemple partial quand un disque manque), lvm_raid_sync_percent et lvm_raid_mismatch_count. Le fichier est écrit dans un fichier temporaire puis renommé avec mv, pour qu’un scrape ne tombe jamais sur un fichier à moitié écrit. Quatre alertes s’appuient dessus :

- alert: LvmRaidDegraded        # un disque manque ou lâche
  expr: lvm_raid_healthy == 0
  for: 5m
- alert: LvmRaidNotInSync       # resynchronisation qui ne finit pas
  expr: lvm_raid_sync_percent < 100
  for: 12h
- alert: LvmRaidMismatches      # le dernier scrub a trouvé des différences
  expr: lvm_raid_mismatch_count > 0
- alert: LvmRaidMetricsStale    # le script lui-même ne tourne plus
  expr: time() - lvm_raid_textfile_last_run_seconds > 900

Côté matériel, des disques WD de 1, 2 et 3 To attendent sur l’étagère. Le jour où une alerte se déclenche : on remplace le disque, puis pvcreate, vgextend et lvconvert --repair, et LVM reconstruit le miroir à chaud.

En résumé

  • Un /boot de 1 Go, c’est juste dès qu’un pilote embarque de gros firmwares dans l’initramfs. Vérifiez avec lsinitrd.
  • Déplacer /boot vers une nouvelle partition est plus simple et moins risqué que d’agrandir l’existante. En BIOS/GPT, il ne faut pas oublier grub2-install.
  • LVM RAID1 a besoin d’une extent libre par disque pour ses métadonnées. Un LV en 100%FREE sur deux disques identiques ne peut pas être converti sur place.
  • Un miroir sans alerte, c’est une panne qui attend son heure. Le collecteur textfile de node_exporter se prête très bien à ce genre de petit ajout.