Aller au contenu
English
Virtualisation medium

Projet final KVM : une VM qui naît, tombe, revient et se déplace

55 min de lecture

Logo KVM

Ce projet enchaîne toute la formation sur deux hôtes, et chaque étape rend un chiffre. Une machine naît par cloud-init, sert une application, se fait sauvegarder à chaud, perd son disque, revient de sa sauvegarde, puis se déplace sur l'autre hôte sans être arrêtée. À la fin, il ne reste rien. C'est ce parcours-là, pas un score de quiz, qui dit que vous savez exploiter KVM.

  • Monter un socle partagé que deux hôtes voient de la même façon
  • Chronométrer une VM de la commande jusqu'au service qui répond
  • Sauvegarder une machine allumée avec virsh backup-begin
  • Mesurer un RTO réel et prouver l'intégrité des données restaurées
  • Déplacer la machine à chaud, puis vérifier qu'il ne reste aucun résidu

Deux hyperviseurs, kvmlab-1 et kvmlab-2, portent chacun un bridge br0 sur le même segment. C'est ce qui permet à la machine invitée de garder son adresse en changeant d'hôte : son trafic passe par un pont, pas par un NAT local.

Un partage NFS porte les disques. Il est exporté par le premier hôte et monté par les deux, au même chemin. Cette symétrie n'est pas cosmétique : sans elle, libvirt voit un disque local côté source et refuse la migration.

ÉlémentValeur dans ce projet
Hôte 1kvmlab-1, 192.168.190.11
Hôte 2kvmlab-2, 192.168.190.12
Partage/mnt/shared, NFS, monté des deux côtés
Machine invitéeappvm, 192.168.190.30, 1 Gio de RAM, disque de 10 Gio
Application témoinun service HTTP qui sert un fichier d'état

Un pool de stockage déclaré sur le chemin partagé donne à libvirt un vocabulaire commun aux deux hôtes. Il n'est pas obligatoire pour migrer, mais il rend les volumes inventoriables au lieu d'être des fichiers posés à la main.

Fenêtre de terminal
virsh pool-define-as capstone dir --target /mnt/shared
virsh pool-build capstone
virsh pool-start capstone
virsh pool-autostart capstone
virsh pool-info capstone

Sur le lab de référence, le pool annonce 28,02 Gio de capacité pour 24,86 Gio disponibles. Retenez ce point de départ : il servira à juger, à la fin, ce que le projet a réellement laissé derrière lui.

L'amorce cloud-init porte trois fichiers, et l'un d'eux est souvent oublié. meta-data est obligatoire : sans lui, la source de données NoCloud n'est pas détectée du tout, aucun utilisateur n'est créé, le réseau n'est pas appliqué, et la machine prend un bail DHCP anonyme sans qu'aucun message ne le signale.

Fenêtre de terminal
genisoimage -quiet -output /mnt/shared/appvm-seed.iso -volid cidata \
-joliet -rock /mnt/shared/seed-appvm

L'ISO se construit depuis un répertoire. Passer les fichiers un par un les renomme en 8.3, USER_DAT.;1, et cloud-init ne reconnaît plus rien.

La création elle-même se mesure :

Fenêtre de terminal
virt-install --connect qemu:///system --name appvm \
--memory 1024 --vcpus 2 --cpu host-model \
--disk path=/mnt/shared/appvm.qcow2,format=qcow2,bus=virtio,cache=none,io=native \
--disk path=/mnt/shared/appvm-seed.iso,device=cdrom \
--network bridge=br0,model=virtio --os-variant ubuntu24.04 \
--graphics none --noautoconsole --import
RepèreMesure
Domaine défini et démarré2,6 s
Service applicatif qui répond38,8 s après la commande

Les 2,6 secondes ne veulent rien dire pour un exploitant : ce qui compte est le moment où l'application répond. Chronométrez toujours jusque-là, c'est le seul délai que votre utilisateur constate.

Deux options du disque méritent d'être remarquées. --cpu host-model est posé explicitement, parce que virt-install mettrait sinon host-passthrough et la machine ne pourrait pas migrer à l'étape 7. Et io=native exige cache=none : QEMU refuse la combinaison inverse.

Sans agent, l'hôte ne sait de la machine que ce qu'il lui a donné. Avec lui, il l'interroge.

