Retour au cours

infra / linux-bash

Automatisation avec Ansible (niveau expert)

Leçon 261 exercice

Explication

Ce que vous allez apprendre

  • Comprendre pourquoi gérer un parc de serveurs à la main en SSH ne passe pas à l'échelle
  • Distinguer l'approche déclarative d'Ansible de l'approche impérative d'un script bash
  • Comprendre le fonctionnement "agentless" d'Ansible, basé uniquement sur SSH
  • Organiser un inventaire de serveurs par groupes de rôles ([web], [db])
  • Toujours valider un playbook avec --check --diff avant de le lancer en production

Dans quel contexte ?

Tu dois installer une mise à jour de sécurité nginx sur 12 serveurs web avant ce soir. Te connecter un par un en SSH pour taper la même commande douze fois prend du temps et risque l'oubli d'un serveur — c'est exactement le problème qu'Ansible résout.

D'abord, le problème très concret qu'Ansible résout

Gérer dix serveurs en se connectant un par un en SSH pour taper les mêmes commandes ne passe pas à l'échelle : un serveur oublié, une commande tapée différemment, et le parc devient incohérent. Ansible décrit une seule fois l'état voulu et l'applique identiquement sur tous les serveurs concernés.

Ensuite, il faut comprendre une différence fondamentale avec un script bash

Un script bash décrit une suite d'étapes ("installe ceci, puis fais cela"). Un playbook Ansible décrit un état final voulu ("ce paquet doit être présent", "ce service doit être actif") : Ansible compare l'état actuel à l'état voulu et n'agit que si nécessaire, ce qui le rend idempotent sans effort particulier.

ApprocheExempleSi l'état voulu existe déjà
Script bash (impératif)apt install nginxRéinstalle ou ne fait rien selon le paquet
Playbook Ansible (déclaratif)apt: name=nginx state=presentNe touche à rien, constat "déjà conforme"

Une fois ce principe posé, comment Ansible se connecte-t-il aux machines

Contrairement à d'autres outils qui nécessitent d'installer un agent permanent sur chaque serveur géré, Ansible se contente d'une connexion SSH classique déjà en place. C'est ce qui rend le déploiement initial très simple : si ssh web1.exemple.com fonctionne déjà, Ansible fonctionne.

Il reste une question d'organisation : comment cibler un groupe précis de machines

Un fichier inventory.ini regroupe les serveurs par rôle, comme [web] et [db], ce qui permet une commande comme ansible web -i inventory.ini -a "systemctl status nginx" sans lister chaque machine individuellement.

Prérequis

Ansible suppose que ssh utilisateur@serveur fonctionne déjà sans mot de passe interactif (clé SSH en place). Si ce n'est pas le cas, règle d'abord l'accès SSH avant de toucher à Ansible.

Maintenant, regardons un playbook réel

Une tâche comme apt: name: ["python3-venv", "nginx"] state: present installe un paquet seulement s'il n'est pas déjà présent. La directive notify: redémarrer mon-app déclenche un handler uniquement si la tâche a réellement changé quelque chose, évitant un redémarrage inutile du service.

Le piège à connaître avant de déployer pour de vrai

Lancer un playbook directement en production sans --check --diff d'abord revient à sauter l'étape de vérification qu'un dry-run t'offrait pourtant gratuitement. ansible-playbook deploy.yml --check --diff montre exactement ce qui va changer, sans rien modifier. Ansible s'appuie sur tout ce que tu as appris jusqu'ici — SSH, systemd, gestion de paquets — pour l'orchestrer à grande échelle ; la prochaine leçon retourne au niveau le plus bas, celui du noyau, en montrant comment Docker isole un processus sans qu'aucune magie n'entre en jeu.

Commandes & code

Automatisation avec Ansible

ini
# inventory.ini : décrit les machines cibles, regroupées logiquement
[web]
web1.exemple.com
web2.exemple.com ansible_port=2222

[db]
db1.exemple.com ansible_user=admin

[prod:children]
web
db

