Date Catégorie posts Étiquettes network

Le contexte

Nous avons un vieil hyperviseur Virtualbox qui héberge quelques VM assez sensibles. Notamment le site web qualité l'ISIMA, nécessaire à la certification ISO9001 de l'école.

Premier souci : le matériel physique n'est plus sous maintenance et donne des signes de faiblesse. Il faut donc migrer le site sur un autre hyperviseur.

Second souci : Le site passqualite est basé sur un framework... comment dire ... on va dire complexe : plone.

Comme la migration de ce site est un peu urgente, et comme toucher à plone exigerait beaucoup d'efforts pour être porté sur une VM récente nous avons donc opté pour le déménagement pur et dur de la VM.

Ce qui était censé être simple et rapide.

Raté.

Le déménagement

La plateforme Galactica, basée sur OpenStack, est donc naturellement désignée pour acceuillir la VM com.isima.fr puisqu'il s'agit de notre infrastructure moderne de virtualisation.

Normalement, une VM se déménage facilement : il suffit de copier le disque dur virtuel et de le redémarrer ailleurs. Mais au reboot voici ce que j'ai eu :

Kernel Panic

Ouch.

Kernel panic.

La VM ne boot pas.

Mais comment boot Linux ?

Un petit rappel s'impose ici. Sur une machine qui utilise un BIOS, Linux boot grosso modo de la façon suivante :

Linux Boot Process

Source : IBM Inside the Linux boot process [1]

  • Le BIOS charge et exécute le code inclus sur le MBR (Master Boot Record) du boot device qui est configuré.
  • Le MBR contient le code du bootloader : ici GRUB. Il a pour objectif de charger le kernel en mémoire, ainsi qu'un initial RAM disk (initrd) optionnel. [2]
  • GRUB passe le contrôle au Kernel : le hardware est énuméré, la mémoire initialisée etc...
  • L'initrd sert de filesystem temporaire et permet ainsi au kernel de booter avec un minimum de drivers. L'initrd contient entre autres choses un mini shell (busybox par exemple) et des drivers supplémentaires fournis sous forme de modules. Dès que le kernel a booté, le root filesystem est pivoté vers l'initrd et le script /init est exécuté.
  • A la fin de ce script, le point de montage / est de nouveau pivoté vers le point de montage définitif qui est défini en argument du kernel lorsque GRUB est lancé : ici il s'agit de l'argument root=/dev/VolGroup00/LogVol00.

Analyse

Le gestionnaire de boot (GRUB) démarre correctement puisque le menu initial s'est affiché, le kernel démarre, le script /init de l'initrd est exécuté : on le voit grâce à la ligne RedHat Nash. Cela veut dire Red Hat Nano Shell qui se trouve dans l'initrd, mais après impossible de monter le root filesystem. Ce qui signifie que le kernel ne peut pas accéder à la partition racine. Mais à quel moment exact ce processus plante ? Et pourquoi ?

Il va falloir décortiquer le script d'init de la VM, et pour se faire nous allons décompresser l'initrd de cette VM. Mais comment y accéder alors que nous n'avons pas pu la booter ?

OpenStack permet de monter le disque d'une VM sur une autre VM de secours afin d'inspecter son disque. La syntaxe est la suivante :

nova rescue --image <ID image de secours> <ID de l'image à sauver>

J'ai donc besoin de trouver les identifiants des images en question pour pouvoir lancer le sauvetage :

(venv) fred@MacdeFrederic:~ > openstack image list --name Rescue-Disk
+--------------------------------------+-------------+--------+
| ID                                   | Name        | Status |
+--------------------------------------+-------------+--------+
| b6269aa1-fa1a-4653-b9ec-6a5a58bf70b1 | Rescue-Disk | active |
+--------------------------------------+-------------+--------+

L'ID de l'image à sauver est :

(venv) fred@MacdeFrederic:~ > openstack server list --name com
+--------------------------------------+------+--------+-----------------------------------------------------+-------+----------+
| ID                                   | Name | Status | Networks                                            | Image | Flavor   |
+--------------------------------------+------+--------+-----------------------------------------------------+-------+----------+
| e1e0ed48-268d-4e41-a0d2-9bade79d02f2 | com  | ACTIVE | dmz1-net=x.x.x.x; net-vers-isima=y.y.y.y            |       | q1.small |
+--------------------------------------+------+--------+-----------------------------------------------------+-------+----------+

Lancement du sauvetage :

(venv) fred@MacdeFrederic:~ > nova rescue --image b6269aa1-fa1a-4653-b9ec-6a5a58bf70b1 e1e0ed48-268d-4e41-a0d2-9bade79d02f2
+-----------+--------------+
| Property  | Value        |
+-----------+--------------+
| adminPass | sx8Wmuz4gKag |
+-----------+--------------+

Et maintenant on se connecte en SSH sur la VM rescue. Le disque /dev/vdb est la VM com.isima.fr

ubuntu@com:~$ sudo lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda    253:0    0   3G  0 disk
└─vda1 253:1    0   3G  0 part /
vdb    253:16   0  80G  0 disk
└─vdb1 253:17   0  80G  0 part

