Installer une VM en ligne de commande

Il n’y a pas si longtemps je suis passé à Archlinux sur l’une de mes machines. Dans le guide d’installation, pas d’installeur graphique ou textuel qui fait tout pour toi. Non, tu boot sur un système minimal, tu partitionnes ton disque, tu crées tes systèmes de fichiers, ta partition de swap, tu bootstrap une installation minimale, tu configures ta fstab, ton réseau et d’autres petites choses, puis tu installes le bootloader de ton choix et tu reboot dans ton nouveau système fraîchement installé… Et ça marche parfaitement.

Ca marche parfaitement et ça ne t’étonnes même pas. Ca te rappelle que tu as connu le temps où pour installer Linux tu utilisais deux disquettes, une root, une boot. Des disquettes 3,5 pouces parceque les disquettes 5,25 pouces dans lesquelles tu découpais un petit réctangle pour doubler leur capacité à 720ko ne suffisaient pas. Ca te rappelle que tu es bientôt un dinosaure…

Bon, bref… après cette installation en bonne et dûe forme, je me suis dis que ça devrait être également faisable pour une Debian et que ça pourrait être intéressant de tester ça pour créer des VMs sans avoir à booter avec une image d’installation.

Création du disque virtuel

Il y a plusieurs options, dont les plus fréquentes sont dd et qemu-img, mais on peut également citer fallocate et truncate.

Avec dd créer un fichier de 10G peut se faire de la manière suivante:

dd if=/dev/zero of=debian.img bs=1k seek=10230k skip=1 count=1

Avec qemu-img c’est un peu plus court:

qemu-img create -f raw debian.img 10G

Ces deux méthodes créent un fichier “sparse”, la taille apparente du fichier est de 10G, mais l’espace pris par le fichier sur le disque est celui des données réellement présentes, c’est à dire par grand chose à ce stade.

ls -lh debian.img
-rw-r--r-- 1 user user 10G 10 mai   18:49 debian.img
du -h debian.img
4,0K	debian.img

Partitionnement

Pour rester simple je me contenterai d’une partition pour le système et d’une partition swap.

Là encore les possibilités sont nombreuses, parted, fdisk, cfdisk, sfdisk. Chacun des ces outils est capable de partitionner un fichier comme s’il s’agissait d’un disque réel. sfdisk a l’avantage d’être scriptable et donc de m’économiser de multiples captures d’écran, alors autant l’utiliser, le script est simple:

echo "label: dos
- 9700M L *
- - S -"| sfdisk debian.img 

La première ligne label: dos demande à sfdisk de créer une table de partition au format dos. Les deux lignes suivantes décrivent les partitions et sont au format [position départ] [taille] [type] [bootable].

Elles créent donc une partition bootable de type Linux (de 9,5G à partir de la fin de la table de partition), et une partition prenant le reste du disque et de type swap.

Le résultat est le suivant:

Vérification que personne n'utilise le disque en ce moment… OK

Disque debian_qemu.img : 10 GiB, 10737418240 octets, 20971520 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets

>>> Script d’en-tête accepté.
>>> Création d'une nouvelle étiquette pour disque de type DOS avec identifiant de disque 0x7e4467d4.
debian.img1: Une nouvelle partition 1 de type « Linux » et de taille 9,5 GiB a été créée.
debian.img2: Une nouvelle partition 2 de type « Linux swap / Solaris » et de taille 539 MiB a été créée.
debian.img3: Terminé.

Nouvelle situation :
Type d'étiquette de disque : dos
Identifiant de disque : 0x7e4467d4

Périphérique     Amorçage    Début      Fin Secteurs Taille Id Type
debian.img1 *            2048 19867647 19865600   9,5G 83 Linux
debian.img2          19867648 20971519  1103872   539M 82 partition d'échange Linux / Solaris

La table de partitions a été altérée.
Synchronisation des disques.

Initialisation des partitions

Il est possible de créer un système de fichiers sans privilèges particuliers en passant la commande suivante:

mkfs.ext4 -E offset=$((2048*512)) debian.img 9700M