[prod:vars]
ansible_ssh_private_key_file=~/.ssh/cle_prod
bash
# Vérifier la connectivité avant tout playbook
ansible prod -i inventory.ini -m ping

# Exécuter une commande ad-hoc sur un groupe sans écrire de playbook
ansible web -i inventory.ini -a "systemctl status nginx" --become
ansible prod -i inventory.ini -m shell -a "df -h" --become

# Lancer un playbook complet
ansible-playbook -i inventory.ini deploy.yml
ansible-playbook -i inventory.ini deploy.yml --check          # dry-run, ne modifie rien
ansible-playbook -i inventory.ini deploy.yml --diff             # montre les changements de fichiers
ansible-playbook -i inventory.ini deploy.yml --limit web1         # limite à une seule machine
ansible-playbook -i inventory.ini deploy.yml -e "version=1.4.2"     # variable passée en ligne de commande
yaml
# deploy.yml : playbook idempotent de déploiement d'une API
---
- name: Déployer l'API backend
  hosts: web
  become: true
  vars:
    app_version: "{{ version | default('latest') }}"
    app_dir: /opt/mon-app

  tasks:
    - name: Installer les dépendances système
      apt:
        name: ["python3-venv", "nginx"]
        state: present
        update_cache: true

    - name: Créer l'utilisateur applicatif
      user:
        name: deploy
        system: true
        shell: /usr/sbin/nologin

    - name: Déployer l'archive de la release
      unarchive:
        src: "https://registry.exemple.com/mon-app/{{ app_version }}.tar.gz"
        dest: "{{ app_dir }}"
        remote_src: true
      notify: redémarrer mon-app

    - name: Déployer le fichier de service systemd
      template:
        src: templates/mon-app.service.j2
        dest: /etc/systemd/system/mon-app.service
        mode: "0644"
      notify:
        - recharger systemd
        - redémarrer mon-app

    - name: Vérifier que le service est actif
      systemd:
        name: mon-app
        enabled: true
        state: started

  handlers:
    - name: recharger systemd
      systemd:
        daemon_reload: true

    - name: redémarrer mon-app
      systemd:
        name: mon-app
        state: restarted
yaml
# templates/mon-app.service.j2 : template Jinja2, {{ }} injecte des variables Ansible
[Unit]
Description=API backend ({{ app_version }})
After=network.target

[Service]
User=deploy
WorkingDirectory={{ app_dir }}
ExecStart={{ app_dir }}/venv/bin/uvicorn app.main:app --port 8000
Restart=on-failure

[Install]
WantedBy=multi-user.target
bash
# Roles : structure recommandée pour un projet Ansible qui grossit
# roles/webserver/tasks/main.yml, roles/webserver/handlers/main.yml, roles/webserver/templates/...
ansible-galaxy init roles/webserver

# Chiffrer les secrets (mots de passe, clés API) directement dans le repo git
ansible-vault create group_vars/prod/secrets.yml
ansible-vault edit group_vars/prod/secrets.yml
ansible-playbook -i inventory.ini deploy.yml --ask-vault-pass

Résumé

  • Ansible est agentless (SSH uniquement) et déclaratif : on décrit l'état voulu, pas les étapes.
  • --check --diff avant tout déploiement réel ; handlers évitent un redémarrage inutile si rien n'a changé.
  • ansible-vault chiffre les secrets pour qu'ils puissent vivre dans le même repo git que le reste de l'infra.

Exercices pratiques

1 disponible
1

Mission : déployer une mise à jour de sécurité sur 12 serveurs

Objectif : Utiliser Ansible de façon déclarative et prudente pour appliquer un correctif nginx à l'échelle sans risque.

Contexte

Tu dois installer une mise à jour de sécurité nginx sur 12 serveurs web avant ce soir. Te connecter un par un en SSH prendrait trop de temps et risquerait un oubli. Ansible doit appliquer ce correctif de façon identique et vérifiable sur tout le groupe [web].

Résoudre l’exercice →