Jusqu’ici, tu as tes playbooks, tes variables, ton Vault pour les secrets. Mais dès que tu dois générer un fichier de config — Nginx, systemd, .env — tu te retrouves à copier des fichiers statiques et les modifier à la main. C’est exactement le genre de tâche répétitive qu’Ansible peut automatiser. La solution : les templates Jinja2, le moteur de templating intégré à Ansible qui transforme des squelettes de fichiers en configurations dynamiques adaptées à chaque serveur.
Dans ce chapitre, tu vas apprendre à écrire des templates Jinja2 qui génèrent automatiquement des fichiers de configuration propres, maintenables et adaptés à chaque environnement.
Pourquoi les templates changent tout
Sans templates, tu te retrouves avec un fichier nginx.conf par serveur. Trois environnements ? Trois fichiers quasi identiques à maintenir. Dix serveurs ? Bonne chance.
Les templates Jinja2 résolvent ce problème en séparant la structure (le template) des données (les variables). Un seul fichier .j2 génère autant de configurations que nécessaire — prod, staging, dev — sans duplication.
🔥 Cas réel : Une équipe infra maintenait 47 fichiers de config Nginx à la main. Après migration vers des templates Jinja2, ils sont passés à 3 templates et un fichier de variables par environnement. Résultat : les erreurs de config ont chuté de 80% et les déploiements sont passés de 45 minutes à 5 minutes.
💡 Tip DevOps : Ajoute toujours un commentaire en haut de tes fichiers générés : # Managed by Ansible - DO NOT EDIT. Ça évitera qu’un collègue modifie le fichier à la main et se fasse écraser au prochain déploiement.
Comprendre Jinja2 : la syntaxe clé
Jinja2 utilise trois types de délimiteurs que tu vas rencontrer partout :
{{ variable }}— affiche une valeur{% if ... %}/{% for ... %}— logique de contrôle{# commentaire #}— commentaire (ignoré à la génération)
Voici un exemple concret qui combine les trois. Ce template génère un bloc de configuration dont le contenu change selon l’environnement cible :
{# Template de log Nginx par environnement #}
{% if env == 'production' %}
error_log /var/log/nginx/error.log warn;
{% elif env == 'staging' %}
error_log /var/log/nginx/error.log info;
{% else %}
error_log /var/log/nginx/error.log debug;
{% endif %}
{% for server in upstream_servers %}
server {{ server }}:{{ app_port | default(3000) }};
{% endfor %}
Les boucles {% for %} itèrent sur des listes ou dictionnaires. Tu as accès à des variables de boucle utiles : loop.index (compteur à partir de 1), loop.first, loop.last, et loop.length.
Les conditions {% if %} testent des valeurs, et tu peux vérifier si une variable existe avec is defined — indispensable pour gérer les options facultatives.
🧠 À retenir : Jinja2 est évalué côté contrôleur (ta machine Ansible), pas sur le serveur cible. Le fichier envoyé est du texte pur — le serveur distant ne voit jamais de syntaxe Jinja2.
Les filtres et macros essentiels
Les filtres transforment les données à la volée avec le pipe |. Voici ceux que tu utiliseras quotidiennement dans tes templates. Le premier bloc couvre la manipulation de texte et de listes :
{# Valeur par défaut si variable non définie #}
listen {{ nginx_port | default(80) }};
{# Joindre une liste en chaîne #}
gzip_types {{ gzip_types | join(' ') }};
{# Conversion JSON pour fichier de config #}
config_data={{ app_config | to_nice_json(indent=2) }}
{# Hash de mot de passe pour /etc/shadow #}
{{ user_password | password_hash('sha512') }}
⚠️ Attention : Le filtre default() ne se déclenche que si la variable est undefined. Une variable définie à "" (chaîne vide) ou false ne sera PAS remplacée par la valeur par défaut. Si tu veux aussi couvrir les valeurs falsy, utilise default('fallback', true).
Pour éviter de répéter du code, Jinja2 propose les macros — des fonctions réutilisables directement dans ton template. Définis une macro une fois, puis appelle-la autant de fois que nécessaire :
{% macro server_block(name, port, root) %}
server {
listen {{ port }};
server_name {{ name }};
root {{ root }};
location / {
try_files $uri $uri/ =404;
}
}
{% endmacro %}
{{ server_block('devopslab.ch', 80, '/var/www/devopslab') }}
{{ server_block('api.devopslab.ch', 8080, '/var/www/api') }}
💡 Tip DevOps : Les macros brillent quand tu as des patterns répétitifs (blocs location Nginx, blocs upstream, entrées iptables). Un template avec 3 macros bien pensées remplace souvent 200 lignes de copier-coller.
Cas concret : Nginx multi-environnement en entreprise
Imaginons que tu gères une app Node.js déployée sur plusieurs serveurs avec un load balancer Nginx devant. Chaque serveur a un nombre d’instances différent, et le SSL n’est activé qu’en prod. Voici les variables que tu définis dans group_vars :
# group_vars/production.yml
app_name: devopslab-api
app_port_start: 3000
app_instances: 4
nginx_server_name: api.devopslab.ch
nginx_ssl: true
nginx_security_headers: true
Et voici le template Nginx qui s’adapte automatiquement au contexte. Il génère le bloc upstream avec le bon nombre d’instances, active le SSL et les headers de sécurité uniquement quand demandé :
# {{ app_name }} - Managed by Ansible
# Generated: {{ ansible_date_time.iso8601 }}
upstream {{ app_name }}_backend {
{% for i in range(0, app_instances) %}
server 127.0.0.1:{{ app_port_start + i }};
{% endfor %}
keepalive 32;
}
{% if nginx_ssl | default(false) %}
server {
listen 80;
server_name {{ nginx_server_name }};
return 301 https://$server_name$request_uri;
}
{% endif %}
server {
{% if nginx_ssl | default(false) %}
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/{{ nginx_server_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ nginx_server_name }}/privkey.pem;
{% else %}
listen 80;
{% endif %}
server_name {{ nginx_server_name }};
{% if nginx_security_headers | default(false) %}
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
{% endif %}
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Le playbook qui déploie ce template utilise le module template d’Ansible. Il pousse le fichier généré et recharge Nginx uniquement si le fichier a changé grâce au handler :
- name: Déployer la config Nginx
ansible.builtin.template:
src: nginx-app.conf.j2
dest: "/etc/nginx/sites-available/{{ app_name }}.conf"
owner: root
mode: "0644"
validate: "nginx -t -c %s"
notify: Reload nginx
🔥 Cas réel : Le paramètre validate est un game-changer. Il exécute nginx -t sur le fichier généré avant de le mettre en place. Si la syntaxe est invalide, Ansible refuse le déploiement — ton Nginx en production reste intact.
Pièges fréquents
Les espaces parasites. Par défaut, Jinja2 conserve tous les espaces et sauts de ligne autour des blocs {% %}. Résultat : des lignes vides partout dans ton fichier généré. Utilise le tiret - pour les supprimer :
{# Sans tiret → lignes vides entre chaque entrée #}
{% for server in servers %}
server {{ server }};
{% endfor %}
{# Avec tiret → résultat propre, sans lignes vides #}
{% for server in servers -%}
server {{ server }};
{% endfor -%}
Variable undefined vs variable vide. Tu testes {% if ssl_cert is defined %} mais la variable est définie à "" — le bloc s’exécute quand même. Teste plutôt {% if ssl_cert is defined and ssl_cert %} pour couvrir les deux cas.
Oublier | mandatory. Si une variable critique manque, Ansible continue silencieusement avec une valeur vide. Utilise {{ db_password | mandatory }} pour forcer une erreur explicite plutôt qu’un déploiement cassé.
⚠️ Attention : Ne mets jamais de logique métier complexe dans tes templates. Si tu as plus de 3 niveaux d’imbrication if/for, c’est un signe que la logique devrait être dans tes variables ou tes tasks, pas dans le template.
Exercice : déploie ta config dynamique
Mets en pratique ce que tu viens d’apprendre avec cet exercice complet :
Objectif : Crée un template Jinja2 pour un fichier /etc/hosts dynamique.
- Définis une liste de serveurs dans
group_vars/all.ymlavec pour chaque entrée :name,ip, etrole(master ou worker) - Écris un template
hosts.j2qui itère sur cette liste avec{% for %} - Ajoute un commentaire
# MASTERà côté des serveurs dont le rôle est master ({% if %}) - Utilise le filtre
| upperpour mettre le nom en majuscules dans le commentaire - Déploie avec un playbook utilisant le module
ansible.builtin.template
Résultat attendu :
# Managed by Ansible
192.168.1.10 db01 # MASTER - DB01
192.168.1.11 db02
192.168.1.20 web01 # MASTER - WEB01
192.168.1.21 web02
Bonus : Ajoute validate: "python3 -c 'open(\"%s\").read()'" pour vérifier que le fichier est lisible avant déploiement.
📝 À retenir
{{ }}affiche,{% %}contrôle,{# #}commente — les trois piliers de Jinja2- Les filtres (
default,join,mandatory) transforment tes données à la volée - Les macros éliminent la duplication dans les templates complexes
- Toujours utiliser
validatedans le moduletemplatepour éviter de casser la prod - Un template bien conçu + des variables organisées = zéro fichier de config dupliqué
- Le tiret
-%}est ton allié contre les lignes vides parasites
🧠 À retenir : Un bon template Jinja2 est comme une bonne fonction — il fait une chose, il la fait bien, et il est facile à lire. Si ton template dépasse 80 lignes, découpe-le en plusieurs fichiers avec {% include %}.
➡️ Prochain chapitre : le déploiement avancé avec Ansible — stratégies rolling update, gestion des erreurs et intégration CI/CD.
🖥️ Pratique sur ton propre serveur
Pour suivre Ansible en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : Ansible
6 / 8Sur cette page
Articles liés
Déploiement avancé et orchestration
Stratégies de déploiement rolling et canary, intégration CI/CD avec GitHub Actions, et AWX / Ansible Automation Platform.
Variables et priorités
Maîtrise la hiérarchie des variables Ansible, host_vars et group_vars, et sécurise tes secrets avec Ansible Vault.
Ansible Galaxy et collections
Exploite Ansible Galaxy pour réutiliser des roles communautaires, gère les dépendances et déploie une stack LAMP complète avec des roles.