Aller au contenu
English
English
Infrastructure as Code medium

Pulumi - première VM KVM avec libvirt

9 min de lecture

logo pulumi

Ce guide vous fait exécuter une première stack Pulumi vraiment concrète. Vous allez créer un réseau NAT, un disque clone depuis une cloud image Ubuntu, un disque cloud-init, puis une vraie VM KVM. Ensuite, vous verifierez avec virsh que le domaine est bien en état running, avant de rejouer pulumi destroy pour revenir a un état propre.

Le scenario a été rejoue intégralement le 1 avril 2026 sur KVM/libvirt. Le résultat attendu n'est plus seulement une ressource réseau, mais une VM pulumi-lab-vm qui demarre réellement.

  • un preview annoncant le réseau, le disque clone, le cloud-init et la VM ;
  • un réseau libvirt nomme pulumi-lab-net ;
  • une VM pulumi-lab-vm en état running ;
  • une verification concrète via virsh ;
  • un destroy qui supprime toutes les ressources sans residu.

Placez-vous dans le projet prepare au guide précédent :

Fenêtre de terminal
cd ~/pulumi-kvm-local
source venv/bin/activate
pulumi stack select dev

Verification : pulumi stack select dev ne doit pas retourner d'erreur.

Remplacez le contenu de __main__.py par ce programme :

import pulumi
import pulumi_libvirt as libvirt
provider = libvirt.Provider(
"libvirt-provider",
uri="qemu:///system",
)
lab_network = libvirt.Network(
"pulumi-lab-network",
name="pulumi-lab-net",
autostart=True,
domain=libvirt.NetworkDomainArgs(name="pulumi.lab"),
forward=libvirt.NetworkForwardArgs(mode="nat"),
ips=[
libvirt.NetworkIpArgs(
address="192.168.151.1",
prefix=24,
dhcp=libvirt.NetworkIpDhcpArgs(
ranges=[
libvirt.NetworkIpDhcpRangeArgs(
start="192.168.151.50",
end="192.168.151.200",
)
]
),
)
],
opts=pulumi.ResourceOptions(provider=provider),
)
vm_disk = libvirt.Volume(
"pulumi-lab-vm-disk",
name="pulumi-lab-vm.qcow2",
pool="default",
capacity=20,
capacity_unit="GiB",
backing_store=libvirt.VolumeBackingStoreArgs(
path="/var/lib/libvirt/images/ubuntu-22.04-cloudimg.qcow2",
format=libvirt.VolumeBackingStoreFormatArgs(type="qcow2"),
),
target=libvirt.VolumeTargetArgs(
format=libvirt.VolumeTargetFormatArgs(type="qcow2"),
),
opts=pulumi.ResourceOptions(provider=provider),
)
cloudinit_disk = libvirt.CloudinitDisk(
"pulumi-lab-cloudinit",
name="pulumi-lab-cloudinit.iso",
meta_data="""instance-id: pulumi-lab-vm
local-hostname: pulumi-lab-vm
""",
user_data="""#cloud-config
hostname: pulumi-lab-vm
manage_etc_hosts: true
package_update: true
packages:
- qemu-guest-agent
runcmd:
- systemctl enable --now qemu-guest-agent
""",
opts=pulumi.ResourceOptions(provider=provider),
)
lab_vm = libvirt.Domain(
"pulumi-lab-vm",
type="kvm",
name="pulumi-lab-vm",
memory=2048,
memory_unit="MiB",
vcpu=2,
running=True,
os=libvirt.DomainOsArgs(
type="hvm",
type_arch="x86_64",
),
devices=libvirt.DomainDevicesArgs(
disks=[
libvirt.DomainDevicesDiskArgs(
device="disk",
source=libvirt.DomainDevicesDiskSourceArgs(
volume=libvirt.DomainDevicesDiskSourceVolumeArgs(
pool="default",
volume=vm_disk.name,
),
),
target=libvirt.DomainDevicesDiskTargetArgs(
dev="vda",
bus="virtio",
),
),
libvirt.DomainDevicesDiskArgs(
device="cdrom",
read_only=True,
source=libvirt.DomainDevicesDiskSourceArgs(
file=libvirt.DomainDevicesDiskSourceFileArgs(
file=cloudinit_disk.path,
),
),
target=libvirt.DomainDevicesDiskTargetArgs(
dev="hda",
bus="ide",
),
),
],
interfaces=[
libvirt.DomainDevicesInterfaceArgs(
source=libvirt.DomainDevicesInterfaceSourceArgs(
network=libvirt.DomainDevicesInterfaceSourceNetworkArgs(
network=lab_network.name,
),
),
model=libvirt.DomainDevicesInterfaceModelArgs(type="virtio"),
),
],
),
opts=pulumi.ResourceOptions(provider=provider),
)
pulumi.export("network_name", lab_network.name)
pulumi.export("network_id", lab_network.id)
pulumi.export("network_uuid", lab_network.uuid)
pulumi.export("vm_name", lab_vm.name)
pulumi.export("vm_disk_name", vm_disk.name)

