You are currently viewing Templates Ansible : guide complet avec exemples pratiques

Templates Ansible : guide complet avec exemples pratiques

  • Auteur/autrice de la publication :
  • Post category:DevOps

Dans cet article

  • Les templates Ansible reposent sur le moteur Jinja2 et permettent de générer dynamiquement n’importe quel fichier de configuration
  • Le module ansible.builtin.template copie un fichier .j2 vers l’hôte distant en résolvant toutes les variables au passage
  • La différence clé entre copy et template : copy transfère un fichier statique, template interprète les expressions Jinja2
  • Les filtres Jinja2 les plus utiles (default, join, regex_replace) couvrent plus de 80 % des cas d’usage courants
  • Organiser ses templates dans un dossier templates/ au même niveau que tasks/ est la convention recommandée par Ansible
  • Combinés à Ansible Vault, les templates permettent d’injecter des secrets sans les exposer en clair dans le dépôt

Quand on gère une dizaine de serveurs, copier manuellement des fichiers de configuration devient vite ingérable. Je l’ai constaté dès mes premiers projets d’automatisation : chaque environnement (développement, staging, production) nécessite des valeurs différentes pour les mêmes fichiers. C’est exactement le problème que résolvent les templates Ansible. En combinant le moteur de rendu Jinja2 avec la puissance des playbooks, vous pouvez générer dynamiquement n’importe quel fichier texte, du simple nginx.conf au docker-compose.yml le plus complexe.

Dans ce guide, je vous accompagne pas à pas pour maîtriser les templates Ansible. On part des fondamentaux jusqu’aux techniques avancées, avec du code que vous pourrez réutiliser directement dans vos projets.

Comprendre les templates Ansible et Jinja2

Un template Ansible est un fichier texte contenant des expressions Jinja2 (délimitées par {{ }} pour les variables et {% %} pour la logique). Lorsque Ansible exécute un playbook, il remplace ces expressions par les valeurs réelles des variables définies dans l’inventaire, les rôles ou les group_vars.

Le moteur Jinja2, développé par le projet Pallets, est un moteur de templates Python très répandu. Ansible l’utilise en interne pour résoudre toutes les expressions, y compris celles présentes directement dans les playbooks. Mais c’est dans les fichiers .j2 que cette fonctionnalité prend tout son sens.

Concrètement, voici le flux de travail :

  1. Vous créez un fichier template avec l’extension .j2 (par convention)
  2. Vous utilisez le module template dans votre playbook pour déployer ce fichier
  3. Ansible résout les variables, exécute la logique conditionnelle, puis copie le résultat sur l’hôte distant

L’avantage principal ? Un seul fichier template suffit pour générer des configurations adaptées à chaque serveur. Si votre application doit écouter sur le port 8080 en développement et 443 en production, une simple variable {{ app_port }} dans le template règle le problème.

Éditeur de code affichant la syntaxe Jinja2 utilisée dans les templates Ansible
Éditeur de code affichant la syntaxe Jinja2 utilisée dans les templates Ansible

Le module ansible.builtin.template en détail

Le module ansible.builtin.template est le cœur du système. Il fonctionne de manière similaire au module Ansible copy, mais avec une étape de rendu Jinja2 en plus. Voici sa syntaxe de base :

- name: Déployer la configuration Nginx
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
  notify: Restart Nginx

Les paramètres essentiels du module sont les suivants :

  • src : chemin du fichier template sur la machine de contrôle (relatif au dossier templates/ du rôle)
  • dest : chemin de destination sur l’hôte distant (chemin absolu obligatoire)
  • owner et group : propriétaire et groupe du fichier généré
  • mode : permissions du fichier (notation octale entre guillemets)
  • backup : créer une sauvegarde du fichier existant avant écrasement (yes ou no)
  • validate : commande de validation exécutée avant le déploiement final

Le paramètre validate est particulièrement utile pour éviter les configurations cassées. Par exemple, pour un fichier Apache :

- name: Déployer la configuration Apache
  ansible.builtin.template:
    src: httpd.conf.j2
    dest: /etc/httpd/conf/httpd.conf
    validate: '/usr/sbin/httpd -t -f %s'

Ansible écrira le fichier dans un emplacement temporaire, exécutera la commande de validation (où %s est remplacé par le chemin temporaire), et ne déploiera le fichier que si la validation réussit. Un filet de sécurité que je recommande systématiquement en production.

Différence entre copy et template dans Ansible

