Jinja + Templates¶
Objectif
Découvrir le module template d'ansible qui permet de déployer des fichiers de configuration dynamiques à l'aide du moteur de templates Jinja2 : injection de variables, boucles, et personnalisation par hôte.
Durée
~35 minutes
Le module copy déploie un fichier tel quel, sans interprétation. Dès qu'un fichier de configuration doit contenir des valeurs qui dépendent de l'hôte cible, copy ne suffit plus : ou alors il faudrait maintenir une variante du fichier par cible.
Le module template résout ce problème en faisant passer le fichier source par le moteur Jinja2 avant de l'écrire sur la cible. Les expressions {{ variable }}, {% for %}, {% if %}… sont évaluées avec le contexte de l'hôte (variables d'inventaire, facts, vars…), exactement comme dans un playbook.
Convention templates/ + extension .j2
ansible cherche automatiquement les fichiers src: du module template dans un dossier templates/ situé dans le même répertoire que le playbook lancé (ou dans roles/<role>/templates/ pour un rôle). Par convention, les fichiers Jinja2 portent l'extension .j2 pour être identifiables d'un coup d'œil MAIS l'extension n'est pas reproduite à l'arrivée (chrony.conf.j2 → chrony.conf).
Mise en place du lab¶
On démarre les VM de l'atelier puis on se connecte au Control Host :
cd $HOME/git/gh/formation-ansible/atelier-18
vagrant up
^up^ssh ansible
# sur le control host
cd ansible/projets/ema/playbooks/
La structure du projet contient déjà un dossier templates/ avec deux templates de démonstration, et un playbook ansible-03.yml qui les déploie comme page d'accueil Apache sur chaque cible.

Premier exemple : variables dans un template¶
Le template index.html.j2 injecte une variable ansible {{ inventory_hostname }} dans le contenu d'une page HTML. Le playbook ansible-03.yml le copie sur chaque Target Host avec le module template, puis je valide le résultat avec curl depuis le Control Host :
Chaque cible renvoie une page personnalisée preuve que le rendu est bien fait par hôte, à partir d'un seul fichier source.

Deuxième exemple : boucles Jinja2¶
index-2.html.j2 va plus loin : il utilise une boucle {% for %} pour générer dynamiquement une liste HTML à partir d'une variable de type liste (all).

À vous de jouer¶
L'objectif est de reprendre le déploiement de Chrony du Workshop 13 qui dupliquait le fichier chrony.conf via le module copy et de le remplacer par un seul template Jinja2. Le fichier de configuration n'a pas besoin d'être différent par distribution, mais son emplacement (/etc/chrony/chrony.conf sous Debian, /etc/chrony.conf partout ailleurs...) et le nom du service (chrony vs chronyd) varient. On reprend donc l'approche include_vars du workshop précédent.
1. Création du template et des variables par famille d'OS¶
On crée le template chrony.conf.j2 dans templates/, ainsi que les fichiers de variables par famille d'OS (vars/chrony_debian.yml, vars/chrony_redhat.yml, vars/chrony_suse.yml). La seule expression Jinja2 utilisée ici est {{ chrony_confdir }} dans l'en-tête du fichier, à titre de marqueur, l'essentiel est que la destination dépend de la distribution.
cat > templates/chrony.conf.j2 <<'EOF'
# {{ chrony_confdir }}/chrony.conf - managed by ansible
server 0.fr.pool.ntp.org iburst
server 1.fr.pool.ntp.org iburst
server 2.fr.pool.ntp.org iburst
server 3.fr.pool.ntp.org iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
EOF
mkdir -p vars
for f in debian redhat suse; do
[ "$f" = "debian" ] && s=chrony c=/etc/chrony || s=chronyd c=/etc
cat > vars/chrony_${f}.yml <<EOF
---
chrony_package: chrony
chrony_service: ${s}
chrony_confdir: ${c}
EOF
done
2. Le playbook chrony.yml¶
Le playbook charge les variables spécifiques à l'OS, installe le paquet chrony via le module générique package, déploie la configuration avec template (qui notifie un handler de redémarrage en cas de changement), démarre le service, puis vérifie l'application de l'état instruit avec chronyc tracking et chronyc sources. L'appel explicite à meta: flush_handlers force l'exécution du handler avant les tâches de vérification, sinon chronyc interrogerait un démon qui n'a pas encore relu sa configuration...
3. Exécution et validation¶
À la première exécution, la tâche template passe en changed (le fichier est créé), le handler restart chrony est notifié puis joué via flush_handlers, et les tâches chronyc tracking et chronyc sources confirment la synchronisation effective sur chacune des quatre distributions.

Une seconde exécution affiche changed=0 : template compare le rendu Jinja2 au contenu déjà présent sur la cible et ne touche au fichier que s'il diffère (c'est ce qui rend le playbook idempotent malgré son apparente complexité...).
Je retiens
- Le module
templateremplacecopyquand le fichier doit contenir des valeurs dynamiques ({{ variable }}). - Les fichiers templates portent l'extension
.j2par convention et sont stockés dans un dossiertemplates/MAIS l'extension n'est pas reproduite à l'arrivée. - Jinja2 supporte les boucles (
{% for %}) et les conditions ({% if %}), ce qui permet de générer des fichiers complexes. - Combiné avec
include_vars(cf. Workshop 13), le moduletemplatepermet de personnaliser les fichiers par distribution tout en gardant un seul playbook.
Cheatsheet¶
Vu par expérience post-playbook.
| Symptôme | Cause probable | Correction |
|---|---|---|
template: src: chrony.conf.j2 → Could not find or access |
Le fichier n'est pas dans ./templates/ relatif au playbook/rôle |
Créer templates/chrony.conf.j2 dans le même répertoire que le playbook |
Jinja2 : TemplateSyntaxError: expected token 'end of print statement' |
Balise mal fermée ({{ var } au lieu de {{ var }}) ou filtre inconnu |
Vérifier la syntaxe avec un linter Jinja ou ansible-playbook --check --diff |
Le template génère des lignes vides en trop à cause des balises {% for %} |
Les balises de contrôle conservent les blancs par défaut | Utiliser les variantes « strip » : {%- for x in liste -%} / {%- endfor -%} |
{{ chrony_confdir }} → undefined variable |
include_vars n'a pas été appelé avant la tâche template |
Placer include_vars en premier dans la liste des tâches |
| Les permissions du fichier déployé ne sont pas appliquées | owner / group / mode absents de la tâche template |
Ajouter explicitement owner, group, mode (comme pour copy) |
| Un changement de template écrase le fichier sans sauvegarde | backup absent |
Ajouter backup: true sur la tâche template pour garder une copie horodatée |
| Debug : quelle valeur a la variable dans le template ? | Pas de façon simple d'inspecter en ligne | Utiliser debug: var=chrony_confdir avant la tâche template, ou --check --diff pour voir le rendu |
Sources (principales)¶
- Docs
ansibleTemplating with Jinja2 - Docs
ansibleansible.builtin.templatemodule - Docs
ansibleFilters (liste complète des filtres Jinja2 utilisables) - Docs
ansibleTests (is defined,is match, etc.) - Docs
ansibleLookups (charger des données externes dans un template) - Jinja2 Template designer documentation
- Jinja2 Whitespace control (
{%-/-%},trim_blocks,lstrip_blocks) - Docs
ansibleansible.builtin.copymodule (pour comparaison)
Précédent : Heterogeneous Targets