Chroot

On pourrait accéder au disque simplement en le montant, mais dans notre cas, je sens que je vais avoir besoin d'un chroot :-) Le chroot est une opération qui change la racine apparente du filesystem pour le processus courant ainsi que tous ses fils.

Un processus qui s'exécute dans un tel environnement ne peut pas accéder à des ressources en dehors de ce chroot [3]. Nous allons donc examiner la VM via un chroot comme si nous avions réellement booté cette machine.

On créé un répertoire afin de monter le disque :

ubuntu@com:~$ sudo mkdir /mnt/data

Montage disque et des périphériques du host :

ubuntu@com:~$ sudo mount /dev/vdb1 /mnt/data
ubuntu@com:~$ sudo mount --bind /dev /mnt/data/dev
ubuntu@com:~$ sudo mount --bind /proc /mnt/data/proc
ubuntu@com:~$ sudo mount --bind /sys /mnt/data/sys

Exécution du chroot :

ubuntu@com:~$ sudo chroot /mnt/data

Maintenant toutes les commandes que nous exécutons sont faites relativement au chroot.

Identifions l'initrd :

root@com:~$ ls -l /boot
total 15957
-rw-r--r-- 1 root root   67541 févr. 22  2012 config-2.6.18-308.el5
-rw-r--r-- 1 root root   67854 févr. 24  2017 config-2.6.18-419.el5
drwxr-xr-x 2 root root    1024 févr.  5 16:59 grub
-rw------- 1 root root 2714127 févr.  2 12:13 initrd-2.6.18-308.el5.img
-rw------- 1 root root 2747678 févr.  5 11:33 initrd-2.6.18-419.el5.img
drwx------ 2 root root   12288 déc. 21  2012 lost+found
-rw-r--r-- 1 root root   80032 mars 12  2009 message
-rw-r--r-- 1 root root  116635 févr. 22  2012 symvers-2.6.18-308.el5.gz
-rw-r--r-- 1 root root  118906 févr. 24  2017 symvers-2.6.18-419.el5.gz
-rw-r--r-- 1 root root 1275921 févr. 22  2012 System.map-2.6.18-308.el5
-rw-r--r-- 1 root root 1284824 févr. 24  2017 System.map-2.6.18-419.el5
-rw-r--r-- 1 root root 2115772 févr. 22  2012 vmlinuz-2.6.18-308.el5
-rw-r--r-- 1 root root 2127884 févr. 24  2017 vmlinuz-2.6.18-419.el5

L'initrd est le fichier initrd-2.6.18-419.el5.img. Décompresssons le :

root@com:~$ mkdir -p /temp/initrd
root@com:~$ cd /temp/initrd
root@com:~$ gzip -dc /boot/initrd-2.6.18-419.el5.img | cpio -id

Voici le contenu de l'initrd :

root@com:~$ ls -l
total 10
drwx------ 2 root root 1024 févr.  2 16:13 bin
drwx------ 3 root root 1024 févr.  2 16:13 dev
drwx------ 3 root root 1024 févr.  2 16:13 etc
-rwx------ 1 root root 2697 févr.  2 16:13 init
drwx------ 3 root root 1024 févr.  2 16:13 lib
drwx------ 2 root root 1024 févr.  2 16:13 proc
lrwxrwxrwx 1 root root    3 févr.  2 16:13 sbin -> bin
drwx------ 2 root root 1024 févr.  2 16:13 sys
drwx------ 2 root root 1024 févr.  2 16:13 sysroot

Jetons un oeil au script init :

#!/bin/nash

mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/urandom c 1 9
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/rtc c 10 135
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd.ko module"
insmod /lib/ehci-hcd.ko
echo "Loading ohci-hcd.ko module"
insmod /lib/ohci-hcd.ko
echo "Loading uhci-hcd.ko module"
insmod /lib/uhci-hcd.ko
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
echo "Loading scsi_mod.ko module"
insmod /lib/scsi_mod.ko
echo "Loading sd_mod.ko module"
insmod /lib/sd_mod.ko
echo "Loading libata.ko module"
insmod /lib/libata.ko
echo "Loading ahci.ko module"
insmod /lib/ahci.ko
echo "Loading ata_piix.ko module"
insmod /lib/ata_piix.ko
echo "Loading dm-mod.ko module"
insmod /lib/dm-mod.ko
echo "Loading dm-log.ko module"
insmod /lib/dm-log.ko
echo "Loading dm-mirror.ko module"
insmod /lib/dm-mirror.ko
echo "Loading dm-zero.ko module"
insmod /lib/dm-zero.ko
echo "Loading dm-snapshot.ko module"
insmod /lib/dm-snapshot.ko
echo "Loading dm-mem-cache.ko module"
insmod /lib/dm-mem-cache.ko
echo "Loading dm-region_hash.ko module"
insmod /lib/dm-region_hash.ko
echo "Loading dm-message.ko module"
insmod /lib/dm-message.ko
echo "Loading dm-raid45.ko module"
insmod /lib/dm-raid45.ko
echo Waiting for driver initialization.
stabilized --hash --interval 1000 /proc/scsi/scsi
mkblkdevs
echo Scanning and configuring dmraid supported devices
echo Scanning logical volumes
lvm vgscan --ignorelockingfailure
echo Activating logical volumes
lvm vgchange -ay --ignorelockingfailure  VolGroup00
resume /dev/VolGroup00/LogVol01
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/VolGroup00/LogVol00
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
echo Switching to new root and running init.
switchroot

