Aller au contenu

Les imports et includes Ansible

Mise à jour :

logo

Dans l’écriture de code d’infrastructures Ansible, il devient nécessaire de diviser les playbooks en blocs plus petits et réutilisables pour simplifier la maintenance et améliorer la clarté du code. Cela devient particulièrement indispensable lorsque l’on gère des environnements complexes avec de nombreuses configurations différentes. Pour répondre à ces besoins, Ansible propose plusieurs modules permettant d’importer et d’inclure des playbooks, rôles, tâches et variables de manière efficace. Ces modules permettent de construire des playbooks modulaires et flexibles, en facilitant la réutilisation du code tout en garantissant une structure logique et bien organisée.

Différence entre import et include

Avant de plonger dans les détails techniques, il est important de bien différencier les modules import et include dans Ansible. Chaque groupe possède ses propres caractéristiques et usage spécifique.

Modules import (statique)

  • import_tasks : Importe une liste de tâches statiquement et les exécute immédiatement.
  • import_playbook : Importe un playbook complet pour l’exécuter comme partie intégrante du playbook principal.
  • import_role : Importe un rôle pour l’exécuter dès le début, sans conditions.

Modules include (dynamique)

  • include_vars : Charge des variables depuis un fichier de manière dynamique, utile pour gérer différentes configurations d’environnement.
  • include_role : Charge et exécute un rôle dynamiquement, avec la possibilité de le conditionner.
  • include_tasks : Inclut une liste de tâches de façon dynamique, exécutée selon des conditions définies.

Un fonctionnement différent

Comprendre la différence entre les modules import et include est indispensable pour bien structurer vos playbooks Ansible. Bien qu’ils semblent similaires, ces deux types de modules ont des comportements très distincts qui influencent la manière dont les tâches, rôles ou variables sont chargés et exécutés.

Avec les modules import, le chargement du contenu est statique. Cela signifie que lorsque le playbook est exécuté, tout le contenu importé (qu’il s’agisse d’un rôle, de tâches ou d’un playbook) est immédiatement chargé et évalué au moment où Ansible lit le fichier. Le traitement des éléments importés se fait donc dès le début, indépendamment de toute condition ou logique d’exécution. Cela garantit que tout ce qui est importé sera exécuté, peu importe les circonstances.

Par exemple, dans le cas de l’utilisation de import_tasks :

---
- hosts: all
tasks:
- import_tasks: taches_communes.yml

Ici, les tâches contenues dans taches_communes.yml seront systématiquement incluses et évaluées au chargement du playbook, avant même l’exécution proprement dite.

En revanche, les modules include fonctionnent de manière dynamique. Le contenu n’est chargé que lorsqu’il est nécessaire, c’est-à-dire au moment où la ligne contenant le module include est atteinte pendant l’exécution du playbook. Ce mécanisme permet d’inclure des tâches, des rôles ou des variables de manière conditionnelle, en fonction du contexte ou des données spécifiques du système.

Voici un exemple avec include_tasks :

---
- hosts: all
tasks:
- include_tasks: taches_specifiques.yml
when: ansible_distribution == "Ubuntu"

Dans ce cas, les tâches ne seront incluses et exécutées que si le système cible utilise Ubuntu. C’est ici que l’aspect dynamique prend tout son sens : le fichier taches_specifiques.yml n’est chargé que si la condition est remplie. Cela vous permet d’adapter votre playbook à différents environnements sans avoir à dupliquer des fichiers ou du code.

En résumé, import est une approche prévisible où tout le contenu est chargé dès le début, tandis que include offre une plus grande flexibilité en s’exécutant uniquement quand les conditions sont remplies. Utiliser import est judicieux lorsque l’on sait que toutes les parties d’un playbook ou d’un rôle doivent être systématiquement exécutées. À l’inverse, include est plus adapté lorsque l’on souhaite conditionner l’exécution à certains critères, ce qui permet de réduire la duplication de code et d’adapter les playbooks à de divers environnements.

import_tasks : Importer une liste de tâches

