Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1523 minIntermédiaire

Versionner l'infrastructure : Git, Galaxy, ansible-lint et CI

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • organiser un dépôt d'infrastructure, son .gitignore et ses rôles Galaxy aux versions figées ;
  • faire passer chaque modification par une branche et une revue ;
  • contrôler les playbooks avec ansible-lint ;
  • écrire un pipeline qui vérifie les playbooks à chaque push ;
  • documenter l'automatisation pour le dossier de l'E6.

4 exercices corrigés

Versionner l'infrastructure, c'est ranger tout le projet Ansible (inventaire, playbooks, rôles, fichiers chiffrés) dans un dépôt Git, faire passer chaque modification par une branche, des contrôles automatiques et une relecture, puis l'appliquer depuis la branche principale. Ce chapitre organise le dépôt de Sorbier et ses dépendances venues d'Ansible Galaxy, contrôle les playbooks avec ansible-lint, écrit un pipeline GitHub Actions qui les vérifie à chaque modification, présente les façons de tester avant la production et dit comment documenter l'automatisation pour l'E6.

Le dépôt et son .gitignore

Le dépôt sorbier-infra contient tout ce qui décrit l'infrastructure, et rien de ce qui peut se reconstruire ou doit rester secret :

text
sorbier-infra/
├── .ansible-lint            configuration d'ansible-lint
├── .github/workflows/
│   └── verifier.yml         le pipeline de contrôle
├── .gitignore
├── README.md                comment utiliser le dépôt
├── ansible.cfg
├── requirements.yml         collections et rôles externes, versions figées
├── inventaire/
│   ├── hosts.yml
│   ├── group_vars/
│   └── host_vars/
├── roles/
│   ├── socle/
│   └── web/
├── files/
├── vars/
│   └── arrivees.yml
├── site.yml                 le playbook principal
├── web.yml
├── fichiers.yml
├── comptes_ad.yml
├── vm_recette.yml
└── terraform/
    └── main.tf

Le fichier .gitignore :

text
# Reconstruits à partir de requirements.yml
collections/
roles_galaxy/

# Secrets et fichiers produits par les playbooks
*.vault-pass
coffre*.txt
identifiants/
sauvegardes/

# Terraform et OpenTofu : fournisseurs téléchargés et état
terraform/.terraform/
*.tfstate
*.tfstate.*

# Divers
*.retry
__pycache__/

Les rôles téléchargés depuis Galaxy vont dans roles_galaxy/ (avec roles_path = roles:roles_galaxy dans ansible.cfg), pour ne pas se mélanger aux rôles de Sorbier, qui, eux, sont versionnés. Les fichiers vault.yml chiffrés sont versionnés ; le mot de passe du coffre, jamais (chapitre 9). Les règles générales du .gitignore et le traitement d'un secret commité par erreur sont dans le cours Git et GitHub.

Ansible Galaxy et requirements.yml

Ansible Galaxy (galaxy.ansible.com) est le dépôt public des rôles et des collections de la communauté. On y trouve des collections officielles ou certifiées (ansible.windows, cisco.ios), des collections communautaires (community.general, community.zabbix, voir le cours Supervision) et des milliers de rôles d'auteurs individuels.

Le dépôt ne contient pas ce code écrit par d'autres : il déclare ce qu'il utilise dans requirements.yml, rôles et collections ensemble :

yaml
---
roles:
  - name: geerlingguy.docker
    version: 8.0.0

collections:
  - name: community.general
    version: ">=14.0.0"
  - name: ansible.windows
    version: ">=3.9.0"
bash
# Les rôles (dans roles_galaxy/) et les collections (dans collections/)
ansible-galaxy role install -r requirements.yml -p roles_galaxy
ansible-galaxy collection install -r requirements.yml -p collections

Un rôle de Galaxy s'utilise comme les tiens (- geerlingguy.docker, pour Docker) ; un rôle fourni par une collection s'appelle par son nom complet (- community.zabbix.zabbix_agent).