Le paramètre offset permet de demander la création du système de fichier non pas au début du disque, mais au début de la première partition.

Dans l’affichage du résultat de l’étape précédente, on peut voir que cette partition (debian.img1) débute au secteur 2048. On peut également voir que la taille d’un secteur est de 512 octets, ce qui donne un offset de 2048*512 octets.

Malheureusement l’initialisation de l’espace de swap et l’installation d’un système minimal par debootstrap nécessitent les privilèges de root. Il est donc plus simple de mapper les partitions sur le système hôte.

Ici j’utiliserai la méthode qemu-nbd, mais il est également possible d’utiliser losetup ou kpartx par exemple.

sudo modprobe nbd
sudo qemu-nbd --connect /dev/nbd0 debian.img

La commande précédente a créé de nouveaux périphériques de blocs. /dev/nbd0p1 désigne la partition racine de l’image, /dev/nbd0p2 la partition swap.

On commence par créer le système de fichier racine sur la partition racine:

sudo mkfs.ext4 /dev/nbd0p1
Rejet des blocs de périphérique : complété                        
En train de créer un système de fichiers avec 2483200 4k blocs et 621376 i-noeuds.
UUID de système de fichiers=9c31d8e5-ab97-412d-9550-a100113669f7
Superblocs de secours stockés sur les blocs : 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocation des tables de groupe : complété                        
Écriture des tables d'i-noeuds : complété                        
Création du journal (16384 blocs) : complété
Écriture des superblocs et de l'information de comptabilité du système de
fichiers : complété

Puis on initialise la partition d’échange:

sudo mkswap /dev/nbd0p2 
Configure l'espace d'échange (swap) en version 1, taille = 539 MiB (565178368 octets)
pas d'étiquette, UUID=17d6dd41-4490-45cd-91b9-7ce75ff3bda5

Installation d’un système minimal

L’installation passe par debootstrap. On commence par monter le système de fichier racine sur /mnt:

sudo mount /dev/nbd0p1 /mnt/

Puis on installe une Debian/buster:

sudo debootstrap buster /mnt/ http://ftp.fr.debian.org/debian/
W: Cannot check Release signature; keyring file not available /usr/share/keyrings/debian-archive-keyring.gpg
I: Retrieving InRelease 
I: Retrieving Packages 
I: Validating Packages 
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Checking component main on http://ftp.fr.debian.org/debian...
I: Retrieving libacl1 2.2.53-4
I: Validating libacl1 2.2.53-4
...
I: Configuring libc-bin...
I: Configuring systemd...
I: Base system installed successfully.

Installation du noyau Linux et du bootloader

Afin d’obtenir un système utilisable, il manque évidemment un noyau. Ce dernier peut être installé simplement avec apt-get dans l’environnement chroot.

Pour le bootloader, plutôt que d’installer le très répandu grub, on peut utiliser une alternative également très utilisée syslinux. Le projet syslinux fourni un bootloader utilisable pour le système de fichiers ext4.

sudo chroot /mnt apt-get install -y linux-image-amd64 extlinux
Get:1 http://ftp.fr.debian.org/debian buster/main amd64 pigz amd64 2.4-1 [57.8 kB]
Get:2 http://ftp.fr.debian.org/debian buster/main amd64 libpython3.7-minimal amd64 3.7.3-2+deb10u3 [589 kB]
Get:3 http://ftp.fr.debian.org/debian buster/main amd64 libexpat1 amd64 2.2.6-2+deb10u1 [106 kB]
Get:4 http://ftp.fr.debian.org/debian buster/main amd64 python3.7-minimal amd64 3.7.3-2+deb10u3 [1737 kB]
Get:5 http://ftp.fr.debian.org/debian buster/main amd64 python3-minimal amd64 3.7.3-1 [36.6 kB]
...
Setting up linux-image-4.19.0-16-amd64 (4.19.181-1) ...
I: /vmlinuz.old is now a symlink to boot/vmlinuz-4.19.0-16-amd64
I: /initrd.img.old is now a symlink to boot/initrd.img-4.19.0-16-amd64
I: /vmlinuz is now a symlink to boot/vmlinuz-4.19.0-16-amd64
I: /initrd.img is now a symlink to boot/initrd.img-4.19.0-16-amd64
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.0-16-amd64
Setting up linux-image-amd64 (4.19+105+deb10u11) ...
Processing triggers for systemd (241-7~deb10u7) ...
Processing triggers for libc-bin (2.28-10) ...
Processing triggers for initramfs-tools (0.133+deb10u1) ...
update-initramfs: Generating /boot/initrd.img-4.19.0-16-amd64