Le module import_tasks permet d’importer une liste de tâches à partir d’un fichier externe dans un playbook Ansible. L’importation est statique, ce qui signifie que les tâches sont chargées et évaluées dès le début de l’exécution du playbook, avant même que les conditions ou les étapes d’exécution ne soient atteintes. Ce mécanisme statique est idéal lorsque vous savez que toutes les tâches doivent être exécutées systématiquement, sans avoir besoin d’une évaluation conditionnelle.

L’importation de tâches est simple. Supposons que vous ayez un fichier de tâches réutilisables appelé taches_communes.yml. Vous pouvez l’importer dans votre playbook comme suit :

---
- hosts: all
tasks:
- name: Importer une liste de tâches communes
ansible.builtin.import_tasks: taches_communes.yml

Dans cet exemple, toutes les tâches définies dans le fichier taches_communes.yml seront importées et évaluées lors du chargement du playbook, ce qui signifie qu’elles seront exécutées sans conditions supplémentaires. C’est une approche idéale si vous avez besoin de structurer un playbook complexe et souhaitez organiser les tâches en plusieurs fichiers.

import_playbook : Importer un playbook

Le module import_playbook permet d’importer et d’exécuter un autre playbook à partir d’un playbook principal. Comme avec les autres modules d’import, cette inclusion est statique, ce qui signifie que le contenu du playbook importé est chargé et évalué au moment où le playbook principal est lancé. L’importation d’un playbook est particulièrement utile pour organiser et diviser des playbooks complexes en plusieurs fichiers, rendant ainsi la gestion et la maintenance plus aisées.

Voici un exemple d’utilisation du module import_playbook. Supposons que vous ayez un playbook secondaire nommé sous_playbook.yml que vous souhaitez inclure dans votre playbook principal :

---
- name: Playbook principal
hosts: all
- name: Importer un autre playbook
ansible.builtin.import_playbook: sous_playbook.yml

Dans cet exemple, le playbook sous_playbook.yml sera importé et toutes les tâches qu’il contient seront exécutées de manière statique, comme si elles étaient directement écrites dans le playbook principal. Cela permet de séparer logiquement les playbooks, tout en conservant une exécution linéaire.

Le contenu de sous_playbook.yml pourrait être le suivant :

---
- name: Tâches du sous-playbook
hosts: all
tasks:
- name: Installer Apache
ansible.builtin.apt:
name: apache2
state: present

Dans cet exemple, lorsque le playbook principal inclut sous_playbook.yml, la tâche d’installation d’Apache sera exécutée comme si elle était directement incluse dans le playbook principal.

Variables globales partagées

Lorsque vous utilisez import_playbook, les variables définies dans le playbook principal peuvent être utilisées dans les playbooks importés, et vice versa. Cela permet de partager facilement des informations entre les différentes parties d’un projet.

Exemple :

---
- name: Playbook principal
hosts: all
- ansible.builtin.import_playbook: config_web.yml
vars:
http_port: 8080

Dans le playbook config_web.yml, vous pouvez accéder à la variable http_port sans avoir besoin de la redéclarer :

---
- name: Configurer le serveur web
hosts: all
tasks:
- name: Configurer Apache pour écouter sur le bon port
ansible.builtin.lineinfile:
path: /etc/apache2/ports.conf
regexp: '^Listen'
line: "Listen {{ http_port }}"

Cette fonctionnalité rend l’utilisation de import_playbook très puissante, car elle permet de centraliser les variables et de les partager entre plusieurs playbooks.

import_role : Importer un rôle dans un playbook

Le module import_role permet d’importer un rôle directement dans un playbook Ansible. Contrairement à include_role, qui charge les rôles de manière dynamique, import_role les charge de façon statique, c’est-à-dire que l’exécution du rôle est déterminée dès que le playbook est chargé par Ansible. Cela garantit que toutes les tâches du rôle seront exécutées et l’importation se fait sans condition.

Ce module est particulièrement utile pour des scénarios où l’exécution des rôles est systématique et où il n’est pas nécessaire de l’évaluer dynamiquement, ce qui simplifie la structure du playbook tout en assurant une exécution immédiate et cohérente des rôles.

L’utilisation du module import_role est simple et directe. Voici un exemple d’importation d’un rôle nommé mon_role :

---
- hosts: all
tasks:
- name: Importer un rôle
ansible.builtin.import_role:
name: mon_role