Cette question revient très souvent chez mes étudiants en reconversion informatique. La réponse est simple, mais la nuance est importante.

Le module copy transfère un fichier tel quel, sans aucune interprétation. Si votre fichier contient {{ variable }}, cette chaîne apparaîtra littéralement sur le serveur distant. Le module template, lui, passe le fichier par le moteur Jinja2 avant de le déposer. Toutes les expressions sont résolues.

Critère Module copy Module template
Interprétation Jinja2 Non Oui
Extension conventionnelle Aucune (.conf, .txt, etc.) .j2
Répertoire source par défaut files/ templates/
Paramètre remote_src Oui (copie entre chemins distants) Non supporté
Cas d’usage Fichiers statiques (certificats, binaires) Fichiers de configuration dynamiques
Performance Légèrement plus rapide Étape de rendu supplémentaire

Ma règle : si le fichier contient la moindre variable ou condition, utilisez template. Si c’est un fichier binaire ou un certificat SSL qui ne doit jamais être modifié, utilisez copy. En cas de doute, partez sur template : le surcoût de performance est négligeable et vous garderez la flexibilité d’ajouter des variables plus tard.

Syntaxe Jinja2 : variables, filtres et conditions

Maîtriser la syntaxe Jinja2 est indispensable pour écrire des templates Ansible efficaces. Voici les trois éléments fondamentaux que vous utiliserez quotidiennement.

Les variables

Les variables s’insèrent avec la double accolade {{ nom_variable }}. Ansible résout ces variables en puisant dans plusieurs sources, par ordre de priorité : les extra vars, les variables de tâche, les variables de bloc, les variables de rôle, les facts, et l’inventaire.

# template : app.conf.j2
server_name={{ server_hostname }}
listen_port={{ app_port }}
workers={{ ansible_processor_vcpus * 2 }}
log_level={{ log_level | default('info') }}

Les filtres

Les filtres transforment les valeurs à la volée grâce au caractère pipe |. Ansible ajoute ses propres filtres à ceux de Jinja2. Voici les plus utilisés :

  • default(‘valeur’) : fournit une valeur de repli si la variable n’est pas définie
  • upper / lower : transforme la casse
  • join(‘,’) : concatène les éléments d’une liste avec un séparateur
  • to_yaml / to_json : sérialise un objet dans le format souhaité
  • regex_replace : applique une expression régulière
  • ipaddr : valide et manipule des adresses IP
# Exemples de filtres dans un template
allowed_hosts={{ allowed_hosts | join(', ') }}
database_url={{ db_url | regex_replace('password=.*@', 'password=***@') }}
config_dump={{ app_config | to_yaml }}

Les conditions

Les blocs conditionnels permettent d’inclure ou d’exclure des portions du fichier généré :

{% if enable_ssl %}
ssl_certificate /etc/ssl/certs/{{ domain }}.crt;
ssl_certificate_key /etc/ssl/private/{{ domain }}.key;
{% else %}
# SSL désactivé sur cet environnement
{% endif %}

Vous pouvez combiner plusieurs conditions avec and, or et not. Le test is defined est particulièrement utile pour vérifier qu’une variable existe avant de l’utiliser.

Exécution d'un playbook Ansible déployant des templates de configuration
Exécution d’un playbook Ansible déployant des templates de configuration

Exemples pratiques de templates Ansible

Passons à la pratique avec trois exemples concrets que j’utilise régulièrement dans mes formations et en production.

Exemple 1 : template Nginx avec virtual hosts

Ce template génère un virtual host Nginx complet, avec gestion optionnelle du SSL :

# templates/nginx_vhost.conf.j2
server {
    listen {{ http_port | default(80) }};
    server_name {{ server_name }};

{% if enable_ssl | default(false) %}
    listen {{ https_port | default(443) }} ssl;
    ssl_certificate /etc/ssl/certs/{{ server_name }}.crt;
    ssl_certificate_key /etc/ssl/private/{{ server_name }}.key;
    ssl_protocols TLSv1.2 TLSv1.3;
{% endif %}

    root {{ document_root }};
    index index.html index.php;

    access_log /var/log/nginx/{{ server_name }}_access.log;
    error_log /var/log/nginx/{{ server_name }}_error.log;

{% for location in custom_locations | default([]) %}
    location {{ location.path }} {
        {{ location.directive }};
    }
{% endfor %}
}

Et le playbook associé :

- name: Configurer les virtual hosts Nginx
  ansible.builtin.template:
    src: nginx_vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ server_name }}.conf"
    validate: '/usr/sbin/nginx -t -c %s'
  notify: Reload Nginx

