Skip to content

Ansible

Ansible est un outil open source d'automatisation informatique développé par Red Hat. Il permet de gérer la configuration, le déploiement d'applications et l'orchestration de plusieurs machines à partir d'une machine de contrôle unique, sans avoir besoin d'installer d'agent sur les hôtes gérés.

Source : Documentation officielle

Qu'est-ce qu'Ansible ?

Ansible répond à un problème classique : maintenir un parc de serveurs dans un état cohérent et reproductible, sans exécuter manuellement les mêmes commandes machine par machine. Il repose sur quelques principes structurants :

  • Agentless : rien à installer sur les machines gérées. La communication passe par SSH (Linux/Unix) ou WinRM (Windows) ; seul Python doit être présent côté cible.
  • Déclaratif : on décrit l'état souhaité (« nginx doit être installé et démarré »), pas la suite d'étapes pour y arriver.
  • Idempotent : rejouer un playbook plusieurs fois donne toujours le même résultat. Si l'état voulu est déjà là, Ansible ne fait rien (ok, pas changed).
  • YAML : les playbooks sont écrits en YAML, un format simple à lire, même sans expérience de script.
  • Push-based : c'est la machine de contrôle qui pousse la configuration vers les hôtes, contrairement à un modèle pull (Puppet par exemple).