A priori c'est la commande mkblkdevs qui ne créé pas les devices qui vont bien dans /dev, car juste après ça plante.

Pour que mkblkdevs fonctionne encore faut-il que les drivers qui vont bien soient chargés : d'où toutes les commandes d'insertion de module que nous voyons juste au dessus. Il manquerait donc un driver ? Ce qui me rappelle quelque chose : l'hyperviseur que nous utilisons (qemu-kvm) utilise des drivers disque et réseau qui s'appellent virtio_blk et virtio_net. Toutes les distributions Linux modernes intègrent ces drivers par défaut dans l'initrd. Centos5 étant assez vieux, il est possible que ces drivers n'y ai pas été inclus. En tout cas, le script /init ne les insère pas, ce qui est déjà un problème.

Liste des modules inclus dans l'initrd :

root@com:~$ ls -al lib
total 1986
-rw------- 1 root root  94656 févr.  2 16:13 ahci.ko
-rw------- 1 root root  69928 févr.  2 16:13 ata_piix.ko
-rw------- 1 root root  51872 févr.  2 16:13 dm-log.ko
-rw------- 1 root root  41904 févr.  2 16:13 dm-mem-cache.ko
-rw------- 1 root root  39264 févr.  2 16:13 dm-message.ko
-rw------- 1 root root  63488 févr.  2 16:13 dm-mirror.ko
-rw------- 1 root root 135600 févr.  2 16:13 dm-mod.ko
-rw------- 1 root root 117152 févr.  2 16:13 dm-raid45.ko
-rw------- 1 root root  51760 févr.  2 16:13 dm-region_hash.ko
-rw------- 1 root root  61856 févr.  2 16:13 dm-snapshot.ko
-rw------- 1 root root  37448 févr.  2 16:13 dm-zero.ko
-rw------- 1 root root  78168 févr.  2 16:13 ehci-hcd.ko
-rw------- 1 root root 230688 févr.  2 16:13 ext3.ko
drwx------ 2 root root   1024 févr.  2 16:13 firmware
-rw------- 1 root root 137480 févr.  2 16:13 jbd.ko
-rw------- 1 root root 287984 févr.  2 16:13 libata.ko
-rw------- 1 root root  65624 févr.  2 16:13 ohci-hcd.ko
-rw------- 1 root root 297592 févr.  2 16:13 scsi_mod.ko
-rw------- 1 root root  69184 févr.  2 16:13 sd_mod.ko
-rw------- 1 root root  66920 févr.  2 16:13 uhci-hcd.ko

Bingo : pas la trace d'un moindre module virtio. C'est la bonne piste :) donc l'initrd ne contient aucun driver virtio, mais est-ce que la VM les a quelque part dans son filesystem ?

root@com:~$ find / -name virtio*
/lib/modules/2.6.18-419.el5/kernel/drivers/net/virtio_net.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/char/virtio_console.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/block/virtio_blk.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/virtio
/lib/modules/2.6.18-419.el5/kernel/drivers/virtio/virtio_ring.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/virtio/virtio.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/virtio/virtio_balloon.ko
/lib/modules/2.6.18-419.el5/kernel/drivers/virtio/virtio_pci.ko

Parfait, les modules existent, il suffit de refaire un initrd avec les modules manquants :

root@com:~$ mv /boot/initrd-2.6.18-419.el5.img /boot/initrd-2.6.18-419.el5.img.old
root@com:~$ mkinitrd -v --with=virtio_blk --with=virtio_pci --with=virtio  --with=virtio_console --with=virtio_net /boot/initrd-2.6.18-419.el5.img 2.6.18-419.el5

On sort du chroot, on sort de la VM rescue et on termine la procédure de sauvetage d'OpenStack :

root@com:~$ exit
ubuntu@com:~$ exit
logout
Connection to xxx.xxx.xxx.xxx closed.
(venv) fred@MacdeFrederic:~ > nova unrescue e1e0ed48-268d-4e41-a0d2-9bade79d02f2

La VM reboot et là ça boot jusqu'au bout.

Conclusion

En définitive ce qui devait être un simple déménagement ne s'est pas révelé si simple. Difficile d'identifier de prime abord quand le processus de démarrage plante, il est nécessaire de bien connaitre les différents étapes de boot et leur fonction pour identifier l'étape fautive.

Désormais tout fonctionne bien, Le webmaster va avoir le temps de penser à upgrader plone . Dans l'intervalle, il me doit maintenant une chouffe pour la galère qu'il m'a occasionné :-)

Notes

[1] https://www.ibm.com/developerworks/library/l-linuxboot/index.html

[2] En théorie bien entendu car il existe des exploits permettant de sortir d'un chroot jail.

[3] L'initrd sur les versions Linux récentes s'appelle initramfs.