Exemple 2 : fichier Docker Compose dynamique

Ce cas illustre bien la puissance des templates pour les environnements conteneurisés. Si vous travaillez avec des pods Kubernetes ou Docker, la logique est similaire :

# templates/docker-compose.yml.j2
version: '3.8'
services:
{% for service in app_services %}
  {{ service.name }}:
    image: {{ service.image }}:{{ service.tag | default('latest') }}
    ports:
{% for port in service.ports | default([]) %}
      - "{{ port }}"
{% endfor %}
    environment:
{% for key, value in service.env_vars | default({}) | dictsort %}
      {{ key }}: "{{ value }}"
{% endfor %}
{% if service.volumes is defined %}
    volumes:
{% for volume in service.volumes %}
      - {{ volume }}
{% endfor %}
{% endif %}
{% endfor %}

Exemple 3 : fichier de configuration applicative

Un cas classique de configuration pour une application Python/Django :

# templates/settings_local.py.j2
# Fichier généré par Ansible ; ne pas modifier manuellement
DEBUG = {{ django_debug | default(false) | string }}
ALLOWED_HOSTS = [{{ allowed_hosts | map('regex_replace', '^(.*)$', "'\\1'") | join(', ') }}]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.{{ db_engine }}',
        'NAME': '{{ db_name }}',
        'USER': '{{ db_user }}',
        'PASSWORD': '{{ db_password }}',
        'HOST': '{{ db_host }}',
        'PORT': '{{ db_port | default(5432) }}',
    }
}

Pour les secrets comme db_password, je recommande vivement de les stocker avec Ansible Vault. C’est le moyen le plus sûr d’éviter d’exposer des mots de passe en clair dans votre dépôt.

Organisation du répertoire templates et bonnes pratiques

L’organisation de vos fichiers templates a un impact direct sur la maintenabilité de votre infrastructure as code. Voici la structure que je recommande, alignée sur les bonnes pratiques de la documentation officielle Ansible :

roles/
└── webserver/
    ├── tasks/
    │   └── main.yml
    ├── templates/
    │   ├── nginx.conf.j2
    │   ├── vhost.conf.j2
    │   └── logrotate.conf.j2
    ├── vars/
    │   └── main.yml
    ├── defaults/
    │   └── main.yml
    └── handlers/
        └── main.yml

Quelques règles que j’applique systématiquement :

  • Extension .j2 : toujours ajouter cette extension pour distinguer les templates des fichiers statiques
  • Nommage cohérent : le nom du template reflète le fichier de destination (nginx.conf.j2/etc/nginx/nginx.conf)
  • Commentaire d’en-tête : ajoutez systématiquement un commentaire indiquant que le fichier est géré par Ansible
  • Valeurs par défaut : utilisez le filtre default() pour chaque variable optionnelle
  • Tests unitaires : validez vos templates avec Molecule ou en mode --check
# En-tête recommandé pour chaque template
# {{ ansible_managed }}
# Ce fichier est géré par Ansible. Toute modification manuelle sera écrasée.

La variable spéciale {{ ansible_managed }} insère automatiquement une chaîne configurable (par défaut : « Ansible managed ») qui avertit quiconque ouvrirait le fichier sur le serveur.

Serveurs en production dont la configuration est gérée par des templates Ansible
Serveurs en production dont la configuration est gérée par des templates Ansible

Templates avancés : boucles, includes et macros

Une fois les bases maîtrisées, vous pouvez exploiter des fonctionnalités avancées de Jinja2 pour réduire la duplication et améliorer la lisibilité de vos templates.

Boucles avec for

Les boucles {% for %} itèrent sur des listes ou dictionnaires :

# Générer des entrées DNS
{% for host in dns_records %}
{{ host.ip }}    {{ host.fqdn }}    {{ host.aliases | default([]) | join(' ') }}
{% endfor %}

Pour les boucles sur des dictionnaires, utilisez dictsort afin de garantir un ordre déterministe :

{% for key, value in environment_variables | dictsort %}
export {{ key }}="{{ value }}"
{% endfor %}

Includes et imports

Vous pouvez découper un template volumineux en sous-fichiers avec {% include %} :

# templates/nginx.conf.j2
http {
    {% include 'nginx_upstream.conf.j2' %}
    {% include 'nginx_gzip.conf.j2' %}

{% for vhost in virtual_hosts %}
    {% include 'nginx_vhost.conf.j2' %}
{% endfor %}
}