Dans cet exemple, le rôle mon_role sera importé statiquement et exécuté avec toutes ses tâches dès que le playbook est lancé. Cela permet de s’assurer que tout le contenu du rôle sera exécuté, sans possibilité d’exécution conditionnelle.

Voici un exemple de contenu d’un rôle nommé mon_role qui se trouve dans le répertoire roles/mon_role/ :

roles/mon_role/tasks/main.yml
---
- name: Installer Apache
ansible.builtin.apt:
name: apache2
state: present
- name: Démarrer le service Apache
ansible.builtin.systemd:
name: apache2
state: started

Lorsque le rôle est importé via import_role dans un playbook principal, les tâches de ce fichier seront exécutées immédiatement après le chargement du playbook.

Utilisation de variables avec import_role

Vous pouvez passer des variables spécifiques à un rôle lors de son importation. Cela permet de personnaliser l’exécution du rôle sans avoir à modifier directement les fichiers de rôle eux-mêmes. Voici un exemple où des variables sont passées à un rôle :

---
- hosts: all
tasks:
- name: Importer le rôle avec des variables personnalisées
ansible.builtin.import_role:
name: config_web
vars:
http_port: 8080
server_name: "example.com"

Dans cet exemple, le rôle config_web sera importé avec les variables http_port et server_name, qui peuvent ensuite être utilisées dans les tâches du rôle pour personnaliser la configuration du serveur web.

Voici un exemple de l’utilisation de ces variables dans le rôle :

roles/config_web/tasks/main.yml
---
- name: Configurer Apache pour écouter sur le bon port
ansible.builtin.lineinfile:
path: /etc/apache2/ports.conf
regexp: '^Listen'
line: "Listen {{ http_port }}"
- name: Configurer le nom de serveur
ansible.builtin.lineinfile:
path: /etc/apache2/apache2.conf
regexp: '^ServerName'
line: "ServerName {{ server_name }}"

Grâce à l’utilisation de variables, vous pouvez adapter le rôle à des environnements spécifiques sans avoir à modifier le code source du rôle lui-même.

include_vars : Charger des variables dynamiquement

Le module include_vars permet de charger des variables depuis un fichier externe directement dans un playbook Ansible. Il est extrêmement utile pour rendre un playbook plus dynamique et modulaire en séparant les variables de configuration du reste du code. Contrairement à une déclaration de variables standard dans un playbook, include_vars permet de les charger de manière conditionnelle et d’ajuster leur contenu en fonction de l’environnement ou des spécificités d’un hôte.

Le module include_vars permet de charger des variables depuis plusieurs formats de fichier, y compris YAML, JSON ou INI. En utilisant ce module, vous pouvez facilement gérer différentes configurations pour des environnements de production, de développement ou de tests, sans devoir dupliquer vos playbooks.

Prenons un exemple où nous avons un fichier vars_fichier.yml contenant des variables spécifiques à une configuration :

Contenu du fichier vars_fichier.yml :

http_port: 8080
db_user: admin
db_password: secret

Dans le playbook, nous allons utiliser include_vars pour charger ces variables :

---
- hosts: all
tasks:
- name: Charger les variables depuis un fichier
ansible.builtin.include_vars: vars_fichier.yml
- name: Afficher les variables
ansible.builtin.debug:
var: http_port

Dans cet exemple, les variables contenues dans vars_fichier.yml (comme http_port, db_user, etc.) sont chargées au moment de l’exécution et peuvent être utilisées dans le playbook immédiatement après leur inclusion.

Inclusion conditionnelle des variables

L’un des avantages majeurs de include_vars est la possibilité de charger des variables de manière conditionnelle, en fonction du système d’exploitation, de la distribution, ou de tout autre paramètre pertinent.

Voici un exemple qui charge des variables spécifiques en fonction de la distribution Linux de l’hôte :

---
- hosts: all
tasks:
- name: Charger les variables pour Debian
ansible.builtin.include_vars: vars_debian.yml
when: ansible_os_family == "Debian"
- name: Charger les variables pour RedHat
ansible.builtin.include_vars: vars_redhat.yml
when: ansible_os_family == "RedHat"

