fr

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 :

  1. Détecte sur quelle plateforme il tourne (AWS, GCP, Azure, Scaleway, etc.)
  2. Récupère les métadonnées et la configuration fournie par l’utilisateur
  3. 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 :

DatasourceProvider
DataSourceEc2Amazon AWS
DataSourceAzureMicrosoft Azure
DataSourceGCEGoogle Cloud
DataSourceScalewayScaleway
DataSourceOpenStackOpenStack
DataSourceDigitalOceanDigitalOcean
DataSourceNoCloudEnvironnements locaux (Proxmox, QEMU, etc.)
DataSourceLXDLXD/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.

Source : Boot stages

É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éseaublocks 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 consoleblocks 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_setup et mounts (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

FormatEn-têteMoment d’exécutionFréquence par défaut
Cloud-config YAML#cloud-configDépend du module (stages 3 à 5)Dépend du module
Script shell#!/bin/bashStage 5 (Final)Une fois par instance
Cloud Boothook#cloud-boothookStage 3 (Network), très tôtChaque boot
Include#includeStage 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équenceComportement
per-instanceS’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-bootS’exécute à chaque démarrage.
per-onceS’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 :

ModuleFonctionFréquence
cc_package_update_upgrade_installMise à jour et installation de paquetsper-instance
cc_users_groupsCréation d’utilisateurs et groupesper-instance
cc_write_filesÉcriture de fichiersper-instance
cc_runcmdExécution de commandes (stage Config)per-instance
cc_bootcmdCommandes au démarrage (très tôt)per-boot
cc_sshGestion des clés SSHper-instance
cc_growpartRedimensionnement des partitionsper-boot
cc_disk_setupPartitionnement de disquesper-instance
cc_mountsMontage de systèmes de fichiersper-instance
cc_apt_configureConfiguration APT (miroirs, sources)per-instance
cc_ntpConfiguration NTPper-instance
cc_ansibleIntégration Ansibleper-instance

Source : Module reference

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èretemplatefile() seulcloudinit_config
Un seul fichier cloud-configSuffisantOverkill
Cloud-config + script shellPas possibleNécessaire
Compression gzipManuelleIntégrée
PerformanceRapide~23x plus lent (mesuré par Ned Bellavance)
SimplicitéDirecteAjout d’un provider

7.6 Chaque cloud provider expose cloud-init différemment

ProviderRessource TerraformAttributEncodage requis
AWSaws_instanceuser_data ou user_data_base64Base64 optionnel
Azureazurerm_linux_virtual_machinecustom_dataBase64 obligatoire
GCPgoogle_compute_instancemetadata_startup_script ou metadata.user-dataTexte brut
Scalewayscaleway_instance_servercloud_initUTF-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]
  }
}

Source : Puppeteers

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 /tmp depuis 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 :

  1. DMI : le champ system-manufacturer contient "Scaleway"
  2. Fichier sentinelle : présence de /var/run/scaleway
  3. 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 :

EndpointContenu
http://169.254.42.42/conf?format=jsonMeta-data (instance ID, hostname, clés SSH)
http://169.254.42.42/user_data/cloud-initUser-data (votre configuration)
http://169.254.42.42/vendor_data/cloud-initVendor-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-init key is reserved; use the cloud_init attribute 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

FichierContenu
/var/log/cloud-init.logLog principal. Tous les événements de priorité DEBUG ou supérieure. « The primary log file. Verbose, but useful. »
/var/log/cloud-init-output.logStdout/stderr de chaque stage. « Captures the output from each stage. Output from user scripts goes here. »
/run/cloud-init/ds-identify.logDé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.logLogs du générateur systemd.

Source : Log files

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.disabled existe
  • disabled-by-kernel-command-line : paramètre kernel cloud-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

Source : Performance

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 :

  1. Accéder aux logs via la console série du provider
  2. Créer un utilisateur avec mot de passe via user-data pour le prochain essai
  3. Monter le filesystem localement pour inspecter les logs

Cloud-init ne s’est pas exécuté :

  1. cloud-init status --long → vérifier boot_status_code
  2. /run/cloud-init/ds-identify.log → problème de détection de plateforme ?
  3. systemctl status cloud-init-local.service cloud-init-network.service cloud-config.service cloud-final.service

Comportement inattendu :

  1. cloud-init schema --system → le user-data est-il valide ?
  2. Vérifier les clés errors et recoverable_errors dans cloud-init status --long
  3. 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 :

  1. dmesg -T | grep -i -e warning -e error -e fatal -e exception
  2. systemctl --failed
  3. systemctl 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 (chpasswd avec HASH) 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

SituationRecommandation
Simple configuration au premier bootCloud-config YAML avec file() dans Terraform
Configuration avec variables dynamiquestemplatefile()
Cloud-config + scripts shell combinéscloudinit_config (sauf Scaleway : concaténer manuellement)
ScalewayAttribut cloud_init direct, texte brut, pas de gzip
Commandes à chaque bootbootcmd (et non runcmd)
Debug rapidecloud-init status --long + cat /var/log/cloud-init-output.log
Préparation d’imagecloud-init clean --logs --machine-id avant capture

Sources principales