Fenêtre de terminal
virsh domifaddr appvm --source agent
virsh domfsinfo appvm
enp1s0 52:54:00:e3:63:cc ipv4 192.168.190.30/24
Mountpoint Name Type Target
/ vda1 ext4 vda
/boot vda16 ext4 vda
/boot/efi vda15 vfat vda

L'adresse est rendue sans DHCP à consulter ni console à ouvrir, et la carte des systèmes de fichiers vient de l'intérieur de la machine. C'est ce même canal qui permettra, à l'étape 5, de figer les écritures le temps d'un cliché cohérent.

Étape 4 : mesurer les entrées-sorties là où elles comptent

Section intitulée « Étape 4 : mesurer les entrées-sorties là où elles comptent »

Première tentation, et première erreur : mesurer depuis l'hôte. QEMU refuse, et il a raison.

Fenêtre de terminal
qemu-img bench -f qcow2 -c 2000 -w /mnt/shared/appvm.qcow2
qemu-img: Could not open '/mnt/shared/appvm.qcow2': Failed to get "write" lock
Is another process using the image?

Le disque d'une machine allumée ne se mesure pas depuis l'hôte. Le verrou protège exactement ce qu'il faut : deux écrivains sur un même qcow2 le corrompent. La mesure se prend dans l'invité, où elle a d'ailleurs plus de sens, puisque c'est ce que l'application voit.

Fenêtre de terminal
dd if=/dev/zero of=/var/temoin/bench.bin bs=1M count=256 oflag=direct
dd if=/var/temoin/bench.bin of=/dev/null bs=1M iflag=direct
SensRésultatCe qu'il mesure
Écriture256 Mio en 6,65 s, 40,4 Mo/sle chemin complet jusqu'au stockage
Lecture256 Mio en 0,096 s, 2,8 Go/sle cache, pas le disque

Étape 5 : sauvegarder à chaud, sans couper le service

Section intitulée « Étape 5 : sauvegarder à chaud, sans couper le service »

virsh backup-begin copie le disque d'une machine en fonctionnement. En mode push, libvirt écrit lui-même le fichier de destination.

Fenêtre de terminal
cat > backup.xml <<'XML'
<domainbackup mode='push'>
<disks>
<disk name='vda' backup='yes' type='file'>
<target file='/mnt/shared/backup2/vda-complet.qcow2'/>
<driver type='qcow2'/>
</disk>
</disks>
</domainbackup>
XML
virsh backup-begin appvm --backupxml backup.xml
virsh domjobinfo appvm --completed

Le résultat, mesuré :

RepèreMesure
Durée annoncée par libvirt6 282 ms
Volume parcouru10,000 Gio, soit le disque entier
Fichier produit2,41 Gio
qemu-img checkNo errors, 24,08 % alloué
Service pendant l'opérationdebout, sans interruption

Deux choses à retenir de ce tableau. Le volume parcouru est la taille virtuelle, pendant que le fichier produit ne pèse que ce qui est réellement alloué : la sauvegarde d'un disque provisionné large coûte donc du temps proportionnel à sa taille annoncée, et de l'espace proportionnel à son contenu. Et une sauvegarde qui n'a pas passé qemu-img check n'est pas une sauvegarde, c'est un fichier.

Une sauvegarde ne vaut que restaurée. On détruit donc pour de vrai.

  1. Arrêter et effacer la machine et son disque de production.

    Fenêtre de terminal
    virsh destroy appvm
    virsh undefine appvm
    rm -f /mnt/shared/appvm.qcow2
  2. Constater que le service ne répond plus. C'est le point de départ du chronomètre : l'incident commence quand l'utilisateur le voit.

  3. Restaurer le disque et la définition, puis démarrer.

    Fenêtre de terminal
    cp /mnt/shared/backup/appvm-base.qcow2 /mnt/shared/appvm.qcow2
    virsh define /mnt/shared/backup/appvm.xml
    virsh start appvm
  4. Vérifier l'intégrité par une empreinte prise avant l'incident, jamais par un simple « ça redémarre ».

    Fenêtre de terminal
    sha256sum /var/temoin/commandes.txt
RepèreMesure
RTO, de la commande de restauration au service qui répond16,2 s
Empreinte avant l'incident3822e7c9e7f6…
Empreinte après restauration3822e7c9e7f6…
Verdictidentique