Dans cet exemple, si l’hôte exécute un système Debian, le fichier vars_debian.yml sera chargé. Si l’hôte est sur RedHat, c’est le fichier vars_redhat.yml qui sera inclus. Cela permet de gérer des environnements multiples dans un seul playbook, en adaptant dynamiquement les variables en fonction du contexte d’exécution.

Utilisation avancée : Chargement de plusieurs fichiers

Le module include_vars peut également être utilisé pour charger un ensemble de fichiers dans un répertoire, ce qui est pratique pour centraliser les variables par rôle, par hôte, ou par environnement.

Exemple de chargement de toutes les variables présentes dans un dossier variables/ :

---
- hosts: all
tasks:
- name: Charger toutes les variables depuis un répertoire
ansible.builtin.include_vars:
dir: variables/
files_matching: ".yml"
- name: Afficher les variables
ansible.builtin.debug:
var: http_port

Dans cet exemple, Ansible charge toutes les variables contenues dans les fichiers .yml présents dans le répertoire variables/. Cela permet de structurer les variables par fichier et de les organiser de façon plus claire.

Charger des variables dynamiquement avec include_vars et first_found

Dans Ansible, il est souvent nécessaire d’adapter les configurations en fonction de l’environnement ou du type de système d’exploitation de l’hôte. Pour répondre à ce besoin, Ansible offre des moyens puissants pour charger des fichiers de variables dynamiquement selon des critères spécifiques, tout en ayant des options de secours en cas de non-correspondance. L’utilisation du module include_vars avec le plugin lookup first_found permet de réaliser cela de manière efficace.

Le module include_vars est utilisé pour charger un fichier de variables dans un playbook Ansible. Ce fichier de variables contient des valeurs clés qui peuvent ensuite être utilisées dans les tâches du playbook. En combinant ce module avec le plugin lookup, notamment la fonction first_found, Ansible peut rechercher un fichier de variables parmi plusieurs options et charger le premier fichier trouvé.

Le principe de first_found est de parcourir une liste de fichiers possibles et de charger le premier fichier valide qu’il trouve. Cela permet d’adapter les variables selon différents critères comme le nom de la distribution, la famille du système d’exploitation, ou, à défaut, un fichier de variables par défaut.

- name: Load a variable file based on the OS type, or a default if not found. Using free-form to specify the file.
ansible.builtin.include_vars: "{{ lookup('ansible.builtin.first_found', params) }}"
vars:
params:
files:
- '{{ansible_distribution}}.yaml'
- '{{ansible_os_family}}.yaml'
- default.yaml
paths:
- 'vars'

Dans cet exemple :

  • include_vars est utilisé pour inclure un fichier de variables.
  • lookup(‘ansible.builtin.first_found’, params) : Cette expression utilise le plugin first_found pour rechercher le premier fichier de variables existant dans une liste.
  • params : Définit les fichiers à rechercher et le chemin dans lequel chercher ces fichiers.
    • files : La liste des fichiers potentiels. Les fichiers sont construits dynamiquement en fonction des facts Ansible :
      • {{ansible_distribution}}.yaml : Un fichier correspondant à la distribution de l’OS, par exemple Ubuntu.yaml ou CentOS.yaml.
      • {{ansible_os_family}}.yaml : Un fichier correspondant à la famille de l’OS, par exemple Debian.yaml ou RedHat.yaml.
      • default.yaml : Un fichier de variables par défaut, utilisé si aucun des fichiers spécifiques à l’OS n’est trouvé.
    • paths : Indique où Ansible doit chercher ces fichiers. Dans cet exemple, les fichiers de variables sont recherchés dans le répertoire vars/.

Cas d’utilisation

Prenons un exemple pratique où un hôte sous Ubuntu exécute ce playbook. Ansible procédera comme suit :

  1. Cherche un fichier nommé Ubuntu.yaml dans le répertoire vars/.
  2. Si ce fichier n’existe pas, il cherche Debian.yaml (car Ubuntu appartient à la famille Debian).
  3. Si aucun de ces deux fichiers n’est trouvé, Ansible charge default.yaml.

Ce comportement garantit que des variables spécifiques à l’environnement seront utilisées si elles existent et que des valeurs par défaut seront employées en dernier recours si aucun fichier spécifique n’est trouvé.