Pour savoir si un rôle a été écrit pour ton parc, commence par son meta/main.yml : il donne l'auteur, la licence, la version minimale d'Ansible et les systèmes testés. Celui du rôle socle de Sorbier :

yaml
---
galaxy_info:
  author: Service informatique de Sorbier Logistique
  description: Socle commun des serveurs Debian de Sorbier
  license: MIT
  min_ansible_version: "2.18"
  platforms:
    - name: Debian
      versions:
        - trixie
dependencies: []

Un rôle dont les plateformes ne citent ni Debian 13 (trixie) ni ta distribution n'a sans doute jamais été essayé dessus : à toi de le tester en recette avant toute chose.

RôleCollection
ContenuDes tâches, handlers, modèles, variables pour une fonctionDes modules, plugins (connexion, inventaire, filtres) et des rôles
Nomauteur.role ou simplement role en localespace.collection
Installationansible-galaxy role installansible-galaxy collection install
Exemplegeerlingguy.dockercommunity.general, microsoft.ad

Une modification, du poste à la production

Chez Sorbier, personne ne modifie un serveur à la main, et personne ne pousse directement sur la branche main. Une modification suit ce chemin :

  1. Une branche : git switch -c ajout-module-headers sur le bastion (branches Git).
  2. La modification, testée sur le serveur de recette : ansible-playbook site.yml --limit srv-recette, deux fois, pour vérifier l'idempotence.
  3. Des commits au message clair : feat(web): active le module headers et l'en-tête HSTS.
  4. Une demande de fusion (pull request) : le pipeline lance les contrôles, un collègue relit le code et le résultat d'un --check --diff sur la production joint à la demande (pull request et revue de code).
  5. La fusion dans main, puis l'application depuis main : git pull sur le bastion, puis ansible-playbook site.yml.

Ce circuit donne à chaque changement un auteur, une date, une raison, une relecture, et un retour arrière simple : git revert du commit fautif, puis on rejoue le playbook.

Contrôler les playbooks avec ansible-lint

ansible-lint analyse les playbooks et les rôles sans rien exécuter : syntaxe YAML, noms complets des modules, tâches sans nom, droits de fichiers absents, commandes qui devraient être des modules, idempotence douteuse. Version 26.10 en novembre 2026 :

bash
pipx install ansible-lint
ansible-lint

Sur un playbook écrit trop vite :

yaml
---
- hosts: linux
  become: yes
  tasks:
    - name: installer apache
      apt:
        name: apache2
        state: latest

    - shell: systemctl restart apache2

    - name: Déposer la page
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html

ansible-lint répond (extrait) :

text
name[play]: All plays should be named.
mauvais.yml:2:3

yaml[truthy]: Truthy value should be one of [false, true]
mauvais.yml:3

name[casing]: All names should start with an uppercase letter.
mauvais.yml:5:13 Task/Handler: installer apache

package-latest: Package installs should not use latest.
mauvais.yml:5 Task/Handler: installer apache

fqcn[action-core]: Use FQCN for builtin module actions (apt).
mauvais.yml:6:7 Use `ansible.builtin.apt` or `ansible.legacy.apt` instead.

command-instead-of-module: systemctl used in place of systemd module
mauvais.yml:10 Task/Handler: shell systemctl restart apache2

name[missing]: All tasks should be named.
mauvais.yml:10 Task/Handler: shell systemctl restart apache2

no-changed-when: Commands should not change things if nothing needs doing.
mauvais.yml:10 Task/Handler: shell systemctl restart apache2

risky-file-permissions: File permissions unset or incorrect.
mauvais.yml:12 Task/Handler: Déposer la page

Failed: 11 failure(s), 0 warning(s) in 1 files processed of 1 encountered. Profile 'production' was required, but 'min' profile passed.

Chaque ligne donne la règle (entre crochets, sa variante), le fichier et la ligne. Les règles à connaître :

RègleCe qu'elle signaleCorrection
name[play], name[missing], name[casing]Un play ou une tâche sans nom, un nom sans majusculeNommer chaque play et chaque tâche
yaml[truthy]yes, no, on, offtrue, false
fqcnUn module sans son nom completansible.builtin.apt
package-lateststate: latest : la version change au gré des dépôtsstate: present, et les mises à jour dans un playbook dédié
command-instead-of-module, command-instead-of-shellUne commande là où un module existe ; shell sans besoin du shellLe module dédié ; command
no-changed-whenUne commande toujours changedchanged_when, creates ou un module
risky-file-permissionsUn fichier déposé sans modemode: "0644"
no-handlerUne tâche qui teste changed d'une autre pour redémarrer un serviceUn handler
var-naming[no-role-prefix]Une variable de rôle sans le préfixe du rôleweb_racine dans le rôle web

ansible-lint classe ses règles en profils de plus en plus exigeants : min, basic, moderate, safety, shared, production. Le fichier .ansible-lint à la racine du dépôt fixe le profil et les dossiers à ignorer :

yaml
---
profile: production
exclude_paths:
  - collections/
  - roles_galaxy/

Tous les playbooks et les rôles de ce cours passent le profil production. Pour une exception justifiée, un commentaire # noqa: nom-de-la-regle en fin de ligne désactive la règle sur cette seule ligne ; écris toujours pourquoi.

Un pipeline qui vérifie chaque modification

Le pipeline d'intégration continue lance les contrôles à chaque push et à chaque demande de fusion, sur une machine propre : un playbook cassé ne peut pas arriver dans main sans que tout le monde le voie. Le fichier .github/workflows/verifier.yml pour GitHub Actions :

yaml
name: Vérifier les playbooks

on:
  push:
    branches: [main]
  pull_request:

jobs:
  verifier:
    runs-on: ubuntu-24.04
    steps:
      - name: Récupérer le dépôt
        uses: actions/checkout@v7

      - name: Installer Python 3.13
        uses: actions/setup-python@v7
        with:
          python-version: "3.13"

      - name: Installer ansible-core et ansible-lint
        run: pip install "ansible-core==2.22.0" "ansible-lint==26.10.1"

      - name: Installer les collections du projet
        run: ansible-galaxy collection install -r requirements.yml -p collections

      - name: Vérifier la syntaxe des playbooks
        run: ansible-playbook site.yml --syntax-check

      - name: Lancer ansible-lint
        run: ansible-lint
  • Les versions sont figées : le pipeline teste avec exactement les outils du bastion, et une nouvelle version d'ansible-lint qui ajoute une règle ne casse pas le pipeline par surprise.
  • Le pipeline n'a pas le mot de passe du coffre : la vérification de syntaxe et ansible-lint n'en ont pas besoin (ils ne déchiffrent pas les fichiers vault.yml, ansible-lint se contente d'un avertissement).
  • Il ne se connecte à aucun serveur : un exécuteur hébergé par GitHub ne peut pas joindre le réseau interne de Sorbier, et c'est voulu.

La même logique s'écrit pour GitLab CI dans un fichier .gitlab-ci.yml. La structure d'un pipeline, les déclencheurs, les secrets et les exécuteurs auto-hébergés sont détaillés dans le cours Intégration continue ; l'analyse automatique des secrets commités par erreur, dans son chapitre DevSecOps.

Tester avant la production

Les contrôles statiques ne disent pas si le playbook fonctionne. Trois niveaux de test, du plus simple au plus complet :

  1. Une recette : un inventaire inventaire/recette/ avec des VM qui ressemblent à la production (comme srv-recette), où l'on applique d'abord chaque modification. Deux exécutions de suite : la première doit réussir, la seconde ne rien changer.
  2. --check --diff sur la production, joint à la demande de fusion : la liste exacte de ce qui va changer. Il faut pour cela un exécuteur qui joint le réseau, par exemple un exécuteur auto-hébergé sur le bastion, avec le mot de passe du coffre dans les secrets du dépôt.
  3. Molecule : l'outil de test des rôles Ansible. Il crée une machine jetable (souvent un conteneur), y applique le rôle, vérifie l'idempotence par une deuxième exécution, lance des vérifications, puis détruit la machine. C'est la méthode des rôles publiés sur Galaxy.

Séparer recette et production dans deux dossiers d'inventaire, avec chacun ses group_vars, permet d'utiliser les mêmes playbooks avec des valeurs différentes :

bash
ansible-playbook -i inventaire/recette site.yml
ansible-playbook -i inventaire/production site.yml --check --diff

Planifier et centraliser l'exécution

Lancé à la main depuis le bastion, Ansible ne corrige la dérive que quand quelqu'un y pense. Pour aller plus loin :

SolutionPrincipe
Tâche planifiée (cron, timer systemd) sur le bastionansible-playbook site.yml --check chaque nuit, avec un rapport ; ou l'application réelle sur les rôles sûrs
ansible-pullChaque serveur clone le dépôt et s'applique lui-même le playbook, en mode tiré, comme Puppet
Semaphore UI, AWX, Red Hat Ansible Automation PlatformUne interface web : projets liés au dépôt Git, inventaires, secrets rangés côté serveur, planification, droits par équipe, historique de chaque exécution

Une interface centralisée a un avantage de sécurité : les administrateurs n'ont plus besoin du mot de passe du coffre ni des clés sur leur poste ; ils lancent des modèles d'exécution autorisés, et tout est journalisé.

Documenter pour le dossier de l'E6

Une réalisation d'automatisation se juge sur ce que la commission peut voir et comprendre. Dans ton dossier et ta documentation :

  • le besoin : ce qui était fait à la main, combien de temps, quels problèmes (oublis, dérive) ;
  • un schéma du laboratoire et des flux d'Ansible, comme celui du chapitre 2 ;
  • le dépôt : son arborescence, le rôle de chaque playbook et de chaque rôle, la liste des variables réglables (defaults/) ;
  • les choix : pourquoi Ansible, pourquoi WinRM avec Kerberos ou SSH, pourquoi un compte dédié, comment les secrets sont protégés ;
  • les preuves : la sortie de deux exécutions successives (la seconde avec changed=0), le résultat d'ansible-lint, une capture du pipeline vert, un --check --diff qui détecte une modification faite à la main ;
  • les limites et les améliorations possibles : Molecule, interface centralisée, gestionnaire de secrets.

Le jour de l'épreuve, pendant l'entretien de la phase 2, la preuve la plus parlante tient en deux minutes : modifier une variable, relancer le playbook, montrer le changed sur le seul élément concerné, relancer, montrer changed=0. Le cours sur l'épreuve E6 SISR détaille le déroulement des deux phases et la préparation du dossier.

Exercices

Exercice 1 sur 4 Ce qui va dans le dépôt Réussi À revoir

Pour chaque fichier, dis s'il doit être commité dans sorbier-infra, et pourquoi.

  1. roles/socle/tasks/main.yml
  2. inventaire/group_vars/windows/vault.yml, chiffré
  3. ~/.config/ansible/coffre-sorbier.txt
  4. collections/ansible_collections/community/general/…
  5. identifiants/eberard.txt
  6. terraform/terraform.tfstate
  7. requirements.yml
Voir la réponseMasquer la réponse de l'exercice 1
  1. Oui : c'est le code du rôle.
  2. Oui : chiffré, il est illisible sans le mot de passe, et il doit suivre l'évolution du projet.
  3. Non : c'est le mot de passe du coffre ; il est d'ailleurs hors du dossier du projet.
  4. Non : les collections se reconstruisent avec ansible-galaxy collection install -r requirements.yml.
  5. Non : un mot de passe initial en clair, à remettre puis à supprimer.
  6. Non : l'état de Terraform contient des secrets et se range dans un backend distant verrouillé.
  7. Oui : il fige les dépendances du projet.

Tu avais trouvé ?

Exercice 2 sur 4 Corriger d'après ansible-lint Réussi À revoir

Corrige le playbook mauvais.yml de ce chapitre pour qu'il passe le profil production. Le service doit être redémarré seulement si la page change.

Voir la réponseMasquer la réponse de l'exercice 2
yaml
---
- name: Installer Apache et sa page d'accueil
  hosts: linux
  become: true

  tasks:
    - name: Installer Apache
      ansible.builtin.apt:
        name: apache2
        state: present

    - name: Déposer la page
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
        owner: www-data
        group: www-data
        mode: "0644"
      notify: Redémarrer Apache

  handlers:
    - name: Redémarrer Apache
      ansible.builtin.service:
        name: apache2
        state: restarted

Le play est nommé, become: true remplace yes, les modules ont leur FQCN, la tâche est nommée avec une majuscule, present remplace latest, le fichier a des droits explicites, et le redémarrage passe par un handler au lieu d'une commande shell lancée à chaque fois. Pour une simple page HTML, Apache n'a d'ailleurs pas besoin d'être redémarré : le handler sert ici à l'exercice ; pour une page, on le supprimerait.

Tu avais trouvé ?

Exercice 3 sur 4 Un pipeline pour GitLab Réussi À revoir

Sorbier migre son dépôt sur un GitLab interne. Écris le fichier .gitlab-ci.yml qui fait les mêmes contrôles que le workflow GitHub Actions de ce chapitre, dans une image python:3.13.

Voir la réponseMasquer la réponse de l'exercice 3
yaml
verifier:
  image: python:3.13
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - pip install "ansible-core==2.22.0" "ansible-lint==26.10.1"
    - ansible-galaxy collection install -r requirements.yml -p collections
    - ansible-playbook site.yml --syntax-check
    - ansible-lint

Les rules lancent le travail sur les demandes de fusion et sur la branche par défaut, comme pull_request et push: branches: [main] côté GitHub. Le travail n'a besoin d'aucun secret ni d'aucun accès au réseau interne. Les détails de la syntaxe de GitLab CI sont dans le cours Intégration continue.

Tu avais trouvé ?

Exercice 4 sur 4 Adopter un rôle de Galaxy Réussi À revoir

Moussa a trouvé sur Galaxy un rôle qui installe et configure l'agent de supervision. Il propose d'ajouter - name: auteur.agent_supervision sans version à requirements.yml et de l'appliquer à tout le parc dès ce soir. Quelles vérifications et quelles modifications proposes-tu, dans l'ordre ?

Voir la réponseMasquer la réponse de l'exercice 4
  1. Vérifier le rôle : auteur identifiable, dernière version récente, compatible avec Debian 13 et ansible-core 2.22 (meta/main.yml), tickets ouverts, licence.
  2. Lire ses tâches : ce qu'il installe, d'où il télécharge (dépôt officiel de l'éditeur ou source inconnue), ce qu'il ouvre dans le pare-feu, s'il désactive des protections.
  3. Figer la version dans requirements.yml (version: 3.2.0 par exemple) pour que rien ne change sans relecture.
  4. Tester en recette sur un seul serveur (--limit), avec --check --diff d'abord, puis deux exécutions pour vérifier l'idempotence.
  5. Passer par une branche et une revue dans Git avant de fusionner, comme toute modification du dépôt, puis déployer progressivement (serial).

Si le rôle est trop générique ou mal maintenu, un rôle maison court, appuyé sur la collection officielle de l'éditeur quand elle existe, reste souvent le meilleur choix.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Exercices Ansible corrigés type E6 SISRRevoir le sommaire du cours