Ansible recherche automatiquement les fichiers inclus dans le même répertoire templates/. Cette technique est idéale pour les configurations complexes qui dépassent les 100 lignes.

Macros Jinja2

Les macros sont l’équivalent des fonctions dans un template. Elles permettent de réutiliser des blocs de configuration :

{% macro upstream_block(name, servers, method='round_robin') %}
upstream {{ name }} {
{% if method == 'least_conn' %}
    least_conn;
{% elif method == 'ip_hash' %}
    ip_hash;
{% endif %}
{% for server in servers %}
    server {{ server.host }}:{{ server.port }} weight={{ server.weight | default(1) }};
{% endfor %}
}
{% endmacro %}

{{ upstream_block('app_backend', app_servers, 'least_conn') }}
{{ upstream_block('api_backend', api_servers) }}

Les macros sont particulièrement utiles lorsque le même motif se répète plusieurs fois dans un fichier. Pour les architectures impliquant de la virtualisation de serveurs avec de multiples services, cette approche réduit considérablement la taille des templates.

Contrôle des espaces blancs

Un problème fréquent avec Jinja2 : les blocs logiques génèrent des lignes vides indésirables. Pour y remédier, utilisez le tiret - dans les balises :

{%- if enable_feature -%}
feature_enabled=true
{%- endif -%}

