Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Cloud-init : automatiser la configuration des VMs cloud

Par mis à jour le

Améliorer cette page

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.

  • Écrire un fichier user-data (utilisateurs, paquets, fichiers, commandes)
  • Situer chaque action dans les 5 boot stages de cloud-init
  • Valider le fichier avant déploiement, puis le tester dans un conteneur Incus
  • Déboguer un déploiement avec les logs et cloud-init status
  • Éviter les pièges courants (réseau, secrets, idempotence)

Prérequis : les bases de l'administration Linux (fichiers, paquets, utilisateurs), et la leçon précédente, Images, cloud-init et service de métadonnées, qui explique ce qu'est une image et pourquoi le user-data ne doit jamais contenir de secret. Pour l'exercice pratique, il faut un hôte Linux avec Incus installé.

Cloud-init est un outil d'initialisation, pas de gestion de configuration continue : il ne repasse pas régulièrement remettre une machine en conformité, contrairement à Ansible ou Puppet.

La nuance compte, parce que la formule « cloud-init, c'est le premier démarrage » est répandue et inexacte. Le service participe à chaque démarrage, et ce sont ses modules qui portent une fréquence : per-instance pour la majorité, ce qui explique l'impression du premier démarrage, mais aussi per-once, per-boot et always pour ceux qui doivent rejouer. Concrètement, l'essentiel de ce que vous écrivez dans les données utilisateur s'applique une fois par instance, tandis qu'un module déclaré per-boot rejoue à chaque redémarrage. Le détail des fréquences et la façon de forcer un rejeu sont traités plus bas.

Il sait notamment :

  • 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 stages de cloud-init : Detect identifie le datasource avec ds-identify, Local s'exécute avant le réseau (bootcmd), Network récupère user-data une fois le réseau monté, Config applique utilisateurs, paquets et write_files, Final lance runcmd, les scripts et phone-home ; les datasources fournissent metadata, vendor-data, user-data et network-config.1. Detect ds-identify, datasource ?2. Local avant réseau, bootcmd3. Network réseau up, user-data fetch4. Config users, packages, write_files5. Final runcmd, scripts, phone-homeDatasources : metadata, vendor-data,user-data, network-config
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

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

Vos commandes runcmd ne s'exécutent donc qu'à ce stage, après les paquets et les fichiers. Si un runcmd ne trouve pas encore ce qu'il attend, vérifiez d'abord que le stage Final est terminé.

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 la plupart 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'?)

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.

Une instance Incus n'a aucune adresse publique, ce qui reproduit le cas d'une machine cloud créée sans adresse publique : elle reçoit une adresse privée du pont géré par Incus, et sa sortie vers Internet passe par la traduction d'adresses (NAT) de ce pont. C'est ce chemin qui permet à package_update et packages de télécharger leurs paquets. Le détail, avec les équivalents Scaleway et OUTSCALE, est dans la leçon Images, cloud-init et service de métadonnées.

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

    Enregistrez ce contenu dans user-data.yaml. La première ligne du fichier doit être exactement #cloud-config : toute ligne placée avant, même le nom du fichier en commentaire, rend le fichier invalide et cloud-init l'ignore. La règle sudo: ALL=(ALL) NOPASSWD:ALL donne les droits root sans mot de passe : elle n'est acceptable que dans ce conteneur jetable de test, jamais sur une machine durable.

    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)"

    Le \ en fin de ligne poursuit la commande sur la ligne suivante : n'ajoutez aucun espace après lui.

  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
routes:
- to: default
via: 10.0.0.1
nameservers:
addresses:
- 8.8.8.8
EOF

Ces erreurs reviennent constamment et produisent des VMs « à moitié configurées ». Les connaître évite des heures de diagnostic au premier boot.

runcmd:
- 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 de données séparée. Passez par cloud-init.network-config (Incus) ou par le metadata service du provider.

users:
- name: deploy

Symptôme : le fichier est ignoré, aucune configuration n'est appliquée.

Cause : sans #cloud-config en première ligne, cloud-init ne reconnaît pas le format.

#cloud-config
users:
- name: deploy

Vous cherchez 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. Attendez avec cloud-init status --wait, ou consultez /var/log/cloud-init-output.log.

Un script est idempotent quand le rejouer ne change pas le résultat. Or cloud-init clean suivi d'un redémarrage rejoue tout le user-data.

runcmd:
- useradd deploy

Symptôme : erreur user already exists après un cloud-init clean suivi d'un reboot.

Cause : useradd échoue si l'utilisateur existe déjà. Préférez la directive users:, qui est idempotente.

users:
- name: deploy
groups: sudo
write_files:
- path: /etc/app/secrets.env
content: API_KEY=sk-xxx

Symptôme : les secrets sont exposés via le metadata service, lisible par toute la VM, et restent écrits en clair sur le disque (voir la leçon Images, cloud-init et service de métadonnées).

Cause : user-data est accessible via http://169.254.169.254/latest/user-data (chez AWS ; l'adresse du service de métadonnées varie selon le fournisseur). Utilisez un gestionnaire de secrets, ou récupérez les secrets au moment de l'exécution plutôt qu'à la création.

packages:
- nginx
- git

Symptôme : erreur de parsing ou comportement inattendu.

Cause : YAML est strict sur l'indentation, des espaces, jamais de tabulations. Validez systématiquement avec cloud-init schema -c fichier.yaml --annotate.

package:
- nginx

Symptôme : la directive est ignorée, les paquets ne sont pas installés.

Cause : la directive correcte est packages, avec un « s ».

packages:
- 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.

Comme expliqué plus haut, vos données utilisateur ne s'appliquent qu'une fois par instance, car cloud-init mémorise ce qu'il a fait. Pour lui faire oublier cet état et forcer un rejeu complet en phase de mise au point :

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. Pour rejouer les commandes runcmd, il faut lancer scripts_user, car cc_runcmd ne fait qu'écrire le script :

Fenêtre de terminal
sudo cloud-init single --name scripts_user --frequency always

La plupart des modules ne s'exécutent qu'une fois par instance : sans l'option --frequency always, cloud-init peut considérer le module comme déjà passé et ne rien faire. Pour rejouer un autre module, remplacez scripts_user par son nom (write_files, par exemple).

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.

Ce tableau permet de savoir qui est responsable de chaque source de données lue par cloud-init : le provider cloud, l'image ou vous. Lisez-le ligne par ligne pour repérer ce que vous pouvez modifier (le user-data) et ce que vous subissez.

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

La documentation officielle de cloud-init est la source qui fait foi quand une directive ou un comportement change d'une version à l'autre. Ces cinq pages sont celles qui servent le plus après cette leçon.

Le lab fait naître la première machine de la boutique sous Incus : vous complétez un user-data (la démarche de la section « Tester avec Incus ») qui installe et démarre son service, sur un réseau privé préparé par le lab, sans adresse publique. dsoxlab vérifie l'état de la machine, pas vos commandes : cloud-init terminé sans erreur, service actif, aucune adresse joignable depuis l'extérieur, puis machine et réseau détruits à la fin de l'essai.

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

La configuration des données utilisateur s'applique une fois par instance, alors que cloud-init tourne à chaque démarrage : un module déclaré per-boot ou always rejoue, les autres non. Pour tout rejouer en mise au point, cloud-init clean puis redémarrage, en gardant à l'esprit que vos scripts doivent être idempotents.

Cloud-init a 5 stages. 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.

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

6 questions
6 min.
70% requis

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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn