Ansible est un outil en ligne de commande (CLI), développé par Red Hat, qui permet d'automatiser la configuration de serveurs selon le principe de l'Infrastructure as Code (IaC).
Il se connecte généralement aux machines à configurer via SSH, puis exécute différentes tâches à l'aide de modules Ansible, qui s'appuient notamment sur Python côté machine distante. Les tâches à effectuer sont décrites dans des fichiers de configuration, comme des playbooks YAML, qui définissent l'état souhaité des machines.

 

Plongeons ensemble dedans !

 

1. Pourquoi Ansible ?

Faire son premier serveur, c'est chouette. On le bichonne.

Son deuxième ? C'est chouette aussi, on le bichonne.

Le troisième ? C'est toujours chouette… on le bichonne encore…

ZzzzzZZZZ…

Au bout d'un moment, ça devient répétitif. Et quand on arrive au dixième serveur, il faut arrêter de les traiter comme des pièces uniques et commencer à penser autrement.

C'est d'ailleurs pour ça que, dans la communauté DevOps, on entend souvent cet adage :

"Traitez vos serveurs comme du bétail, pas comme des animaux de compagnie." 

C'est un peu violent comme phrase, je te l'accorde. Mais l'infrastructure, ce n'est pas fait pour être cajolée. Un serveur ne devrait pas être irremplaçable, ni nécessiter des heures de manipulations manuelles pour retrouver une configuration fonctionnelle.

J'adore faire de l'infra. Mais quand un serveur tombe en rade, c'est là que le stress monte. Il faut comprendre ce qui s'est passé, réparer, parfois tout reconstruire… et espérer ne rien oublier au passage.

Et si on pouvait reconstruire un serveur sans repartir de zéro à la main ?

C'est justement là qu'Ansible entre en jeu.

En décrivant la configuration de nos serveurs dans des fichiers, on peut automatiser leur installation et leur configuration. On sait ce qui doit être installé, quels services doivent tourner et quels fichiers doivent être présents.

Résultat : moins de manipulations manuelles, moins d'oublis et surtout, la possibilité de reconstruire un serveur de manière reproductible.

Ansible ne supprime pas les pannes. Mais il permet de mieux s'y préparer, et de passer moins de temps à tout reconstruire dans l'urgence.

2. L'inventaire et la connexion

2.1. L'inventaire

Pour qu'Ansible puisse se connecter à nos serveurs, il lui faut un inventaire de ceux-ci.

Pour ça, on crée un dossier (pour moi ce sera ~/lab) et un fichier : lab/inventory.yml :

web:
  hosts:
    web01:
      ansible_host: ipduserveur
      ansible_user: root
      ansible_port: 22
      ansible_ssh_private_key_file: ~/.ssh/id_ansible
      ansible_ssh_common_args: "-o StrictHostKeyChecking=no"

Voici un exemple de notre inventaire.

  • web c'est le nom du groupe pour regrouper plusieurs hosts.
  • hosts c'est pour indiquer qu'on va ajouter des machines
  • web01 c'est le nom d'une machine

Bien évidemment, web et web01 sont des noms arbitraires et on peut totalement les changer. On verra juste après à quoi ils servent.

ansible_* sont des variables qu'Ansible va utiliser quand on voudra se connecter.

Pour le tuto, j'ai ajouté StrictHostKeyChecking=no. C'est pratique sur un serveur jetable ou qu'on vient d'acheter : ça évite qu'Ansible reste bloqué sur une erreur de vérification (car l'hôte n'est pas encore connu). Mais c'est à enlever pour les playbooks qui n'initialisent pas les serveurs !

On peut bien entendu ajouter des machines à la suite. Il faut juste respecter l'indentation car on est sur un yaml.

Quelques commandes intéressantes pour vérifier qu'on a bien notre machine dans la liste : ansible all -i inventory.yml --list-hosts.

Liste des hosts du inventory.yml

On peut aussi filtrer par groupe : ansible web -i inventory.yml --list-hosts.

Décortiquons la commande :

  • ansible appelle le tool
  • web / all appelle le groupe ou l'intégralité des groupes
  • -i inventory.yml indique l'inventaire à utiliser pour cette commande
  • --list-hosts permet d'avoir la liste des machines

2.2. La connexion

Pour vérifier qu'Ansible a accès à notre serveur, on a à disposition un module ping :

ansible web -i inventory.yml -m ping

-m permet d'appeler un module suivi de son nom

ping pongSi tout se passe bien, la machine répond par un "pong".

3. Les facts

Ansible peut collecter des informations liées à la machine que l'on peut consulter à l'aide du module setup : ansible web -i inventory.yml -m setup.

le module setup

Attention ... cette commande a tendance à rendre un pavé de résultats. En fait Ansible va récupérer tout un tas d'infos liées à notre machine et on va pouvoir plus tard les utiliser.

Avec filter, on peut réduire drastiquement la liste : ansible web -i inventory.yml -m setup -a "filter=ansible_hostname"

setup filter

 

4. Les playbooks

Bien, maintenant que l'on a vu qu'Ansible pouvait se connecter à des machines via l'inventaire et récupérer automatiquement des infos via les facts, on va pouvoir commencer à automatiser un peu les tâches !

On a jusque-là uniquement fait des commandes unitaires. Mais le but c'est pas de taper des commandes pour appeler des modules à la mano. Nous on veut au-to-ma-ti-ser !

4.1. Mon premier playbook

Un playbook c'est un fichier avec un ensemble de tâches. Voyons un peu un exemple dans ~/lab/playbook.yml :

---
- name: Tuto Playbook
  hosts: all
  tasks:
    - name: Affichage d'un message
      ansible.builtin.debug:
        msg: Mon premier message en console

Décomposons le fichier :

  • --- Initialisation du playbook
  • name nom du play (ensemble de machines et de tâches)
  • hosts on cible le type d'hosts (ici all)
  • tasks ce sont les tâches qu'on lançait juste avant en commande.

Ansible propose des modules comme ici : ansible.builtin.debug qui prend en paramètre un msg .

Dans tasks, on peut avoir une ou plusieurs task qui se composent toujours de la même manière :

  • name qui est le nom de la tâche
  • ansible.builtin.debug qui est le nom du module et sa catégorie (ici ansible, module debug)
  • des paramètres pour le module

Pour le lancer : ansible-playbook -i inventory.yml playbook.yml .

La majorité des erreurs que j'ai rencontrées étaient des erreurs dues à l'indentation. Le yaml c'est vraiment un langage structuré, parfois je cherchais ailleurs alors que c'était juste une tabulation en trop ou absente.

Le retour de la commande se compose de plusieurs étapes :
Lancement du premier playbook

Notre playbook a bien lancé le play "Tuto Playbook".

On voit qu'il a lancé deux tâches. Mais c'est étrange car dans notre play, on en a déclaré qu'une seule. Il faut savoir qu'à chaque fois qu'on va lancer notre commande de playbook, Ansible va de lui-même appeler en début de play, l'équivalent de la commande ansible all -i inventory.yml -m setup pour y récupérer nos facts que l'on a vus juste avant.

Mais à quoi ça peut servir ? Pour pouvoir les avoir à dispo dans nos tasks !

Voici un exemple pour récupérer le nom de la machine : 

---
- name: Tuto Playbook
	hosts: all
	tasks:
		- name: Affichage d'un message
			ansible.builtin.debug:
				msg: Mon premier message en console
				
		- name: Affichage de la date
			ansible.builtin.debug:
				var: ansible_facts['date_time'].date

En utilisant var à la place de msg, j'affiche directement une variable récupérée depuis les facts.

Affichage de la date

ansible_facts est l'intégralité des facts, on réduit à la date_time puis à date. Si je n'utilise pas la date :

Affichage de l'objet

Là on a juste affiché finalement la propriété d'un objet récupéré depuis les facts. Mais on peut aussi enregistrer des variables récupérées à la volée :

---
- name: Tuto Playbook
  hosts: all
  tasks:
    - name: Affichage d'un message
      ansible.builtin.debug:
        msg: Mon premier message en console

    - name: Collecte de la date en commande
      ansible.builtin.command: date
      register: result_date  

    - name: Affichage de la date 
      ansible.builtin.debug:
        var: result_date.stdout

Grâce à register on envoie le résultat de la commande dans la variable result_date puis on affiche la sortie avec le debug et stdout.

4.2. Les Statuts

Parfois en fonction de l'exécution du playbook, la sortie dans le terminal peut afficher différents statuts à différents endroits. Pour les statuts les plus communs :

  • ok tout s'est bien passé, rien à changer dans l'état
  • changed tout s'est bien passé mais il est possible que l'état d'un fichier ait été modifié
  • unreachable en général c'est l'hôte qui ne répond pas
  • failed c'est un échec, une commande qui n'existe pas, une mauvaise indentation, etc.
  • skipped c'est une tâche qui a été ignorée (on le verra plus tard)

On va s'attarder sur changed car il est un peu tricky. Quand Ansible effectue une tâche, il va indiquer à la fin son statut. En fonction de la tâche effectuée, Ansible peut indiquer que l'état a provoqué un changement sur l'hôte même si ce n'est pas le cas.

En effet, quand on utilise ce module : ansible.builtin.command: date en soi, rien n'a été modifié sur l'hôte, mais Ansible ne regarde pas la commande en elle-même, il détecte juste le module command.  Par sécurité, il indique changed.

C'est pour cela que lors de l'exécution du playbook précédent, j'avais eu un changed=1.

Statut changed

Ça peut être problématique au niveau de la lecture quand on a beaucoup de tâches car ça peut brouiller les vraies tâches qui auraient provoqué un changement. 

Heureusement, on peut indiquer si une tâche peut rester sans changement avec changed_when.

    - name: Collecte de la date en commande
      ansible.builtin.command: date
      register: result_date
      changed_when: false

De cette manière, si on relance, aucun changement n'a été détecté sur le système :

Aucun changement sur le système

Attention cependant avec cette méthode, car si la commande est vraiment susceptible de faire un changement sur le serveur, alors on ne sera pas au courant.

4.3. Les variables

On vient de voir avec register qu'on pouvait stocker des variables.

Mais on peut aller plus loin que ça avec. On peut déclarer une variable directement dans le play.

---
- name: Premier Playbook
  hosts: all
  vars: 
    server_name: web01

 

Ici on a initialisé notre variable server_name. Et on peut les appeler par interpolation :

---
- name: Premier Playbook
  hosts: all

  vars: 
    server_name: web01

  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        msg: "Hello world ! [{{ server_name }}]"

Et comme vu plus haut, on peut les appeler directement via var pour la commande debug :

  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        var: server_name

On peut donc déclarer à la volée ou à l'initialisation.

Dans un playbook parfois ce n'est pas pratique, on peut aussi les déclarer dans un fichier. Je vais créer un ~/lab/vars.yml :

---
server_name: "web01"

 

J'appelle ensuite ce fichier dans mon playbook : 

---
- name: Premier Playbook
  hosts: all

  var_files: 
    - vars.yml

  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        msg: "Hello world ! [{{ server_name }}]"

 

On verra plus tard qu'on peut aussi trier les variables par groups (web) ou par hosts (web01).

4.4. Les conditions

Ansible nous permet de rendre des tâches conditionnelles en fonction d'opérateurs (comme en dev).

---
- name: Premier Playbook
  hosts: all

  vars: 
    server_name: web01

  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        msg: "Hello world ! [{{ server_name }}]"
      when: ansible_facts["distribution"] == "Debian"

De cette façon, si le when ne passe pas à true, la tâche est ignorée et le statut de la tâche passe à SKIPPED.

On trouve toutes sortes d'opérateurs :

Opérateurs Signification
== Est égal à
!= Est différent de
and ET : les deux conditions doivent être vraies
or OU : au moins une condition doit être vraie
> Supérieur à
< Inférieur à
>= Supérieur ou égal à
<= Inférieur ou égal à

 

4.5. Les boucles

On peut ajouter une boucle à une tâche :

- name: "Que la force soit avec toi"
  ansible.builtin.debug:
    msg: "Hello {{ item }}"
  loop:
    - Obiwan
    - Anakin
    - Quigon

item correspond à chaque entrée de la boucle.

- name: "Que la force soit avec toi"
  ansible.builtin.debug:
    msg: "Hello {{ item }}"
  loop:
    - Obiwan
    - Anakin
    - Quigon
  when: item == Anakin

Et en combinant tout ce qu'on a vu, on peut remplacer les entrées de la loop avec un var : 

- name: Premier Playbook
  hosts: all
  vars: 
    server_name:
	  - Obiwan
    - Anakin
    - Quigon
  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        msg: "Hello world ! [{{ server_name }}]"
      when: ansible_facts["distribution"] == "Debian"

    - name: "Que la force soit avec toi"
      ansible.builtin.debug:
        msg: "Hello {{ item }}"
      loop: "{{ server_name }}"

 

4.6. Les handlers

Les handlers sont des actions qui sont effectuées à la fin du play. Prenons un exemple : 

- name: Premier Playbook
  hosts: all
  tasks:
    - name: "Affichage d'un premier message"
      ansible.builtin.debug:
        msg: "Hello city !"
    - name: "Affichage d'un second message"
      ansible.builtin.debug:
        msg: "Hello country !"
     - name: "Affichage d'un troisième message"
      ansible.builtin.debug:
        msg: "Hello world !"

Ici on a donc 3 messages d'affilée.

J'aimerais qu'à la fin du second message, s'il y a eu un changement, cela déclenche un message. Pour ça on va ajouter un handler :

---
- name: Premier Playbook
  hosts: all
  tasks:
    - name: "Affichage d'un premier message"
      ansible.builtin.debug:
        msg: "Hello city !"
    - name: "Affichage d'un second message"
      ansible.builtin.debug:
        msg: "Hello country !"
      changed_when: true
      notify: "Trigger second message"
    - name: "Affichage d'un troisième message"
      ansible.builtin.debug:
        msg: "Hello world !"
        
  handlers:
    - name: "Trigger second message"
      ansible.builtin.debug:
        msg: "Le second message a trigger ce handler"

Si une tâche est censée déclencher une action, alors on va mettre un paramètre notify qui va prendre en valeur le nom du handler.

Dans l'exemple on utilise ansible.builtin.debug donc par défaut il ne déclenche pas de changement, on force ici le changement d'état avec changed_when: true.

Ce qui aura pour action à la fin du play, de déclencher le handler correspondant.

Handlers Ansible

Bien sûr ici on a un exemple simple mais voici ce qui se passe si on utilise nginx :

---
- name: Configurer Nginx
  hosts: web
  tasks:
    - name: Déployer la configuration Nginx
      ansible.builtin.copy:
        src: ./nginx.conf
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Redémarrer Nginx

  handlers:
    - name: Redémarrer Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

J'ai fait un changement dans nginx (copie de fichiers) et il faut redémarrer nginx pour prendre en compte les changements. Pour ça on voit bien l'intérêt du handler.

Récap d'un playbook

4.7. Actions sur les fichiers et dossiers

On va voir à présent comment gérer les fichiers et les dossiers et ça tombe bien car l'exemple du dessus a entamé tout ça.

---
- name: Configurer Nginx
  hosts: web
  tasks:
	  - name: Création d'un dossier
		  ansible.builtin.file:
			  state: directory
			  path: /root/test-ansible
			  owner: root
			  group: root
			  mode: '0755'

Ici on dit : le path ciblé doit être un dossier (directory). S'il n'existe pas, crée-le.

Ensuite il regarde les permissions. Si le dossier existe, alors il ne vérifiera que les permissions.

Bien entendu on peut faire beaucoup d'opérations avec le module ansible.builtin.file. Je te laisse aller voir la doc.

Dans le point juste au-dessus on a vu brièvement comment copier : 

---
- name: Configurer Nginx
  hosts: web
  tasks:
    - name: Déployer la configuration Nginx
      ansible.builtin.copy:
        src: ./nginx.conf
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Redémarrer Nginx

  handlers:
    - name: Redémarrer Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Ici src est la machine qui exécute la commande Ansible, pas l'hôte. Mais si je rajoute remote_src: true alors src n'est plus la machine qui contrôle Ansible mais la machine contrôlée (host) :

    - name: Déployer la configuration Nginx
      ansible.builtin.copy:
        src: /etc/nginx/nginx.bkp
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        remote_src: true
      notify: Redémarrer Nginx

 

4.8. Les services

Imaginons que je souhaite redémarrer le service ssh.

Sur une approche "classique" j'aurais fait : systemctl restart sshd.service.

Avec Ansible, on utilise le module ansible.builtin.service :

- name: Redémarrer SSH
  ansible.builtin.service:
    name: ssh
    state: restarted

On peut aussi pour un service que l'on vient d'installer comme nginx l'activer au démarrage :

- name: Redémarrer Nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

De cette manière on a fait l'équivalent de : systemctl enable --now nginx.service .

Pour le state on a plusieurs paramètres : started , stopped , restarted , reloaded qui ont les mêmes fonctions que leurs commandes respectives sur les serveurs.