Le tiret avant %} supprime les espaces après le bloc ; le tiret après {% supprime ceux avant. Testez progressivement, car un usage trop agressif peut fusionner des lignes qui devraient rester séparées.

Déboguer ses templates : erreurs courantes et solutions

Même avec de l’expérience, les templates Ansible peuvent générer des erreurs déroutantes. Voici les problèmes que je rencontre le plus souvent dans mes projets et en accompagnant des équipes de gestion de projets informatiques.

Variable non définie (UndefinedError)

C’est l’erreur numéro un. Ansible s’arrête avec un message du type AnsibleUndefinedVariable: 'db_host' is undefined. Solutions :

  • Ajoutez | default('valeur') pour les variables optionnelles
  • Vérifiez l’existence avec {% if variable is defined %}
  • Assurez-vous que les variables sont définies au bon niveau (host_vars, group_vars, defaults)

Problèmes d’indentation YAML

Si votre template génère du YAML (comme un fichier Docker Compose), une indentation incorrecte provoquera une erreur sur l’hôte distant. Utilisez les filtres to_nice_yaml ou indent :

config:
{{ complex_dict | to_nice_yaml | indent(2, true) }}

Mode check et diff

Avant de déployer en production, lancez toujours votre playbook en mode check + diff pour voir exactement ce qui sera modifié :

ansible-playbook site.yml --check --diff

Ce mode exécute le rendu du template et affiche les différences sans appliquer les changements. C’est un filet de sécurité indispensable, surtout quand on travaille avec des services Kubernetes ou des infrastructures critiques.

Debug avec le filtre comment

Pour inspecter la valeur d’une variable pendant le développement :

- name: Afficher le rendu du template
  ansible.builtin.debug:
    msg: "{{ lookup('template', 'mon_template.j2') }}"

Le lookup template rend le fichier sans le déployer, ce qui vous permet de vérifier le résultat directement dans la console.

Tableau comparatif des paramètres du module template

Pour référence rapide, voici les paramètres complets du module ansible.builtin.template avec leur comportement par défaut et leurs cas d’usage :

Paramètre Obligatoire Valeur par défaut Description
src Oui Chemin du fichier .j2 sur la machine de contrôle
dest Oui Chemin absolu de destination sur l’hôte distant
owner Non utilisateur courant Propriétaire du fichier généré
group Non groupe courant Groupe propriétaire du fichier
mode Non système Permissions en octal (ex : ‘0644’)
backup Non no Créer une copie de sauvegarde avant écrasement
validate Non Commande de validation (%s = chemin temporaire)
force Non yes Écraser même si le fichier distant existe déjà
newline_sequence Non \n Séquence de fin de ligne (\n, \r, \r\n)
trim_blocks Non yes Supprimer le saut de ligne après un bloc Jinja2
lstrip_blocks Non no Supprimer les espaces avant un bloc Jinja2
output_encoding Non utf-8 Encodage du fichier de sortie

Le paramètre mode mérite une attention particulière. Je recommande de toujours le spécifier explicitement entre guillemets simples (ex : '0644'). Sans guillemets, YAML peut interpréter le nombre en octal de manière inattendue, ce qui conduit à des permissions incorrectes sur le fichier déployé.

Notez que contrairement au module copy, template ne supporte pas le paramètre remote_src. Le fichier source doit toujours se trouver sur la machine de contrôle Ansible. Si vous avez besoin de modifier un fichier déjà présent sur l’hôte distant, le module lineinfile ou blockinfile sera plus adapté.

Pour aller plus loin dans la gestion centralisée de vos configurations, Ansible Tower (AWX) offre une interface graphique qui simplifie le suivi des déploiements de templates à grande échelle. C’est un complément naturel quand votre infrastructure dépasse la vingtaine de serveurs.

Les templates Ansible sont l’un des piliers de l’infrastructure as code. En combinant la flexibilité de Jinja2 avec la puissance des variables et de l’inventaire Ansible, vous obtenez un système de gestion de configuration à la fois puissant et maintenable. Que vous gériez trois serveurs ou trois cents, les principes restent les mêmes : un template bien écrit, des variables bien organisées, et une validation systématique avant chaque déploiement.

À retenir

  • Utilisez le module template pour les fichiers dynamiques et copy uniquement pour les fichiers statiques
  • Ajoutez systématiquement le filtre default() sur chaque variable optionnelle pour éviter les erreurs UndefinedError
  • Activez le paramètre validate sur tous les templates de configuration critique (Nginx, Apache, sudoers)
  • Lancez –check –diff avant chaque déploiement en production pour visualiser les changements
  • Stockez les secrets dans Ansible Vault plutôt que de les écrire en clair dans les variables

Questions fréquentes


Qu’est-ce qu’un template Ansible ?

Un template Ansible est un fichier texte contenant des expressions Jinja2 (variables, conditions, boucles) qui sont résolues au moment de l’exécution du playbook. Le module ansible.builtin.template génère le fichier final en remplaçant les expressions par les valeurs réelles des variables, puis le dépose sur l’hôte distant. L’extension conventionnelle est .j2.


Où placer les fichiers templates dans un rôle Ansible ?

Les fichiers templates doivent être placés dans le répertoire templates/ à la racine du rôle. Ansible recherche automatiquement les fichiers .j2 dans ce dossier lorsque vous utilisez un chemin relatif dans le paramètre src du module template. Pour un playbook hors rôle, le template doit se trouver dans le même répertoire que le playbook ou dans un sous-dossier templates/.


Quelle est la différence entre copy et template dans Ansible ?

Le module copy transfère un fichier statique sans interprétation : les expressions Jinja2 restent telles quelles. Le module template, lui, passe le fichier par le moteur Jinja2 et résout toutes les variables et conditions avant de déposer le résultat. Autre différence notable : copy supporte le paramètre remote_src (copie entre chemins distants), tandis que template exige que le fichier source soit sur la machine de contrôle.


Comment déboguer un template Ansible qui génère des erreurs ?

Trois techniques complémentaires permettent de déboguer efficacement. Premièrement, utilisez le mode –check –diff pour visualiser le rendu sans appliquer les changements. Deuxièmement, le lookup template dans une tâche debug affiche le contenu généré dans la console. Troisièmement, ajoutez le paramètre validate au module template pour vérifier la syntaxe du fichier généré avant son déploiement effectif.


Peut-on utiliser des boucles et des conditions dans les templates Ansible ?

Oui, les templates Ansible supportent toute la syntaxe Jinja2 : boucles for, conditions if/elif/else, macros, includes et filtres. Les boucles s’écrivent avec la balise pour itérer sur des listes ou dictionnaires. Les conditions utilisent les tests Jinja2 comme is defined, is number ou des comparaisons classiques. On peut aussi utiliser des macros pour factoriser des blocs de configuration récurrents.


Comment gérer les espaces blancs indésirables dans un template Jinja2 ?

Jinja2 offre deux mécanismes principaux. Le premier consiste à utiliser le tiret dans les balises : un tiret après l’ouverture ou avant la fermeture supprime les espaces adjacents. Le second utilise les paramètres trim_blocks (supprime le saut de ligne après un bloc) et lstrip_blocks (supprime les espaces avant un bloc), configurables directement dans le module template d’Ansible.


Lucie Moreau
Lucie Moreau

Formatrice IT indépendante depuis 2016, ancienne étudiante BTS SIO SLAM. 6 ans d'expérience en entreprise.

Lucie Moreau

Formatrice IT indépendante depuis 2016, ancienne étudiante BTS SIO SLAM. 6 ans d'expérience en entreprise.