Bricoler les images cloud d'Ubuntu 24.04 (Noble Numbat) pour résoudre un problème de déploiement sur VMware/VSphere

Ubuntu 24.04 est sorti depuis quelques mois maintenant, et je profite de l'été pour migrer de vieilles machines vers cette nouvelle version. Il ne s'agit pas d'une simple mise à jour de machines existantes depuis la LTS précédente, mais d'un remaniement plus global de notre infra qui implique le déploiement de nouvelles machines toutes neuves.

Cette partie de notre infra est basée sur la solution de virtualisation VSphere de VMware, et les machines sont provisionnées à l'aide de Terraform et d'Ansible.

Normalement il n'y a pas de difficultés particulières : on importe la cloudimg d'Ubuntu 24.04 sur VSphere puis on peut commencer à déployer des machines avec Terraform... Sauf que tout ne s'est évidemment pas passé comme prévu [bah oui, sinon il n'y aurait pas d'article 😜️].

Aujourd'hui je vais donc vous expliquer quels problèmes j'ai rencontrés et comment je les ai réglés.

Note

NOTE : Entre le moment où j'ai rédigé cet article (il y a environ deux mois) et sa publication aujourd'hui, les choses ont bougé du côté de VMware. Ils ont finalement sorti des mises à jour fin juillet pour VSphere 7.x et 8.x. Une fois ces mises à jour déployées chez les cloud providers le problème ne devrait plus se présenter.

Je laisse toutefois cet article en ligne car il décrit la démarche pour se sortir de ce genre de situation et comment bricoler concrètement des templates de VM.

Le commencement

Pour créer ma machine, je commence par télécharger la dernière version de l'image cloud d'Ubuntu 24.04 pour VMware. Il s'agit du fichier "noble-server-cloudimg-amd64.ova" trouvable à cette adresse :

J'upload ensuite ce fichier sur mon VSphere, puis je crée la config Terraform pour déployer une première machine de teste. Ça ressemble plus ou moins à ça (sauf que là j'ai omis plein de trucs car Terraform n'est pas vraiment le sujet de l'article) :

# [...]

data "vsphere_virtual_machine" "template_ubuntu2404" {
  name          = "ubuntu-noble-24.04-cloudimg"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

resource "vsphere_virtual_machine" "testvm" {
  name = "testvm.XXXXXXXXXXXXXXXXXXXXXXX"

  clone {
    template_uuid = data.vsphere_virtual_machine.template_ubuntu2404.id
    customize {
      # [...]
    }
  }

  # [...]

}

Il ne me reste plus qu'à appliquer ma config pour que Terraform crée la machine :

terraform apply

Au début ça se présente bien... :

[...]

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

vsphere_virtual_machine.testvm: Creating...
vsphere_virtual_machine.testvm: Still creating... [10s elapsed]
vsphere_virtual_machine.testvm: Still creating... [20s elapsed]
vsphere_virtual_machine.testvm: Still creating... [30s elapsed]
vsphere_virtual_machine.testvm: Still creating... [40s elapsed]

... et puis tout à coup, c'est le drame :

╷
│ Error:
│ Virtual machine customization failed on "/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/vm/infra/testvm.XXXXXXXXXXXXXXXXXXXXX":
│
│ An error occurred while customizing VM testvm.XXXXXXXXXXXXXXXXXXXXX. For details reference the log file /var/log/vmware-imc/toolsDeployPkg.log in the guest OS.
│
│ The virtual machine has not been deleted to assist with troubleshooting. If
│ corrective steps are taken without modifying the "customize" block of the
│ resource configuration, the resource will need to be tainted before trying
│ again. For more information on how to do this, see the following page:
│ https://www.terraform.io/docs/commands/taint.html
│
│
│   with vsphere_virtual_machine.testvm,
│   on testvm.tf line 1, in resource "vsphere_virtual_machine" "testvm":
│    1: resource "vsphere_virtual_machine" "testvm" {
│

'amarchepô™ 🙁️

Le problème

La provision de VM se passe globalement en trois étapes :

  • premièrement on crée la machine à partir d'une machine template (ici, "template_ubuntu2404"),
  • puis la machine est démarrée, et lors de ce premier démarrage des scripts sont chargés et exécutés depuis une image ISO montée dans le lecteur CD-ROM de la VM. Ces scripts permettent de customiser la machine (expansion de la partition racine sur le disque, paramétrage du réseau, configuration de l'horloge, etc.).
  • Une fois que tous les scripts se sont exécutés, la machine finit de démarrer et est alors prête au service.

En analysant un peu le message d'erreur, on s'aperçoit que le problème survient à l'étape de customisation de la VM ; quelque chose se passe donc mal lors de l'exécution de l'un des scripts. Heureusement, le message d'erreur nous pointe vers un fichier de log ("/var/log/vmware-imc/toolsDeployPkg.log") qui nous en apprendra probablement davantage.

Pour aller lire notre fichier de log, on va devoir aller le chercher sur le disque de la machine nouvellement créée. Comme le système n'est pas vraiment fonctionnel, le plus simple est de monter l'image ISO de n'importe quelle distribution Linux dans le lecteur CD de la VM, de la démarrer, puis de monter le disque pour y récupérer notre fichier.

Voici la partie intéressante du journal que j'ai obtenu :

DEBUG: Command: 'whereis hwclock'
DEBUG: Exit Code: 0
DEBUG: Result: hwclock:

ERROR: Fatal error occurred during customization !! Customization halted.
ERROR: Error : Path to hwclock not found. hwclock:

INFO: Return code is 251

Ici on apprend que l'un des scripts aurait besoin d'exécuter la commande hwclock mais que celle-ci n'est à priori pas disponible dans l'image, et donc la configuration échoue.

Explications

L'outil hwclock sert à configurer l'horloge matérielle de la machine, et permet notamment de définir si elle est à l'heure UTC ou locale.

L'un des scripts de VMware cherche à appeler cet outil pour configurer l'horloge, sauf que Canonical l'a retiré de ses images depuis Ubuntu 23.10. Pourquoi me demanderez-vous ? Et bah par ce que systemd [qui a décidément la fâcheuse habitude de se trouver dans les parages quand un problème survient...] implémente également cette fonctionnalité à travers timedatectl [un jour ce truc va devenir un OS à part entière...]. Résultat : Ubuntu a décidé d'utiliser cette fonctionnalité qui était de toute façon déjà embarquée et de supprimer hwclock qui faisait doublon.

Pour en apprendre davantage, vous pouvez lire ce ticket sur Launchpad :

En résumé les développeurs d'Ubuntu et de VMware se renvoient la balle. Les premiers ne veulent pas remettre hwclock par ce que ça fait doublon et que ça impliquerait de repasser le paquet fake-hwclock (qui simule le fonctionnement de hwclock tout en appelant l'implémentation de systemd en arrière plan) dans le dépôt principal, et les seconds ne peuvent que mettre à jour leur script... Mais il faudra attendre les prochaines versions de VSphere, qui ne vont pas sortir tout de suite, sans compter le temps de déploiement chez les cloud providers.

Bref, on est actuellement un peu coincés... 🙄️

La solution

J'ai essayé pas mal de choses pour résoudre le problème et à chaque fois j'ai rencontré des soucis différents comme l'impossibilité de monter l'image disque au format VMDK en écriture (alors qu'en lecture seule ça fonctionne), ou encore en essayant de faire les choses « proprement » en installant le paquet fake-hwclock dans l'image, ce qui a causé un « gonflement » du fichier qu'il n'était alors plus possible d'uploader sur VSphere (entre autres problèmes encore liés à — je vous laisse deviner — systemd ! 😓️).

Au final je suis arrivé à une solution plus ou moins satisfaisante à base de conversion de format d'image disque et d'ajout d'un script bash nommé hwclock qui ne fera rien mais qui permettra au script de VMware de ne pas planter.

On met les mains dans le cambouis

Voici les étapes que j'ai effectuées pour bricoler le template de VM au format OVA (Open Virtual Application).

Pour commencer on va télécharger la dernière image cloud d'Ubuntu 24.04 disponible :

wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.ova

Ensuite on va l'extraire (les fichiers .ova ne sont en fait que des archives au format TAR) :

tar -xf noble-server-cloudimg-amd64.ova

On se retrouve alors avec un certain nombre de fichiers :

  • "ubuntu-noble-24.04-cloudimg.mf" : contient les sommes de contrôle des deux autres fichiers,
  • "ubuntu-noble-24.04-cloudimg.ovf" : fichier XML qui contient la description complète de la machine virtuelle (matériel, configurations,...),
  • "ubuntu-noble-24.04-cloudimg.vmdk" : l'image disque de la machine virtuelle.

Celui qui va nous intéresser, c'est le VMDK qui contient notre système.

Comme je l'ai dit plus tôt, je n'ai pas réussi à le monter en écriture, on va donc devoir convertir le VMDK en image disque RAW. Pour commencer on va installer quelques dépendances pour manipuler et modifier les images disques :

sudo apt install libguestfs-tools qemu-utils

Puis on effectue la conversion VMDK → RAW :

qemu-img convert \
    -f vmdk ubuntu-noble-24.04-cloudimg.vmdk \
    -O raw ubuntu-noble-24.04-cloudimg.img

Une fois la conversion terminée, on va pouvoir créer le script bash qui simulera la présence de la commande hwclock et l'intégrer dans l'image :

sudo virt-customize \
    -a ubuntu-noble-24.04-cloudimg.img \
    --write /usr/bin/hwclock:'#!/bin/bash' \
    --chmod 0755:/usr/bin/hwclock \
    --truncate /etc/machine-id

Note

NOTE : La commande ci-dessus va créer le fichier "/usr/bin/hwclock" et le rendre exécutable. Elle va également supprimer le contenu du fichier "/etc/machine-id" qui est automatiquement rempli par virt-customize. Lorsque ce fichier contient un id, cela indique que la machine a déjà été initialisée et les scripts de customisation ne s'exécuterons donc pas au prochain démarrage... or nous on veut qu'ils s'exécutent la prochaine fois 😉️.

Maintenant que l'image a été bricolée, on va pouvoir la reconvertir au format VMDK pour VMware :

qemu-img convert \
    -f raw ubuntu-noble-24.04-cloudimg.img \
    -O vmdk -o subformat=streamOptimized ubuntu-noble-24.04-cloudimg.vmdk

Idéalement, on devrait à présent modifier la taille du disque dans le fichier .ovf puis remettre à jour les sommes de contrôle dans le fichier .mf, et éventuellement replacer le tout dans une archive pour retrouver notre .ova d'origine... Mais en réalité c'est pas nécessaire : VMware s'en fiche que la taille soit à jour dans le fichier .ovf, et il peut se passer du .mf.

Pour importer la machine dans VSphere, il suffit donc de sélectionner les fichiers .ovf et .vmdk lors de l'importation du template et cela fonctionnera très bien. 😜️

Capture d'écran : importation des fichiers .ovf et .vmdk dans VSphere

Importation des fichiers .ovf et .vmdk dans VSphere

Conclusion

Ubuntu est l'un des systèmes les plus déployés dans le cloud, et je trouve assez dommage de devoir faire des bricolages dans ce genre pour pouvoir déployer sa dernière version sur VSphere, surtout que le problème était connu depuis des mois. Je pense que Canonical ou VMware aurait dû proposer une solution avant la sortie de la dernière LTS d'Ubuntu.

Pour terminer sur une note positive, cette aventure aura au moins été pour moi l'occasion d'en apprendre plus sur le bricolage d'images et sur le mécanisme de provisionnement de VSphere que je n'avais fait qu'effleurer jusqu'ici.

J'espère en tout cas que cet article pourra être utile à ceux qui se trouvent dans une situation similaire car je n'avais pas trouvé grand-chose sur les zinternets quand j'ai rencontré ce problème il y a quelques mois.

Je vous souhaite une bonne journée et je vous retrouve prochainement pour de nouveaux articles ! 😁️


EDIT 2024-08-29: Ajout de la note sur la résolution du problème chez VMware suite au commentaire de Ago. :)