Aller au contenu
Virtualisation medium

Cloud-init avec KVM : VMs prêtes en 2 minutes

16 min de lecture

Logo KVM

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.

  • 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

Sans cloud-init, créer une VM demande :

  1. Télécharger une ISO (~2 Go)
  2. Lancer l’installateur (~15-30 min)
  3. Répondre aux questions (langue, partitions, utilisateur…)
  4. Installer les packages manuellement
  5. Configurer SSH, sudo, etc.

Temps total : 30-60 minutes par VM. Et si vous devez en créer 10 ? 🤯

  1. Télécharger une image cloud (~600 Mo, une seule fois)
  2. Écrire votre config dans un fichier YAML (1 minute)
  3. 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).

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Image cloud │ + │ Seed ISO │ = │ VM configurée │
│ (Ubuntu, ...) │ │ (user-data) │ │ prête à SSH │
└─────────────────┘ └─────────────────┘ └─────────────────┘
ÉlémentCe que c’estAnalogie
Image cloudDisque pré-installé (OS minimal)Le plat préparé
user-dataVotre configuration (users, SSH, packages)Vos épices et ingrédients
meta-dataIdentité de la VM (hostname, instance-id)L’étiquette du plat
  1. La VM démarre sur l’image cloud
  2. Cloud-init détecte le seed ISO (monté comme CD-ROM)
  3. Il lit user-data et meta-data
  4. Il applique la configuration : crée les users, installe les packages, exécute vos scripts
  5. Résultat : VM prête à l’emploi, accessible en SSH
Fenêtre de terminal
sudo apt install cloud-image-utils

Ce paquet fournit cloud-localds, l’outil pour créer le seed ISO.

  1. 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/images
    sudo wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

    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.

  2. 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-config
    hostname: ubuntu-cloud
    users:
    - name: devops
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo
    shell: /bin/bash
    ssh_authorized_keys:
    - ssh-ed25519 AAAAC3Nza... votre-cle-publique
    package_update: true
    packages:
    - vim
    - htop
    - curl
    runcmd:
    - echo "Cloud-init terminé à $(date)" > /var/log/cloud-init-done.log
    EOF

    Décryptage ligne par ligne :

    DirectiveCe 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 update avant d’installer
    packages:Liste des paquets à installer
    runcmd:Commandes à exécuter à la fin
  3. 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-001
    local-hostname: ubuntu-cloud
    EOF

    Pourquoi 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.

  4. 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-data
    sudo 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.

  5. 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 20G

    Comment ça marche ?

    • L’image cloud reste intacte (lecture seule)
    • Le nouveau disque ubuntu-cloud.qcow2 ne 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.

  6. Créer la VM avec virt-install

    On assemble tout : le disque VM + le seed ISO. L’option --import dit à 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 \
    --noautoconsole

    Décryptage des options clés :

    OptionCe qu’elle faitPourquoi c’est important
    --disk ...seed.iso,device=cdromMonte le seed ISO comme CD-ROM virtuelCloud-init le détecte automatiquement
    --importDémarre sans installationL’image cloud est déjà prête
    --noautoconsoleRetourne au prompt immédiatementLa VM continue en arrière-plan
  7. 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 default

    Sortie 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-cloud

    Lecture : la VM ubuntu-cloud a obtenu l’IP 192.168.122.91 via DHCP.

    Connectez-vous :

    Fenêtre de terminal
    ssh devops@192.168.122.91

C’est le moment de vérité ! Testons chaque élément de notre configuration :

Fenêtre de terminal
ssh devops@192.168.122.91 "hostname && whoami && sudo whoami"

Sortie réelle :

ubuntu-cloud
devops
root

Verdict :

  • hostname = ubuntu-cloud → directive hostname: appliquée
  • whoami = devops → utilisateur créé
  • sudo whoami = root sans demander de mot de passe → sudo NOPASSWD configuré
Fenêtre de terminal
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.

Fenêtre de terminal
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.

Fenêtre de terminal
ssh devops@192.168.122.91 "sudo cloud-init status"

Sortie réelle :

status: done

done = tout s’est bien passé. Si vous voyez error, consultez la section Déboguer plus bas.

Maintenant que vous avez vu cloud-init fonctionner, détaillons les directives les plus utiles :

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 possibles

Pour définir un mot de passe utilisateur (en plus de la clé SSH) :

Fenêtre de terminal
# Installer mkpasswd
sudo apt install whois
# Générer un hash SHA-512
mkpasswd --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...

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:root

Deux directives, deux moments différents :

DirectiveQuandFréquenceUsage
bootcmdTrès tôt (avant réseau)Chaque bootConfig système bas niveau
runcmdÀ la finPremier boot seulementInstallation, scripts
# S'exécute une seule fois, à la fin
runcmd:
- systemctl enable nginx
- systemctl start nginx
- echo "Setup terminé" | logger

Voici un user-data réaliste pour un serveur web :

#cloud-config
# Hostname
hostname: web-prod-01
fqdn: web-prod-01.example.com
# Utilisateurs
users:
- 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
# Packages
package_update: true
package_upgrade: true
packages:
- nginx
- certbot
- python3-certbot-nginx
- fail2ban
- ufw
# Fichiers de configuration
write_files:
- path: /etc/ssh/sshd_config.d/hardening.conf
content: |
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
permissions: '0600'
# Commandes finales
runcmd:
- 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-custom

Une fois cloud-init exécuté, vous pouvez éjecter le CD-ROM :

Fenêtre de terminal
virsh change-media ubuntu-cloud sda --eject

Ou modifier la VM pour supprimer le disque seed :

Fenêtre de terminal
virsh detach-disk ubuntu-cloud sda --config
ProblèmeCauseSolution
SSH refusedCloud-init pas terminéAttendre 60s, vérifier cloud-init status
Permission deniedClé SSH incorrecteVérifier la clé publique dans user-data
Hostname pas changé#cloud-config manquantPremière ligne obligatoire
Packages pas installésErreur réseauVérifier cloud-init status --long
Cloud-init ne se relance pasMême instance-idChanger l’instance-id dans meta-data
Fenêtre de terminal
ssh devops@IP "sudo cat /var/log/cloud-init-output.log"
Fenêtre de terminal
ssh devops@IP "sudo cloud-init status --long"

Sortie si OK :

status: done
extended_status: done
boot_status_code: enabled-by-generator
detail:
DataSourceNoCloud [seed=/dev/sr0][dsmode=net]
Fenêtre de terminal
# Sur la VM
sudo cloud-init clean --logs
sudo reboot
  1. Image cloud + seed ISO = VM configurée sans installation manuelle

  2. user-data contient votre config (users, SSH, packages, scripts)

  3. meta-data contient l’instance-id (doit être unique)

  4. cloud-localds génère le seed ISO à partir de user-data + meta-data

  5. virt-install —import démarre la VM directement depuis l’image cloud

  6. Cloud-init s’exécute une seule fois au premier boot (sauf si instance-id change)

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.