
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 gain n'est pas un gain de confort, c'est un changement de nature : la configuration d'une machine passe d'une suite de gestes à un fichier versionnable. Les deux sections ci-dessous fabriquent la même VM, la première à la main depuis une ISO, la seconde à partir d'une image déjà installée.
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 curl -fLO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.imgsudo curl -fLO https://cloud-images.ubuntu.com/noble/current/SHA256SUMSsha256sum --check --ignore-missing SHA256SUMSNe sautez pas la vérification d'empreinte. Cette image devient le système de fichiers racine de toutes les VM que vous en dériverez : une image altérée se propage à chaque machine créée ensuite. La sortie attendue est
noble-server-cloudimg-amd64.img: OK, ou: Réussien français. Canonical publie aussiSHA256SUMS.gpgau même endroit, pour vérifier la signature du fichier de sommes lui-même.Pourquoi 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 s'en sert pour décider s'il est sur une machine neuve. Il ne compare pas cet identifiant dans l'absolu : il le confronte à celui qu'il a mémorisé dans
/var/lib/cloud/sur le disque. Deux situations, souvent confondues, en découlent.Si vous repartez d'une image cloud vierge, ce cache n'existe pas : c'est un premier démarrage, et tout rejoue, même en réutilisant le même
instance-id. Si vous redémarrez un disque déjà provisionné en gardant le même identifiant, cloud-init reconnaît la même instance et ne rejoue pas les modules marqués once-per-instance. C'est ce second cas, et lui seul, que l'on débloque en changeant l'identifiant. -
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 de base.
-
Créer la VM avec virt-install
On assemble tout : le disque VM et le seed ISO. L'option
--importdit àvirt-installde 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é »Une VM qui répond au ping ne prouve rien : cloud-init peut avoir démarré, buté sur un user-data invalide et s'être arrêté en chemin sans que personne ne le remarque. Les quatre tests ci-dessous se lancent depuis votre poste en moins d'une minute et contrôlent chacun une directive précise du fichier. Le dernier, cloud-init status, est celui qui tranche : il donne le verdict global que les trois autres ne font que confirmer.
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 le 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 »Ce test contrôle la directive packages:, celle qui échoue le plus discrètement. which ne répond que pour les binaires présents dans le PATH : une ligne absente de la sortie signale un paquet non installé, pas un simple alias manquant. Quand il en manque un, la cause est presque toujours un échec réseau au premier boot, et cloud-init status --long le confirme.
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 »La directive runcmd s'exécute en toute fin de séquence, une fois les utilisateurs créés et les paquets installés. Retrouver le fichier qu'elle écrit prouve donc que cloud-init est allé jusqu'au bout de votre configuration, et pas seulement qu'il a démarré. La date inscrite dans ce fichier est celle du premier boot : elle ne bougera plus aux démarrages suivants, puisque runcmd ne rejoue pas.
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 »Les trois tests précédents observent des effets ; celui-ci interroge cloud-init lui-même. Tant que la séquence n'est pas terminée, le statut n'est pas encore done, ce qui explique les faux négatifs quand on interroge la VM trop tôt après son démarrage. C'est la commande à lancer en premier lors d'un diagnostic, avant de soupçonner votre YAML.
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é »La directive passwd: attend un hash, jamais un mot de passe en clair : le user-data est recopié dans le seed ISO et dans /var/lib/cloud/ sur la VM, deux emplacements lisibles après coup. L'outil mkpasswd, fourni par le paquet whois sur Debian et Ubuntu, produit le hash SHA-512 attendu par /etc/shadow. Le mot de passe reste un complément : la clé SSH demeure le moyen d'accès de référence.
# 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 dépose un fichier complet sur le système, contenu, permissions et propriétaire compris, sans passer par un script. Elle s'applique avant runcmd, ce qui permet d'écrire une configuration puis de démarrer le service qui la lit dans la foulée. Quotez toujours le mode ('0644') : sans les guillemets, YAML n'y voit plus une chaîne et les droits obtenus ne sont pas ceux que vous avez écrits.
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 lancent des commandes, et les confondre coûte cher. bootcmd intervient très tôt dans la séquence de démarrage et rejoue à chaque boot : tout ce qu'il contient doit donc supporter d'être exécuté des dizaines de fois. runcmd intervient à la fin, réseau disponible, et une seule fois pour l'instance. Installer un paquet ou démarrer un service relève de runcmd ; préparer un disque ou une interface avant le reste du système relève de bootcmd.
| 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 »Ce fichier rassemble les directives vues plus haut dans l'ordre où cloud-init les traite, et il tient en une page. Trois choix méritent d'être relevés : disable_root: true interdit la connexion SSH directe en root, le durcissement SSH passe par un fichier déposé dans sshd_config.d/ plutôt que par une retouche du fichier principal, et le pare-feu n'est activé qu'après l'ouverture du port 22, sans quoi la VM se couperait d'elle-même. Remplacez les clés publiques avant de le rejouer : sans elles, la machine démarre inaccessible.
#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 »Le seed ISO porte votre user-data en clair, clés publiques et éventuel hash de mot de passe compris, et il reste attaché à la VM tant que vous ne le retirez pas. L'enlever une fois la configuration appliquée réduit cette exposition. Les deux commandes ne font pas la même chose : virsh change-media --eject sort le média du lecteur, virsh detach-disk --config supprime le lecteur de la définition XML, donc de façon permanente.
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é sur un disque déjà provisionné, qui empêche cloud-init de rejouer. 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 | Disque déjà provisionné, même instance-id | Changer l'instance-id, ou cloud-init clean |
Déboguer cloud-init
Section intitulée « Déboguer cloud-init »Deux sources d'information suffisent à traiter presque tous les cas, et elles ne disent pas la même chose. Le journal /var/log/cloud-init-output.log contient ce que vos commandes ont affiché ; le statut rendu par cloud-init status --long dit où cloud-init en est et quel module a échoué. Commencez par le statut, il oriente vers la bonne partie du journal.
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) »cloud-init clean efface l'état conservé dans /var/lib/cloud/, y compris l'instance-id déjà traité. Au redémarrage suivant, cloud-init se croit sur une machine neuve et rejoue l'intégralité du user-data : c'est le moyen de tester une modification sans recréer la VM. L'option --logs supprime en plus les journaux, pour repartir d'une trace lisible. Les utilisateurs et les paquets déjà créés, eux, ne sont pas retirés.
# Sur la VMsudo cloud-init clean --logssudo rebootContrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
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
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À 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 par instance : un disque neuf rejoue toujours, un disque déjà provisionné non
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