Ce premier vrai exemple assemble quatre briques que vous devez apprendre a distinguer :

  • le réseau NAT ;
  • le disque clone qui part d'une cloud image Ubuntu ;
  • le disque cloud-init qui donne son identité initiale a la VM ;
  • le domaine libvirt qui correspond a la VM elle-même.
Fenêtre de terminal
pulumi preview --stack dev

Le preview doit annoncer la création des éléments suivants :

  • la stack Pulumi ;
  • le provider libvirt ;
  • le réseau pulumi-lab-net ;
  • le disque pulumi-lab-vm.qcow2 ;
  • le disque pulumi-lab-cloudinit.iso ;
  • le domaine pulumi-lab-vm.

Si ce n'est pas le cas, corrigez le projet avant de lancer up.

Fenêtre de terminal
pulumi up --stack dev --yes

Verification : le resume final doit annoncer la création du domaine et exposer au minimum network_name, vm_name et vm_disk_name.

Contrôlez maintenant le résultat du point de vue de libvirt :

Fenêtre de terminal
virsh net-list --all | grep pulumi-lab-net
virsh net-dumpxml pulumi-lab-net
virsh list --all | grep pulumi-lab-vm
virsh domstate pulumi-lab-vm
virsh domiflist pulumi-lab-vm

Vous devez observer au minimum :

  • un réseau pulumi-lab-net en active ;
  • Autostart a yes ;
  • un domaine pulumi.lab ;
  • l'adresse 192.168.151.1/24 ;
  • une plage DHCP de 192.168.151.50 a 192.168.151.200 ;
  • une VM pulumi-lab-vm en état running ;
  • une interface virtio raccordee au réseau pulumi-lab-net.

Cette verification est importante : elle prouve que le state Pulumi et l'état réel de libvirt racontent la même histoire.

Fenêtre de terminal
pulumi destroy --stack dev --yes

Puis vérifiez l'absence des ressources :

Fenêtre de terminal
virsh net-list --all | grep pulumi-lab-net
virsh list --all | grep pulumi-lab-vm

Si ces commandes ne retournent rien, le nettoyage est correct et le scenario est rejouable.

Le bloc libvirt.Provider(...) indique a Pulumi quelle cible il doit piloter. Ici, qemu:///system signifie que le provider travaille contre votre instance système de libvirt.

Le bloc libvirt.Network(...) décrit un réseau NAT. Vous y fixez :

  • son nom ;
  • son comportement d'autostart ;
  • le domaine DNS local ;
  • l'adressage IP et la plage DHCP.

Le bloc libvirt.Volume(...) cree un disque de travail a partir d'une cloud image Ubuntu déjà presente sur l'hôte. Cela permet de partir d'une base propre sans retaper toute l'installation d'un système.

Le bloc libvirt.CloudinitDisk(...) donne une identité initiale a la VM : nom d'hôte, mise a jour initiale et installation de qemu-guest-agent.

Le bloc libvirt.Domain(...) représente la VM elle-même. C'est lui qui assemble les disques, le réseau, la mémoire et les vCPU, puis demande a libvirt de démarrer la machine.

Les appels pulumi.export(...) rendent visibles des informations utiles a la fin du déploiement. C'est une bonne habitude des le premier guide : vous voyez ce que la stack a produit sans aller lire tout de suite le state brut.

SymptômeCause probableSolution
preview echoue sur pulumi_libvirtLe package n'a pas été ajoute ou installeRejouez le guide de préparation locale puis relancez pulumi package add terraform-provider dmacvicar/libvirt
permission denied ou erreur qemu:///systemAccès libvirt insuffisantCorrigez l'environnement libvirt avant de poursuivre
network already existsUn ancien run a laisse le réseau en placeVérifiez avec virsh net-list --all, detruisez proprement puis relancez le workflow
la VM ne passe pas en runningle domaine n'a pas pu booter correctementVérifiez virsh list --all, virsh domstate pulumi-lab-vm et la présence de l'image Ubuntu de base
destroy termine mais une ressource existe encoreLa suppression n'a pas atteint l'état réelRelancez pulumi destroy --stack dev --yes, puis reverifiez virsh
  • Pulumi suit ici une boucle claire : preview -> up -> verification -> destroy.
  • Le premier vrai exemple de la section fait démarrer une VM KVM, pas seulement une ressource abstraite.
  • virsh sert de preuve externe que le domaine et le réseau existent vraiment.
  • Un bon premier lab n'est pas seulement un up réussi, c'est aussi un destroy propre.

Ce site vous est utile ?

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

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. 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