Avantages

  1. Adaptabilité : En utilisant les facts d’Ansible, vous pouvez facilement adapter les variables à différentes distributions ou familles de systèmes d’exploitation.
  2. Robustesse : Le fichier par défaut (default.yaml) assure qu’aucun playbook ne manque de variables essentielles, même si les fichiers spécifiques à l’OS ne sont pas trouvés.
  3. Centralisation des variables : Les variables sont centralisées dans des fichiers séparés, facilitant la gestion et la maintenance de configurations multi-environnements. Par exemple, vous pouvez avoir un fichier de configuration pour chaque distribution Linux ou famille d’OS et un fichier de secours pour les cas imprévus.

include_tasks : Inclure dynamiquement une liste de tâches

Le module include_tasks permet d’inclure une liste de tâches provenant d’un fichier externe, de manière dynamique, dans un playbook Ansible. Ce chargement dynamique permet également de conditionner l’inclusion des tâches, en les exécutant uniquement si certaines conditions sont remplies.

L’utilisation de include_tasks est assez simple. Supposons que nous ayons un fichier de tâches appelé taches_communes.yml qui contient des tâches réutilisables, nous pouvons l’inclure dans notre playbook comme suit :

---
- hosts: all
tasks:
- name: Inclure une liste de tâches communes
ansible.builtin.include_tasks: taches_communes.yml

Dans cet exemple, le fichier taches_communes.yml est inclus dynamiquement lorsque la ligne est atteinte pendant l’exécution du playbook. Toutes les tâches contenues dans ce fichier seront exécutées comme si elles faisaient partie du playbook principal.

Voici un exemple de ce à quoi pourrait ressembler le fichier taches_communes.yml :

---
- name: Installer les paquets nécessaires
ansible.builtin.yum:
name: httpd
state: present
- name: Démarrer le service Apache
ansible.builtin.systemd:
name: httpd
state: started

En incluant ce fichier avec include_tasks, ces tâches seront ajoutées au flux principal du playbook et exécutées dans l’ordre, exactement comme si elles étaient définies dans le playbook lui-même.

Inclusion conditionnelle de tâches

L’un des grands avantages de include_tasks est la possibilité d’exécuter des tâches uniquement si certaines conditions sont remplies. Par exemple, vous pouvez inclure une liste de tâches spécifiques en fonction du système d’exploitation de l’hôte :

---
- hosts: all
tasks:
- name: Inclure des tâches pour les systèmes Debian
ansible.builtin.include_tasks: taches_debian.yml
when: ansible_os_family == "Debian"
- name: Inclure des tâches pour les systèmes RedHat
ansible.builtin.include_tasks: taches_redhat.yml
when: ansible_os_family == "RedHat"

Dans cet exemple, si l’hôte appartient à la famille de systèmes d’exploitation Debian, les tâches de taches_debian.yml seront incluses et exécutées. Si l’hôte est sous RedHat, les tâches de taches_redhat.yml seront incluses à la place. Cela vous permet de gérer des configurations spécifiques à des environnements différents tout en maintenant un playbook modulaire et flexible.

Gestion des erreurs avec include_tasks

Vous pouvez également gérer les erreurs de manière plus fine en utilisant include_tasks avec des blocs ou des instructions comme ignore_errors ou failed_when. Cela vous permet d’assurer une certaine robustesse dans vos playbooks, en contrôlant la façon dont Ansible doit se comporter lorsqu’une erreur survient pendant l’inclusion des tâches.

---
- hosts: all
tasks:
- block:
- name: Inclure des tâches spécifiques
ansible.builtin.include_tasks: taches_specifiques.yml
rescue:
- name: Gérer les erreurs d'inclusion
ansible.builtin.debug:
msg: "Erreur lors de l'inclusion des tâches spécifiques"

Dans cet exemple, si une erreur se produit lors de l’inclusion des tâches de taches_specifiques.yml, le bloc rescue est exécuté, affichant un message de débogage. Cela permet de maintenir un contrôle précis sur l’exécution du playbook, même en cas de problème.

Inclure des tâches dynamiquement avec include_vars et with_first_found