Si un service est déjà allumé, et qu'on demande de le started , Ansible agit de manière idempotente : la tâche passe en ok, rien n'est modifié.

4.9. Installation de paquets

Un peu le même principe que la gestion des services mais avec un module différent : 

- name: Installer Nginx
  ansible.builtin.apt:
    name: nginx
    state: present

state permet de dire ce que l'on veut, ici vérifier que nginx est installé. S'il ne l'est pas, il l'installe.

state peut aussi prendre comme valeur absent pour vérifier qu'un paquet n'est pas présent (le désinstaller s'il est présent).

Avant d'installer des paquets, le mieux sur une machine c'est d'update sa liste des paquets avec un sudo apt update .

Avec Ansible tout pareil :

- name: Actualiser la liste des paquets
  ansible.builtin.apt:
    update_cache: true

Et si je veux sudo apt update && apt upgrade ?

- name: Actualiser la liste des paquets et mettre à jour
  ansible.builtin.apt:
    update_cache: true
	  upgrade: true

 

Pour les exemples, je suis en root. Ce n'est pas une bonne pratique, on verra par la suite que sudo se gère avec la propriété become un peu plus bas pour une meilleure gestion des privilèges.

4.10. Les utilisateurs et groupes

Étant donné qu'on peut gérer l'ensemble du serveur via des fichiers, on va voir maintenant pour les users.

Si je veux ajouter un user, sur un serveur je fais : sudo adduser obiwan .

En playbook c'est ça :

- name: Créer un utilisateur
  ansible.builtin.user:
    name: obiwan
    state: present

Tu remarques qu'on utilise souvent state , c'est le même principe, on vérifie qu'il existe, sinon on le crée. Si on met state: absent ça supprimera le user.

Si je veux créer le répertoire (pour un user qui n'est pas un user de service) :

- name: Créer un utilisateur
  ansible.builtin.user:
    name: obiwan
    state: present
    create_home: true

Si je veux ajouter un groupe à un user c'est très simple aussi : 

- name: Promotion d'un user
  ansible.builtin.user:
    name: anakin
    state: present
    groups: chevalier
    append: true

On ajoute un groupe avec groups suivi de son nom. Mais à quoi sert append ?

Si anakin était dans le groupe maître sans le append alors il aurait perdu son rang de maître au profit de chevalier. (Bon en réalité Anakin n'a jamais été promu au rang de maître !)

Tout ça n'est possible que si les groupes sont déjà existants, sinon il faut les créer en avance :

- name: Créer le groupe des chevaliers jedi
  ansible.builtin.group:
    name: chevalier
    state: present
    
- name: Promotion d'un user
   ansible.builtin.user:
     name: anakin
     state: present
     groups: chevalier
     append: true

 

4.11. Become or not become

Comme sur un serveur directement, certaines tâches nécessitent de passer sudo pour ce faire. Comme pour créer un utilisateur, il faut être sudo.

Jusque là je n'avais jamais parlé de droits et privilèges. C'est le moment. 

Si on regarde le tuto, on voit bien qu'on exécute certaines actions qui, si elles avaient été faites sur le serveur, auraient nécessité sudo :

  • ansible.builtin.apt
  • ansible.builtin.group
  • ansible.builtin.user
  • ansible.builtin.service

En général ces actions demandent du sudo. Pour ça, on va utiliser un paramètre : become .

Reprenons notre installation de nginx :

- name: Installer Nginx
  ansible.builtin.apt:
    name: nginx
    state: present

Si Ansible se connecte avec un user qui n'a pas les privilèges nécessaires alors il aura un échec de permission pour exécuter ce play. Pour ça on va ajouter une élévation temporaire de privilèges : 

- name: Installer Nginx
  ansible.builtin.apt:
    name: nginx
    state: present
    become: true

Grâce à ça, plus d'erreurs. Par contre … il faut bien entendu qu'Ansible dispose d'un user capable de sudo.

Ici j'ai placé become au niveau de la tâche, mais on peut aussi placer become au niveau du play en entier :

---
- name: Configurer Nginx
  hosts: web
  become: true

 

Si sudo demande un mot de passe, il le demandera également pour Ansible.

Pour ça on peut ajouter l'argument -K à la commande : ansible-playbook -i inventory.yml playbook.yml -K .

On peut aussi le passer en variable via : ansible_become_password mais ce n'est pas une très bonne pratique de laisser des mots de passe Linux traîner dans des fichiers !!

Petit retex à propos du become à partir d'Ubuntu 25.10... Durant l'écriture de cet article, j'ai utilisé un Ubuntu et impossible de passer sudo depuis Ansible. J'avais une erreur qui m'a fait littéralement rager devant l'ordi :

FAILED! => {"msg": "Timeout (12s) waiting for privilege escalation prompt: "}

En fait, cette erreur, si elle t'arrive, est due à sudo qui est passé depuis Ubuntu 25.10 à sudo-rs écrit en Rust. (Ce changement vise à renforcer la sécurité : le sudo historique a d'ailleurs fait l'objet en 2025 d'une CVE critique, la CVE-2025-32463.) Et c'est cette subtilité qui m'a fait rager. (Coucou @Cédric)

Alors voici la solution à cette galère.

On vérifie sur le serveur la version de sudo : /usr/bin/sudo --version. Si la sortie est sudo-rs, alors c'est bien la version écrite en Rust et on est bien sur ce problème.

Ajoute simplement sur l'host dans le inventory.yml la variable ansible_become_exe: sudo.ws pour indiquer à Ansible d'utiliser le sudo classique (écrit en C) plutôt que sudo-rs quand become est appelé. Les versions récentes d'ansible-core gèrent désormais sudo-rs nativement.

web:
  hosts:
    web01:
      ansible_host: ipduserveur
      ansible_user: root
      ansible_port: 22
      ansible_ssh_private_key_file: ~/.ssh/id_ansible
      ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
      ansible_become_exe: sudo.ws

 

5. Les tags

Les tags nous permettent de mieux organiser notre playbook et pouvoir aussi exécuter uniquement certaines tâches plutôt que l'ensemble :

- name: Premier Playbook
  hosts: all
  vars: 
    server_name:
	  - obiwan
      - anakin
      - quigon
  tasks:
    - name: "Affichage d'un message"
      ansible.builtin.debug:
        msg: "Hello world ! [{{ server_name }}]"
      when: ansible_facts["distribution"] == "Debian"
      tags: world

    - name: "On s'amuse !!!"
      ansible.builtin.debug:
        msg: "Hello {{ item }}"
      loop: "{{ server_name }}"
      tags: persons

Ici on a ajouté deux tags : world et persons.

Si je veux exécuter le play uniquement avec le tag world : ansible-playbook -i inventory.yml playbook.yml --tags world .

Ça me permet d'avoir un fichier playbook avec des tâches généralistes, et pouvoir en exécuter certaines plutôt que d'autres sans réécrire le fichier.

6. Les rôles

On le voit avec les tags, c'est bien quand le play en contient une vingtaine.

Quand on commence à voir grossir le fichier, il est temps de le ranger. Un peu comme dans du code, on va refactoriser tout ça :

Si mon playbook installe nginx et apache2 :

---
- name: Installation et configuration de Nginx et Apache2
  hosts: web
  become: true

  tasks:
    - name: Installer Nginx et Apache2
      ansible.builtin.apt:
        name:
          - nginx
          - apache2
        state: present
        update_cache: true

    - name: Copier la configuration nginx.conf
      ansible.builtin.copy:
        src: files/nginx.conf
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        validate: 'nginx -t -c %s'
      notify: Restart Nginx

    - name: Activer et démarrer Nginx
      ansible.builtin.systemd:
        name: nginx
        enabled: true
        state: started

    - name: Activer et démarrer Apache2
      ansible.builtin.systemd:
        name: apache2
        enabled: true
        state: started

  handlers:
    - name: Restart Nginx
      ansible.builtin.systemd:
        name: nginx
        state: restarted

 

On voit qu'on a 5 tâches, on sent que c'est facile d'avoir un playbook.yml à rallonge très (trop) facilement si on n'a pas de rigueur. 

6.1 La galaxy

Pour ça, Ansible peut nous aider avec cette commande : ansible-galaxy init web-server.

Cette commande nous permet d'avoir un dossier préconfiguré pour y ranger tout ce qui concerne le sujet de notre choix (ici nginx et apache2).

Je vais placer mes tâches de mon playbook dans web-server/tasks/main.yml :

---
- name: Installer Nginx et Apache2
  ansible.builtin.apt:
    name:
      - nginx
      - apache2
    state: present
    update_cache: true

- name: Copier la configuration nginx.conf
  ansible.builtin.copy:
    src: files/nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    validate: 'nginx -t -c %s'
  notify: Restart Nginx

- name: Activer et démarrer Nginx
  ansible.builtin.systemd:
    name: nginx
    enabled: true
    state: started

- name: Activer et démarrer Apache2
  ansible.builtin.systemd:
    name: apache2
    enabled: true
    state: started

Et le handler il est où ? Dans le fichier web-server/handlers/main.yml :

---
- name: Restart Nginx
  ansible.builtin.systemd:
    name: nginx
    state: restarted

Tu auras remarqué que dans les fichiers, on ne précise plus tasks: ou handlers: car les fichiers les rendent implicites.

Mais du coup, mon playbook comment il sait quelle tâche lancer ?

Chaque dossier représente un rôle, on va l'inclure dans le playbook :

---
- name: Installation et configuration de Nginx et Apache2
  hosts: web
  roles:
	  - web-server

Et la commande pour lancer reste la même. Un playbook plus court, plus simple à lire.

Pour encore organiser et gagner plus de place, import_tasks nous permet de conserver la propagation des tags lors du lancement du playbook et ainsi gagner aussi de la place dans le main.yml:

- name: Importer les tâches Nginx
  ansible.builtin.import_tasks: tasks/nginx.yml

- name: Importer les tâches Apache
  ansible.builtin.import_tasks: tasks/apache.yml

 

ansible-galaxy init nous crée beaucoup de dossiers, mais si on n'en utilise que quelques-uns, libre à nous de supprimer les dossiers vides.

6.2 Les variables et la priorité

La gestion des variables dans Ansible est un peu plus complexe, car toutes les variables ne possèdent pas la même priorité.

Dans un rôle, le répertoire defaults/ permet de définir des valeurs par défaut que l'utilisateur pourra facilement personnaliser. C'est l'endroit idéal pour exposer les paramètres configurables du rôle, comme le port d'écoute de Nginx ou le répertoire racine du site web.

À l'inverse, le répertoire vars/ permet de définir des variables dont la priorité est supérieure. On y placera plutôt des variables internes au rôle, que l'on souhaite moins facilement remplacer.

Par exemple, une variable définie dans roles/web-server/vars/main.yml aura la priorité sur une variable portant le même nom définie dans roles/web-server/defaults/main.yml.

On peut rapprocher ce fonctionnement de celui des fichiers .env et .env.local, dans lesquels certaines valeurs peuvent être remplacées par d'autres. Il faut néanmoins garder à l'esprit qu'Ansible possède son propre mécanisme de priorité, plus complexe.

Privilégie defaults/main.yml pour les paramètres que l'utilisateur doit pouvoir personnaliser facilement et réserve vars/main.yml aux variables internes qui nécessitent une priorité plus élevée.

 7. Les collections

Avec ansible.builtin.xxx on se servait de ce qu'Ansible connaissait comme modules. Mais on peut aussi s'aider des modules que la communauté nous propose.

Par exemple moi pour mes serveurs, j'aime bien utiliser ufw comme surcouche de pare-feu.

Mais Ansible ne propose pas nativement cette gestion. Eh bien la communauté d'Ansible a résolu le problème en créant ce module. Je ne me gêne pas pour l'utiliser !

Pour ça il faut d'abord installer la collection qui possède ufw. On va la chercher : ansible-doc -l | grep ufw :

ansible-doc

On installe la collection community.general à l'aide de cette commande :

ansible-galaxy collection install community.general

Ensuite je peux créer un play utilisant ufw :

---
- name: Configurer le pare-feu
  hosts: web
  become: true

  tasks:
    - name: Installation de UFW
      ansible.builtin.apt:
        name: ufw
        state: present

    - name: Police de sécurité entrante générale
      community.general.ufw:
        direction: incoming
        default: deny

    - name: Police de sécurité sortante générale
      community.general.ufw:
        direction: outgoing
        default: allow

    - name: Activer UFW
      community.general.ufw:
        state: enabled

 

On en a terminé pour ce tour d'horizon d'Ansible. Je n'ai fait que survoler les possibilités dingues de ce tool. Je ne saurais trop te conseiller d'aller éplucher la doc, c'est une vraie pépite !

Et surtout souviens-toi... treat your servers like cattle, not pets. 

treat yours servers like cattle, not like pet
LinkedIn