
Vous voulez créer des VMs KVM déjà configurées, sans installation manuelle ? Cloud-init + images cloud = VMs opérationnelles en moins de 2 minutes. Ce guide montre comment utiliser cloud-init spécifiquement avec KVM/libvirt.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre le principe image cloud + seed ISO
- Créer un fichier user-data pour configurer vos VMs
- Générer le seed ISO avec
cloud-localds - Déployer une VM pré-configurée avec
virt-install --import - Vérifier que cloud-init a bien appliqué votre configuration
Pourquoi cloud-init change tout
Section intitulée « Pourquoi cloud-init change tout »Le problème sans cloud-init
Section intitulée « Le problème sans cloud-init »Sans cloud-init, créer une VM demande :
- Télécharger une ISO (~2 Go)
- Lancer l'installateur (~15-30 min)
- Répondre aux questions (langue, partitions, utilisateur...)
- Installer les packages manuellement
- Configurer SSH, sudo, etc.
Temps total : 30-60 minutes par VM. Et si vous devez en créer 10 ? Le processus manuel ne passe pas à l'échelle.
Avec cloud-init
Section intitulée « Avec cloud-init »La logique s'inverse : au lieu d'installer puis de configurer, vous partez d'une image déjà installée et vous ne fournissez que la configuration. Les trois étapes ci-dessous se répètent à l'identique pour chaque VM, ce qui rend le provisionnement reproductible et scriptable.
- Télécharger une image cloud (~600 Mo, une seule fois)
- Écrire votre config dans un fichier YAML (1 minute)
- Lancer la VM, prête en 2 minutes
Le principe en détail
Section intitulée « Le principe en détail »Le mécanisme repose sur la combinaison de deux disques au démarrage : l'image cloud qui porte le système, et un petit seed ISO qui porte votre configuration. Cloud-init, présent dans l'image, lit le seed au premier boot et applique ce qu'il y trouve. Retenez cette séparation, c'est elle qui permet de dériver des dizaines de machines distinctes d'une seule image de base.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│ Image cloud │ + │ Seed ISO │ = │ VM configurée ││ (Ubuntu, ...) │ │ (user-data) │ │ prête à SSH │└─────────────────┘ └─────────────────┘ └─────────────────┘Les 3 ingrédients
Section intitulée « Les 3 ingrédients »Trois éléments suffisent à produire une VM configurée. L'image cloud est fournie par l'éditeur de la distribution et ne change pas ; les deux autres, user-data et meta-data, sont vos fichiers à vous. C'est la distinction à garder en tête : user-data décrit la configuration voulue, meta-data porte l'identité de l'instance, et cette dernière conditionne la ré-exécution de cloud-init.
| Élément | Ce que c'est |
|---|---|
| Image cloud | Disque pré-installé (OS minimal) |
| user-data | Votre configuration (users, SSH, packages) |
| meta-data | Identité de la VM (hostname, instance-id) |
Comment ça marche au boot
Section intitulée « Comment ça marche au boot »La séquence ci-dessous se déroule sans intervention, en quelques dizaines de secondes après le démarrage. Le point à comprendre est l'ordre : le réseau et les utilisateurs sont en place avant l'exécution de vos commandes finales, ce qui explique pourquoi un runcmd peut déjà télécharger des paquets ou appeler un service distant.
- La VM démarre sur l'image cloud
- Cloud-init détecte le seed ISO (monté comme CD-ROM)
- Il lit user-data et meta-data
- Il applique la configuration : crée les users, installe les packages, exécute vos scripts
- Résultat : VM prête à l'emploi, accessible en SSH
Prérequis
Section intitulée « Prérequis »- KVM/libvirt installé (guide installation)
- Outils cloud-init :
sudo apt install cloud-image-utilsCe paquet fournit cloud-localds, l'outil pour créer le seed ISO.
Workflow complet
Section intitulée « Workflow complet »-
Télécharger une image cloud
Une image cloud est un disque pré-installé, optimisé pour être configuré par cloud-init. Contrairement à une ISO d'installation, elle contient déjà l'OS, il n'y a rien à installer.
Fenêtre de terminal cd /var/lib/libvirt/imagessudo wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.imgPourquoi dans
/var/lib/libvirt/images/? C'est le pool de stockage par défaut de libvirt. QEMU a les permissions pour y accéder. -
Créer le fichier user-data
Le user-data est le cœur de cloud-init. C'est un fichier YAML qui décrit ce que vous voulez dans votre VM :
Fenêtre de terminal cat > /tmp/user-data << 'EOF'#cloud-confighostname: ubuntu-cloudusers:- name: devopssudo: ALL=(ALL) NOPASSWD:ALLgroups: sudoshell: /bin/bashssh_authorized_keys:- ssh-ed25519 AAAAC3Nza... votre-cle-publiquepackage_update: truepackages:- vim- htop- curlruncmd:- echo "Cloud-init terminé à $(date)" > /var/log/cloud-init-done.logEOFDécryptage ligne par ligne :
Directive Ce qu'elle fait #cloud-configIndique que c'est un fichier cloud-init (obligatoire en 1ère ligne) hostname:Définit le nom de la machine users:Crée des utilisateurs avec leurs permissions sudo: ALL=(ALL) NOPASSWD:ALLAutorise sudo sans mot de passe ssh_authorized_keys:Ajoute votre clé publique pour SSH package_update: trueLance apt updateavant d'installerpackages:Liste des paquets à installer runcmd:Commandes à exécuter à la fin -
Créer le fichier meta-data
Le meta-data contient l'identité de la VM. C'est minimaliste mais important :
Fenêtre de terminal cat > /tmp/meta-data << 'EOF'instance-id: ubuntu-cloud-001local-hostname: ubuntu-cloudEOFPourquoi l'instance-id est crucial ? Cloud-init utilise cet ID pour savoir si la VM a déjà été configurée. Si vous détruisez la VM et la recréez avec le même instance-id, cloud-init ne se ré-exécutera pas (il pense que c'est la même instance). Changez l'ID pour forcer une nouvelle exécution.
-
Générer le seed ISO
Le seed ISO est une mini-image ISO qui contient vos fichiers user-data et meta-data. Cloud-init le détecte automatiquement au boot.
Fenêtre de terminal cloud-localds /tmp/seed.iso /tmp/user-data /tmp/meta-datasudo cp /tmp/seed.iso /var/lib/libvirt/images/Sortie réelle :
$ ls -la /tmp/seed.iso-rw-rw-r-- 1 bob libvirt 374784 janv. 31 18:35 /tmp/seed.iso~370 Ko, c'est léger ! L'ISO contient juste vos fichiers de config.
-
Créer le disque VM (backing file)
Au lieu de copier l'image cloud (600 Mo), on crée un disque qui pointe vers elle. C'est le principe du backing file :
Fenêtre de terminal sudo qemu-img create -f qcow2 -F qcow2 \-b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \/var/lib/libvirt/images/ubuntu-cloud.qcow2 20GComment ça marche ?
- L'image cloud reste intacte (lecture seule)
- Le nouveau disque
ubuntu-cloud.qcow2ne stocke que les différences - Résultat : création instantanée, économie d'espace disque
L'image cloud reste intacte et réutilisable : la personnalisation vit dans un fichier séparé, lu au premier démarrage, ce qui permet de dériver des dizaines de machines différentes d'une seule image.
-
Créer la VM avec virt-install
On assemble tout : le disque VM + le seed ISO. L'option
--importdit à virt-install de démarrer directement sans chercher à installer quoi que ce soit.Fenêtre de terminal virt-install \--name ubuntu-cloud \--memory 2048 \--vcpus 2 \--disk /var/lib/libvirt/images/ubuntu-cloud.qcow2 \--disk /var/lib/libvirt/images/seed.iso,device=cdrom \--network network=default \--os-variant ubuntu24.04 \--import \--graphics vnc \--noautoconsoleDécryptage des options clés :
Option Ce qu'elle fait Pourquoi c'est important --disk ...seed.iso,device=cdromMonte le seed ISO comme CD-ROM virtuel Cloud-init le détecte automatiquement --importDémarre sans installation L'image cloud est déjà prête --noautoconsoleRetourne au prompt immédiatement La VM continue en arrière-plan -
Attendre et se connecter
Cloud-init s'exécute au premier boot. Comptez 30-60 secondes pour qu'il :
- Configure le réseau (DHCP)
- Crée l'utilisateur
- Installe les packages
- Exécute vos commandes
Récupérez l'IP avec :
Fenêtre de terminal virsh net-dhcp-leases defaultSortie réelle :
Expiry Time MAC address Protocol IP address Hostname------------------------------------------------------------------------------------2026-01-31 19:40:02 52:54:00:49:47:a3 ipv4 192.168.122.91/24 ubuntu-cloudLecture : la VM
ubuntu-clouda obtenu l'IP192.168.122.91via DHCP.Connectez-vous :
Fenêtre de terminal ssh devops@192.168.122.91
Vérifier que cloud-init a fonctionné
Section intitulée « Vérifier que cloud-init a fonctionné »C'est le moment de vérité ! Testons chaque élément de notre configuration :
Test 1 : Connexion SSH + utilisateur + sudo
Section intitulée « Test 1 : Connexion SSH + utilisateur + sudo »Ce premier test valide d'un coup trois directives du user-data. En enchaînant hostname, whoami et sudo whoami sur une seule ligne SSH, vous vérifiez que le nom de machine, la création d'utilisateur et les droits sudo sans mot de passe ont tous été appliqués. Si le SSH lui-même aboutit, c'est aussi que votre clé publique a bien été déployée.
ssh devops@192.168.122.91 "hostname && whoami && sudo whoami"Sortie réelle :
ubuntu-clouddevopsrootVerdict :
hostnamevautubuntu-cloud, la directivehostname:est appliquéewhoamivautdevops, l'utilisateur est créésudo whoamivautrootsans demande de mot de passe, le sudo NOPASSWD est configuré
Test 2 : Packages installés
Section intitulée « Test 2 : Packages installés »ssh devops@192.168.122.91 "which vim htop curl"Sortie réelle :
/usr/bin/vim/usr/bin/htop/usr/bin/curlLes 3 paquets de la directive packages: sont bien présents dans le PATH.
Test 3 : Commande runcmd exécutée
Section intitulée « Test 3 : Commande runcmd exécutée »ssh devops@192.168.122.91 "cat /var/log/cloud-init-done.log"Sortie réelle :
Cloud-init terminé à Sat Jan 31 17:40:13 UTC 2026La commande runcmd: a bien créé le fichier avec la date du premier boot.
Test 4 : Statut global de cloud-init
Section intitulée « Test 4 : Statut global de cloud-init »ssh devops@192.168.122.91 "sudo cloud-init status"Sortie réelle :
status: donedone = tout s'est bien passé. Si vous voyez error, consultez la section Déboguer plus bas.
Comprendre le fichier user-data en détail
Section intitulée « Comprendre le fichier user-data en détail »Maintenant que vous avez vu cloud-init fonctionner, détaillons les directives les plus utiles :
Gestion des utilisateurs
Section intitulée « Gestion des utilisateurs »Le bloc users est celui que vous ajusterez le plus souvent. Deux options méritent votre vigilance : lock_passwd: false réautorise l'authentification par mot de passe, à ne faire que si vous en avez besoin en console VNC, et groups place l'utilisateur dans des groupes sensibles comme docker, qui équivaut à un accès root sur l'hôte du conteneur. Privilégiez toujours ssh_authorized_keys comme moyen d'accès principal.
users: - name: devops # Nom de l'utilisateur sudo: ALL=(ALL) NOPASSWD:ALL # Droits sudo sans mot de passe groups: sudo, docker # Groupes supplémentaires shell: /bin/bash # Shell par défaut lock_passwd: false # Autoriser l'auth par mot de passe ssh_authorized_keys: # Clés SSH autorisées - ssh-ed25519 AAAAC3... - ssh-rsa AAAAB3... # Plusieurs clés possiblesGénérer un mot de passe hashé
Section intitulée « Générer un mot de passe hashé »Pour définir un mot de passe utilisateur (en plus de la clé SSH) :
# Installer mkpasswdsudo apt install whois
# Générer un hash SHA-512mkpasswd --method=SHA-512 --rounds=4096 "votremotdepasse"Sortie :
$6$rounds=4096$2v2qIMF2mpxo0e2J$cHMrDN7oMGUBwQ...Utilisez ce hash dans user-data :
users: - name: devops lock_passwd: false passwd: $6$rounds=4096$2v2qIMF2mpxo0e2J$cHMrDN7oMGUBwQ... ssh_authorized_keys: - ssh-ed25519 AAAAC3...Écrire des fichiers de configuration
Section intitulée « Écrire des fichiers de configuration »La directive write_files permet de déposer des fichiers sur le système :
write_files: - path: /etc/motd content: | ==================================== Serveur configuré par cloud-init Ne pas modifier manuellement ! ==================================== permissions: '0644' owner: root:rootExécuter des commandes
Section intitulée « Exécuter des commandes »Deux directives, deux moments différents :
| Directive | Quand | Fréquence | Usage |
|---|---|---|---|
bootcmd | Très tôt (avant réseau) | Chaque boot | Config système bas niveau |
runcmd | À la fin | Premier boot seulement | Installation, scripts |
# S'exécute une seule fois, à la finruncmd: - systemctl enable nginx - systemctl start nginx - echo "Setup terminé" | loggerExemple complet : user-data de production
Section intitulée « Exemple complet : user-data de production »Voici un user-data réaliste pour un serveur web :
#cloud-config
# Hostnamehostname: web-prod-01fqdn: web-prod-01.example.com
# Utilisateursusers: - name: deploy sudo: ALL=(ALL) NOPASSWD:ALL groups: sudo, docker shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... admin@laptop - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... ci-runner
# Désactiver l'utilisateur par défaut (ubuntu, debian...)disable_root: true
# Packagespackage_update: truepackage_upgrade: truepackages: - nginx - certbot - python3-certbot-nginx - fail2ban - ufw
# Fichiers de configurationwrite_files: - path: /etc/ssh/sshd_config.d/hardening.conf content: | PasswordAuthentication no PermitRootLogin no MaxAuthTries 3 permissions: '0600'
# Commandes finalesruncmd: - ufw allow 22/tcp - ufw allow 80/tcp - ufw allow 443/tcp - ufw --force enable - systemctl enable nginx fail2ban - systemctl start nginx fail2ban - echo "Provisioning complete" | logger -t cloud-init-customSupprimer le seed ISO après configuration
Section intitulée « Supprimer le seed ISO après configuration »Une fois cloud-init exécuté, vous pouvez éjecter le CD-ROM :
virsh change-media ubuntu-cloud sda --ejectOu modifier la VM pour supprimer le disque seed :
virsh detach-disk ubuntu-cloud sda --configErreurs fréquentes
Section intitulée « Erreurs fréquentes »La quasi-totalité des échecs de provisionnement cloud-init se ramènent aux cinq cas ci-dessous. Deux reviennent sans cesse : la ligne #cloud-config oubliée, qui fait silencieusement ignorer tout le fichier, et le même instance-id réutilisé, qui empêche cloud-init de rejouer sur une VM recréée. Commencez toujours votre diagnostic par cloud-init status, avant de suspecter votre YAML.
| Problème | Cause | Solution |
|---|---|---|
| SSH refused | Cloud-init pas terminé | Attendre 60s, vérifier cloud-init status |
| Permission denied | Clé SSH incorrecte | Vérifier la clé publique dans user-data |
| Hostname pas changé | #cloud-config manquant | Première ligne obligatoire |
| Packages pas installés | Erreur réseau | Vérifier cloud-init status --long |
| Cloud-init ne se relance pas | Même instance-id | Changer l'instance-id dans meta-data |
Déboguer cloud-init
Section intitulée « Déboguer cloud-init »Logs complets
Section intitulée « Logs complets »Ce fichier capture la sortie standard et les erreurs de tout ce que cloud-init a lancé, y compris vos runcmd. C'est le premier endroit où regarder quand un paquet ne s'installe pas ou qu'un script échoue : le message d'erreur exact du programme fautif s'y trouve, ligne par ligne.
ssh devops@IP "sudo cat /var/log/cloud-init-output.log"Statut détaillé
Section intitulée « Statut détaillé »Là où cloud-init status répond simplement done ou error, la variante --long précise quel module a échoué et quel datasource a été utilisé. La ligne DataSourceNoCloud confirme que votre seed ISO a bien été détecté, un point à vérifier en priorité si la configuration ne s'applique pas.
ssh devops@IP "sudo cloud-init status --long"Sortie si OK :
status: doneextended_status: doneboot_status_code: enabled-by-generatordetail:DataSourceNoCloud [seed=/dev/sr0][dsmode=net]Forcer une ré-exécution (debug)
Section intitulée « Forcer une ré-exécution (debug) »# Sur la VMsudo cloud-init clean --logssudo rebootÀ retenir
Section intitulée « À retenir »-
Image cloud + seed ISO = VM configurée sans installation manuelle
-
user-data contient votre config (users, SSH, packages, scripts)
-
meta-data contient l'instance-id (doit être unique)
-
cloud-localds génère le seed ISO à partir de user-data + meta-data
-
virt-install --import démarre la VM directement depuis l'image cloud
-
Cloud-init s'exécute une seule fois au premier boot (sauf si instance-id change)
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Snapshots, clones et backups : Une VM produite par cloud-init sert de gabarit, encore faut-il la cloner sans dupliquer son identité.
- Terraform + libvirt : Le même user-data devient une ressource
libvirt_cloudinit_diskversionnée avec l'infrastructure. - Dépannage réseau KVM/libvirt : Utile quand la VM démarre mais que cloud-init ne récupère ni bail DHCP ni DNS.
Références
Section intitulée « Références »- cloud-init Documentation, Documentation officielle
- NoCloud Datasource, Le datasource utilisé avec libvirt
- Ubuntu Cloud Images, Images officielles Ubuntu