Le principe décrit dans le chapitre sur include_vars peut être reproduit de la même manière :

- name: Include tasks only if one of the files exists, otherwise skip
ansible.builtin.include_tasks: '{{ tasks_file }}'
when: tasks_file != ""
vars:
tasks_file: "{{ lookup('ansible.builtin.first_found', files=['tasks.yaml', 'other_tasks.yaml'], errors='ignore') }}"

include_role : Charger et exécuter un rôle de façon dynamique

Le module include_role permet de charger et exécuter un rôle de manière dynamique dans un playbook Ansible. Contrairement à import_role, qui charge et exécute un rôle au moment où le playbook est lu, include_role est évalué dynamiquement, c’est-à-dire que son exécution ne se fait que lorsque la ligne contenant le module est atteinte. Cela permet une exécution conditionnelle des rôles, offrant ainsi plus de flexibilité pour ajuster les opérations en fonction des paramètres de l’environnement ou d’une situation particulière.

Voici un exemple simple d’utilisation de include_role pour charger et exécuter un rôle nommé mon_role :

---
- hosts: all
tasks:
- name: Inclure dynamiquement un rôle
ansible.builtin.include_role:
name: mon_role

Dans cet exemple, Ansible va inclure et exécuter le rôle mon_role lorsque cette tâche sera atteinte. Cela peut être pratique pour des playbooks où l’exécution d’un rôle dépend de certaines conditions spécifiques.

L’un des principaux avantages de include_role est la possibilité de conditionner l’exécution d’un rôle en fonction de variables ou de faits recueillis sur l’hôte. Par exemple, on peut vouloir exécuter un rôle spécifique uniquement sur des systèmes Linux de type Debian.

---
- hosts: all
tasks:
- name: Inclure un rôle uniquement pour les systèmes Debian
ansible.builtin.include_role:
name: debian_role
when: ansible_os_family == "Debian"

Dans ce cas, le rôle debian_role ne sera exécuté que si la condition ansible_os_family == "Debian" est remplie, ce qui permet d’adapter dynamiquement le playbook en fonction de l’environnement de l’hôte. Si l’hôte utilise une autre famille de systèmes d’exploitation, le rôle ne sera pas exécuté.

Passage de paramètres avec include_role

Vous pouvez également passer des paramètres spécifiques au rôle lorsque vous utilisez include_role. Cela permet de personnaliser davantage l’exécution d’un rôle sans modifier directement le contenu du rôle lui-même. Voici un exemple où un rôle reçoit des variables dynamiques :

---
- hosts: all
tasks:
- name: Inclure un rôle avec des paramètres personnalisés
ansible.builtin.include_role:
name: config_serveur_web
vars:
http_port: 8080
server_name: "example.com"

Dans cet exemple, le rôle config_serveur_web recevra les variables http_port et server_name, qui peuvent être utilisées à l’intérieur du rôle pour configurer un serveur web de manière flexible.

Conclusion

L’utilisation des modules d’import et d’inclusion dans Ansible, tels que import_playbook, import_role, import_tasks, include_role, include_tasks et include_vars, permet de structurer vos playbooks de manière modulaire, tout en optimisant leur maintenabilité et leur flexibilité. Chaque module offre des avantages distincts en fonction des besoins, que ce soit pour une exécution systématique avec les modules d’import ou pour une gestion plus conditionnelle et dynamique avec les modules d’inclusion.

Maîtriser ces modules vous permet de gérer des infrastructures complexes tout en maintenant une architecture claire et réutilisable. En fonction de vos besoins, vous pouvez facilement charger des tâches, des rôles, des variables, ou même des playbooks entiers en fonction des environnements et des conditions spécifiques. Cette approche modulaire garantit un déploiement fiable et adaptable dans des environnements variés, tout en minimisant la duplication du code.

Avec des outils comme include_vars et le plugin first_found, vous pouvez également rendre vos configurations encore plus dynamiques, en adaptant vos playbooks aux spécificités des systèmes d’exploitation et des environnements cibles. En somme, une bonne utilisation de ces modules permet de concevoir des playbooks puissants, maintenables et évolutifs, capables de répondre aux besoins d’infrastructures modernes.