Automatisation GitOps : Déployer ses configurations de serveurs automatiquement avec GitHub/GitLab Actions et Ansible

Pourquoi GitOps change vraiment la gestion de configuration (et pourquoi l’Ansible « à la main » ne suffit plus)
Le scénario classique, vous le connaissez. Un mardi soir, petite modif « rapide » sur un serveur de prod. Juste un fichier de conf. Juste un restart. Et puis, deux semaines plus tard, quelqu’un déploie la même appli sur un autre serveur. Sauf que... il n’a pas la modif. Ou pire, il a une autre modif, faite « à la main » aussi, mais pas exactement pareil. Et là on découvre le drift. Les serveurs qui étaient censés être identiques ne le sont plus. Les rollbacks deviennent douloureux parce qu’on ne sait plus ce qui a changé, ni quand, ni pourquoi.
Ansible a souvent été la première marche. On écrit des playbooks, on les lance depuis son laptop, on se dit que c’est déjà mieux que du SSH artisanal. Oui. Mais « Ansible à la main », c’est souvent une promesse incomplète.
GitOps, dit simplement, c’est ça : Git comme source de vérité + automatisation + traçabilité. En clair, si ce n’est pas dans Git, ce n’est pas censé exister. Et si c’est dans Git, un mécanisme automatique est capable de l’appliquer, de manière répétable, et de laisser une trace.
La différence entre « infrastructure as code » et « GitOps » est subtile mais importante. L’IaC dit : « on décrit l’état voulu dans du code ». GitOps ajoute : « et on met une boucle de contrôle qui déclenche l’application de cet état à partir de Git, avec des garde fous ». Ce n’est pas juste du code. C’est un processus.
Concrètement, vous gagnez :
- Reproductibilité : un serveur reconstruit ou un nouveau node rejoint le cluster, on applique la même config, point.
- Audit : qui a changé quoi, quand, via un commit, une PR, une approbation.
- Rollback : revert d’un commit, redeploy, on revient à une config connue.
- Standardisation : mêmes users, mêmes règles SSH, mêmes packages, mêmes services.
- Onboarding plus simple : un nouveau dans l’équipe lit le repo, lance les commandes standard, comprend le flux.
Mais. GitOps ne résout pas tout tout seul.
- Si vos playbooks sont mal écrits, non idempotents, fragiles, GitOps va juste automatiser le chaos.
- Les secrets restent un sujet brûlant.
- La gouvernance compte : qui peut déployer quoi, sur quels environnements.
- Les tests aussi : si vous ne testez rien, vous déployez juste plus vite... des erreurs.
Le modèle cible : GitHub/GitLab Actions + Ansible pour configurer des serveurs automatiquement
Le modèle qu’on vise ici est volontairement pragmatique.
Un flux type :
commit → CI/CD → tests → déploiement Ansible → validation post-déploiement.
Ansible s’insère parfaitement au milieu : playbooks et rôles pour configurer l’OS, installer des packages, gérer des services, des users, faire du hardening, déployer des templates. Et tout ça, de façon idempotente.
Pourquoi GitHub Actions ou GitLab CI, plutôt qu’un serveur CI dédié ? Souvent pour des raisons simples :
- Zéro infra CI à maintenir au début.
- Intégration native au repo : PR, reviews, checks, environnements, permissions.
- Coûts et friction réduits.
- Et si le réseau est un problème, vous pouvez utiliser des runners auto hébergés (self hosted) dans votre VPC, votre LAN, derrière VPN, etc.
Quand choisir GitHub Actions vs GitLab CI ? Ça dépend de votre contexte, mais les critères pratiques sont souvent :
- Disponibilité et gestion des runners.
- Variables et secrets, scopes par environnement.
- Modèle de permissions et protection.
- Expérience de l’équipe : si vous êtes déjà très GitLab, restez simple.
Dernier point important sur le périmètre. Ici, on parle surtout de configuration « day 1 / day 2 » : durcissement, comptes, services, tuning, observabilité. Pas forcément du provisioning complet de VM, même si ça peut venir après.
Pré-requis et choix d’architecture (pour éviter de se tirer une balle dans le pied)
Avant d’écrire votre premier workflow, posez ces bases. Sérieusement. Parce que beaucoup de projets GitOps échouent sur des détails « invisibles » au début.
Inventaire
Statique ou dynamique.
inventories/dev/hosts.iniinventories/prod/hosts.ini- Dynamique : plugin cloud (AWS, GCP, Azure), CMDB, etc. Puissant, mais plus de moving parts.
Organisez vos groupes (dev, staging, prod) et vos variables par host et group. Ne mélangez pas tout. C’est souvent là que la prod se fait surprendre.
Accès aux serveurs
Vous avez besoin de SSH. Donc :
- Clés SSH (idéalement dédiées à la CI).
- Bastion si nécessaire.
- Réseau : VPN, ip allowlist, accès des runners.
- Si vous utilisez des runners partagés cloud, attention : ouvrir la prod à Internet « juste pour la CI » est une pente dangereuse. Le plus propre est souvent un runner self hosted dans le réseau.
Idempotence
En GitOps, l’idempotence n’est pas un bonus. C’est non négociable.
Si vous devez rejouer le playbook demain, dans une semaine, après un incident, ça doit fonctionner et ne pas casser l’existant. Sinon, vous ne pouvez pas faire confiance au modèle.
Stratégie d’environnements
Branches vs dossiers vs fichiers de vars. Il n’y a pas une vérité unique, mais il faut une règle claire.
mainprod- Dossiers d’inventaire séparés par environnement, c’est souvent le plus simple.
- Promotion staging → prod via merge, tag, ou pipeline manuel validé.
Gestion des permissions
Qui peut déployer en prod ?
- Protection des branches.
- Approvals obligatoires.
- Environnements protégés (GitHub Environments, GitLab protected environments).
- Jobs manuels pour prod, au minimum au début.
Structurer le dépôt Git : une organisation simple qui tient dans la durée
Un repo Ansible GitOps, c’est comme une armoire. Si vous jetez tout en vrac, ça marche trois semaines. Après… c’est fini.
Une arborescence simple et durable :
text . ├── playbooks/ │ ├── base.yml │ ├── web.yml │ ├── db.yml │ └── hardening.yml ├── roles/ │ ├── users/ │ ├── ssh_hardening/ │ ├── firewall/ │ ├── nginx/ │ └── monitoring/ ├── inventories/ │ ├── dev/ │ │ ├── hosts.ini │ │ └── group_vars/ │ ├── staging/ │ │ ├── hosts.ini │ │ └── group_vars/ │ └── prod/ │ ├── hosts.ini │ └── group_vars/ ├── group_vars/ ├── host_vars/ ├── files/ ├── templates/ ├── requirements.yml ├── Makefile └── README.md
Conventions de nommage :
base.ymlweb.ymlhardening.yml--tags ssh,users,firewall- Handlers bien nommés, pas « restart service » partout.
userssshfirewallnginxdockermonitoring
Et surtout, versionnez tout ce qui est configuration. Le drift, c’est l’ennemi silencieux. Une règle quasi politique aide beaucoup : pas de modif manuelle en prod. Si quelqu’un doit « hotfix », ok, mais il ouvre une PR ensuite pour réconcilier Git avec la réalité. Sinon Git cesse d’être la source de vérité.
Ajoutez une doc légère dans le repo : un README qui explique comment lancer en local, comment marche le pipeline, et un petit schéma texte du flux. Pas besoin d’un roman.
Mettre Ansible « prêt CI » : lint, tests et exécution locale identique à la CI
La CI sert à éliminer les « chez moi ça marche ».
ansible-lint
ansible-lint
- tâches non nommées,
shell- templates qui changent à chaque run,
- permissions incohérentes,
- pratiques douteuses sur les handlers.
Vous pouvez démarrer avec les règles par défaut, puis assouplir si nécessaire. Mais évitez l’inverse. Si vous commencez sans lint, vous n’y reviendrez pas.
Tests rapides
Avant de déployer, au minimum :
ansible-playbook --syntax-check--check--diff
--check
Molecule (optionnel)
Molecule, c’est super pour tester des rôles dans des containers ou VMs éphémères. Mais c’est un coût de setup.
Quand ça vaut le coup ? Quand vos rôles sont réutilisés, partagés, ou critiques. Ou quand vous commencez à avoir des régressions fréquentes.
Versions figées
Un classique : le pipeline cassé parce qu’une collection Ansible a changé.
requirements.ymlrequirements.txtpyproject
Une commande standard local = CI
Makefile
Exemple :
makefile lint: \tansible-lint
syntax: \tansible-playbook -i inventories/staging/hosts.ini playbooks/base.yml --syntax-check
check: \tansible-playbook -i inventories/staging/hosts.ini playbooks/base.yml --check --diff
deploy-staging: \tansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml
deploy-prod: \tansible-playbook -i inventories/prod/hosts.ini playbooks/site.yml
L’idée : tout le monde lance les mêmes commandes. Moins de débats. Moins de surprises.
Gestion des secrets en GitOps : la partie la plus sensible (et la plus souvent ratée)
On ne commit jamais :
- clés privées SSH,
- mots de passe,
- tokens API,
- certificats non publics,
.env
Options réalistes :
- Ansible Vault : simple, intégré. Mais la gestion de la clé Vault devient votre point sensible.
- GitHub Secrets / GitLab CI variables : pratique pour injecter des secrets au runtime CI. Bon pour commencer.
- SOPS (souvent avec age ou KMS) : bon compromis GitOps, secrets chiffrés versionnés dans Git, déchiffrés en CI.
- HashiCorp Vault : plus lourd, mais excellent pour rotation, TTL, politiques.
-vvv
Séparez les secrets par environnement. Et limitez les scopes : un secret de prod ne doit jamais être accessible à un job qui tourne sur une branche non protégée.
Implémentation GitHub Actions : workflow complet (lint → dry-run → déploiement)
Un workflow GitHub Actions typique, en étapes.
- Checkout du code
- Setup Python + Ansible
- Installation des dépendances
ansible-lintsyntax-checkcheck- Déploiement staging
- Déploiement prod avec approbation
Un exemple de workflow simplifié :
yaml name: ansible-gitops
on: pull_request: push: branches: [ "main" ] workflow_dispatch:
jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: install deps
run: |
python -m pip install --upgrade pip
pip install ansible ansible-lint
ansible-galaxy collection install -r requirements.yml
- name: ansible-lint
run: ansible-lint
- name: syntax-check
run: ansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml --syntax-check
- name: check mode (staging)
run: ansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml --check --diff
deploy-staging: if: github.ref == 'refs/heads/main' needs: [ lint-and-test ] runs-on: ubuntu-latest environment: staging steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: install deps run: | pip install ansible ansible-galaxy collection install -r requirements.yml
- name: deploy staging
env:
SSH_PRIVATE_KEY: ${{ secrets.STAGING_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml
deploy-prod: if: github.ref == 'refs/heads/main' needs: [ deploy-staging ] runs-on: ubuntu-latest environment: prod steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: install deps run: | pip install ansible ansible-galaxy collection install -r requirements.yml
- name: deploy prod
env:
SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ansible-playbook -i inventories/prod/hosts.ini playbooks/site.yml --serial 2
Deux points importants ici :
deploy-prodprodmain--serial
Côté artefacts et logs, vous pouvez rediriger la sortie Ansible vers un fichier et l’uploader, ou générer un petit résumé. Le minimum, c’est que l’historique de run soit consultable.
Stratégie de déclenchement recommandée :
- PR : lint + tests uniquement.
mainworkflow_dispatch
Implémentation GitLab CI : pipeline équivalent (stages, environnements, approbations)
stageslinttestdeploy
.gitlab-ci.yml
yaml stages:
- lint
- test
- deploy
image: python:3.12-slim
before_script:
- pip install --upgrade pip
- pip install ansible ansible-lint
- ansible-galaxy collection install -r requirements.yml
lint: stage: lint script: - ansible-lint rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH
syntax_check: stage: test script: - ansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml --syntax-check rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH
deploy_staging: stage: deploy environment: name: staging script: - mkdir -p ~/.ssh - echo "$STAGING_SSH_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ansible-playbook -i inventories/staging/hosts.ini playbooks/site.yml rules: - if: $CI_COMMIT_BRANCH == "main"
deploy_prod: stage: deploy environment: name: prod script: - mkdir -p ~/.ssh - echo "$PROD_SSH_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ansible-playbook -i inventories/prod/hosts.ini playbooks/site.yml --serial 2 when: manual rules: - if: $CI_COMMIT_BRANCH == "main"
Runners :
- Shared runners, c’est bien pour lint et tests.
- Pour déployer vers des serveurs privés, un runner self managed, tagué, dans le bon réseau, c’est souvent obligatoire.
Variables GitLab :
maskedprotected- scopes par environnement si besoin.
- et attention aux pipelines sur branches non protégées : ne leur donnez jamais accès à la prod.
Approvals et protections :
- branches protégées,
- merge requests obligatoires,
when: manual- environnements protégés selon édition GitLab.
Comparaison rapide, côté ops : GitLab est très confortable sur la notion d’environnements et de variables protégées, GitHub est très fluide sur l’intégration repo et les environnements avec reviewers. Les deux marchent. Le point qui compte le plus reste souvent le réseau et les runners.
Stratégies de déploiement sûres : comment éviter le « push qui casse tout »
Trois axes.
Déploiement progressif
--serialprod_canaryprod--tags nginx
Gating
- lint + tests obligatoires avant deploy.
- review de PR.
- approbations pour prod.
Ça paraît bureaucratique, mais c’est juste de la mémoire externe. Quand vous êtes fatigué, c’est la CI qui vous empêche de faire une bêtise.
Concurrence et verrous
Empêchez deux déploiements simultanés sur prod.
- GitHub : concurrency groups.
- GitLab : resource groups.
Sinon, vous aurez un run qui change un fichier pendant qu’un autre redémarre un service. Et vous allez passer une heure à comprendre un bug qui n’en est pas un.
Re-run safe
Si vous ne pouvez pas relancer un déploiement sans stresser, vous n’êtes pas en GitOps. Utilisez l’idempotence, les handlers, et faites attention aux tâches non idempotentes. Parfois il faut refactorer. Oui, c’est du boulot. Mais c’est un investissement direct.
Validation post-déploiement : le chaînon manquant (et pourtant simple)
Déployer, ce n’est pas terminer. Terminer, c’est valider.
Tests de santé simples :
systemdwait_foruri- version de package, checksum de config, etc.
Vous pouvez le faire en Ansible, dans un playbook « post ».
Exemples utiles :
assertfailed_whenchanged_when
Observabilité :
- envoyer un résumé Slack/Teams/email.
- annoter une release.
- journaliser l’ID de pipeline et le commit déployé.
Rollback :
- re run d’un tag Git connu.
- revert commit.
- revenir à une version précédente d’un rôle ou d’une collection.
Définissez une « definition of done » pour un déploiement de config. Par exemple : « playbook OK + endpoint 200 + service actif + pas d’erreurs dans les logs Nginx sur 2 minutes ». Ce n’est pas parfait, mais c’est déjà un filet.
Exemple de cas réel : durcissement SSH + utilisateurs + firewall + Nginx (workflow de bout en bout)
Mini scope, réaliste, très fréquent :
roles/ssh_hardeningroles/usersroles/firewallroles/nginx
Variables par environnement.
Exemples :
- En dev, vous autorisez le port 22 depuis votre IP de bureau.
- En prod, SSH passe par un bastion, et vous limitez à une allowlist stricte.
- En prod, Nginx écoute sur 80 et 443, en staging peut être seulement 80.
devops_test
Flux GitOps :
- Une PR introduit un changement, par exemple désactiver l’auth par mot de passe SSH.
ansible-lintsyntax-check--check- Review. On discute. On ajuste.
main- Déploiement auto sur staging.
- Validation post déploiement : SSH OK, Nginx répond.
--serial 2- Re validation prod.
Et surtout, traçabilité : le commit vous dit exactement « qui a changé quoi, quand, pourquoi ». Vous avez les commentaires de PR, les approvals, l’historique des runs. Quand un audit arrive, ou quand un incident arrive, vous ne fouillez pas des terminaux. Vous lisez Git.
Les erreurs fréquentes (et comment les éviter)
- Playbooks non idempotents
shellchanged_when- Inventaires mal séparés
inventories/devstagingprod- Runners sans accès réseau maîtrisé
- Ouvrir SSH prod à tous les IP d’un provider, ça arrive. Solution : runner self hosted dans le réseau, bastion, VPN, allowlist stricte.
- Absence de garde fous
- Déploiement direct sur prod, sans approbation, sans tests. Solution : gating obligatoire, job manuel pour prod, environnements protégés.
- Couplage trop fort
site.yml
Conclusion : le « minimum viable GitOps » à mettre en place dès cette semaine
Si vous devez faire simple et utile, dès cette semaine :
- un repo propre (structure claire, inventaires séparés),
ansible-lint- un pipeline CI qui fait lint + syntax check + éventuellement check mode,
- secrets gérés proprement (Vault, secrets CI, ou SOPS), séparés par environnement,
- déploiement automatique sur staging, puis prod avec approbation.
ssh_hardening
Le vrai cœur de GitOps, ce n’est pas l’outil. C’est la discipline : Git comme source de vérité, PRs, reviews, logs, et l’interdiction tacite des changements invisibles.
Et la prochaine étape, quand tout ça tient ? Inventaire dynamique, SOPS ou Vault plus mature, tests Molecule, runners dédiés, policies plus strictes. Mais pas avant d’avoir un socle simple qui marche, tous les jours.
Questions fréquemment posées
Qu'est-ce que GitOps et pourquoi révolutionne-t-il la gestion de configuration ?
GitOps est une approche qui combine Git comme source de vérité, automatisation et traçabilité pour gérer la configuration des infrastructures. Contrairement à l'Ansible "à la main", GitOps assure que toute modification est versionnée dans Git, appliquée automatiquement de manière répétable, et trace chaque changement, évitant ainsi le drift entre serveurs et facilitant les rollbacks.
Pourquoi l'utilisation d'Ansible seule ne suffit plus pour une gestion efficace ?
Bien qu'Ansible permette d'automatiser la configuration via des playbooks, son utilisation manuelle reste sujette aux erreurs humaines, au drift des configurations et à un manque de traçabilité. Sans intégration dans un processus GitOps, les modifications peuvent être appliquées de façon non standardisée, rendant les rollbacks et audits difficiles.
Quels sont les principaux bénéfices concrets apportés par GitOps ?
GitOps offre plusieurs avantages clés : reproductibilité des configurations sur tous les serveurs, audit complet des changements via commits et PRs, possibilité de rollback rapide en cas de problème, standardisation des environnements (utilisateurs, règles SSH, services), et facilitation de l'onboarding grâce à un flux clair basé sur le dépôt Git.
Comment s'intègre Ansible dans un pipeline GitOps avec GitHub Actions ou GitLab CI ?
Dans un modèle pragmatique GitOps, Ansible est utilisé pour configurer automatiquement les serveurs via des playbooks idempotents. Le flux typique est : commit → CI/CD (GitHub Actions ou GitLab CI) → tests → déploiement Ansible → validation post-déploiement. Ce processus permet d'automatiser la configuration tout en bénéficiant des outils natifs du dépôt pour la revue et la gestion des permissions.
Quels sont les critères pour choisir entre GitHub Actions et GitLab CI dans un contexte GitOps ?
Le choix dépend du contexte spécifique de l'équipe : gestion et disponibilité des runners (auto-hébergés ou cloud), gestion fine des variables et secrets par environnement, modèle de permissions et protection des branches, ainsi que l'expérience préalable de l'équipe avec une plateforme donnée. Par exemple, une équipe déjà familière avec GitLab privilégiera souvent GitLab CI pour sa simplicité d'intégration.
Quelles précautions prendre avant de lancer un projet GitOps pour éviter les échecs ?
Avant d'écrire le premier workflow, il est crucial de poser des bases solides telles qu'un inventaire clair (statique ou dynamique), garantir que les playbooks Ansible sont bien écrits et idempotents, définir une gouvernance claire sur qui peut déployer quoi et où, gérer correctement les secrets sensibles, et mettre en place une stratégie de tests rigoureuse afin d'éviter d'automatiser des erreurs.
0 Commentaires