Ansible : templates et handlers avec un exemple nginx
Générez une configuration nginx avec un template Jinja2, rechargez le service uniquement après un changement, puis vérifiez l’idempotence en lançant le play deux fois.
Ce qu’ajoutent les templates et les handlers Ansible à votre premier playbook
Les templates et les handlers Ansible sont les deux éléments qui transforment un playbook statique en outil utile. Un template génère un fichier de configuration à partir de vos variables. Un seul fichier peut ainsi convenir à tous les hôtes. Un handler ne s’exécute que lorsqu’une tâche a réellement modifié quelque chose. Le service est donc rechargé après une modification effective de la configuration et reste inchangé le reste du temps.
Ce guide reprend exactement là où s’arrête votre premier playbook Ansible sur un VPS. Vous disposez déjà d’un play qui installe un paquet et démarre un service. Tout ce qui suit s’exécute sur une seule machine, car le play cible localhost via une connexion locale. Vous n’avez pas besoin d’un deuxième serveur pour suivre ce guide. Le même play s’exécute sur des hôtes réels de l’inventaire sans modifier les tâches. La dernière section décrit les changements nécessaires.
Configurer le répertoire de travail
sudo apt update
sudo apt install -y ansible nginx
ansible --version
mkdir -p ~/ansible-templates/templates
cd ~/ansible-templatesnginx est utilisé ici uniquement parce qu’il s’agit d’un véritable service avec un fichier de configuration et une commande de reload. Cela suffit pour cet exemple. ansible --version affiche la version d’ansible-core et l’interpréteur Python qu’il utilisera. Notez ces deux informations. Le playbook ci-dessous utilise des noms de modules pleinement qualifiés tels que ansible.builtin.template. Cette syntaxe nécessite Ansible 2.10 ou une version ultérieure. Les paquets fournis par les distributions actuelles sont largement plus récents.
Créez inventory.ini :
[local]
localhost ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}"ansible_connection=local indique à Ansible d’exécuter chaque tâche comme un processus local au lieu d’ouvrir une session SSH vers lui-même. Le deuxième paramètre n’est pas décoratif. Lorsque vous écrivez localhost dans un fichier d’inventaire, cette valeur devient un hôte ordinaire. Elle ne bénéficie plus de l’interpréteur qu’Ansible fournit automatiquement à localhost implicite. Ansible utilise alors la détection de l’interpréteur et peut sélectionner un autre Python que celui qui exécute le play. ansible_playbook_python correspond à l’interpréteur qui exécute ansible-playbook actuellement. Les deux utilisent ainsi le même interpréteur.
Créez ansible.cfg :
[defaults]
inventory = inventory.iniSans ce fichier, vous devez indiquer -i inventory.ini dans chaque commande. Sans inventaire, Ansible affiche [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all', puis un play contenant hosts: all ne correspond à aucun hôte. Autre point concernant ansible.cfg : Ansible l’ignore lorsqu’il se trouve dans un répertoire accessible en écriture par tous. Conservez donc le projet dans votre répertoire personnel. Un fichier d’inventaire contient plus qu’une liste d’hôtes, et celui-ci est le plus petit qui suffit.
template et copy : lequel utiliser et dans quel cas
ansible.builtin.copy transfère un fichier tel quel. ansible.builtin.template traite d’abord le fichier avec Jinja2, puis transfère le résultat. La documentation du module décrit template comme « un module virtuel entièrement implémenté sous forme de plugin d’action et exécuté sur le contrôleur ». Il faut en retenir une conséquence importante : le rendu est effectué sur la machine sur laquelle vous avez saisi ansible-playbook. L’hôte cible ne voit jamais vos variables et n’a pas besoin de Jinja2 installé.
Utilisez copy lorsque le fichier est identique sur tous les hôtes. Utilisez template dès qu’une valeur diffère selon l’hôte, ou lorsque vous avez besoin d’une boucle {% for %} ou d’un bloc {% if %}. copy possède bien un paramètre content:, et les variables qu’il contient sont remplacées comme dans n’importe quel autre argument de tâche. Toutefois, il n’y a ni boucle ni condition dans ce paramètre. Toute structure de ce type doit donc être placée dans un template. Les deux modules acceptent les mêmes options de fichier, car ils utilisent les mêmes fragments de documentation. owner, group, mode, backup et validate se comportent donc de la même manière dans les deux cas.
Écrire le template : une variable, une boucle
Enregistrez ce contenu dans templates/app.conf.j2 :
# {{ ansible_managed }}
upstream {{ app_name }}_backend {
{% for backend in app_backends %}
server {{ backend.host }}:{{ backend.port }} weight={{ backend.weight }};
{% endfor %}
}
server {
listen {{ app_listen_port }};
server_name {{ app_server_name }};
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
}
}Deux types de balises Jinja2 font le travail ici. {{ ... }} est une expression qui affiche sa valeur. {% ... %} est une instruction qui n’affiche rien par elle-même. app_backends est une liste de dictionnaires. backend.host lit donc une clé dans chaque entrée, et la boucle écrit une ligne server par entrée, quel que soit le nombre d’entrées que vous définissez.
Un point concernant les espaces blancs peut surprendre les personnes qui connaissent Jinja2 dans d’autres contextes. Ansible définit trim_blocks sur yes par défaut, ce que Jinja2 ne fait pas. Le saut de ligne qui suit immédiatement une balise {% ... %} est donc supprimé, et la boucle ne laisse pas de ligne vide derrière elle. Ansible laisse lstrip_blocks à no. Les espaces placés devant une balise {% sont donc conservés et apparaissent dans le fichier généré. Si le fichier généré contient une indentation inattendue, définissez lstrip_blocks: true dans la tâche de template.
{{ ansible_managed }} génère par défaut le texte littéral Ansible managed. Laissez cette valeur telle quelle. Il est fréquent de redéfinir ansible_managed dans ansible.cfg pour y inclure une date. Dans ce cas, le fichier généré diffère à chaque exécution, la tâche signale une modification à chaque exécution et le service est rechargé à chaque exécution. Ce paramètre suffit à détruire la propriété sur laquelle repose le reste de ce guide. L’extension .j2 est une convention. Ansible ne la vérifie pas.
Le playbook
Enregistrez-le sous site.yml :
- name: Render an nginx site from a template
hosts: local
become: true
vars:
app_name: learn
app_listen_port: 8080
app_server_name: learn.example.com
app_backends:
- host: 127.0.0.1
port: 9001
weight: 3
- host: 127.0.0.1
port: 9002
weight: 1
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Render the site configuration
ansible.builtin.template:
src: templates/app.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}.conf"
owner: root
group: root
mode: '0644'
backup: true
notify: nginx config changed
- name: Make sure nginx is enabled and running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test the nginx configuration
ansible.builtin.command:
cmd: /usr/sbin/nginx -t
changed_when: false
listen: nginx config changed
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: nginx config changedmode: '0644' est volontairement placé entre guillemets. La documentation de l’option file recommande de mettre les nombres octaux entre guillemets « afin qu’Ansible reçoive une chaîne et puisse effectuer lui-même la conversion de la chaîne en nombre ». Sans guillemets, l’analyseur YAML interprète 0644 comme un nombre ordinaire, et vous pouvez obtenir des permissions différentes de celles demandées.
notify: nginx config changed désigne un sujet, pas un handler. Les deux handlers contiennent listen: nginx config changed ; une seule notification les déclenche donc tous les deux. Vous pouvez ajouter ultérieurement un troisième handler avec la même ligne listen, sans modifier la tâche de template. cache_valid_time: 3600 empêche une deuxième exécution au cours de la même heure d’interroger à nouveau les miroirs de paquets.
Exécutez-la une fois, puis lisez ce qu’elle affiche
ansible-playbook site.ymlSi sudo vous demande un mot de passe, ajoutez -K et Ansible vous le demandera.
Lisez d’abord les lignes de chaque tâche, puis le PLAY RECAP en bas. Chaque tâche affiche changed: lorsqu’Ansible a dû effectuer une modification, ou ok: lorsque l’hôte était déjà dans l’état souhaité. Le récapitulatif totalise ces compteurs pour chaque hôte. Une fois toutes les tâches du play terminées, et pas avant, vous obtenez RUNNING HANDLER [Test the nginx configuration] suivi de RUNNING HANDLER [Reload nginx].
Vérifiez maintenant la machine elle-même au lieu de vous fier à la sortie :
sudo cat /etc/nginx/conf.d/learn.conf
sudo /usr/sbin/nginx -t
curl -sI http://127.0.0.1:8080/nginx -t affiche nginx: configuration file /etc/nginx/nginx.conf test is successful lorsque la configuration assemblée est valide. curl renvoie une ligne d’état de nginx, et 502 Bad Gateway est le résultat attendu ici, car le server block est actif et aucun processus n’écoute sur les ports 9001 ou 9002. sudo tail /var/log/nginx/error.log indique clairement la raison : connect() failed (111: Connection refused) while connecting to upstream.
Exécutez-le une deuxième fois pour prouver l’idempotence
ansible-playbook site.ymlC’est cette exécution qui compte. Comparez donc sa sortie avec celle de la première exécution, ligne par ligne. La tâche template doit maintenant afficher ok: à l’endroit où elle affichait changed:, et aucun des deux handlers ne doit apparaître dans la sortie.
Le mécanisme est simple et mérite d’être connu, car c’est sur lui que porte le diagnostic. template génère le fichier sur le contrôleur, puis compare la somme de contrôle du résultat avec celle du fichier déjà présent à dest. Si le contenu, le propriétaire et le mode correspondent, il n’y a rien à faire. La tâche indique donc ok, notify ne se déclenche jamais et le handler ne s’exécute pas. Les handlers se déclenchent uniquement sur changed, et rien d’autre.
Vérifiez également le cas inverse. Remplacez weight: 3 par weight: 1 dans vars, exécutez à nouveau le play, puis vérifiez que la tâche template indique changed, que les deux handlers s’exécutent et que sudo cat /etc/nginx/conf.d/learn.conf affiche la nouvelle valeur.
Si une deuxième exécution identique indique encore une modification, le rendu n’est pas stable. Recherchez d’abord dans la sortie un élément dépendant du temps, car c’est la cause la plus fréquente. Un ansible_managed personnalisé est généralement responsable. Vérifiez ensuite que mode et owner définis dans la tâche correspondent à ce qui se trouve réellement sur le disque. Une différence à ce niveau constitue une modification, même si les octets sont identiques.
Voir les changements avant de les appliquer
ansible-playbook site.yml --check --diff--check exécute le play sans modifier l’hôte. --diff affiche ce que chaque tâche aurait modifié ; pour template, il s’agit d’une différence ligne par ligne entre le rendu et le fichier présent sur le disque. Ensemble, ces options répondent à la question « que ferait cette exécution ? » sans rien appliquer. Le check mode a lui aussi ses limites, principalement pour les tâches dont le résultat dépend d’une tâche précédente que le check mode n’a pas réellement exécutée.
Pourquoi les handlers attendent la fin du play
La documentation des handlers est claire : « Par défaut, les handlers s’exécutent après la fin de toutes les tâches d’un play donné. Les handlers notifiés s’exécutent automatiquement après chacune des sections suivantes, dans l’ordre suivant : pre_tasks, roles/tasks et post_tasks. »
La raison est le regroupement des changements. Un play qui génère quatre fichiers de configuration pour un service doit redémarrer ce service une seule fois, à la fin, lorsque les quatre fichiers sont en place. Le redémarrer après chaque fichier provoquerait quatre redémarrages, dont trois chargeraient une configuration incomplète. La même page énonce clairement cette garantie : « Notifier plusieurs fois le même handler ne l’exécute qu’une seule fois, quel que soit le nombre de tâches qui le notifient. »
L’ordre est également fixe : « Les handlers s’exécutent dans l’ordre où ils sont définis dans la section handlers, et non dans l’ordre indiqué dans l’instruction notify. » C’est pourquoi Test the nginx configuration se trouve au-dessus de Reload nginx dans le playbook. Le test s’exécute en premier parce qu’il est écrit en premier, et rien dans la ligne notify ne modifie ce comportement.
Comment exécuter les handlers plus tôt et après un échec
Parfois, une tâche ultérieure du même play doit utiliser le service déjà démarré avec la nouvelle configuration. Exécutez à ce moment-là les handlers notifiés avec le module meta, que la documentation décrit comme permettant à « Ansible d’exécuter toutes les tâches de handler qui ont été notifiées jusque-là ».
- name: Run the notified handlers now instead of at the end of the play
ansible.builtin.meta: flush_handlers
- name: Wait for the new listener to accept connections
ansible.builtin.wait_for:
host: 127.0.0.1
port: 8080
timeout: 10Supprimez la ligne meta. La tâche wait_for s’exécute alors tandis que nginx sert encore l’ancienne configuration. Lors de la première exécution, aucun processus n’écoute encore sur le port 8080. La tâche attend donc les dix secondes complètes, puis échoue.
Le deuxième cas concerne les échecs. « Si une tâche notifie un handler, mais qu’une autre tâche échoue plus tard dans le play, le handler ne s’exécute pas par défaut sur cet hôte. L’hôte peut alors rester dans un état inattendu. » Un play qui génère une configuration puis échoue à cause d’une tâche sans rapport laisse donc le nouveau fichier sur le disque, tandis que l’ancien paramétrage reste chargé dans le service en cours d’exécution. Remplacez ce comportement par --force-handlers sur la ligne de commande ou par force_handlers: true dans le play. Le même paramètre existe sous la forme force_handlers = True dans [defaults] de ansible.cfg, ainsi que dans la variable d’environnement ANSIBLE_FORCE_HANDLERS. La valeur par défaut est False.
Les noms des handlers entrent en conflit, et le perdant reste silencieux
La documentation énonce la règle suivante : « Chaque handler doit avoir un nom globalement unique. Si plusieurs handlers portent le même nom, seul le dernier chargé dans le play peut être notifié et exécuté. » Les handlers définis dans un role ne sont pas limités à ce role. Ils sont ajoutés à une liste globale de handlers pour l’ensemble du play. Ainsi, deux roles qui définissent chacun Restart nginx vous laissent un nom qui ne désigne exactement que l’un des deux. C’est l’ordre de chargement qui détermine lequel, et non le role depuis lequel vous avez envoyé la notification.
Testez cette règle avant de vous y fier. Enregistrez ceci dans handlers-dup.yml :
- name: Two handlers, one name
hosts: local
gather_facts: false
tasks:
- name: Notify the duplicated name
ansible.builtin.command:
cmd: /bin/true
changed_when: true
notify: Duplicated handler
handlers:
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-first
state: touch
mode: '0644'
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-second
state: touch
mode: '0644'rm -f /tmp/dup-first /tmp/dup-second
ansible-playbook handlers-dup.yml
ls -l /tmp/dup-first /tmp/dup-secondLe play réussit, RUNNING HANDLER [Duplicated handler] apparaît une fois, et ls affiche une ligne pour /tmp/dup-first et ls: cannot access '/tmp/dup-second': No such file or directory pour l’autre. Le handler exécuté est celui qui est écrit en premier, et non le dernier chargé. C’est l’inverse de ce que cette phrase laisse prévoir.
Cette différence mérite d’être comprise, car la règle documentée concerne les blocs de handlers, et non les lignes d’un fichier. Les handlers provenant de sources distinctes, d’abord un role puis un autre, appartiennent à des blocs distincts. Un bloc chargé plus tard masque donc un bloc précédent. Une simple liste handlers: dans un play constitue un seul bloc. La recherche effectuée dans ce bloc se fait de haut en bas et s’arrête au premier nom correspondant. Dans un même fichier, la première définition est donc utilisée et la seconde est inaccessible. Entre plusieurs roles, le masquage fonctionne comme le décrit la documentation. Dans les deux cas, vous ne pouvez jamais atteindre les deux handlers, et aucune de ces deux possibilités ne doit servir de base à votre configuration.
Deux solutions simples existent. Donnez à chaque handler un préfixe propre à son role, ou envoyez une notification vers la forme qualifiée role_name : handler_name. La documentation indique que cette forme permet « de garantir qu’un handler d’un role est notifié plutôt qu’un handler externe portant le même nom ». Les espaces autour des deux-points font partie de cette syntaxe. Le problème devient concret dès que vous commencez à utiliser des roles que vous n’avez pas écrits.
La même page énonce une autre règle : « Évitez de placer des variables dans le nom du handler. Les noms des handlers étant templatisés tôt, Ansible peut ne pas disposer d’une valeur pour un nom de handler de ce type. » Un handler nommé Restart {{ service_name }} fait échouer l’ensemble du play si cette variable n’est pas définie au moment où le nom est templatisé. En conservant des noms de handlers sous forme de chaînes fixes et en les regroupant avec listen, vous évitez ce problème.
valider : refuser d’installer une configuration invalide
validate exécute une commande sur le fichier rendu avant qu’Ansible ne le mette en place. La documentation précise : « Commande de validation à exécuter avant de copier le fichier mis à jour vers sa destination finale. Un chemin de fichier temporaire est utilisé pour la validation. Il est transmis via %s, qui doit être présent comme dans les exemples ci-dessous. La commande est également transmise de manière sécurisée : les fonctionnalités du shell, comme l’expansion et les pipelines, ne fonctionnent donc pas. »
Deux règles découlent directement de ce texte. %s est obligatoire, et une chaîne de validation qui ne le contient pas fait échouer la tâche avec validate must contain %s. De plus, aucun shell n’est utilisé : les pipelines, la redirection, la génération de noms de fichiers et && ne fonctionnent pas. Une commande, un argument de fichier.
Les exemples officiels du module montrent les deux cas où cela fonctionne parfaitement :
- name: Copy a new sudoers file into place, after passing validation with visudo
ansible.builtin.template:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -cf %s
- name: Update sshd configuration safely, avoid locking yourself out
ansible.builtin.template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
backup: yesLes deux fonctionnent, car chaque outil de vérification prend un seul fichier et l’évalue selon ses propres règles. visudo -cf lit un fichier sudoers. sshd -t -f lit un fichier sshd_config complet.
Pourquoi validate ne peut pas vérifier le fichier nginx dans ce guide
Ajoutez validate: /usr/sbin/nginx -t -c %s à la tâche de template ci-dessus : la tâche échoue. Le message indique la cause :
nginx: [emerg] "upstream" directive is not allowed here in <ansible temporary path>:2nginx -t -c attend une configuration complète qui commence au niveau supérieur par les blocs events et http. Le fichier rendu par ce playbook est un fragment, inclus dans le bloc http par include /etc/nginx/conf.d/*.conf; à l’intérieur de /etc/nginx/nginx.conf. Pris seul, hors de ce contexte, upstream est effectivement une directive placée au mauvais endroit. nginx rejette donc un fichier qui est parfaitement correct à l’endroit où il est réellement utilisé. Le validateur a reçu un fragment et devait le traiter comme une configuration complète.
La solution applicable est déjà présente dans le playbook. Installez le fragment, puis vérifiez la configuration assemblée dans un handler défini au-dessus du handler de rechargement. Comme les handlers s’exécutent dans l’ordre où ils sont définis, nginx -t voit le véritable /etc/nginx/nginx.conf avec votre fragment inclus. En cas d’échec, le playbook s’arrête avant l’appel à systemctl reload. Tenez compte du coût de cette méthode : le fichier incorrect est présent sur le disque lorsque la vérification échoue, et nginx continue de servir la dernière configuration chargée jusqu’à un redémarrage.
C’est le rôle de backup: true. Il écrit une copie de l’ancien fichier à côté de l’original avant de l’écraser. Cette copie est nommée basename.PID.YYYY-MM-DD@HH:MM:SS~, et le répertoire contient alors des entrées comme learn.conf.4127.2026-08-20@11:42:09~. Exécutez sudo ls -l /etc/nginx/conf.d/ après une modification : vous en trouverez une.
Ce détail du nom est plus important qu’il n’y paraît. La sauvegarde ne pose pas de problème dans /etc/nginx/conf.d/, car la configuration principale inclut uniquement conf.d/*.conf et le nom de la sauvegarde se termine par un tilde. Elle n’est pas inoffensive dans un répertoire inclus avec un * sans motif. Sur Debian et Ubuntu, /etc/nginx/nginx.conf inclut /etc/nginx/sites-enabled/* exactement de cette manière. Si vous utilisez un template dans sites-enabled avec backup: true, nginx charge la sauvegarde comme un deuxième server block actif. C’est pourquoi ce playbook écrit plutôt dans conf.d.
Exécuter le même play sur les hôtes réels de l’inventaire
Remplacez hosts: local par le nom du groupe que vous utilisez, et rien d’autre dans le play ne change. Le template est rendu une fois par hôte. Ainsi, app_listen_port et app_backends peuvent provenir de group_vars et host_vars, tandis que le fichier template reste unique. C’est l’intérêt de placer les valeurs dans des variables plutôt que dans le fichier.
Deux éléments changent. become: true a désormais besoin d’un mot de passe sudo sur chaque cible, sauf si sudo y est configuré sans mot de passe. Ajoutez donc -K. De plus, tout secret présent dans ce template, comme un mot de passe de base de données ou un token d’API, ne doit pas rester en clair dans vars: dans un fichier que vous committez. Chiffrez ces valeurs avec Ansible Vault et référencez-les par leur nom exactement comme vous le faites maintenant, car le template ne dépend pas de l’origine d’une variable.
Lorsque le play dépasse le cadre d’un seul service, vars:, templates/ et handlers: ont déjà chacun un emplacement standard prévu à cet effet. Les y déplacer est précisément l’objectif de la séparation entre un playbook et un rôle.
FAQ
Pourquoi mon handler Ansible ne s’est-il pas exécuté ?
Presque toujours parce que la tâche qui le notifie a renvoyé ok au lieu de changed. Les handlers se déclenchent uniquement en cas de changement. Une tâche template dont le rendu correspond déjà au fichier présent sur le disque ne notifie donc rien. Vérifiez ensuite quatre points. La chaîne dans notify doit correspondre exactement au handler name ou à un topic listen, y compris la casse et les espaces. Une tâche ultérieure qui échoue sur cet hôte empêche l’exécution des handlers notifiés, sauf si vous passez --force-handlers. Un handler défini dans un autre play n’est pas visible depuis celui-ci. Enfin, une tâche de notification ignorée à cause d’une condition when ne notifie jamais le handler.
Pourquoi mon playbook signale-t-il un changement à chaque exécution ?
Le texte rendu n’est pas stable d’une exécution à l’autre. La cause la plus fréquente est un horodatage dans la sortie. Une chaîne ansible_managed personnalisée qui contient une date produit exactement ce problème. Vérifiez ensuite mode et owner dans la tâche. S’ils ne correspondent pas au fichier déjà présent sur le disque, Ansible les corrige et signale un changement, même si le contenu est identique. Exécutez ansible-playbook site.yml --check --diff pour déterminer lequel des deux pose problème. --diff affiche la différence que la tâche prévoit d’appliquer.
Quelle est la différence entre template et copy dans Ansible ?
ansible.builtin.copy envoie un fichier sans le modifier. ansible.builtin.template le rend d’abord avec Jinja2 sur le controller, puis envoie le résultat. Les variables et les boucles sont donc résolues avant que le fichier n’atteigne l’hôte cible. Utilisez copy pour un fichier identique octet par octet sur tous les hôtes. Utilisez template pour tout fichier dont le contenu varie selon l’hôte. Ces deux modules partagent les mêmes options de fichier. mode, owner, backup et validate fonctionnent donc de la même manière dans les deux cas.
Comment faire exécuter un handler au milieu d’un play ?
Ajoutez ansible.builtin.meta: flush_handlers comme tâche à l’endroit où vous voulez exécuter les handlers. Cette tâche déclenche tous les handlers notifiés jusque-là, puis le play continue normalement. Utilisez-la lorsqu’une tâche ultérieure du même play dépend d’un service qui exécute déjà la nouvelle configuration, par exemple un wait_for sur un port qui n’existe qu’après le reload. C’est la méthode prise en charge pour exécuter un handler avant la fin du play.
Puis-je utiliser validate avec un fragment de configuration nginx ?
Pas avec nginx -t -c %s. Cette commande attend une configuration complète qui commence par les blocs de niveau supérieur events et http. Elle rejette donc un fragment conf.d avec un message tel que "upstream" directive is not allowed here. Le fragment est valide à l’intérieur du bloc http, mais pas utilisé seul. Installez le fichier, puis exécutez nginx -t sur la configuration assemblée dans un handler défini avant le handler de reload. Les handlers s’exécutent dans l’ordre où ils sont définis. Une configuration incorrecte fait donc échouer le play avant le reload. Définissez backup: true sur la tâche template afin que le fichier précédent puisse toujours être restauré.