Il nous reste encore à configurer et installer le bootloader, ainsi qu’à configurer la fstab. Pour ces deux opérations nous aurons besoin des UUID des deux partitions:

blkid /dev/nbd0p1 
/dev/nbd0p1: UUID="5a6c1b9e-ee35-427b-aa0e-8d78bf737e7f" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="97cb4e36-01"
blkid /dev/nbd0p2 
/dev/nbd0p2: UUID="17d6dd41-4490-45cd-91b9-7ce75ff3bda5" TYPE="swap" PARTUUID="97cb4e36-02"

Pour la fstab, il suffit de créér le fichier /mnt/etc/fstab avec le contenu suivant:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
UUID=5a6c1b9e-ee35-427b-aa0e-8d78bf737e7f  /               ext4    errors=remount-ro 0       1
UUID=17d6dd41-4490-45cd-91b9-7ce75ff3bda5 none            swap    sw              0       0

Pour extlinux il faut créer le fichier /mnt/boot/syslinux.cfg avec le contenu suivant:

DEFAULT linux
  SAY Booting to linux
 LABEL linux
  KERNEL /vmlinuz
  APPEND ro root=UUID=17d6dd41-4490-45cd-91b9-7ce75ff3bda5 initrd=/initrd.img

L’installation d’extlinux nécessite d’avoir accès à l’intérieur du chroot au périphérique /dev/nbd0p1. Il suffit de monter le /dev de l’hôte sur le /dev/ du chroot:

sudo mount --bind /dev/ /mnt/dev/

On install alors extlinux:

sudo chroot /mnt extlinux --install /boot/ --device /dev/nbd0p1
/boot/ is device /dev/nbd0p1
Warning: unable to obtain device geometry (defaulting to 64 heads, 32 sectors)
         (on hard disks, this is usually harmless.)

Il reste encore a installer un MBR. extlinux fourni un MBR que l’on récupère sur l’hôte:

cp /mnt/usr/lib/syslinux/mbr/mbr.bin .

On peut maintenant démonter les partitions et déconnecter les périphériques:

sudo umount /mnt/dev
sudo umount /mnt
sudo qemu-nbd -d /dev/nbd0
/dev/nbd0 disconnected

La dernière étape consiste à installer le MBR sur le disque virtuel:

ls -l mbr.bin
-rw-r--r-- 1 user user 440 12 mai   15:19 mbr.bin
dd bs=440 count=1 conv=notrunc if=mbr.bin of=debian.img

Ou plus simplement:

cat mbr.bin > debian.img

Démarrage de la VM

Le moment est enfin arrivé, on va pouvoir vérifier que tout fonctionne comme prévu:

qemu-system-x86_64 -drive file=debian_qemu.img,if=virtio -m 1G -machine pc-i440fx-1.5,accel=kvm

Si tout va bien le prompt de login devrait s’afficher. Et là, déception le login en root ne peut fonctionner, et pour cause, on a jamais fixé de mot de passe.

Mais là commence tout une autre partie. A partir du moment ou l’image de la VM est fonctionnelle il est toujours possible de faire toutes les modifications et ajouts nécessaires en faisant les opérations sur l’hôte.

Tout d’abord on monte le système de fichier:

sudo mount -o loop,offset=$((2048*512)) debian.img /mnt

Puis on effectue nos modifications/ajouts, par exemple:

sudo chroot /mnt apt-get install vim-nox

Et enfin on démonte le système de fichier:

sudo umount /mnt