Cloud-init : comprendre le provisioning de VM chez un cloud provider et avec Terraform — Guide complet
Explication approfondie de cloud-init : cycle de vie, formats user-data, datasources, intégration Terraform, spécificités Scaleway, logs et debugging. Tout ce qu'il faut savoir pour provisionner des VM de manière automatisée.
Quand on crée une VM chez un cloud provider, il y a un moment magique : la machine démarre avec une image générique (Ubuntu, Debian, etc.), et pourtant, quelques secondes plus tard, elle a le bon hostname, les bonnes clés SSH, les bons paquets installés, et parfois même un service qui tourne. Ce n’est pas de la magie — c’est cloud-init.
Cet article est un tour d’horizon complet : ce que fait cloud-init, comment il fonctionne en interne, comment l’utiliser avec Terraform, les particularités de Scaleway, et comment debugger quand ça ne marche pas.
1. Qu’est-ce que cloud-init ?
Cloud-init est « the industry standard multi-distribution method for cross-platform cloud instance initialization » — le standard de l’industrie pour initialiser des instances cloud, quel que soit le provider ou la distribution Linux. Source : cloud-init documentation
Concrètement, cloud-init est un programme Python pré-installé dans les images cloud des principales distributions. Au premier démarrage d’une instance, il :
- Détecte sur quelle plateforme il tourne (AWS, GCP, Azure, Scaleway, etc.)
- Récupère les métadonnées et la configuration fournie par l’utilisateur
- Configure la machine : réseau, hostname, clés SSH, utilisateurs, paquets, fichiers, commandes
Le projet a été créé chez Canonical (l’éditeur d’Ubuntu) en 2007, initialement pour Ubuntu dans Amazon EC2. Aujourd’hui, il supporte 27+ datasources (clouds), est écrit en Python (95.9% du code), et compte plus de 3 600 étoiles sur GitHub. La version actuelle est la 25.3 (septembre 2025). Source : GitHub canonical/cloud-init
Ce que cloud-init provisionne
- Configuration réseau (IP statique, DHCP, DNS, routes)
- Configuration du stockage (partitions, systèmes de fichiers, montages)
- Gestion des clés SSH (host keys, authorized keys)
- Création d’utilisateurs et groupes
- Installation et mise à jour de paquets
- Écriture de fichiers de configuration
- Exécution de commandes et scripts arbitraires
- Intégration avec des outils de configuration management (Ansible, Puppet, Chef)
2. Les trois types de données
Cloud-init manipule trois catégories de données, qu’il est essentiel de distinguer :
Meta-data
Les métadonnées sont fournies par le cloud provider. Elles contiennent les informations intrinsèques à l’instance : son ID, son hostname, sa zone, ses clés SSH publiques. Elles sont traitées en premier et doivent obligatoirement contenir un instance-id. Source : cloud-init datasources
User-data
Les données utilisateur sont fournies par le propriétaire de l’instance (vous). C’est là qu’on met la configuration cloud-init : cloud-config YAML, scripts shell, etc. Elles sont traitées après les métadonnées.
Vendor-data
Les données fournisseur sont des configurations optionnelles que le cloud provider injecte (ex. : miroirs de paquets, agents de monitoring). Le user-data peut les surcharger.
« Cloud-init acts upon optional vendor data and user data after it reads any metadata and initializes the system. » Source : cloud-init datasources
3. Les datasources : d’où cloud-init obtient sa configuration
Un datasource est le mécanisme par lequel cloud-init récupère ses données (meta-data, user-data, vendor-data). Chaque cloud provider expose ces données différemment, et cloud-init sait les trouver automatiquement :
« In most cases, users of cloud-init should not have to configure cloud-init to specify which datasource cloud-init is running on; cloud-init should be able to identify the platform. » Source : cloud-init datasources
Les datasources les plus courants :
| Datasource | Provider |
|---|---|
DataSourceEc2 | Amazon AWS |
DataSourceAzure | Microsoft Azure |
DataSourceGCE | Google Cloud |
DataSourceScaleway | Scaleway |
DataSourceOpenStack | OpenStack |
DataSourceDigitalOcean | DigitalOcean |
DataSourceNoCloud | Environnements locaux (Proxmox, QEMU, etc.) |
DataSourceLXD | LXD/Incus |
Le datasource NoCloud mérite une mention spéciale : il permet d’utiliser cloud-init sans cloud provider, en fournissant les données via un système de fichiers labellisé CIDATA, un serveur HTTP local, ou même la ligne de commande du noyau. C’est ce qui rend cloud-init utilisable dans des environnements non-cloud comme Proxmox ou des VM locales. Source : NoCloud datasource
4. Le cycle de vie : les 5 étapes du boot
Cloud-init s’exécute en 5 étapes séquentielles, chacune correspondant à un service systemd distinct. Comprendre cette séquence est fondamental pour savoir quand vos scripts s’exécutent.
Étape 1 — Detect (ds-identify)
L’outil ds-identify identifie la plateforme. S’il ne détecte aucun datasource valide, cloud-init est désactivé pour ce boot. C’est un gatekeeper : « This tool is integrated into the init system to disable cloud-init when no platform is found, and enable cloud-init when a valid platform is detected. »
Étape 2 — Local (cloud-init-local.service)
S’exécute « as soon as possible with / mounted read-write ». Cette étape bloque le démarrage du réseau (« blocks as much of boot as possible, must block network »). Son rôle : localiser les sources de données locales et rendre la configuration réseau. Aucun module cloud-init ne s’exécute ici — c’est purement de la préparation.
Pourquoi bloquer le réseau ? Pour éviter de diffuser un ancien hostname ou d’appliquer une vieille config réseau avant que cloud-init ait eu le temps de charger la nouvelle.
Étape 3 — Network (cloud-init-network.service)
S’exécute après la mise en place du réseau. Cette étape bloque SSH et le login console (« blocks majority of remaining boot (e.g. SSH and console login) »). C’est ici que le gros du travail se fait :
- Téléchargement et traitement complet des user-data
- Résolution des directives
#include(récursivement, y compris via HTTP) - Décompression des contenus compressés
- Exécution des part-handlers
- Modules
disk_setupetmounts(partitionnement, systèmes de fichiers)
Étape 4 — Config (cloud-config.service)
S’exécute après Network. Ne bloque rien. Execute les modules de configuration qui n’ont pas d’impact sur le boot, dont le très utilisé runcmd.
Étape 5 — Final (cloud-final.service)
S’exécute en dernier (« as final part of boot (traditional ‘rc.local’) »). Ne bloque rien. C’est ici que tournent :
- L’installation de paquets
- Les outils de configuration management (Ansible, Puppet, Chef)
- Les scripts utilisateur (ceux avec
#!/bin/bash)
La logique est claire : les scripts shell fournis en user-data s’exécutent « relatively late in the boot process, during cloud-init’s final stage ». Source : user-data script format
Résumé visuel
Detect → Local (bloque réseau) → Network (bloque SSH) → Config → Final
↓ ↓ ↓ ↓ ↓
ds-identify config réseau user-data complet runcmd packages
disk_setup scripts user
mounts ansible/puppet
5. Quand est exécuté un script chargé par cloud-init ?
C’est une question cruciale. La réponse dépend du format et de la fréquence du module.
Formats et moment d’exécution
| Format | En-tête | Moment d’exécution | Fréquence par défaut |
|---|---|---|---|
| Cloud-config YAML | #cloud-config | Dépend du module (stages 3 à 5) | Dépend du module |
| Script shell | #!/bin/bash | Stage 5 (Final) | Une fois par instance |
| Cloud Boothook | #cloud-boothook | Stage 3 (Network), très tôt | Chaque boot |
| Include | #include | Stage 3 (Network) | Dépend du contenu inclus |
Les trois fréquences de module
Chaque module cloud-init a une fréquence d’exécution configurée. Cloud-init utilise des fichiers sémaphores dans /var/lib/cloud/instance/sem/ pour savoir ce qui a déjà été exécuté :
| Fréquence | Comportement |
|---|---|
per-instance | S’exécute au premier boot d’une instance. Si l’on clone l’instance ou crée une nouvelle instance depuis une image, les modules per-instance se ré-exécutent. |
per-boot | S’exécute à chaque démarrage. |
per-once | S’exécute une seule fois, jamais ré-exécuté, même pour un nouvel instance-id. |
Source : First boot determination
Premier boot vs boots suivants
Cloud-init détermine s’il s’agit d’un premier boot en comparant l’instance-id en cache avec celui détecté au runtime. En mode check (défaut), si les IDs diffèrent, c’est un nouveau premier boot et le cache est nettoyé.
Il existe aussi un mode trust (manual_cache_clean: true) où cloud-init « will trust the existing cache (and therefore not clean it) » — il ne détectera jamais une nouvelle instance tant que le cache existe. Utile dans certains cas, mais dangereux si on clone des VM sans nettoyer.
Point important : si vous clonez une VM sans faire
cloud-init clean, cloud-init ne ré-exécutera pas les modules per-instance. Pire, toutes les instances partageront les mêmes clés SSH host — c’est une vulnérabilité de sécurité. Source : First boot determination
6. Les formats de user-data en détail
6.1 Cloud-config (YAML)
C’est le format le plus courant. Il commence obligatoirement par #cloud-config et utilise des clés YAML pour décrire l’état désiré de l’instance :
#cloud-config
package_update: true
package_upgrade: true
packages:
- nginx
- curl
- git
users:
- name: deploy
groups: sudo
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... deploy@laptop
write_files:
- path: /etc/nginx/sites-available/app
content: |
server {
listen 80;
server_name app.example.com;
root /var/www/app;
}
owner: root:root
permissions: "0644"
runcmd:
- ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
- systemctl restart nginx
Les modules cloud-config sont très nombreux. Les plus utilisés :
| Module | Fonction | Fréquence |
|---|---|---|
cc_package_update_upgrade_install | Mise à jour et installation de paquets | per-instance |
cc_users_groups | Création d’utilisateurs et groupes | per-instance |
cc_write_files | Écriture de fichiers | per-instance |
cc_runcmd | Exécution de commandes (stage Config) | per-instance |
cc_bootcmd | Commandes au démarrage (très tôt) | per-boot |
cc_ssh | Gestion des clés SSH | per-instance |
cc_growpart | Redimensionnement des partitions | per-boot |
cc_disk_setup | Partitionnement de disques | per-instance |
cc_mounts | Montage de systèmes de fichiers | per-instance |
cc_apt_configure | Configuration APT (miroirs, sources) | per-instance |
cc_ntp | Configuration NTP | per-instance |
cc_ansible | Intégration Ansible | per-instance |
6.2 Scripts shell
Tout fichier user-data commençant par un shebang (#!/bin/bash, #!/usr/bin/env python3, etc.) est traité comme un script et exécuté au stage Final, une seule fois par instance :
#!/bin/bash
apt-get update && apt-get install -y docker.io
systemctl enable docker
usermod -aG docker deploy
6.3 Cloud Boothook
Exécuté très tôt (stage Network), à chaque boot. Utile pour des configurations qui doivent être appliquées avant les modules cloud-init :
#cloud-boothook
#!/bin/bash
echo "169.254.169.254 metadata.internal" >> /etc/hosts
6.4 MIME Multipart
Permet de combiner plusieurs formats dans un seul user-data. C’est ce que le provider Terraform cloudinit génère automatiquement :
cloud-init devel make-mime \
-a config.yaml:cloud-config \
-a script.sh:x-shellscript \
> user-data.mime
6.5 Templates Jinja
Cloud-init supporte le templating Jinja pour rendre les configurations dynamiques. Il faut ajouter ## template: jinja en première ligne :
## template: jinja
#cloud-config
runcmd:
- echo "Running on {{ v1.cloud_name }}, instance {{ v1.instance_id }}"
« Any instance-data variables may be used as jinja template variables. » Source : Jinja format
7. Cloud-init avec Terraform
7.1 Trois approches
Il y a trois manières de passer du cloud-init via Terraform :
Approche 1 — file() directe : la plus simple. On passe un fichier cloud-config tel quel.
Approche 2 — templatefile() : on injecte des variables Terraform dans un template cloud-config.
Approche 3 — cloudinit_config : on assemble plusieurs parties en un document MIME multipart.
7.2 Approche simple avec file()
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
user_data = file("${path.module}/cloud-init.yaml")
}
7.3 Injection de variables avec templatefile()
La fonction templatefile() remplace l’ancienne data source template_file (dépréciée). Elle est intégrée nativement dans Terraform et s’exécute beaucoup plus rapidement :
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
user_data = templatefile("${path.module}/templates/cloud-init.yaml.tpl", {
hostname = "web-${count.index}"
packages = var.packages
ssh_key = var.ssh_public_key
})
}
Le template cloud-init.yaml.tpl :
#cloud-config
hostname: ${hostname}
users:
- name: deploy
ssh_authorized_keys:
- ${ssh_key}
packages:
%{ for pkg in packages ~}
- ${pkg}
%{ endfor ~}
On peut aussi utiliser des conditionnels :
%{ if enable_monitoring == "true" }
packages:
- prometheus-node-exporter
%{ endif }
Astuce :
jsonencode()permet de passer des listes et maps HCL dans les templates, puisque YAML est un sur-ensemble de JSON. Source : Grant Orchard
7.4 Assemblage MIME multipart avec cloudinit_config
Le provider hashicorp/cloudinit permet d’assembler plusieurs fichiers (cloud-config + scripts shell + boothooks) en un seul document MIME :
terraform {
required_providers {
cloudinit = {
source = "hashicorp/cloudinit"
version = "~> 2.3"
}
}
}
data "cloudinit_config" "server" {
gzip = true
base64_encode = true
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = templatefile("${path.module}/templates/cloud-config.yaml.tpl", {
hostname = var.hostname
})
}
part {
filename = "setup.sh"
content_type = "text/x-shellscript"
content = file("${path.module}/scripts/setup.sh")
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
user_data_base64 = data.cloudinit_config.server.rendered
}
« The cloud-init provider supports rendering cloud-init configurations in a MIME multi-part file. » Source : GitHub terraform-provider-cloudinit
7.5 Quand utiliser cloudinit_config vs templatefile()
| Critère | templatefile() seul | cloudinit_config |
|---|---|---|
| Un seul fichier cloud-config | Suffisant | Overkill |
| Cloud-config + script shell | Pas possible | Nécessaire |
| Compression gzip | Manuelle | Intégrée |
| Performance | Rapide | ~23x plus lent (mesuré par Ned Bellavance) |
| Simplicité | Directe | Ajout d’un provider |
7.6 Chaque cloud provider expose cloud-init différemment
| Provider | Ressource Terraform | Attribut | Encodage requis |
|---|---|---|---|
| AWS | aws_instance | user_data ou user_data_base64 | Base64 optionnel |
| Azure | azurerm_linux_virtual_machine | custom_data | Base64 obligatoire |
| GCP | google_compute_instance | metadata_startup_script ou metadata.user-data | Texte brut |
| Scaleway | scaleway_instance_server | cloud_init | UTF-8 |
Sources : HashiCorp Developer, Microsoft Learn
7.7 Bonne pratique : lifecycle.ignore_changes
Si cloud-init ne doit s’exécuter qu’au premier boot, évitez que Terraform recrée l’instance à chaque modification du user-data :
resource "aws_instance" "web" {
# ...
user_data = file("cloud-init.yaml")
lifecycle {
ignore_changes = [user_data]
}
}
7.8 Fusion multi-fichiers avec merge_type
Quand on assemble plusieurs cloud-config YAML via MIME multipart, cloud-init doit savoir comment fusionner les clés. On utilise merge_how :
#cloud-config
merge_how:
- name: list
settings: [append]
- name: dict
settings: [no_replace, recurse_list]
Cela évite qu’un second fichier cloud-config écrase les listes du premier (par défaut, les listes sont remplacées, pas mergées).
7.9 Validation avant déploiement
On peut valider un cloud-config avant de le déployer :
# Validation locale avec cloud-init
cloud-init schema --config-file cloud-init.yaml
# Via un output Terraform pour prévisualiser le rendu
output "rendered_cloud_init" {
value = data.cloudinit_config.server.rendered
}
« Start only with config.tf file, and templates… perform terraform apply and check that the output is rendering the way I want it to. » Source : Grant Orchard
Attention : ne pas écrire de fichiers dans
/tmpdepuis cloud-init — utiliser/run/somedirà la place. Source : m-t-w
8. Scaleway et cloud-init : les spécificités
8.1 Le datasource Scaleway
Scaleway dispose d’un datasource natif intégré dans le projet cloud-init officiel (DataSourceScaleway). La première contribution upstream a été soumise par Edouard Bonlieu en octobre 2015. Source : Launchpad merge proposal
Le datasource détecte un environnement Scaleway par trois méthodes :
- DMI : le champ
system-manufacturercontient"Scaleway" - Fichier sentinelle : présence de
/var/run/scaleway - Ligne de commande kernel : contient
"scaleway"
Source : DataSourceScaleway.py
8.2 Le service de métadonnées
Depuis l’intérieur d’une instance Scaleway, les données sont accessibles via :
| Endpoint | Contenu |
|---|---|
http://169.254.42.42/conf?format=json | Meta-data (instance ID, hostname, clés SSH) |
http://169.254.42.42/user_data/cloud-init | User-data (votre configuration) |
http://169.254.42.42/vendor_data/cloud-init | Vendor-data (configuration Scaleway) |
L’adresse IPv6 fd00:42::42 est aussi disponible. Source : cloud-init Scaleway datasource
8.3 Restriction de sécurité : ports privilégiés
Spécificité majeure de Scaleway : les requêtes vers l’API user-data/vendor-data doivent provenir d’un port source inférieur à 1024, ce qui garantit que seul root peut y accéder :
« For security reasons, viewing and editing user data is only allowed to queries originating from a port below 1024. » Source : Scaleway documentation
Pour interroger l’API manuellement :
curl --local-port 1-1023 http://169.254.42.42/user_data/cloud-init
8.4 Limitation : texte brut uniquement
Contrairement à AWS ou GCP, Scaleway ne supporte pas les formats compressés pour le user-data :
« The Scaleway Instances API only supports plain text representations of user-data, meaning that any compressed format, like gzip, cannot be used. » Source : Scaleway API/CLI docs
Cela signifie que le multipart MIME avec gzip (comme ce que produit cloudinit_config avec gzip = true) ne fonctionnera pas. Il faut s’en tenir au cloud-config YAML en texte brut.
8.5 Fallback NoCloud pour instances sans IP publique
Les instances sans IP publique ne peuvent pas accéder au service de métadonnées HTTP. Scaleway a un mécanisme de contournement :
« If the Instance has no public IP address when powered on, a CD-ROM drive is plugged into it, serving a medium containing the NoCloud configuration files with the filesystem labeled
cidata, which is automatically detected and consumed by Cloud-init. » Source : Scaleway documentation
8.6 Trois méthodes pour passer du cloud-init sur Scaleway
Via la console web : dans les réglages avancés lors de la création d’une instance.
Via la CLI Scaleway :
# Création avec cloud-init
scw instance server create type=DEV1-S image=ubuntu_focal \
zone=fr-par-1 cloud-init=@cloudconfig.yml
# Gestion du user-data
scw instance user-data set --server-id=<UUID> \
--key=cloud-init --content=@cloudconfig.yml
Via l’API REST :
curl -H 'X-Auth-Token: <SECRET_KEY>' \
https://api.scaleway.com/instance/v1/zones/fr-par-1/servers/<SERVER_ID>/user_data/cloud-init \
-X PATCH \
-H 'Content-Type: text/plain' \
-d @cloudconfig.yml
Point critique : « For user_data to be effective, it has to be added prior to the creation of the Instance since cloud-init gets activated early in the first phases of the boot process. » Source : Scaleway docs
8.7 Scaleway avec Terraform
Le provider Terraform Scaleway expose un attribut dédié cloud_init (et non pas user_data) :
resource "scaleway_instance_server" "web" {
type = "DEV1-S"
image = "ubuntu_focal"
cloud_init = file("${path.module}/cloud-init.yml")
}
« The
cloud-initkey is reserved; use thecloud_initattribute instead. » Source : Terraform Registry
Avec templatefile() pour injecter des variables :
resource "scaleway_instance_server" "web" {
type = "DEV1-S"
image = "ubuntu_focal"
cloud_init = templatefile("${path.module}/templates/cloud-init.yaml.tpl", {
hostname = "web-${count.index}"
ssh_key = var.ssh_public_key
})
}
Ne pas utiliser cloudinit_config avec gzip = true pour Scaleway — l’API ne supporte que le texte brut.
8.8 Premier boot uniquement
« With the default configuration of Cloud-init, many of its modules are intended to only activate during the Instance’s first boot, and not subsequent ones. Therefore, updating the user-data of an Instance may not have the expected outcome when the system reboots. » Source : Scaleway docs
Modifier le user-data d’une instance existante et la rebooter ne relancera pas les modules per-instance. Pour forcer une ré-exécution, il faut cloud-init clean --logs --reboot (voir section debugging).
8.9 Images et distributions supportées
Toutes les images OS et InstantApp fournies par Scaleway sont livrées avec cloud-init pré-installé et activé, à l’exception des images Microsoft Windows. Les distributions supportées incluent Ubuntu, Debian et Fedora. Source : Scaleway docs
9. Consulter les logs et debugger cloud-init
9.1 Les fichiers de logs
| Fichier | Contenu |
|---|---|
/var/log/cloud-init.log | Log principal. Tous les événements de priorité DEBUG ou supérieure. « The primary log file. Verbose, but useful. » |
/var/log/cloud-init-output.log | Stdout/stderr de chaque stage. « Captures the output from each stage. Output from user scripts goes here. » |
/run/cloud-init/ds-identify.log | Détection de plateforme (early boot). « Useful to understand why cloud-init didn’t run or why it detected an unexpected platform. » |
/run/cloud-init/cloud-init-generator.log | Logs du générateur systemd. |
Attention : les logs sont ajoutés (appended) et non écrasés : « Logs are appended to the files in
/var/log: files may contain logs from multiple boots. »
9.2 Vérifier le statut
# Statut simple
cloud-init status
# → status: done
# Statut détaillé
cloud-init status --long
# → status: done
# → extended_status: done
# → boot_status_code: enabled-by-generator
# → detail: DataSourceScaleway
# → errors: []
# Attendre la fin de cloud-init (utile dans un script)
cloud-init status --wait
# Sortie JSON (pour scripting)
cloud-init status --format json
Les valeurs possibles de boot_status_code :
enabled-by-generator: un datasource a été détectédisabled-by-generator: aucun datasource détectédisabled-by-marker-file:/etc/cloud/cloud-init.disabledexistedisabled-by-kernel-command-line: paramètre kernelcloud-init=disabled
Source : CLI commands, Reported status
9.3 Analyser les performances
# Quels modules prennent le plus de temps ?
cloud-init analyze blame
# → 00.80300s (init-network/config-growpart)
# → 00.64300s (init-network/config-resizefs)
# → 00.62100s (init-network/config-ssh)
# Chronologie complète par stage
cloud-init analyze show
# Timestamps kernel (boot, init, cloud-init start)
cloud-init analyze boot
9.4 Interroger les métadonnées
# Nom du cloud provider
cloud-init query cloud_name
# → scaleway
# Toutes les métadonnées en JSON
cloud-init query --all
# Clés disponibles
cloud-init query --list-keys
# Template Jinja custom
cloud-init query --format 'Instance {{instance_id}} in {{region}}'
9.5 Valider un fichier cloud-config
# Valider un fichier
cloud-init schema --config-file cloud-init.yaml --annotate
# Valider le user-data du système actuel
cloud-init schema --system
9.6 Collecter les logs pour un rapport de bug
cloud-init collect-logs
# → Crée une archive .tar.gz avec tous les logs, la version, dmesg, journalctl
9.7 Forcer la ré-exécution de cloud-init
Avertissement : « Making cloud-init run again may be destructive and must never be done on a production system. Artefacts such as ssh keys or passwords may be overwritten. » Source : How to re-run cloud-init
# Méthode 1 : clean + reboot (ré-exécution complète)
sudo cloud-init clean --logs --reboot
# Méthode 2 : exécuter un seul module
sudo cloud-init single --name cc_ssh --frequency always
# Méthode 3 : simple reboot (ne ré-exécute que les modules per-boot)
sudo reboot
9.8 Guide de troubleshooting
Impossible de se connecter à l’instance :
- Accéder aux logs via la console série du provider
- Créer un utilisateur avec mot de passe via user-data pour le prochain essai
- Monter le filesystem localement pour inspecter les logs
Cloud-init ne s’est pas exécuté :
cloud-init status --long→ vérifierboot_status_code/run/cloud-init/ds-identify.log→ problème de détection de plateforme ?systemctl status cloud-init-local.service cloud-init-network.service cloud-config.service cloud-final.service
Comportement inattendu :
cloud-init schema --system→ le user-data est-il valide ?- Vérifier les clés
errorsetrecoverable_errorsdanscloud-init status --long - Lire les logs : « When you find an error or warning, read backwards in the cloud-init log to understand what cloud-init was attempting before it hit the error or warning. » Source : Azure VM troubleshooting
Cloud-init ne termine jamais :
dmesg -T | grep -i -e warning -e error -e fatal -e exceptionsystemctl --failedsystemctl list-jobs --after
Source : How to debug cloud-init
9.9 Attendre cloud-init dans vos scripts
Si vous avez un service qui doit démarrer après cloud-init :
[Unit]
Description=Mon service
After=cloud-init.target multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mon-script.sh
[Install]
WantedBy=multi-user.target
Ou dans un script provisionner en dehors de cloud-init :
cloud-init status --wait
# cloud-init est terminé, on peut continuer
Source : How to wait for cloud-init
10. Arborescence des fichiers clés
/etc/cloud/cloud.cfg # Configuration principale
/etc/cloud/cloud.cfg.d/ # Fragments de configuration
/etc/cloud/cloud.cfg.d/05_logging.cfg # Configuration du logging
/etc/cloud/cloud-init.disabled # Marker pour désactiver cloud-init
/var/lib/cloud/ # Cache et état interne
/var/lib/cloud/instance/sem/ # Sémaphores des modules (per-instance)
/var/lib/cloud/seed/ # Seed directory (données locales NoCloud)
/var/log/cloud-init.log # Log principal (DEBUG+)
/var/log/cloud-init-output.log # stdout/stderr des stages
/run/cloud-init/ds-identify.log # Détection plateforme (non persisté)
/run/cloud-init/cloud-init-generator.log # Générateur systemd (non persisté)
/run/cloud-init/status.json # Statut courant
11. Considérations de sécurité
Le user-data est stocké en clair sur l’instance (dans /var/lib/cloud/instances/<id>/user-data.txt). Le base64 n’est pas du chiffrement :
« Creating an instance with a plaintext password in user-data will be encoded in base64 and easily seen. » Source : Red Hat
Bonnes pratiques :
- Utiliser des hashes de mots de passe (
chpasswdavecHASH) plutôt que du texte en clair - Ne pas stocker de secrets (API keys, tokens) directement dans le user-data
- Préférer un vault (HashiCorp Vault, AWS Secrets Manager) ou des variables d’environnement injectées par d’autres moyens
- Cloud-init sépare les données sensibles : elles ne sont lisibles que par root. Les utilisateurs non-root reçoivent des données expurgées.
12. Résumé des choix
| Situation | Recommandation |
|---|---|
| Simple configuration au premier boot | Cloud-config YAML avec file() dans Terraform |
| Configuration avec variables dynamiques | templatefile() |
| Cloud-config + scripts shell combinés | cloudinit_config (sauf Scaleway : concaténer manuellement) |
| Scaleway | Attribut cloud_init direct, texte brut, pas de gzip |
| Commandes à chaque boot | bootcmd (et non runcmd) |
| Debug rapide | cloud-init status --long + cat /var/log/cloud-init-output.log |
| Préparation d’image | cloud-init clean --logs --machine-id avant capture |
Sources principales
- cloud-init 25.3 documentation — documentation officielle complète
- Boot stages — les 5 étapes de boot
- User-data formats — tous les formats supportés
- Module reference — référence des modules
- CLI commands — commandes CLI
- Datasources — sources de données
- Scaleway datasource — datasource Scaleway
- How to debug — guide de debugging
- Terraform cloudinit provider — provider Terraform
- HashiCorp Developer tutorial — tutoriel Terraform + cloud-init
- Scaleway — How to use cloud-init — guide Scaleway
- Scaleway — Using cloud-init with API and CLI — guide API/CLI
- Scaleway Terraform provider — documentation Terraform Scaleway
- DataSourceScaleway.py — code source du datasource