Cas d'usage typiques

  • Provisioning de serveurs (paquets, utilisateurs, fichiers de configuration).
  • Déploiement d'applications (mise à jour de code, redémarrage de services).
  • Orchestration multi-tiers (base de données puis API puis frontend, dans l'ordre).
  • Durcissement et conformité, appliqués de façon identique sur tout un parc.

Installation

Ansible s'installe uniquement sur la machine de contrôle (celle depuis laquelle on pilote les autres). Les machines gérées n'ont besoin que de Python et d'un accès SSH.

bash
# Debian/Ubuntu
sudo apt update
sudo apt install ansible

# Fedora/RHEL/Alma
sudo dnf install ansible

# Arch
sudo pacman -S ansible

# Via pip (dans un environnement virtuel, recommandé pour maîtriser la version)
python3 -m venv ~/.venvs/ansible
source ~/.venvs/ansible/bin/activate
pip install ansible
bash
# Vérifier l'installation et la version
ansible --version

Concepts clés

ConceptDescription
Control nodeLa machine depuis laquelle Ansible est exécuté.
Managed nodeUne machine gérée par Ansible (aussi appelée host).
InventoryLa liste des machines gérées, organisées en groupes.
ModuleUne unité de code réutilisable exécutant une action précise (apt, copy, service, user...).
TaskL'appel d'un module avec des paramètres, au sein d'un playbook.
PlaybookUn fichier YAML décrivant un ensemble de tâches à appliquer sur des hôtes.
PlayUne section d'un playbook associant un groupe d'hôtes à une liste de tâches.
RoleUne structure de répertoires standardisée pour organiser et réutiliser des tâches, variables, templates et handlers.
FactUne information collectée automatiquement sur un hôte (OS, IP, mémoire...) via le module setup.
HandlerUne tâche déclenchée uniquement par une notification (notify), typiquement pour redémarrer un service après un changement de configuration.
CollectionUn format de distribution regroupant modules, plugins, rôles et playbooks (ex. community.general, ansible.posix).

Bonnes pratiques

  • Structurer avec des rôles dès que le playbook dépasse quelques tâches. Un rôle isole une responsabilité (webserver, database, firewall) et se réutilise d'un projet à l'autre.
  • Ne jamais coder en dur ce qui peut varier (IP, chemins, versions) : utiliser des variables (group_vars/, host_vars/, defaults/main.yml).
  • Privilégier les modules déclaratifs (apt, copy, template, service...) plutôt que shell/command, non idempotents par nature et à encadrer (creates, when, changed_when) si on doit vraiment les utiliser.
  • Chiffrer les secrets avec Ansible Vault, jamais de mot de passe ou de clé en clair dans un dépôt Git.
bash
ansible-vault encrypt group_vars/prod/vault.yml
ansible-playbook site.yml --ask-vault-pass
  • Toujours tester avant de jouer en production :
    • ansible-playbook site.yml --syntax-check pour valider la syntaxe YAML.
    • ansible-playbook site.yml --check (dry-run) pour simuler sans appliquer.
    • ansible-playbook site.yml --diff pour visualiser les changements de fichiers.
    • ansible-lint pour détecter les mauvaises pratiques.
  • Limiter le blast radius avec --limit et serial pour un déploiement progressif (rolling update) plutôt que de tout jouer d'un coup sur un parc entier.
  • Versionner l'inventaire et les playbooks dans Git, comme n'importe quel code source (voir IaC).
  • Nommer explicitement chaque tâche (name:) : c'est ce qui s'affiche à l'exécution et rend les logs et les erreurs lisibles.
  • Séparer les environnements (dev/staging/prod) via des inventaires distincts plutôt qu'avec des conditions dans les playbooks.
  • Épingler les versions des collections et rôles externes (requirements.yml) pour des exécutions reproductibles.

Créer son premier playbook

Un playbook minimal se compose d'un fichier YAML décrivant un ou plusieurs plays.

Arborescence de départ recommandée :

mon-projet-ansible/
├── ansible.cfg
├── inventory.ini
└── playbook.yml

ansible.cfg (configuration locale au projet, évite de dépendre de la config globale de la machine) :

ini
[defaults]
inventory = inventory.ini
host_key_checking = False
retry_files_enabled = False

inventory.ini (ici, uniquement la machine locale pour commencer) :

ini
[local]
localhost ansible_connection=local

playbook.yml :

yaml
---
- name: Mon premier playbook
  hosts: local
  become: false
  tasks:
    - name: Afficher un message
      debug:
        msg: "Ansible fonctionne correctement !"

    - name: Créer un fichier de test
      copy:
        content: "Bonjour depuis Ansible\n"
        dest: /tmp/ansible-test.txt

Exécution :

bash
ansible-playbook playbook.yml

Points clés de ce premier exemple :

  • hosts: cible le groupe (ou l'hôte) défini dans l'inventaire.
  • tasks: liste les actions à exécuter, dans l'ordre, sur chaque hôte du groupe.
  • Chaque tâche appelle un module (debug, copy) avec ses paramètres.
  • Rejouer ce playbook une seconde fois : la tâche debug reste ok, et la tâche copy aussi si le contenu du fichier n'a pas changé c'est l'idempotence en action.

Créer un playbook de déploiement de serveur

Exemple concret et complet : déploiement d'un serveur web Nginx, structuré en rôle pour être réutilisable et maintenable.

Arborescence

mon-projet-ansible/
├── ansible.cfg
├── inventory.ini
├── site.yml
└── roles/
    └── webserver/
        ├── defaults/
        │   └── main.yml
        ├── tasks/
        │   └── main.yml
        ├── handlers/
        │   └── main.yml
        └── templates/
            └── nginx.conf.j2

roles/webserver/defaults/main.yml

yaml
webserver_package: nginx
webserver_port: 80
webserver_document_root: /var/www/mon-site

roles/webserver/tasks/main.yml

yaml
---
- name: Installer Nginx
  apt:
    name: "{{ webserver_package }}"
    state: present
    update_cache: true

- name: Créer le répertoire du site
  file:
    path: "{{ webserver_document_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: "0755"

- name: Déployer une page d'accueil basique
  copy:
    content: "<h1>Déployé avec Ansible</h1>\n"
    dest: "{{ webserver_document_root }}/index.html"

- name: Déployer la configuration Nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/mon-site.conf
    owner: root
    group: root
    mode: "0644"
  notify: Recharger Nginx

- name: Activer le site
  file:
    src: /etc/nginx/sites-available/mon-site.conf
    dest: /etc/nginx/sites-enabled/mon-site.conf
    state: link
  notify: Recharger Nginx

- name: S'assurer que le service est démarré et activé au boot
  service:
    name: nginx
    state: started
    enabled: true

roles/webserver/handlers/main.yml

yaml
---
- name: Recharger Nginx
  service:
    name: nginx
    state: reloaded

roles/webserver/templates/nginx.conf.j2

jinja
server {
    listen {{ webserver_port }};
    server_name _;
    root {{ webserver_document_root }};
    index index.html;
}

site.yml (playbook principal)

yaml
---
- name: Déployer un serveur web
  hosts: webservers
  become: true
  roles:
    - webserver

Le handler Recharger Nginx n'est déclenché que si la configuration ou le lien symbolique changent réellement s'il n'y a rien à modifier, aucun rechargement inutile n'a lieu, conformément au principe d'idempotence.

Configurations de playbook

Un play et une tâche acceptent tous les deux un ensemble de clés de configuration qui changent leur comportement (sur qui elles s'exécutent, dans quelles conditions, ce qu'elles déclenchent...). Voici les plus utilisées.

Clés au niveau d'un play

CléRôle
hostsGroupe ou hôte(s) ciblés (défini dans l'inventaire).
become / become_userÉlève les privilèges (sudo) pour exécuter les tâches, en tant qu'utilisateur donné (root par défaut).
gather_factsCollecte (ou non) les facts de l'hôte en début de play. À mettre à false si inutile, pour gagner du temps.
vars / vars_filesVariables déclarées en ligne ou importées depuis un fichier externe.
tagsÉtiquette(s) permettant de jouer ou d'exclure une partie du playbook (--tags, --skip-tags).
serialNombre (ou pourcentage) d'hôtes traités simultanément, pour un déploiement progressif.
handlersTâches déclenchées en fin de play par un notify, une seule fois même si plusieurs tâches l'ont notifié.

Clés au niveau d'une tâche

CléRôle
whenCondition d'exécution (when: ansible_facts['os_family'] == "Debian").
loopRépète la tâche pour chaque élément d'une liste.
registerStocke le résultat de la tâche dans une variable, réutilisable ensuite.
changed_when / failed_whenRedéfinit ce qui compte comme un changement ou un échec, utile avec command/shell.
notifyDéclenche un ou plusieurs handlers si la tâche modifie l'état du système.
delegate_toExécute la tâche sur un autre hôte que celui ciblé par le play (ex. mettre à jour un load-balancer).
run_onceN'exécute la tâche que sur le premier hôte du groupe, même si le play en cible plusieurs.
ignore_errorsPoursuit l'exécution du playbook même si la tâche échoue.

Exemple combiné : un rôle de provisioning

L'exemple suivant illustre ces options sur un cas concret : un rôle base, appliqué à tout le parc, qui installe Docker (avec son dépôt officiel), déploie un message du jour personnalisé et durcit la configuration de sshd. Chaque partie est isolée dans son propre fichier de tâches et taguée, pour pouvoir être jouée indépendamment.

roles/base/
├── tasks/
│   ├── main.yml
│   ├── docker.yml
│   ├── motd.yml
│   └── sshd.yml
├── templates/
│   └── 99-custom-motd.sh.j2
└── handlers/
    └── main.yml

roles/base/tasks/main.yml :

yaml
---
- name: Installer Docker
  import_tasks: docker.yml
  tags: [docker]

- name: Déployer le MOTD personnalisé
  import_tasks: motd.yml
  tags: [motd]

- name: Durcir la configuration sshd
  import_tasks: sshd.yml
  tags: [sshd]

Installer Docker en ajoutant son dépôt officiel

roles/base/tasks/docker.yml :

yaml
---
- name: Installer les prérequis
  apt:
    name:
      - ca-certificates
      - curl
      - gnupg
    state: present
    update_cache: true

- name: Créer le dossier des clés APT
  file:
    path: /etc/apt/keyrings
    state: directory
    mode: "0755"

- name: Télécharger la clé GPG officielle Docker
  get_url:
    url: "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg"
    dest: /etc/apt/keyrings/docker.asc
    mode: "0644"

- name: Ajouter le dépôt APT Docker
  apt_repository:
    repo: >-
      deb [arch={{ ansible_facts['architecture'] }} signed-by=/etc/apt/keyrings/docker.asc]
      https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}
      {{ ansible_facts['distribution_release'] }} stable
    state: present
    filename: docker

- name: Installer Docker Engine
  apt:
    name:
      - docker-ce
      - docker-ce-cli
      - containerd.io
      - docker-compose-plugin
    state: present
    update_cache: true

- name: Ajouter l'utilisateur courant au groupe docker
  user:
    name: "{{ ansible_user }}"
    groups: docker
    append: true

- name: Démarrer et activer Docker
  service:
    name: docker
    state: started
    enabled: true

Le dépôt est ajouté via apt_repository, en pointant vers la clé GPG téléchargée (signed-by), à la manière de la procédure officielle d'installation de Docker sur Debian/Ubuntu.

Déployer un message du jour (MOTD) personnalisé

Sur Debian/Ubuntu, chaque script exécutable placé dans /etc/update-motd.d/ est exécuté à la connexion SSH, dans l'ordre numérique, et sa sortie est affichée. On y dépose un script shell (.sh) rendu exécutable par Ansible.

roles/base/templates/99-custom-motd.sh.j2 :

bash
#!/bin/sh
echo ""
echo "Bienvenue sur {{ inventory_hostname }}"
echo "Système  : $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2)"
echo "Uptime   : $(uptime -p)"
echo "Géré par : Ansible - ne pas modifier manuellement"
echo ""

roles/base/tasks/motd.yml :

yaml
---
- name: Déployer le script de MOTD personnalisé
  template:
    src: 99-custom-motd.sh.j2
    dest: /etc/update-motd.d/99-custom
    owner: root
    group: root
    mode: "0755"

- name: Désactiver le MOTD statique par défaut
  copy:
    content: ""
    dest: /etc/motd

Comme le fichier est un template Jinja (.j2), est remplacé par le nom réel de chaque hôte à chaque exécution le message reste personnalisé même joué sur plusieurs machines à la fois.

Modifier la configuration de sshd

roles/base/tasks/sshd.yml :

yaml
---
- name: Désactiver la connexion SSH en root
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?PermitRootLogin'
    line: 'PermitRootLogin no'
  notify: Redémarrer sshd

- name: Désactiver l'authentification par mot de passe
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?PasswordAuthentication'
    line: 'PasswordAuthentication no'
  notify: Redémarrer sshd

- name: Limiter les tentatives d'authentification
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?MaxAuthTries'
    line: 'MaxAuthTries 3'
  notify: Redémarrer sshd

roles/base/handlers/main.yml :

yaml
---
- name: Redémarrer sshd
  service:
    name: sshd
    state: restarted
    validate: /usr/sbin/sshd -t -f %s

Attention à ne jamais désactiver PasswordAuthentication avant d'avoir vérifié qu'une authentification par clé fonctionne déjà sur l'hôte, sous peine de se retrouver hors ligne. L'option validate du handler exécute sshd -t sur le fichier avant de redémarrer le service : si la syntaxe est invalide, Ansible s'arrête et ne coupe pas l'accès SSH existant.

Jouer le rôle en ciblant une partie précise

bash
# Tout le rôle
ansible-playbook -i inventory.ini site.yml

# Uniquement Docker
ansible-playbook -i inventory.ini site.yml --tags docker

# Tout sauf le durcissement sshd (ex. lors d'un premier test)
ansible-playbook -i inventory.ini site.yml --skip-tags sshd

Déployer son playbook sur sa machine

Pour tester localement avant de viser un vrai parc, deux approches :

1. Connexion locale explicite (recommandé, l'inventaire reste cohérent avec un futur déploiement distant) :

ini
# inventory.ini
[webservers]
localhost ansible_connection=local
bash
ansible-playbook -i inventory.ini site.yml --ask-become-pass

2. Cibler localhost sans inventaire dédié, utile pour un test rapide :

bash
ansible-playbook site.yml -i localhost, --connection=local --ask-become-pass

La virgule après localhost est nécessaire : elle indique à Ansible qu'il s'agit d'une liste d'hôtes en ligne de commande, et non d'un chemin vers un fichier d'inventaire.

--ask-become-pass demande le mot de passe sudo de façon interactive. Sur une machine où l'utilisateur courant a un accès sudo sans mot de passe (NOPASSWD), ce n'est pas nécessaire.

Déployer son playbook sur plusieurs machines

Construire l'inventaire

L'inventaire regroupe les hôtes par rôle logique, ce qui permet de cibler précisément un sous-ensemble du parc.

ini
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.11

[dbservers]
db1.example.com

[production:children]
webservers
dbservers

[production:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519_prod

Un inventaire au format YAML est équivalent et souvent plus lisible pour des structures complexes :

yaml
# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
          ansible_host: 192.168.1.11
    dbservers:
      hosts:
        db1.example.com:

Préparer l'accès SSH

La machine de contrôle doit pouvoir s'authentifier en SSH sur chaque hôte, idéalement par clé.

bash
# Copier la clé publique sur chaque hôte cible
ssh-copy-id deploy@web1.example.com
ssh-copy-id deploy@web2.example.com
ssh-copy-id deploy@db1.example.com
bash
# Vérifier que tous les hôtes répondent avant de jouer un playbook
ansible all -i inventory.ini -m ping

Une réponse pong de chaque hôte confirme la connectivité SSH et la présence de Python sur la cible.

Jouer le playbook sur le parc

bash
# Sur l'ensemble de l'inventaire
ansible-playbook -i inventory.ini site.yml

# Uniquement sur le groupe "webservers"
ansible-playbook -i inventory.ini site.yml --limit webservers

# Sur un hôte précis
ansible-playbook -i inventory.ini site.yml --limit web1.example.com

Variables par groupe et par hôte

Plutôt que de multiplier les conditions dans les playbooks, on externalise les variables spécifiques à un groupe ou à un hôte :

mon-projet-ansible/
├── group_vars/
│   ├── webservers.yml
│   └── dbservers.yml
├── host_vars/
│   └── web2.example.com.yml
yaml
# group_vars/webservers.yml
webserver_port: 8080

Ansible fusionne automatiquement ces variables selon leur portée (defaults du rôle < group_vars < host_vars < variables passées en ligne de commande), sans modification du playbook lui-même.

Déploiement progressif (rolling update)

Pour éviter d'appliquer un changement sur tout le parc en une seule fois (et donc de propager une éventuelle erreur partout), on utilise serial :

yaml
---
- name: Déployer un serveur web (rolling update)
  hosts: webservers
  become: true
  serial: 1          # une machine à la fois
  # serial: "30%"     # ou un pourcentage du groupe
  roles:
    - webserver

Combiné à des tâches de vérification (uri, wait_for) après chaque batch, cela permet de stopper le déploiement dès qu'un hôte échoue, avant d'affecter le reste du parc.

Parallélisme

Par défaut, Ansible traite 5 hôtes en parallèle (forks). Sur un grand parc, augmenter cette valeur accélère nettement l'exécution :

bash
ansible-playbook -i inventory.ini site.yml --forks 20

ou dans ansible.cfg :

ini
[defaults]
forks = 20

Aller plus loin

Publié sous lience MIT.