Aller au contenu

Déboguer les scripts user-data EC2 qui n'ont jamais roulé : lire les journaux cloud-init

Quand un script user-data EC2 n'a jamais roulé, la réponse est dans les journaux cloud-init. Voici où regarder, la règle une-fois-par-instance qui attrape tout le monde, et les erreurs de shebang et de syntaxe qui échouent en silence.

Quand un script user-data EC2 « n’a jamais roulé », une de trois choses est vraie : il a roulé et a échoué (le plus fréquent), il a roulé lors d’un démarrage précédent et ne roulera plus parce que le user data s’exécute une fois par instance par défaut, ou cloud-init ne l’a jamais reconnu comme script parce que le shebang ou le formatage MIME est incorrect. Les trois cas laissent des preuves dans les deux mêmes fichiers : /var/log/cloud-init.log et /var/log/cloud-init-output.log. Lisez-les avant de changer quoi que ce soit — deviner le user data en relançant des instances est la boucle de débogage la plus lente sur AWS.

Ce qui se passe

Sur Amazon Linux, Ubuntu, Debian et la plupart des distributions modernes, cloud-init est l’agent qui récupère le user data depuis le service de métadonnées d’instance pendant le démarrage et décide quoi en faire. Un script shell (première ligne #!/bin/bash ou semblable) est écrit dans /var/lib/cloud/instance/scripts/ et exécuté tard dans le démarrage, en root, par le module scripts-user. Un document YAML #cloud-config est plutôt interprété. Tout ce qui ne commence par ni l’un ni l’autre est ignoré en silence — ce qui se lit comme « mon script n’a jamais roulé ».

La règle qui attrape tout le monde : le module scripts-user roule une fois par instance, indexé sur l’ID d’instance, suivi sous /var/lib/cloud/instances/. Les redémarrages ne le relancent pas. Arrêter une instance, modifier le user data et la redémarrer ne le relance pas non plus, parce que l’ID d’instance n’a pas changé. Cette surprise explique une grosse part des rapports « le user data est brisé ».

L’environnement d’exécution est aussi moins indulgent que votre terminal : le script roule en root avec un environnement minimal et non interactif — pas de $HOME significatif, un PATH court, pas de TTY. Les commandes qui posent des questions (apt-get install sans -y), qui dépendent d’un profil utilisateur, ou qui supposent le réseau d’une machine complètement démarrée vont geler ou mourir. Et sur Windows, rien de tout ça ne s’applique — EC2Launch v2 gère le user data avec son propre journal à C:\ProgramData\Amazon\EC2Launch\log\agent.log.

Quoi vérifier en premier

  • Y a-t-il du user data, et commence-t-il par un shebang ou #cloud-config? EC2 > Instances > Actions > Paramètres de l’instance > Modifier les données utilisateur montre ce que l’instance a réellement reçu.
  • Est-ce le premier démarrage de cette instance? Si l’instance a roulé avant l’ajout ou la modification du user data, la règle une-fois-par-instance explique déjà tout.
  • A-t-il roulé mais échoué? cloud-init-output.log contient le stdout/stderr du script — une erreur de paquet à la ligne 3 ressemble exactement à « il n’a jamais roulé » si vous ne regardez jamais.
  • L’AMI est-elle basée sur cloud-init? Les AMI minimales ou maison arrivent parfois sans cloud-init, et Windows utilise EC2Launch v2.
  • Le script a-t-il besoin du réseau ou de l’IMDS? Les appels au service de métadonnées doivent utiliser les jetons IMDSv2 — voyez le problème des 401 IMDSv2 — et les téléchargements externes exigent une sortie réseau fonctionnelle au démarrage.

Comment vérifier

Sur l’instance, dans l’ordre :

# cloud-init a-t-il terminé, et avec quel résultat?
cloud-init status --long

# La sortie et les erreurs du script vivent ici
sudo less /var/log/cloud-init-output.log

# Les décisions internes de cloud-init (modules exécutés, analyse du user data)
sudo grep -iE 'scripts-user|userdata|Traceback|WARN|FAIL' /var/log/cloud-init.log

# Le script a-t-il même été extrait? Si ce répertoire est vide, l'analyse a échoué
sudo ls -l /var/lib/cloud/instance/scripts/

Confirmez le user data que l’instance détient réellement (depuis l’instance, via IMDSv2) :

TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/user-data

Ou de l’extérieur : aws ec2 describe-instance-attribute --instance-id i-0123456789abcdef0 --attribute userData (encodé en base64). L’analyse de synchronisation, quand l’ordre de démarrage est suspect, vient de cloud-init analyze show. La référence du comportement de cloud-init fait partie de la documentation EC2 à https://docs.aws.amazon.com/ec2/.

Le correctif

  1. Le script a roulé et a échoué : corrigez l’erreur réelle. cloud-init-output.log montre la ligne fautive. Les classiques : apt-get install sans -y, un dépôt de paquets pas encore joignable, une commande absente du PATH minimal (utilisez des chemins absolus), ou un fichier édité sous Windows avec des fins de ligne CRLF qui brisent le shebang. Ajoutez set -euxo pipefail en tête pour que les prochains échecs soient bruyants et localisés.
  2. La règle une-fois-par-instance vous a eu : forcez une réexécution ou relancez. Pour un cas ponctuel, sudo cloud-init clean --logs && sudo reboot remet l’état de cloud-init à zéro pour que le user data roule au prochain démarrage. Pour un comportement répétable, intégrez la logique dans l’AMI, ou utilisez un user data MIME multi-parties avec une partie #cloud-config réglant cloud_final_modules: [[scripts-user, always]] pour que le script roule à chaque démarrage — et rendez le script idempotent avant.
  3. Pas de shebang ou mauvais format : la première ligne compte. Une simple liste de commandes n’est pas un script pour cloud-init. Commencez les scripts shell par #!/bin/bash comme premier octet — pas de ligne vide avant, pas de BOM. Le YAML doit commencer par #cloud-config exactement.
  4. User data modifié sur une instance arrêtée en espérant qu’il roule : il ne roulera pas, par défaut. Même correctif qu’au point 2 — cloud-init clean avant de démarrer, ou acceptez que le user data est un mécanisme de premier démarrage et mettez les changements du jour 2 dans SSM Run Command ou votre gestion de configuration.
  5. Le script doit être débogué interactivement : roulez-le à la main. Le script extrait se trouve dans /var/lib/cloud/instance/scripts/part-001. Exécutez-le en root dans un environnement propre (sudo env -i /bin/bash /var/lib/cloud/instance/scripts/part-001) et vous reproduirez le comportement du démarrage bien plus vite qu’en relançant des instances.

Quand c’est la mauvaise approche

Si vous itérez sur un script user-data de 300 lignes, arrêtez — le user data est le mauvais endroit pour de la vraie logique de provisionnement. Construisez une AMI dorée (Packer ou EC2 Image Builder) et limitez le user data à quelques lignes de configuration propre à l’instance; vos groupes Auto Scaling démarreront plus vite et échoueront moins, ce qui compte quand la vitesse de montée en charge est l’enjeu. Sautez aussi le terrier du user data quand l’instance n’est jamais devenue joignable du tout — un démarrage qui meurt avant cloud-init est un autre problème avec d’autres journaux.

Erreurs courantes

  • Redémarrer pour « redéclencher » le user data et conclure qu’il est brisé quand rien ne se passe — le redémarrage ne le relance jamais par défaut.
  • Pas de -y sur les installations de paquets, alors le script attend éternellement une réponse que personne ne peut donner.
  • Supposer que le script roule comme l’utilisateur par défaut — il roule en root, donc ~ et les chemins appartenant à l’utilisateur pointent ailleurs.
  • Tester le script dans un shell interactif où le PATH et l’environnement diffèrent du démarrage, puis le livrer sans test contre l’environnement réel.

Prévention

  • Commencez chaque script user-data par #!/bin/bash et set -euxo pipefail, et terminez-le par une ligne marqueur explicite que vous pouvez retrouver au grep.
  • Gardez le user data court et idempotent; mettez le provisionnement lourd dans l’AMI.
  • Journalisez aussi vers la console (exec > >(tee /var/log/user-data.log | logger -t user-data) 2>&1) pour que la sortie apparaisse dans Obtenir le journal système même quand SSH est brisé.
  • Versionnez le user data dans vos modèles de lancement pour que « qu’est-ce qui a changé » soit un diff, pas un souvenir.
  • Testez le nouveau user data sur une instance jetable avant de l’intégrer au modèle que votre flotte utilise pour démarrer.

Dix minutes à lire les journaux cloud-init battent un après-midi à relancer des instances sur une intuition. Si personne dans votre équipe ne possède vraiment l’environnement AWS de bout en bout, c’est la racine de la plupart de ces problèmes — Stack Harbor opère des comptes AWS au quotidien sous Opérations AWS, et vous pouvez réserver une revue AWS.