Construire un template VMware vSphere RHEL 8 avec Packer

J’ai récemment eu à automatiser la construction d’un template de VM RedHat Enterprise Linux 8 sur vSphere avec Packer, et j’ai passé quelques heures à me casser les dents sur le sujet.

Pour ce tout premier billet, je vais donc essayer de vous éviter de vivre les mêmes souffrances 😉.

Packer est un outil proposé par HashiCorp (l’éditeur de Terraform et Vault, notamment), qui permet la création d’image de machines as code. Packer prend en charge les fournisseurs de VM les plus courants, autant dans le cloud qu’on-premise. Dans notre cas, c’est sur VMware vSphere que portera ce billet.

Je ne vais pas m’étendre sur le sujet de l’installation de Packer, vous trouverez facilement de nombreuses références sur le sujet, notamment le quickstart d’HashiCorp.

Dans le monde RHEL, le fichier kickstart.cfg est un script d’initialisation permettant d’automatiser l’initialisation d’un système. Il permet notamment la configuration du réseau, du mot de passe root et des users de base, des partitions système, du serveur X (si vous installez une version desktop), des packages de base, etc.

Le chemin du fichier se transmet directement dans la commande de boot. Exemple pour un fichier présent à la racine de l’ISO de l’OS :

shell

vmlinuz initrd=initrd.img inst.ks=cdrom:/kickstart.cfg

Plus d’infos sur l’utilisation du kickstart.cfg sur la documentation officielle de Red Hat.

vSphere est une plateforme de virtualisation de VMware, qui s’appuie sur son hyperviseur maison ESXi. Il est nécessaire que votre cluster ESXi soit en version 6.7 ou supérieure, notamment afin de supporte la version 14 des VM.

Passons maintenant à la pratique !

Pour que Packer sache comment construire l’image de VM, il lui faut un fichier de configuration template.json (peut également être écrit en Hashicorp Configuration Language) :

json

{
  "variables": {
    "TEMPLATE_NAME":              "my-rhel8.4-template",
    "TEMPLATE_VERSION":           "1.0.0",
    "VCENTER_PACKER_PASSWORD":    "{{env `VCENTER_PACKER_PASSWORD`}}",
    "SSH_USER":                   "{{env `SSH_USER`}}",
    "SSH_PASSWORD":               "{{env `SSH_PASSWORD`}}"
  },
  "sensitive-variables": [
    "VCENTER_PACKER_PASSWORD",
    "SSH_PASSWORD"
  ],
  "builders": [{
    "type":                     "vsphere-iso",

    "vcenter_server":           "10.11.12.13",
    "username":                 "mypackeruser@vsphere.local",
    "password":                 "{{user `VCENTER_PACKER_PASSWORD`}}",
    "cluster":                  "My-vSphere-Cluster",

    "folder":                   "VM_TEMPLATES",
    "datastore":                "VM_TEMPLATES_DATASTORE",
    "vm_name":                  "{{user `TEMPLATE_NAME`}}-{{user `TEMPLATE_VERSION`}}",
    "vm_version":               14,
    "CPUs":                     1,
    "RAM":                      2048,
    "RAM_reserve_all":          false,
    "guest_os_type":            "rhel8_64Guest",
    "tools_upgrade_policy":     true,
    "firmware":                 "bios",
    "storage": [{
      "disk_size":                61440,
      "disk_thin_provisioned":    true
    }],
    "network_adapters": [{
      "network":                "vnet-default",
      "network_card":           "vmxnet3"
    }],
    "iso_paths":                "[VM_ISO_DATASTORE] iso/RHEL/linux-rhel-8.4.2137.iso",
    "cd_files":                 [ "./kickstart.cfg" ],
    "boot_command":             "<esc> <wait>vmlinuz initrd=initrd.img inst.ks=hd:sr1:/kickstart.cfg <enter><wait>",

    "ssh_username":             "{{user `SSH_USER`}}",
    "ssh_password":             "{{user `SSH_PASSWORD`}}",
    "convert_to_template":      "true"
  }]
}

Détaillons un peu ce fichier.

Packer petmet de définir des user variables que l’on peut ensuite utiliser dans le reste du fichier template :

json

{
  "variables": {
    "TEMPLATE_NAME":              "my-rhel8.4-template",
    "TEMPLATE_VERSION":           "1.0.0",
    "VCENTER_PACKER_PASSWORD":    "{{env `VCENTER_PACKER_PASSWORD`}}",
    "SSH_USER":                   "{{env `SSH_USER`}}",
    "SSH_PASSWORD":               "{{env `SSH_PASSWORD`}}"
  },
  "sensitive-variables": [
    "VCENTER_PACKER_PASSWORD",
    "SSH_PASSWORD"
  ]
}

2 variables sont ici déclarées comme sensitive variables. Cela permet qu’elles ne soient jamais affichées dans les logs quand Packer est exécuté en mode debug.

Le rôle d’un builder est de construire une VM puis une image de cette VM, en s’appuyant sur les paramètres qui lui sont fournis dans le template.json. C’est sur les builders qui repose le fonctionnement de Packer. Explorons la configuration de notre builder.

On sélectionne tout d’abord le type de builder que packer doit exécuter. Tous les autres paramètres varient en fonction du builder sélectionné. Ici, nous utilisons le builder vsphere-iso :

json

{
    "type": "vsphere-iso"
}

On configure la connexion au VMware vCenter, et on choisir le cluster vSphere sur lequel on va travailler :

json

{
    "vcenter_server":           "vcenter.localdomain",
    "username":                 "mypackeruser@vsphere.local",
    "password":                 "{{user `VCENTER_PACKER_PASSWORD`}}",
    "cluster":                  "My-vSphere-Cluster",
}

On définit tous les paramètres de la VM, notamment hardware, ainsi que la commande de boot :

json

{
    "folder":                   "VM_TEMPLATES",
    "datastore":                "VM_TEMPLATES_DATASTORE",
    "vm_name":                  "{{user `TEMPLATE_NAME`}}-{{user `TEMPLATE_VERSION`}}",
    "vm_version":               14,
    "CPUs":                     1,
    "RAM":                      2048,
    "RAM_reserve_all":          false,
    "guest_os_type":            "rhel8_64Guest",
    "tools_upgrade_policy":     true,
    "firmware":                 "bios",
    "storage": [{
        "disk_size":                61440,
        "disk_thin_provisioned":    true
    }],
    "network_adapters": [{
        "network":                "VLAN100",
        "network_card":           "vmxnet3"
    }],
    "iso_paths":                "[VM_ISO_DATASTORE] iso/RHEL/linux-rhel-8.4.2137.iso",
    "cd_files":                 [ "./kickstart.cfg" ],
    "boot_command":             "<esc> <wait>vmlinuz initrd=initrd.img inst.ks=hd:sr1:/kickstart.cfg <enter><wait>"
}

Quelques points d’attention ici :

  • vm_version : Ce paramètre définit la version hardware de la VM. La version 14 est nécessaire pour que le guest_os_type supporte la valeur rhel8_64Guest. Cela implique que votre cluster ESXi doit être en version 6.7 ou supérieure. – iso_paths : On préfixe ici le chemin de l’iso par le nom entre crochets du datastore vSphere dans lequel le fichier est situé
  • cd_files : Cette option permet de packager à la volée un fichier ISO, et de le connecter sur la VM en tant que 2° lecteur cdrom. Vous aurez besoin pour ça d’un outil de création d’ISO comme xorriso. Plus d’infos sur la documentation Packer vsphere-iso

Enfin, on configure le mode de passage de la VM à un template, et le communicator qui va s’assurer grâce à SSH que la VM est bien joignable avant de la transformer en template :

json

{
    "ssh_username":             "{{user `SSH_USER`}}",
    "ssh_password":             "{{user `SSH_PASSWORD`}}",
    "convert_to_template":      "true"
}

On va maintenant créer un fichier kickstart.cfg assez basique afin qu’il se charge de l’installation de base de l’OS.

cfg

text
cdrom

lang fr_FR
keyboard fr
timezone Europe/Paris

###############################################################################
# Partitioning
###############################################################################
zerombr
# On utilise uniquement le premier et seul disque
bootloader --location=mbr --boot-drive=sda
clearpart --all --drives=sda --initlabel
ignoredisk --only-use=sda
# On laisse le partitionnement par défaut
autopart
# On peut également le définir soi-même en utilisant les commandes part, volgroup, et logvol (pour LVM)
# Plus d'infos : https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/performing_an_advanced_rhel_8_installation/kickstart-commands-and-options-reference_installing-rhel-as-an-experienced-user#part-or-partition_kickstart-commands-for-handling-storage


# On paramètre la carte réseau en DHCP
network --onboot=yes --bootproto=dhcp --hostname=rhel8template
# Pour une configuration statique, on utilisera plutôt :
# network --onboot=yes --bootproto=static --hostname=rhel8template --ip=10.12.14.16 --netmask=255.255.255.0 --gateway=10.12.14.254 --nameserver 10.11.12.252,10.11.12.253

#Ensure SElinux is disabled
selinux --disabled

#Ensure firewalld is disabled
firewall --disabled

skipx

# On définit le mot de passe root et on crée notre utilisateur sudoer
# Pour générer les mots de passes hashés en SHA512, on peut utiliser la commande suivante : openssl passwd -6 myplaintextpassword
rootpw rootcryptedpassword --iscrypted
user --name=mysudoeruser --groups=wheel --password=mycryptedpassword --iscrypted 

%packages
@Core
%end

%post --log=/var/log/ks-post.log
#!/bin/sh

# Install OSS VMWare tools
echo "Installing VM Tools..."
# Le package open-vm-tools est l'implémentation Open Source des VMware tools pour Linux. Il est indispensable, car il permet à vSphere de détecter l'adresse IP
# Les VMware tools utilisent le langage PERL pour récupérer des informations sur la VM. L'interpréteur perl est donc lui aussi indispensable.
dnf install -y open-vm-tools perl-interpreter

# On active le service
systemctl enable vmtoolsd
systemctl start vmtoolsd
%end

reboot

Maintenant que notre configuration est prête, on peut définir les variables d’environnement nécessaires, et lancer la construction du template:

shell

export VCENTER_PACKER_PASSWORD=myvcenterpackeruserpassword
export SSH_USER=mysudoeruser
export SSH_PASSWORD=mysudoerusersuperpassword
packer build template.json

Packer va maintenant :

  1. Construire le fichier ISO à la volée à partir des cd_files et charger cet ISO sur le cluster vSphere
  2. Contacter le vCenter pour créer la VM avec les paramètres demandés
  3. Attendre que l’installation de l’OS se termine et que vSphere récupère l’IP de la machine
  4. S’assurer que la connexion SSH vers la VM est fonctionnelle
  5. Convertir la VM en template

shell

packer build $PACKER_OPTS ./template.json
vsphere-iso: output will be in this color.
==> vsphere-iso: Creating CD disk...
    vsphere-iso: xorriso 1.5.4 : RockRidge filesystem manipulator, libburnia project.
    vsphere-iso: Drive current: -outdev 'stdio:/tmp/packer2146570282.iso'
    vsphere-iso: Media current: stdio file, overwriteable
    vsphere-iso: Media status : is blank
    vsphere-iso: Media summary: 0 sessions, 0 data blocks, 0 data, 49.1g free
    vsphere-iso: xorriso : WARNING : -volid text does not comply to ISO 9660 / ECMA 119 rules
    vsphere-iso: Added to ISO image: directory '/'='/tmp/packer_to_cdrom4086516809'
    vsphere-iso: xorriso : UPDATE :       1 files added in 1 seconds
    vsphere-iso: xorriso : UPDATE :       1 files added in 1 seconds
    vsphere-iso: ISO image produced: 188 sectors
    vsphere-iso: Written to medium : 188 sectors at LBA 0
    vsphere-iso: Writing to 'stdio:/tmp/packer2146570282.iso' completed successfully.
    vsphere-iso: Done copying paths from CD_dirs
==> vsphere-iso: Uploading packer2146570282.iso to packer_cache/packer2146570282.iso
==> vsphere-iso: Creating VM...
==> vsphere-iso: Customizing hardware...
==> vsphere-iso: Mounting ISO images...
==> vsphere-iso: Adding configuration parameters...
==> vsphere-iso: Set boot order temporary...
==> vsphere-iso: Power on VM...
==> vsphere-iso: Waiting 10s for boot...
==> vsphere-iso: Typing boot command...
==> vsphere-iso: Waiting for IP...
==> vsphere-iso: IP address: 10.12.14.16
==> vsphere-iso: Using SSH communicator to connect: 10.12.14.16
==> vsphere-iso: Waiting for SSH to become available...
==> vsphere-iso: Connected to SSH!
==> vsphere-iso: Shutting down VM...
==> vsphere-iso: Deleting Floppy drives...
==> vsphere-iso: Eject CD-ROM drives...
==> vsphere-iso: Convert VM into template...
==> vsphere-iso: Clear boot order...
Build 'vsphere-iso' finished after 7 minutes 21 seconds.
==> Wait completed after 7 minutes 21 seconds
==> Builds finished. The artifacts of successful builds are:
--> vsphere-iso: my-rhel8.4-template

Initialement, je partais d’un template pour CentOS 7 existant et fonctionnel. J’étais (naïvement) assez confiant dans le fait que la création d’un nouveau template RHEL 8 ne me prendrait pas tant de temps que ça.

Seulement, mon template montait le fichier kickstart.cfg dans un floppy disk virtuel. Et à partir de RHEL 8 (et ses semblables), les floppy disks ne sont plus supportés.

Ne connaissant pas les release notes de RHEL 8 par cœur, il m’a fallu un certain temps pour comprendre que mon kickstart.cfg n’était tout simplement pas détecté au boot (car pas monté sur la VM 🙄). Le message d’erreur était tout sauf intuitif :

text

Warning: /dev/root does not exist

Après quelques heures de recherche au fin fond des forums de discussion sur le sujet, j’ai fini par trouver la solution : remplacer le paramètre floppy_files par cd_files (et installer xorriso).

Un autre solution aurait pu être d’utiliser http_directory et de passer inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/kickstart.cfg en commande de boot pour bénéficier du serveur HTTP interne de Packer, mais dans mon cas, Packer s’exécute dans un container créé par mon pipeline GitLab CI, et je ne peux donc pas exposer son serveur HTTP à l’extérieur.

Cet exercice m’a permis de vraiment mettre les mains dans l’installation automatisée de distributions RHEL-like via kickstart.cfg, et d’en apprendre plus sur Packer.

Je pourrai revenir, dans un prochain billet, sur les provisioners de Packer, qui permettent d’exécuter des étapes de configuration supplémentaire aux actions faites par le builder avant de transformer la VM en template.

Si vous avez aimé ce billet, n’hésitez pas à venir m’en parler sur Twitter ou LinkedIn !