
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 ? 🤯
Avec cloud-init
Section intitulée « Avec cloud-init »- 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
Analogie : c’est comme la différence entre cuisiner un plat de zéro (ISO) et réchauffer un plat préparé en ajoutant vos épices (image cloud + config).
Le principe en détail
Section intitulée « Le principe en détail »┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│ Image cloud │ + │ Seed ISO │ = │ VM configurée ││ (Ubuntu, ...) │ │ (user-data) │ │ prête à SSH │└─────────────────┘ └─────────────────┘ └─────────────────┘Les 3 ingrédients
Section intitulée « Les 3 ingrédients »| Élément | Ce que c’est | Analogie |
|---|---|---|
| Image cloud | Disque pré-installé (OS minimal) | Le plat préparé |
| user-data | Votre configuration (users, SSH, packages) | Vos épices et ingrédients |
| meta-data | Identité de la VM (hostname, instance-id) | L’étiquette du plat |
Comment ça marche au boot
Section intitulée « Comment ça marche au boot »- 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
Analogie : c’est comme un calque transparent sur une photo. La photo (image cloud) ne change pas, vos modifications sont sur le calque.
-
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 »ssh devops@192.168.122.91 "hostname && whoami && sudo whoami"Sortie réelle :
ubuntu-clouddevopsrootVerdict :
- ✅
hostname=ubuntu-cloud→ directivehostname:appliquée - ✅
whoami=devops→ utilisateur créé - ✅
sudo whoami=rootsans demander de mot de passe → sudo NOPASSWD 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/curl✅ Les 3 packages de la directive packages: sont installés.
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 2026✅ La commande runcmd: a bien créé le fichier avec la date.
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 »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 »| 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 »ssh devops@IP "sudo cat /var/log/cloud-init-output.log"Statut détaillé
Section intitulée « Statut détaillé »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)
Prochaines étapes
Section intitulée « Prochaines étapes »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