Le RTO est plus court que la création initiale, 16 secondes contre 39, et la raison est instructive : la restauration ne rejoue pas cloud-init. Elle repart d'un disque déjà initialisé. C'est un argument concret pour sauvegarder l'état plutôt que de compter sur une reconstruction.

La machine tourne, elle porte ses données restaurées, et elle doit changer d'hôte sans que personne ne le remarque.

Fenêtre de terminal
virsh migrate --live --verbose --persistent --undefinesource \
appvm qemu+ssh://root@192.168.190.12/system
RepèreMesure
Durée totale vue du pilote3,1 s
Temps interne QEMU1 973 ms, 3 itérations
Mémoire transférée425,6 Mio
Disque transféréaucun, le partage étant commun
Coupure91 ms

La machine est ensuite absente du premier hôte et présente sur le second, son service répond toujours, et elle a gardé son adresse. C'est le bridge commun qui le permet.

Si la commande échoue avant même de parler du processeur, regardez le transport : la confiance SSH est directionnelle, et l'avoir établie dans un sens ne dit rien du retour.

Un exercice qui ne se démonte pas devient une dette. Le contrôle final porte sur ce qui reste, pas sur ce qui a marché.

Fenêtre de terminal
virsh destroy appvm ; virsh undefine appvm --remove-all-storage
virsh pool-destroy capstone ; virsh pool-undefine capstone
virsh list --all --name ; virsh pool-list --all --name

Sur le lab de référence, les deux hôtes rendent zéro domaine et zéro pool. Vérifiez aussi l'espace repris sur le partage : un pool supprimé ne supprime pas les fichiers qu'il indexait, et c'est la cause la plus commune d'un stockage qui se remplit sans que personne ne sache pourquoi.

Voici l'ensemble des mesures, prises sur deux hôtes Ubuntu 24.04 sous libvirt 10.0.0 et QEMU 8.2.2, avec une machine de 1 Gio de RAM et un disque de 10 Gio. Vos chiffres différeront ; ce qui doit se retrouver, ce sont les ordres de grandeur et les rapports entre eux.

ÉtapeMesureCe qu'elle vous dit
Création du domaine2,6 sle temps de libvirt, pas le vôtre
Machine utilisable38,8 sle seul délai que l'utilisateur constate
E/S en écriture40,4 Mo/sla référence à comparer après réglage
Sauvegarde à chaud6,3 s pour 10 Gio parcourusproportionnelle à la taille annoncée
Fichier de sauvegarde2,41 Gioproportionnel au contenu réel
RTO16,2 splus court que la création, cloud-init n'étant pas rejoué
IntégritéSHA256 identiquela seule preuve qui compte
Coupure de migration91 msinvisible pour la plupart des applications
Résidus0 domaine, 0 pooll'exercice est réellement terminé

Vérifiez ce que le projet a ancré, en particulier les rapports entre les mesures : c'est leur lecture, plus que les chiffres eux-mêmes, qui sert sur une infrastructure réelle.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Le chronomètre qui compte part de la commande et s'arrête quand l'application répond : 38,8 s ici, là où le domaine était défini en 2,6 s.
  • meta-data est obligatoire pour NoCloud, et l'ISO se construit depuis un répertoire : deux oublis silencieux qui font échouer tout le reste.
  • Ce qui doit survivre à un redémarrage s'écrit en unité systemd, jamais dans runcmd, qui ne joue qu'une fois.
  • Le disque d'une machine allumée ne se mesure pas depuis l'hôte : QEMU pose un verrou d'écriture, et la mesure se prend dans l'invité.
  • Une lecture à 2,8 Go/s mesure un cache, pas un disque. Seule l'écriture directe traverse toute la chaîne.
  • virsh backup-begin parcourt la taille virtuelle et écrit la taille allouée : le temps dépend de l'une, l'espace de l'autre.
  • Une sauvegarde se prouve par une empreinte prise avant l'incident, jamais par une machine qui redémarre.
  • Un RTO de 16 s contre 39 s de création initiale : restaurer un disque déjà initialisé est plus rapide que reconstruire.
  • Le projet n'est fini que quand il ne reste aucune ressource : un pool supprimé ne supprime pas les fichiers qu'il indexait.
  • Formation Proxmox VE : La même pile KVM et libvirt, avec le clustering et la bascule automatique que ce projet fait à la main.
  • Restic : Porter la sauvegarde mesurée ici vers un dépôt déduplicable et chiffré, hors de l'hyperviseur.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn