Aller au contenu
Cloud medium

Cloud-init : automatiser la configuration des VMs cloud

17 min de lecture

Cloud-init automatise la configuration de vos VMs cloud au premier démarrage. Vous définissez vos utilisateurs, clés SSH, paquets et scripts dans un fichier YAML appelé user-data, et cloud-init les applique sans intervention manuelle.

Ce guide vous apprend à créer un user-data fonctionnel, à le tester localement avec Incus, et à déboguer quand ça ne marche pas. Vous comprendrez aussi une distinction cruciale : user-data ≠ configuration réseau.

Cloud-init est un outil d’initialisation, pas de gestion de configuration continue. Il s’exécute au premier démarrage de l’instance et ne se relance pas automatiquement ensuite — contrairement à Ansible ou Puppet qui peuvent tourner en boucle.

Concrètement, cloud-init peut :

  • Créer des utilisateurs avec leurs clés SSH et droits sudo
  • Installer des paquets via apt, yum, dnf ou zypper
  • Écrire des fichiers de configuration sur le disque
  • Exécuter des commandes à la fin de l’initialisation
  • Configurer le hostname et d’autres paramètres système

En revanche, cloud-init ne peut pas modifier la configuration réseau via user-data dans la plupart des cas — c’est un point qui crée beaucoup de confusion.

Cloud-init organise son exécution en 5 stages distincts. Comprendre cette séquence vous aidera à déboguer quand vos scripts ne s’exécutent pas au bon moment.

Les 5 boot stages de cloud-init

Ce premier stage détermine si cloud-init doit s’exécuter et quel datasource utiliser. Le script ds-identify cherche des indices (fichiers, métadonnées) pour identifier l’environnement cloud (AWS, GCP, Azure, OpenStack, NoCloud…).

Si aucun datasource n’est détecté, cloud-init peut être désactivé automatiquement. C’est pourquoi sur certaines images “non-cloud”, cloud-init ne fait rien.

Ce stage s’exécute avant que le réseau ne soit configuré. Cloud-init applique la configuration réseau (depuis le datasource, pas depuis user-data) et exécute les commandes bootcmd si présentes.

C’est ici que se joue la configuration des interfaces réseau. À la fin de ce stage, le réseau devrait être opérationnel.

Une fois le réseau actif, cloud-init récupère les données depuis le datasource : metadata, vendor-data et user-data. Les modules “cloud_init_modules” s’exécutent — ils préparent le terrain pour la configuration principale.

C’est le cœur de l’action. Cloud-init exécute les modules “cloud_config_modules” qui appliquent votre user-data :

  • Création des utilisateurs et groupes
  • Configuration SSH
  • Installation des paquets (packages:)
  • Écriture de fichiers (write_files:)

Le dernier stage exécute les modules “cloud_final_modules”, notamment :

  • runcmd : vos commandes personnalisées
  • scripts-user : scripts dans /var/lib/cloud/scripts/
  • phone-home : notification vers un serveur externe

C’est seulement à ce stage que vos commandes runcmd s’exécutent — si vous attendez qu’un service soit configuré avant de lancer runcmd, c’est normal.

Un fichier user-data commence obligatoirement par #cloud-config (sans espace) sur la première ligne. Le reste est du YAML standard.

#cloud-config
# Utilisateurs à créer
users:
- name: deploy
groups: sudo
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
sudo: ALL=(ALL) NOPASSWD:ALL
# Mise à jour des paquets existants
package_update: true
package_upgrade: true
# Paquets à installer
packages:
- nginx
- git
- htop
# Fichiers à créer
write_files:
- path: /etc/motd
content: |
Bienvenue sur ce serveur configuré par cloud-init!
permissions: '0644'
# Commandes à exécuter (stage Final)
runcmd:
- systemctl enable nginx
- systemctl start nginx

Voici les directives que vous utiliserez dans 90% des cas :

  • users crée des utilisateurs avec leurs clés SSH. Chaque utilisateur peut avoir groups, shell, ssh_authorized_keys et sudo.
  • packages liste les paquets à installer. Cloud-init détecte automatiquement le gestionnaire de paquets (apt, yum, dnf…).
  • package_update et package_upgrade mettent à jour la liste des paquets et les paquets installés avant d’en installer de nouveaux.
  • write_files crée des fichiers avec un contenu spécifique. Utile pour déposer des configurations. runcmd exécute des commandes shell après que tout le reste soit configuré. Attention : ces commandes s’exécutent en tant que root.

Ne déployez jamais un user-data sans l’avoir validé. Cloud-init fournit une commande de validation qui détecte les erreurs de syntaxe YAML et les directives inconnues.

Pour valider vos fichiers user-data avant de les déployer, installez cloud-init avec pipx (installation isolée, sans polluer votre système) :

Fenêtre de terminal
# Installer pipx si nécessaire
# Linux: sudo apt install pipx && pipx ensurepath
# macOS: brew install pipx && pipx ensurepath
pipx install cloud-init

Vérifiez l’installation :

Fenêtre de terminal
cloud-init --version
cloud-init schema -c user-data.yaml --annotate

La commande cloud-init schema -c fichier.yaml --annotate fonctionne sans sudo et ne nécessite pas que cloud-init soit “actif” sur le système. C’est parfait pour valider vos fichiers depuis votre poste de développement. Cette installation avec pipx permet de valider la syntaxe, mais pas d’exécuter cloud-init complètement. Pour tester l’exécution réelle, utilisez Incus ou Multipass (voir section “Tester avec Incus”).

Fenêtre de terminal
cloud-init schema -c user-data.yaml --annotate

Le flag --annotate ajoute des commentaires utiles en cas d’erreur, indiquant exactement quelle ligne pose problème.

Si tout va bien :

Valid schema user-data.yaml

Si erreur :

user-data.yaml:12: 'package' is not a valid keyword (did you mean 'packages'?)

Validation sur une instance en cours d’exécution

Section intitulée « Validation sur une instance en cours d’exécution »

Pour vérifier que l’instance a bien reçu et interprété votre user-data :

Fenêtre de terminal
sudo cloud-init schema --system --annotate

Cette commande valide le user-data tel qu’il a été reçu par l’instance — utile pour déboguer les problèmes de transmission.

Incus (fork de LXD) est parfait pour tester vos configurations cloud-init sans consommer de ressources cloud. Les conteneurs Incus avec images /cloud supportent nativement cloud-init.

Fenêtre de terminal
incus image list images: architecture=x86_64 /cloud -c lat | head -20

Vous verrez des images comme ubuntu/noble/cloud, debian/bookworm/cloud, almalinux/9/cloud — ce sont celles qui incluent cloud-init.

  1. Créez votre fichier user-data

    user-data.yaml
    #cloud-config
    users:
    - name: testuser
    groups: sudo
    shell: /bin/bash
    ssh_authorized_keys:
    - ssh-ed25519 AAAAC3...votre-clé...
    sudo: ALL=(ALL) NOPASSWD:ALL
    package_update: true
    packages:
    - curl
    - vim
    runcmd:
    - touch /tmp/cloud-init-complete
  2. Lancez le conteneur avec le user-data

    Fenêtre de terminal
    incus launch images:ubuntu/noble/cloud test-cloud-init \
    --config=cloud-init.user-data="$(cat user-data.yaml)"

    Notez l’espace entre test-cloud-init et --config — c’est une erreur de syntaxe courante.

  3. Attendez que cloud-init termine

    Fenêtre de terminal
    incus exec test-cloud-init -- cloud-init status --wait

    Résultat attendu :

    status: done
  4. Vérifiez que tout est configuré

    Fenêtre de terminal
    # L'utilisateur existe ?
    incus exec test-cloud-init -- id testuser
    # Les paquets sont installés ?
    incus exec test-cloud-init -- which curl
    # Le fichier témoin existe ?
    incus exec test-cloud-init -- ls /tmp/cloud-init-complete

Utiliser un profil Incus pour réutiliser la config

Section intitulée « Utiliser un profil Incus pour réutiliser la config »

Si vous testez souvent, créez un profil plutôt que de passer le user-data à chaque fois :

Fenêtre de terminal
# Créer le profil
incus profile create cloud-init-test
# Éditer pour ajouter le user-data
incus profile edit cloud-init-test

Ajoutez dans le YAML du profil :

config:
cloud-init.user-data: |
#cloud-config
users:
- name: deploy
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
package_update: true
packages:
- nginx

Puis lancez avec le profil :

Fenêtre de terminal
incus launch images:ubuntu/noble/cloud mon-test \
--profile default --profile cloud-init-test

Si vous avez besoin de configurer le réseau (IP statique par exemple), utilisez cloud-init.network-config :

Fenêtre de terminal
incus config set mon-conteneur cloud-init.network-config - << 'EOF'
version: 2
ethernets:
eth0:
dhcp4: false
addresses:
- 10.0.0.50/24
gateway4: 10.0.0.1
nameservers:
addresses:
- 8.8.8.8
EOF

Ces recettes couvrent les cas d'usage les plus fréquents. Cliquez sur un pattern pour voir la formule complète et un exemple prêt à copier.

Valider un fichier user-data Base

Vérifier la syntaxe et les directives avant déploiement

cloud-init schema -c user-data.yaml --annotate
Formule cloud-init schema -c FICHIER --annotate
Exemple
cloud-init schema -c user-data.yaml --annotate
Paramètres
  • FICHIER — Chemin vers le fichier user-data
Vérifier l'état d'exécution Base

Savoir si cloud-init a terminé et s'il y a des erreurs

cloud-init status --long
Formule cloud-init status --long
Exemple
cloud-init status --long
Consulter les logs Base

Voir ce que cloud-init a fait et les erreurs

sudo grep -i error /var/log/cloud-init.log
Formule sudo less /var/log/cloud-init.log
Exemple
sudo grep -i error /var/log/cloud-init.log
Analyser les performances Base

Identifier les modules lents

cloud-init analyze blame | head -10
Formule cloud-init analyze blame
Exemple
cloud-init analyze blame | head -10
Forcer une ré-exécution Base

Supprimer l'état pour rejouer cloud-init au prochain boot

sudo cloud-init clean --logs && sudo reboot
Formule sudo cloud-init clean && sudo reboot
Exemple
sudo cloud-init clean --logs && sudo reboot
Tester avec Incus Base

Lancer un conteneur avec user-data pour test local

incus launch images:ubuntu/noble/cloud test --config=cloud-init.user-data="$(cat user-data.yaml)"
Formule incus launch images:ubuntu/noble/cloud NOM --config=cloud-init.user-data="$(cat FICHIER)"
Exemple
incus launch images:ubuntu/noble/cloud test --config=cloud-init.user-data="$(cat user-data.yaml)"
Paramètres
  • NOM — Nom du conteneur
  • FICHIER — Fichier user-data
Attendre la fin de cloud-init Base

Bloquer jusqu'à ce que cloud-init termine

incus exec test -- cloud-init status --wait
Formule incus exec CONTENEUR -- cloud-init status --wait
Exemple
incus exec test -- cloud-init status --wait
Paramètres
  • CONTENEUR — Nom du conteneur Incus
Rejouer un module spécifique Base

Exécuter un seul module sans rebooter

sudo cloud-init single --name cc_runcmd
Formule sudo cloud-init single --name MODULE
Exemple
sudo cloud-init single --name cc_runcmd
Paramètres
  • MODULE — Nom du module (cc_runcmd, cc_write_files...)

Ces erreurs courantes peuvent faire perdre du temps ou causer des dégâts. Les pièges les plus critiques sont affichés en premier.

Mettre la config réseau dans user-data

runcmd:\n - ip addr add 10.0.0.50/24 dev eth0

Danger
Le piège : runcmd:\n - ip addr add 10.0.0.50/24 dev eth0
Symptôme : Le réseau ne se configure pas, ou se réinitialise au boot suivant
Cause : user-data ne peut PAS configurer le réseau — c'est une source séparée
Correction : Utiliser cloud-init.network-config (Incus) ou le metadata service du provider
Secrets dans user-data

write_files:\n - path: /etc/app/secrets.env\n content: API_KEY=sk-xxx

Danger
Le piège : write_files:\n - path: /etc/app/secrets.env\n content: API_KEY=sk-xxx
Symptôme : Secrets exposés via le metadata service (lisible par toute la VM)
Cause : user-data est accessible via http://169.254.169.254/latest/user-data
Correction : Utiliser Vault, AWS Secrets Manager, ou récupérer les secrets au runtime
Oublier #cloud-config

users:\n - name: deploy

Attention
Le piège : users:\n - name: deploy
Symptôme : Fichier ignoré, aucune configuration appliquée
Cause : Sans #cloud-config en première ligne, cloud-init ne reconnaît pas le format
Correction : Toujours commencer par #cloud-config (sans espace)
#cloud-config\nusers:\n  - name: deploy
Attendre runcmd trop tôt

Chercher un fichier créé par runcmd juste après le boot

Attention
Le piège : Chercher un fichier créé par runcmd juste après le boot
Symptôme : Le fichier n'existe pas encore
Cause : runcmd s'exécute au stage Final, après packages et write_files
Correction : Utiliser cloud-init status --wait ou vérifier /var/log/cloud-init-output.log
Scripts non idempotents avec cloud-init clean

runcmd:\n - useradd deploy

Attention
Le piège : runcmd:\n - useradd deploy
Symptôme : Erreur 'user already exists' après cloud-init clean + reboot
Cause : useradd échoue si l'utilisateur existe déjà
Correction : Utiliser la directive users: (idempotente) ou tester avant création
users:\n  - name: deploy\n    groups: sudo
Indentation YAML incorrecte

packages:\n- nginx\n - git

Attention
Le piège : packages:\n- nginx\n - git
Symptôme : Erreur de parsing ou comportement inattendu
Cause : YAML est strict sur l'indentation (espaces, pas de tabs)
Correction : Valider avec cloud-init schema -c fichier.yaml --annotate
Confondre package et packages

package:\n - nginx

Attention
Le piège : package:\n - nginx
Symptôme : Directive ignorée, paquets non installés
Cause : La directive correcte est packages (avec 's')
Correction : Utiliser packages: pour la liste des paquets
packages:\n  - nginx

Cloud-init génère des logs détaillés. Voici comment les exploiter efficacement.

Fenêtre de terminal
cloud-init status --long

Sortie typique :

status: done
extended_status: done
boot_status_code: enabled-by-generator
last_update: Wed, 29 Jan 2026 10:15:42 +0000
detail: DataSourceNoCloud [seed=/var/lib/cloud/seed/nocloud-net]
errors: []
recoverable_errors: {}

Les champs importants :

  • status : running (en cours), done (terminé), error (échec)
  • detail : le datasource utilisé
  • errors : liste des erreurs bloquantes

Les deux fichiers de log principaux :

Fenêtre de terminal
# Log détaillé de chaque action
sudo less /var/log/cloud-init.log
# Sortie des scripts (stdout/stderr)
sudo less /var/log/cloud-init-output.log

Pour chercher des erreurs rapidement :

Fenêtre de terminal
sudo grep -i error /var/log/cloud-init.log
sudo grep -i warn /var/log/cloud-init.log

Si cloud-init est lent, identifiez les étapes qui prennent du temps :

Fenêtre de terminal
cloud-init analyze blame

Sortie exemple :

-- Boot Record 01 --
45.23s (init-network/config-apt-configure)
12.45s (init-network/config-package-update-upgrade-install)
3.21s (modules-config/config-ssh)

Ici, la mise à jour des paquets prend 45 secondes — c’est souvent le cas sur les premières exécutions.

Cloud-init est conçu pour le first boot uniquement. Cependant, pour le debug, vous pouvez forcer une ré-exécution :

Fenêtre de terminal
# Supprimer les sémaphores (cloud-init "oublie" qu'il a déjà tourné)
sudo cloud-init clean
# Au prochain reboot, cloud-init rejouera tout
sudo reboot

Vous pouvez aussi exécuter un module spécifique sans rebooter :

Fenêtre de terminal
sudo cloud-init single --name cc_runcmd

Cloud-init peut installer Ansible et exécuter un playbook depuis un dépôt Git via ansible-pull. C’est utile pour des configurations plus complexes que ce que cloud-init gère nativement.

#cloud-config
users:
- name: ansible
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
package_update: true
packages:
- git
- python3-pip
write_files:
- path: /home/ansible/.config/pip/pip.conf
content: |
[global]
break-system-packages = true
owner: ansible:ansible
ansible:
install_method: pip
package_name: ansible
run_user: ansible
pull:
url: https://github.com/votre-org/playbooks.git
playbook_name: site.yml

Cette configuration :

  1. Crée un utilisateur ansible avec sudo sans mot de passe
  2. Installe Ansible via pip
  3. Clone le repo Git et exécute site.yml

En plus de user-data (que vous fournissez), cloud-init consomme deux autres sources de données :

metadata contient des informations sur l’instance (ID, région, adresse IP…) fournies par le provider cloud.

vendor-data est une configuration fournie par le provider cloud ou l’image. Elle s’applique avant votre user-data et peut installer des agents, configurer des services par défaut, etc.

SourceFourni parExemple
metadataProvider cloudInstance ID, IP privée
vendor-dataProvider / imageAgent de monitoring, config réseau
user-dataVousUsers, packages, scripts
network-configProvider / datasourceIP statique, routes

Si le vendor-data du provider interfère avec votre config, vous pouvez le filtrer :

#cloud-config
vendordata:
excluded:
- text/part-handler

Cloud-init est puissant mais a des règles précises. Gardez ces points en tête :

Le réseau n’est pas dans user-data. C’est la confusion #1. La config réseau vient du datasource ou de network-config séparé.

Cloud-init s’exécute au first boot uniquement. Pour rejouer, utilisez cloud-init clean + reboot, mais vos scripts doivent être idempotents.

Il y a 5 stages, pas 4. Detect → Local → Network → Config → Final. runcmd tourne au stage Final, après que le réseau et les paquets soient prêts.

Validez toujours avant de déployer. cloud-init schema -c file.yaml --annotate vous évite des heures de debug.

Incus est parfait pour tester. Utilisez les images /cloud et séparez user-data et network-config.

Les logs sont vos amis. /var/log/cloud-init.log et cloud-init status --long répondent à la plupart des questions.

Maintenant que vous maîtrisez cloud-init, vous pouvez aller plus loin avec des outils de gestion de configuration pour les déploiements complexes.

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.