Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Pulumi - premiere VM KVM avec libvirt

9 min de lecture

logo pulumi

Ce guide vous fait executer une premiere stack Pulumi vraiment concrete. Vous allez creer un reseau 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 etat running, avant de rejouer pulumi destroy pour revenir a un etat propre.

Le scenario a ete rejoue integralement le 1 avril 2026 sur KVM/libvirt. Le resultat attendu n’est plus seulement une ressource reseau, mais une VM pulumi-lab-vm qui demarre reellement.

  • un preview annoncant le reseau, le disque clone, le cloud-init et la VM ;
  • un reseau libvirt nomme pulumi-lab-net ;
  • une VM pulumi-lab-vm en etat running ;
  • une verification concrete via virsh ;
  • un destroy qui supprime toutes les ressources sans residu.

Placez-vous dans le projet prepare au guide precedent :

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 reseau NAT ;
  • le disque clone qui part d’une cloud image Ubuntu ;
  • le disque cloud-init qui donne son identite initiale a la VM ;
  • le domaine libvirt qui correspond a la VM elle-meme.
Fenêtre de terminal
pulumi preview --stack dev

Le preview doit annoncer la creation des elements suivants :

  • la stack Pulumi ;
  • le provider libvirt ;
  • le reseau 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 creation du domaine et exposer au minimum network_name, vm_name et vm_disk_name.

Controlez maintenant le resultat 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 reseau 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 etat running ;
  • une interface virtio raccordee au reseau pulumi-lab-net.

Cette verification est importante : elle prouve que le state Pulumi et l’etat reel de libvirt racontent la meme histoire.

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

Puis verifiez 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 systeme de libvirt.

Le bloc libvirt.Network(...) decrit un reseau 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 deja presente sur l’hote. Cela permet de partir d’une base propre sans retaper toute l’installation d’un systeme.

Le bloc libvirt.CloudinitDisk(...) donne une identite initiale a la VM : nom d’hote, mise a jour initiale et installation de qemu-guest-agent.

Le bloc libvirt.Domain(...) represente la VM elle-meme. C’est lui qui assemble les disques, le reseau, la memoire et les vCPU, puis demande a libvirt de demarrer la machine.

Les appels pulumi.export(...) rendent visibles des informations utiles a la fin du deploiement. 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.

SymptomeCause probableSolution
preview echoue sur pulumi_libvirtLe package n’a pas ete ajoute ou installeRejouez le guide de preparation locale puis relancez pulumi package add terraform-provider dmacvicar/libvirt
permission denied ou erreur qemu:///systemAcces libvirt insuffisantCorrigez l’environnement libvirt avant de poursuivre
network already existsUn ancien run a laisse le reseau en placeVerifiez avec virsh net-list --all, detruisez proprement puis relancez le workflow
la VM ne passe pas en runningle domaine n’a pas pu booter correctementVerifiez virsh list --all, virsh domstate pulumi-lab-vm et la presence de l’image Ubuntu de base
destroy termine mais une ressource existe encoreLa suppression n’a pas atteint l’etat reelRelancez 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 demarrer une VM KVM, pas seulement une ressource abstraite.
  • virsh sert de preuve externe que le domaine et le reseau existent vraiment.
  • Un bon premier lab n’est pas seulement un up reussi, c’est aussi un destroy propre.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn