Pour la création du cluster Kubernetes tel que décrit dans l’article précédent, il est nécessaire de partir d’un template de référence. Celui-ci va servir au déploiement de tous les nœuds du cluster via Terraform et chaque serveur pourra ensuite pouvoir être configuré via Ansible.
L’idée est de rester sur un template le plus simple possible. En l’état, il n’a pas besoin d’avoir de spécificité particulière, mais il doit pouvoir être exploitable ultérieurement via une connexion SSH depuis Ansible.
D’ailleurs, sur ce point, je vais reprendre ma logique d’utilisation d’Ansible à partir de mon poste W11 via WSL (Windows Subsystem For Linux).
Je vous encourage à parcourir l’article détaillant la création d’un compte dédié, nommé ansible-windows, pour lequel j’ai généré une paire de clés SSH composée d’une clé privée et d’une clé publique.
C'est le moyen de m'authentifier avec ce comte qu’il va me falloir intégrer à mon template pour qu'il soit reconnu au niveau de chaque serveur qui sera déployé à partir de ce template.
Pour créer notre template, on pourrait simplement faire une installation manuelle d’une première VM sous XCP-ng puis convertir cette dernière en template. Mais pour automatiser davantage l’exercice, nous allons exploiter un outil spécifique : packer.
Cliquez sur l'image pour l'agrandir.
Packer est un outil open source édité par HashiCorp (la société derrière Terraform, Vault, Consul, Nomad…, rachetée par IBM début 2025), créé en 2013 par Mitchell Hashimoto. Il partage l'ADN de Terraform : même éditeur, même langage de configuration HCL2 (HashiCorp Configuration Language), même philosophie déclarative… et depuis 2023, même licence BSL (Business Source License).
Parcker automatise la fabrication d’une image machine, on parle de golden image. Plutôt que d’installer notre OS de référence à la main, on décrit sa configuration dans un langage spécifique pour dire comment construire l’image. Cela permet de versionner et de suivre son paramétrage comme on pourrait le faire avec une application. C’est aussi beaucoup plus facilement reproductible et traçable.
En cas de besoin, on ne modifie pas un serveur en production, on reconstruit une image propre et on redéploie. La config de l'image vit un dans un outil de sourcing de code comme Git et pas dans la tête de l'administrateur.
Packer interprète la description de la configuration pour la mettre en place sur la plateforme cible. D’ailleurs, comme pour Terraform, Packer a besoin de plugins en lien avec l’emplacement où l’image va être déployée. En l’occurrence pour nous, XCP-ng. Ça tombe bien, puisque Vates propose un plugin officiel disponible ici : packer-plugin-xenserver.
Cliquez sur l'image pour l'agrandir.
En termes d’OS, comme décrit dans l’article précédent, j’ai retenu Rocky Linux 10. Il est né fin 2020 suite à une annonce choc : la décision de Red Hat d'abandonner CentOS Linux (la version stable) au profit de CentOS Stream (une version de développement continu). Face à ce changement, Gregory Kurtzer, le fondateur original du projet CentOS, a lancé Rocky Linux pour combler le vide et offrir à nouveau un OS de production stable, gratuit et communautaire.
Un équivalent que l’on retrouve souvent est Alma Linux. Il partage la même philosophie, mais avec une liberté un peu plus grande quant au patching où son éditeur, CloudLinux, n’attend pas forcément la mise à disposition d’un patch RedHat pour corriger une faille.
Pour résumer, Rocky Linux est un clone strict et absolu de RHEL (RedHat Enterprise Linux), et Alma Linux est plus rapide sur certains correctifs, et peut supporter un hardware plus varié, mais avec de légers écarts par rapport à RHEL. L’une comme l’autre sont de très bons choix pour un OS d’entreprise.
Lorsqu’on utilise Packer on enchaine trois composants :
Le tout est décrit en HCL2 dans des fichiers *.pkr.hcl, avec des variables pour tout ce qui change d'un environnement à l'autre (secrets, noms de SR, réseau…). Un plugin par plateforme (required_plugins) apporte le builder adéquat — vatesfr/xenserver pour XCP-ng.
J’apporte tout de suite une information importante : Packer n’est pas terraform et inversement. On pourrait concevoir tous les nodes avec Packer en créant plusieurs images. Mais ce n’est pas le but. Cela serait long, puisque, contrairement à Terraform qui va s’appuyer sur des clonages pour créer les VMs, Packer va déployer l’OS dans chaque serveur.
De plus Packer et Terraform n’ont pas le même rôle : Packer fabrique le moule, Terraform coule les pièces. La ou nos futures taches Terraform seront propres au cluster K8S, ici le template est générique et pourrait très bien service à d’autres usages.
Packer est un simple exécutable qu’on peut récupérer directement sur le site de l’éditeur. Il suffit de télécharger la version propre à son OS et de faire en sorte que le binaire puisse être appelé depuis son invite de commande.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Ensuite, on va organiser notre arborescence projet. Celle-ci va se composer ainsi.
rocky10-base.pkr.hcl # packer{} (plugin), source xenserver-iso, build{} (provisioners)
variables.pkr.hcl # toutes les variables (descriptions FR)
example.pkrvars.hcl # modèle à copier en xkub.auto.pkrvars.hcl (gitignored)
http/ks.cfg # kickstart servi par le serveur HTTP de Packer
http/ansible-windows.pub # clé PUBLIQUE servie au kickstart (versionnée, = ../keys/...)
scripts/10-guest-tools.sh # EPEL + xe-guest-utilities + dnf update
scripts/20-cloud-init.sh # datasource NoCloud
scripts/90-cleanup.sh # généralisation (machine-id, clés SSH, seed cloud-init)
Arborescence du projet Packer.
Arborescence dont notre subagent pour claude code, packer-builder.md, introduit dans le premier article va avoir la charge.
Vous noterez l'usage d'un dossier http. C'est parce que packer expose un serveur web interne durant son exécution afin de pouvoir présenter à l'OS en cours de build les fichiers nécessaires à sa configuration. A prendre en compte si un firewall est présent entre votre instance Packer et votre plateforme ou se déploie le serveur.
On va se concentrer sur quelques fichiers.
rocky10-base.pkr.hcl - Je ne vais pas rentrer dans le détail, mon agent claude a déjà bien commenté les fichiers. Globalement on charge les plugins nécessaires, on récupère les variables dont on a besoin, puis on lance la construction de la VM pour enfin terminer par la customisation nécessaire, notamment l’injection des drivers XCP-ng (l’équivalent des VMware tools).
# ============================================================================
# rocky10-base.pkr.hcl — Source + build du template Rocky Linux 10
# Plugin : vatesfr/xenserver (builder xenserver-iso). Cf. cookbook 01.
# ============================================================================
packer {
required_plugins {
xenserver = {
version = ">= 0.9.0"
source = "github.com/vatesfr/xenserver"
}
}
}
source "xenserver-iso" "rocky10" {
# --- Connexion à l'hôte XCP-ng (XAPI) ---
remote_host = var.remote_host
remote_username = var.remote_username
remote_password = var.remote_password
remote_ssh_port = var.remote_ssh_port
# --- Emplacements XCP-ng ---
sr_name = var.sr_name
sr_iso_name = var.sr_iso_name
network_names = var.network_names
# --- Source ISO ---
iso_url = var.iso_url
iso_checksum = var.iso_checksum
# --- VM de build / futur template ---
vm_name = var.vm_name
vm_description = var.vm_description
vcpus_max = var.vcpus
vcpus_atstartup = var.vcpus
vm_memory = var.vm_memory
disk_size = var.disk_size
firmware = "bios"
# --- Kickstart servi par le serveur HTTP intégré de Packer ---
# Le dossier http/ contient ks.cfg ET ansible-windows.pub (clé publique servie
# au kickstart, qui la récupère par curl — pas de mot de passe nécessaire).
http_directory = "http"
boot_wait = var.boot_wait
install_timeout = var.install_timeout
# Détection d'IP par phone-home HTTP : la VM de build (DHCP) télécharge le kickstart
# depuis le serveur HTTP de Packer, qui capture l'IP source. Le plugin vatesfr/xenserver
# n'expose pas de ssh_host fixe ; "tools" est exclu (guest tools installés seulement au
# provisioning). L'IP reste la même avant/après reboot (bail DHCP stable sur la MAC).
ip_getter = "http"
# PIÈGE EL10 : Rocky Linux 10 amorce l'ISO avec GRUB2 (et non isolinux), même en
# BIOS. Les touches d'édition diffèrent : 'e' pour éditer l'entrée (pas <tab>),
# on complète la ligne 'linux ...', puis Ctrl-X pour démarrer.
boot_command = [
"<wait5>",
"<up><wait>", # sélectionner « Install Rocky Linux Minimal 10.2 »
"e<wait>", # éditer l'entrée GRUB2
"<down><down><end>", # fin de la ligne « linux /images/pxeboot/vmlinuz ... »
" inst.text inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg",
"<wait><leftCtrlOn>x<leftCtrlOff>", # démarrer
]
# --- Connexion SSH post-install (par clé ansible-windows) ---
ssh_username = var.ssh_username
ssh_key_path = var.ssh_private_key_path
ssh_wait_timeout = "60m"
# --- Finalisation : convertir la VM en template ET la conserver sur l'hôte ---
# keep_vm = "always" : le template reste dans XCP-ng à la fin du build (objectif
# phase 1 = template réutilisable dans XO, pas seulement un fichier XVA local).
# Avec "never" (défaut du plugin), la VM était convertie en template, exportée en
# XVA dans output-rocky10/, puis DÉTRUITE de l'hôte → invisible dans XO.
skip_set_template = false
keep_vm = "always"
}
build {
name = "rocky10-base"
sources = ["source.xenserver-iso.rocky10"]
# Hostname OS aligné sur le nom de la VM/template (source unique = var.vm_name).
# Il sera de toute façon redéfini par clone via cloud-init (Terraform, phase 2) ;
# ici c'est surtout une question de cohérence VM XCP-ng <-> hostname OS.
provisioner "shell" {
inline = ["sudo hostnamectl set-hostname ${var.vm_name}"]
}
# ansible-windows dispose de sudo NOPASSWD (posé par le kickstart).
provisioner "shell" {
execute_command = "sudo -E bash '{{ .Path }}'"
scripts = [
"scripts/10-guest-tools.sh",
"scripts/20-cloud-init.sh",
"scripts/90-cleanup.sh",
]
}
}
rocky10-base.pkr.hcl
variables.pkr.hcl - C’est le fichier qui va contenir la définition de toutes les variables dont on va avoir besoin et qui vont être exploitées par ce qu’on vient de voir dans le précédent fichier. Leur nomenclature est assurée par mon fichier CONVENTIONS.md qui va être suivi par l’agent packer-builder.md au niveau de Claude. À noter que c’est ici aussi qu’on indique ou récupérer notre image ISO pour Rocky Linux et donc la version de l’OS à installer (iso_url). Comme vous l’observerez, on peut directement chercher l’image en ligne.
# ============================================================================
# variables.pkr.hcl — Template Rocky Linux 10 (xkub.coolcorp.priv)
# Toutes les variables du build. Les secrets ne portent PAS de valeur ici :
# ils sont fournis par variables d'environnement PKR_VAR_* (cf. CONVENTIONS.md).
# ============================================================================
# --- Connexion à l'hôte XCP-ng (API XAPI) -----------------------------------
variable "remote_host" {
type = string
description = "Adresse de l'hôte XCP-ng (pool master) sur lequel Packer construit la VM."
}
variable "remote_username" {
type = string
default = "root"
description = "Compte XAPI de l'hôte XCP-ng."
}
variable "remote_password" {
type = string
sensitive = true
description = "Mot de passe XAPI. À fournir via PKR_VAR_remote_password (jamais en clair dans un fichier versionné)."
}
variable "remote_ssh_port" {
type = number
default = 22
description = "Port SSH de l'hôte XCP-ng (utilisé par le plugin pour piloter le build)."
}
# --- Emplacements XCP-ng ----------------------------------------------------
variable "sr_name" {
type = string
description = "Nom du Storage Repository où créer le disque de la VM puis le template."
}
variable "sr_iso_name" {
type = string
description = "Nom du SR de type ISO où Packer téléverse l'ISO Rocky Linux."
}
variable "network_names" {
type = list(string)
description = "Réseau(x) XCP-ng à attacher à la VM de build (DHCP + accès aux dépôts requis)."
}
# --- Source ISO Rocky Linux 10 ----------------------------------------------
variable "iso_url" {
type = string
default = "https://download.rockylinux.org/pub/rocky/10/isos/x86_64/Rocky-10.2-x86_64-minimal.iso"
description = "URL de l'ISO Rocky Linux 10 minimal."
}
variable "iso_checksum" {
type = string
default = "sha256:aac6ac3ce781b91a91ce78463405f66c611a5dca4b3840c79e5e01d97302f6c8"
description = "Empreinte de l'ISO (Rocky-10.2-x86_64-minimal.iso)."
}
# --- Définition de la VM de build / template --------------------------------
variable "vm_name" {
type = string
default = "prdtplroc501"
description = "Nom du template produit (convention de nommage prd du projet)."
}
variable "vm_description" {
type = string
default = "Template generique Rocky Linux 10 - xkub.coolcorp.priv (Packer)"
description = "Description du template dans XO."
}
variable "vcpus" {
type = number
default = 2
description = "vCPU de la VM de build (le dimensionnement final est fait par Terraform au clone)."
}
variable "vm_memory" {
type = number
default = 2048
description = "RAM (Mo) de la VM de build."
}
variable "disk_size" {
type = number
default = 20480
description = "Taille (Mo) du disque du template. Étendu par rôle au clone (cloud-init growpart)."
}
# --- Connexion SSH post-install ---------------------------------------------
variable "ssh_username" {
type = string
default = "ansible-windows"
description = "Compte utilisé par Packer pour provisionner la VM (créé par le kickstart)."
}
variable "ssh_private_key_path" {
type = string
description = "Chemin LOCAL (poste W11) vers la clé PRIVÉE ansible-windows. Hors dépôt."
}
# --- Timings -----------------------------------------------------------------
variable "boot_wait" {
type = string
default = "10s"
description = "Délai avant l'envoi du boot_command (laisser le menu d'amorçage s'afficher)."
}
variable "install_timeout" {
type = string
default = "60m"
description = "Durée maximale de l'installation Anaconda."
}
variables.pkr.hcl
http/ks.cfg - C’est le fichier traditionnel associé à l’installation automatisée d’un OS type RedHat. On y trouve le paramètre classique, comme la configuration du clavier, du réseau, du partitionnement. C’est ici qu’il faut agir si on veut modifier la configuration globale de notre image. On dépend d'un DHCP pour l'obtention de l'IP, c'est une dépendance à avoir en tête pour cet exemple. Ce qui est important ici, c’est également la création du compte ansible-windows et son ajout dans un fichier sudo afin de permettre à ce dernier de devenir root si nécessaire. On ne créera pas de mot de passe spécifique pour ce user et on bloque son authentification via password (passwd -l). C’est la clef publique ansible-windows.pub placé dans le même sous-dossier http que ks.cfg qui va être utilisée. Cela implique que toute machine dérivée de cette future image ne pourra être connecté qu’avec la clef ssh privée de ansible-windows.
# ============================================================================
# ks.cfg — Kickstart Rocky Linux 10, template générique xkub.coolcorp.priv
# Servi par le serveur HTTP intégré de Packer (http/). Voir cookbook 01.
# Aucun secret ici : ansible-windows reçoit sa clé publique par curl et a son
# mot de passe verrouillé ; Packer se connecte ensuite par clé privée.
# ============================================================================
text
lang en_US.UTF-8
keyboard --vckeymap=fr --xlayouts='fr'
timezone Europe/Paris --utc
# Source d'installation : dépôts en ligne (ISO minimal + accès réseau au build)
url --mirrorlist="https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=BaseOS-10"
repo --name=AppStream --mirrorlist="https://mirrors.rockylinux.org/mirrorlist?arch=$basearch&repo=AppStream-10"
# Réseau : DHCP au build. La VM de build est éphémère ; Packer découvre son IP via
# ip_getter=http (le système garde la même IP au reboot, bail DHCP stable sur la MAC).
# Le template reste générique : chaque clone reçoit son réseau via cloud-init NoCloud
# (Terraform, phase 2). Les IP FIXES des nœuds du cluster, c'est phase 2, pas ici.
# Le hostname n'est PAS fixé ici : il est aligné sur var.vm_name par un provisioner
# (cf. rocky10-base.pkr.hcl), puis redéfini par clone via cloud-init.
network --bootproto=dhcp --device=link --activate
# Comptes
rootpw --lock
# ansible-windows est créé en %post (clé SSH + sudoers NOPASSWD)
# Sécurité : SELinux enforcing, firewalld actif (cf. CLAUDE.md règle 11)
selinux --enforcing
firewall --enabled --ssh
services --enabled=sshd,chronyd
# Partitionnement : LVM, PAS de swap (prérequis kubelet)
# PIÈGE EL10 : Rocky 10 étiquette le disque en GPT. Un système BIOS sur disque GPT
# exige une partition 'biosboot' de 1 MiB (sinon « Kickstart insufficient » au build).
clearpart --all --initlabel
part biosboot --fstype=biosboot --size=1
part /boot --fstype=xfs --size=1024
part pv.01 --size=1 --grow
volgroup vg00 pv.01
logvol / --fstype=xfs --name=root --vgname=vg00 --size=1 --grow
bootloader --location=mbr
%packages
@^minimal-environment
cloud-init
chrony
python3
tar
curl-minimal
policycoreutils-python-utils
-iwl*-firmware
%end
%post --erroronfail --log=/root/ks-post.log
set -x
# --- Compte d'automatisation ansible-windows ---
useradd -m -G wheel -s /bin/bash ansible-windows
passwd -l ansible-windows
echo 'ansible-windows ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/ansible-windows
chmod 0440 /etc/sudoers.d/ansible-windows
# --- Clé publique récupérée depuis le serveur HTTP de Packer ---
# (même dossier que ce kickstart ; on déduit l'URL de /proc/cmdline)
KSURL=$(sed -n 's/.*inst.ks=\([^ ]*\).*/\1/p' /proc/cmdline)
BASE=$(dirname "$KSURL")
install -d -m 0700 -o ansible-windows -g ansible-windows /home/ansible-windows/.ssh
curl -fsSL "$BASE/ansible-windows.pub" -o /home/ansible-windows/.ssh/authorized_keys || true
# Garde-fou : sans clé valide, le template serait inutilisable (SSH par clé uniquement).
# Couplé à « %post --erroronfail », ce exit 1 fait échouer l'install AU BUILD (et non
# 8 min plus tard au SSH) si ansible-windows.pub manque sur le serveur HTTP de Packer.
if ! grep -q '^ssh-' /home/ansible-windows/.ssh/authorized_keys 2>/dev/null; then
echo "ERREUR: ansible-windows.pub absent/illisible depuis le serveur HTTP de Packer" >&2
exit 1
fi
chmod 0600 /home/ansible-windows/.ssh/authorized_keys
chown ansible-windows:ansible-windows /home/ansible-windows/.ssh/authorized_keys
restorecon -R /home/ansible-windows/.ssh
# --- Durcissement SSH ---
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
# --- Phone-home : permet à Packer (ip_getter=http) d'apprendre l'IP de la VM ---
curl -fsS "$BASE/ks.cfg" > /dev/null || true
%end
reboot
http/ks.cfg
10-guest-tools.sh - C’est le premier script de customisation qui va devoir être joué après la phase de build. Son but va être d’installer et d’activer les drivers propre à l’hyperviseur Xen utilisé par XCP-ng. Cela équivaut à installer le paquet xe-guest-utilities-latest. C’est une dépendance obligatoire pour la suite, car cela va permettre de faire remonter les IPs des VMs déployées plus tard au niveau de Xen Orchestra et XCP-ng.
#!/usr/bin/env bash
# ----------------------------------------------------------------------------
# 10-guest-tools.sh — Guest tools XCP-ng + mise à jour système
# PIÈGE EL10 : sur Rocky Linux 10, xe-guest-utilities n'est PAS dans les dépôts
# de base. Le paquet 'xe-guest-utilities-latest' est fourni par EPEL 10.
# Sans ces guest tools, XO/Terraform ne remontent pas l'IP des VMs.
# ----------------------------------------------------------------------------
set -euxo pipefail
dnf -y update
# EPEL pour disposer de xe-guest-utilities-latest
dnf -y install epel-release
dnf -y install xe-guest-utilities-latest
# Agent invité Xen : sur EL10, xe-linux-distribution.service EST le service principal
# (il lance le démon xe-daemon, qui remonte l'IP/metrics vers XenStore → lues par XO et
# Terraform en phase 2). Il n'existe PAS de xe-daemon.service séparé. On enable sans
# « || true » : si ça échoue, on veut que le build s'arrête (guest tools = critiques).
systemctl enable xe-linux-distribution.service
scripts/10-guest-tools.sh
20-cloud-init.sh - Ici ce sont les dépendances cloud-init qui vont être déployées. Cela va être utilisé plus tard par terraform pour customiser les VMs qu’il va créer à partir de clone de l’image que nous sommes en train de faire avec packer.
#!/usr/bin/env bash
# ----------------------------------------------------------------------------
# 20-cloud-init.sh — Configuration cloud-init pour le datasource NoCloud
# C'est ce datasource qu'utilise ensuite le provider Terraform xenorchestra
# (attributs cloud_config / cloud_network_config) pour injecter hostname/IP.
# ----------------------------------------------------------------------------
set -euxo pipefail
dnf -y install cloud-init
# Restreindre cloud-init au seul datasource NoCloud (évite les sondes inutiles)
cat > /etc/cloud/cloud.cfg.d/90-datasource.cfg <<'EOF'
datasource_list: [ NoCloud, None ]
EOF
systemctl enable cloud-init-local.service cloud-init.service cloud-config.service cloud-final.service
scripts/20-cloud-init.sh
90-cleanup.sh - Important également, c’est la phase finale dans laquelle on vient nettoyer les traces du déploiement pour que l’image ne comporte pas d’éléments qui pourraient poser problème lors d’opération de clonage. Une fois encore, mon subagent packer-builder a bien commenter le fichier.
#!/usr/bin/env bash
# ----------------------------------------------------------------------------
# 90-cleanup.sh — Généralisation du template (DOIT être le dernier provisioner)
# Objectif : chaque clone obtient un machine-id, des clés SSH hôte et un seed
# cloud-init uniques. Sans ça, tous les clones partageraient la même identité.
# ----------------------------------------------------------------------------
set -euxo pipefail
# Effacer les profils réseau du build (connexion DHCP créée par l'installeur) : le
# template reste générique. Chaque clone reçoit son réseau via cloud-init NoCloud
# (provider Terraform xenorchestra, phase 2), sans hériter d'un profil du build.
rm -f /etc/NetworkManager/system-connections/*
rm -f /etc/sysconfig/network-scripts/ifcfg-*
# Réinitialiser cloud-init (seed + logs) pour qu'il rejoue au 1er boot du clone
cloud-init clean --logs --seed || true
# machine-id régénéré au prochain boot
truncate -s 0 /etc/machine-id
# /var/lib/dbus/machine-id : compat ancienne ; sur Rocky 10 minimal le dossier
# /var/lib/dbus n'existe pas (systemd/dbus-broker lit directement /etc/machine-id).
# Ne recréer le lien que si le dossier existe, sinon le build échoue (set -e).
if [ -d /var/lib/dbus ]; then
rm -f /var/lib/dbus/machine-id
ln -sf /etc/machine-id /var/lib/dbus/machine-id
fi
# Clés d'hôte SSH régénérées au 1er boot (unités sshd-keygen de Rocky)
rm -f /etc/ssh/ssh_host_*
# Caches et journaux
dnf clean all
rm -rf /var/cache/dnf/* /tmp/* /var/tmp/*
find /var/log -type f -exec truncate -s 0 {} \;
rm -f /root/ks-post.log
# Historique shell
: > /root/.bash_history || true
: > /home/ansible-windows/.bash_history || true
scripts/90-cleanup.sh
On va d’abord indiquer les variables nécessaires au déploiement. On ne doit pas modifier la définition des variables dans le fichier variables.pkr.hcl, mais plutôt copier le fichier example.pkrvars.hcl en xkub.auto.pkrvars.hcl :
Copy-Item example.pkrvars.hcl xkub.auto.pkrvars.hcl
Cliquez sur l'image pour l'agrandir.
Je n’avais pas parlé encore de ces fichiers, mais il s’agit tout simplement de partir d’un exemple de déclaration des variables nécessaires, example.pkrvars.hcl, pour créer un fichier propre à son déploiement dans lequel on va remplir les valeurs spécifique à notre cas d’usage, xkub.auto.pkrvars.hcl.
# ============================================================================
# example.pkrvars.hcl — MODÈLE de variables (versionné, valeurs fictives)
# Copier en « xkub.auto.pkrvars.hcl » (ignoré par git) puis renseigner.
# PowerShell : Copy-Item example.pkrvars.hcl xkub.auto.pkrvars.hcl
# Le mot de passe XCP-ng NE figure PAS ici : il passe par la variable
# d'environnement PKR_VAR_remote_password.
# ============================================================================
# --- Hôte XCP-ng ---
remote_host = "192.168.10.10" # adresse XAPI du pool master XCP-ng
remote_username = "root"
# --- Emplacements XCP-ng (noms tels qu'affichés dans Xen Orchestra) ---
sr_name = "Local storage" # SR du disque/template
sr_iso_name = "ISOs" # SR de type ISO
network_names = ["Pool-wide network associated with eth0"] # réseau LAN (DHCP + Internet)
# --- Clé privée ansible-windows (sur le poste W11, hors dépôt) ---
ssh_private_key_path = "C:/Users/vburgun/.ssh/ansible-windows"
# --- (Optionnel) surcharges ---
# vm_name = "prdtplroc501"
# disk_size = 20480
example.pkrvars.hcl
Il est important de noter que l’addon fournit par Vates pour packer se base sur l’accès à un serveur XCP-ng et non sur Xen Orchestra.
Il faut donc retenir un serveur de notre pool XCP et indiquer son IP comme source de connexion. Il faudra choisir sur ce dernier l’emplacement du Storage Repository qui va contenir l’ISO de RockyLinux 10 récupéré par Packer (NFS-ISO-Library).
Cliquez sur l'image pour l'agrandir.
De même il va falloir positionner le SR qui va servir à l’hébergement du template (XOSTOR). Le réseau également à connecter à la VM va devoir être indiqué. Tout cela se récupère directement au niveau de la GUI de Xen Orchestra. Si cela ne vous parle pas, je vous invite à consulter le détail de mon LAB sous XCP-ng.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
La clef privée du compte Ansible étant nécessaire, je ne la fais pas reposer dans l'aborscence du projet car c'est une donnée bien trop sensible. Je pointe donc vers son emplacement réel sur mon poste, à savoir un volume Veracrypt.
À noter que si vous êtes sur une autre plateforme, la logique reste exactement la même. À vous d’adapter à votre cible et retenir un autre addon au besoin.
Pour lancer la création de l’image, il faut se positionner à la racine de notre arborescence. On vérifie déjà que l’exécutable packer qu’on a récupéré avant est bien accessible ici en contrôlant sa version.
packer --version
Cliquez sur l'image pour l'agrandir.
Puis on tape la première commande packer init. Celle-ci va permettre de récupérer les dépendances à l’addon de XCP-ng directement sur le repo de Vates.
packer init .
Cliquez sur l'image pour l'agrandir.
On contrôle ensuite la syntaxe de l’ensemble pour s’assurer que tout est correct.
packer fmt .
Cliquez sur l'image pour l'agrandir.
Vient le tour de renseigner les variables confidentielles. Comme on n’a pas indiqué leur valeur dans le fichier xkub.auto.pkrvars.hcl, mais qu’elles sont définies dans variables.pkr.hcl, on va les renseigner maintenant via des variables d’environnement dans le shell (ici powershell). Il s’agit du mot de passe root du serveur XCP-ng qu’on va utiliser pour créer la VM.
$env:PKR_VAR_remote_password = 'MOT_DE_PASSE_ROOT_XCPNG'
Cliquez sur l'image pour l'agrandir.
C’est le mot de passe à utiliser pour se connecter au serveur XCP-ng retenu pour la création de l’image. Ensuite, après le contrôle de la syntaxe, on s’assure que toutes les variables nécessaires sont bien accessible à packer via la commande.
packer validate .
Cliquez sur l'image pour l'agrandir.
Enfin on peut lancer le déploiement.
packer build .
Il ne reste plus qu’à laisser faire la magie 😊
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
On peut constater en live la création et le boot de la VM
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Si tout se passe bien, au bout de plusieurs minutes, on devrait voir apparaitre notre template (qu’on a nommé prdtplroc501) dans la liste des templates associé au serveur XCP-ng qu’on a retenu (ici prdxcpsrv002).
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Tout est prêt pour la suite à un détail prêt. Sous XCP-ng, un template rattaché à un host ne peut être utilisé que sur les autres serveurs appartenant au même pool que ce dernier.
Dans mon cas, j’ai un pool xcp-pool avec trois serveurs, dont prdxcpsrv002. Le template peut donc être utilisé pour créer des VMs sur prdxcpsrv001 et prdxcpsrv003. Malheureusement, il est impossible d’utiliser le template sur mes serveurs prdxcpsrv004 et prdxcpsrv005, qui sont tous deux dans des pools distincts en raison de leur matériel trop différent (il me manque un troisième serveur pour créer un deuxième groupe).
Il faudrait donc créer sur le même modèle, deux autres template, à raison d’un template sur prdxcpsrv004 et un template sur prdxcpsrv005… et donc relancer deux fois packer avec un paramétrage différent quant à la cible xcp-ng. Cela sera inutile, car, depuis Xen Orchestra, il est possible directement de copier un template d’un pool à un autre.
Pour cela, il suffit de retenir le template qu’on vient de créer, puis d’utiliser la fonction copy template pour l’envoyer vers un autre SR appartenant à un serveur d’un autre pool.
Cliquez sur l'image pour l'agrandir.
Chose que je reproduis deux fois et en quelques minutes (c’est très rapide), je dispose de trois templates portant le même nom, mais rattachés à trois pools différents. On va pouvoir donc, dans le prochain article, déployer des nodes kubernetes sur l’ensemble des pools.
Cliquez sur l'image pour l'agrandir.
À noter qu’on pourrait très bien automatiser dans un script l’exécution de Packer, puis la recopie du template vers tous les pools sous gestion de mon instance Xen Orchestra dans le cas où il serait nécessaire de mettre à jour la "golden image".
Voilà la première étape terminée. On dispose d’une image de référence avec les prérequis pour cloner l’ensemble des nodes de notre futur cluster Kubernetes.
L’usage de l’IA ici m’a permis de gagner un temps précieux dans la génération des fichiers propres à Packer. Je n’ai pas pour autant laissé faire l’IA sans chercher à comprendre le fonctionnement de packer. J’ai pris la peine de regarder et de juger les fichiers produits.
J’ai pu bénéficier néanmoins d’une génération de fichiers propre à Packer, commenté et optimisé par rapport à mon besoin. À noter que j’ai dû m’y reprendre à plusieurs reprises, car les premières propositions n’étaient pas fonctionnelles. Mais avec quelques prompts sous claude code et la demande à ce dernier de bien analyser le repo git contenant l’addon de vates, je m’en suis sorti rapidement.
Packer s’emploie donc parfaitement avec XCP-ng et l’usage d’un outil comme Claude Code correctement employé permet de simplifier l’onbording et d’accélérer la mise en œuvre. Je vous propose l’intégralité des fichiers utilisés ici sur mon github, vous y retrouverez aussi le fichier propre au subagent packer que j’ai utilisé avec claude.
On va donc pouvoir enchainer avec Terraform, mais cela c’est pour un prochain article.