Anti-patterns CI/CD
Mise à jour :
Qu’est-ce qu’un anti-pattern ?
Un anti-pattern, c’est une solution qui semble bonne sur le moment mais qui crée des problèmes à long terme. C’est comme prendre un raccourci qui finit par vous faire perdre plus de temps qu’il n’en économise.
Dans le contexte des pipelines CI/CD (Continuous Integration / Continuous Delivery — le système automatisé qui teste et déploie votre code), les anti-patterns sont des pratiques qui :
- Ralentissent le feedback aux développeurs
- Rendent la maintenance difficile
- Créent des bugs difficiles à reproduire
- Accumulent de la dette technique (des problèmes qu’on repousse à plus tard)
La pipeline monolithe
L’analogie : Imaginez une usine où une seule chaîne de montage fabrique tout — de la vis au produit fini. Si un poste tombe en panne, toute l’usine s’arrête. C’est exactement ce qui se passe avec une pipeline monolithe.
Symptôme
Une seule pipeline fait tout : vérification du code (lint), tests, compilation (build), analyse de sécurité (scan), déploiement sur tous les environnements. Elle dure 45 minutes. Personne n’ose y toucher.
Pourquoi c’est problématique
| Problème | Impact concret |
|---|---|
| Feedback lent | 47 minutes pour savoir si une faute de frappe casse le build |
| Fragilité | Un changement dans une étape peut casser toutes les autres |
| Pas de parallélisation | Les étapes s’exécutent une par une, même si elles pourraient tourner en parallèle |
| Debugging difficile | Quand ça échoue, il faut chercher dans 500 lignes de configuration |
Comment corriger
Découper en pipelines indépendantes avec des responsabilités claires. Chaque pipeline a un seul objectif et peut échouer sans bloquer les autres :
Pipeline CI (5 min) ← Vérifie que le code est correct├── lint ← Vérifie le style du code├── tests unitaires ← Teste les fonctions individuellement└── build ← Compile l'application
Pipeline CD-staging (3 min) ← Déploie pour les tests├── deploy staging ← Met en ligne sur l'environnement de test└── tests e2e ← Teste l'application comme un utilisateur
Pipeline CD-prod (2 min) ← Déploie pour les vrais utilisateurs├── deploy production└── smoke tests ← Vérifie que les fonctions essentielles marchentBénéfice immédiat : si le lint échoue (2 minutes), le développeur le sait tout de suite au lieu d’attendre 47 minutes.
La pipeline non-déterministe
L’analogie : Imaginez une recette de cuisine qui donne un gâteau parfait une fois sur deux, sans que vous sachiez pourquoi. Frustrant, non ? C’est exactement ce que vivent les développeurs avec une pipeline non-déterministe.
Une pipeline déterministe donne toujours le même résultat avec les mêmes entrées. Une pipeline non-déterministe introduit de l’aléatoire — et donc de l’incertitude.
Symptôme
La même pipeline sur le même code donne des résultats différents. La phrase révélatrice : « Relance, ça va passer. »
Run 1: ✓ succèsRun 2: ✗ échec (test timeout) ← Pourquoi maintenant ?Run 3: ✓ succèsRun 4: ✗ échec (dépendance introuvable)Run 5: ✓ succès ← Rien n'a changé pourtant...Causes fréquentes
| Cause | Explication simple |
|---|---|
| Dépendances non épinglées | npm install télécharge la dernière version, qui peut avoir changé depuis hier |
| Tests flaky | Tests instables qui dépendent de l’ordre d’exécution, du timing, ou de données externes |
| Ressources partagées | Plusieurs tests utilisent la même base de données et se marchent dessus |
| Rate limiting | Les API externes (GitHub, npm) bloquent parfois les requêtes trop fréquentes |
Comment corriger
| Action | Pourquoi ça aide |
|---|---|
| Épingler toutes les dépendances | Le lockfile garantit les mêmes versions à chaque exécution |
| Isoler les tests | Chaque test crée ses propres données, pas de conflit possible |
| Mocker les API externes | Simuler les réponses au lieu d’appeler les vrais services |
| Corriger les tests flaky | Les ignorer ne fait que repousser le problème |
# ❌ Non-déterministe- run: npm install# Problème : télécharge les dernières versions, qui peuvent avoir changé
# ✓ Déterministe- run: npm ci# npm ci utilise exactement les versions du fichier package-lock.jsonLe copier-coller sans gouvernance
L’analogie : Imaginez photocopier un document 50 fois, puis modifier chaque copie différemment. Au bout de quelques mois, impossible de savoir quelle version est la bonne, et corriger une erreur demande de retrouver et modifier les 50 copies.
Symptôme
Chaque projet a sa propre pipeline, copiée depuis un autre projet, légèrement modifiée. 50 projets = 50 variations incompatibles.
projet-a/.github/workflows/ci.yml (version originale)projet-b/.github/workflows/ci.yml (copié, modifié)projet-c/.github/workflows/ci.yml (copié de B, re-modifié)projet-d/.github/workflows/ci.yml (copié de C, avec les bugs de C !)...Pourquoi c’est problématique
| Problème | Conséquence concrète |
|---|---|
| Incohérence | Un projet scanne les vulnérabilités, un autre non — sans raison valable |
| Maintenance impossible | Une faille de sécurité dans la pipeline = 50 fichiers à corriger manuellement |
| Drift | Les copies divergent au fil du temps, personne ne sait plus quelle version est « la bonne » |
| Pas de capitalisation | Une équipe améliore sa pipeline, les autres n’en bénéficient pas |
Comment corriger
Centraliser la logique dans des templates ou actions réutilisables. C’est comme avoir un document maître : vous modifiez l’original, et tous ceux qui l’utilisent reçoivent automatiquement la mise à jour.
# Avant : 200 lignes de configuration copiées-collées# Après : 5 lignes qui appellent le template centralisé
jobs: ci: uses: org/shared-workflows/.github/workflows/ci.yml@v1 with: language: python python-version: "3.11"Résultat :
- Corriger un bug = modifier un seul fichier
- Ajouter une vérification de sécurité = tous les projets en bénéficient
- Les nouveaux projets démarrent avec les bonnes pratiques
Le rebuild à chaque environnement
L’analogie : Imaginez tester un médicament en laboratoire, puis le refabriquer complètement avant de le donner aux patients. Même recette, mais qui garantit que le résultat est identique ? C’est le risque du rebuild.
Un artefact, c’est le produit fini de votre pipeline : une image Docker, un fichier exécutable, un package. C’est ce qui sera déployé.
Symptôme
L’artefact est reconstruit pour chaque environnement. La phrase révélatrice : « Ça marchait en staging ! »
staging: npm run build ← Compilation n°1 docker push staging
production: npm run build ← Compilation n°2 (potentiellement différente !) docker push productionPourquoi c’est problématique
| Problème | Exemple concret |
|---|---|
| Non-reproductibilité | L’artefact testé en staging ≠ l’artefact déployé en production |
| Bugs fantômes | Un bug apparaît en production alors que « ça marchait en staging » |
| Temps perdu | Chaque déploiement recompile tout, même si rien n’a changé |
Comment corriger
Appliquer le principe « Build once, deploy many » (construire une fois, déployer partout) : un artefact unique, promu d’environnement en environnement.
build: npm run build docker push app:abc123 ← Artefact unique, identifié par son hash
staging: docker pull app:abc123 ← Exactement le même artefact ENV=staging deploy ← Seule la configuration change
production: docker pull app:abc123 ← Toujours le même artefact ENV=production deploy ← Seule la configuration changeGarantie : ce qui a été testé en staging est exactement ce qui tourne en production. Les seules différences sont les variables d’environnement (URLs, clés API, etc.).
Pour aller plus loin : Bonnes pratiques CI/CD
Les tests en fin de chaîne
L’analogie : Imaginez construire une maison entière avant de vérifier si les fondations sont solides. Si elles ne le sont pas, tout le travail est perdu. Mieux vaut vérifier les fondations d’abord !
Symptôme
Les tests lourds (tests e2e — « end-to-end », qui simulent un utilisateur réel) s’exécutent après le build et le déploiement. Le feedback arrive trop tard.
lint (1s) → build (3min) → deploy (2min) → tests e2e (15min) │ └── Échec détecté après 20 minutes !Pourquoi c’est problématique
| Problème | Impact sur le développeur |
|---|---|
| Feedback tardif | 20 minutes pour découvrir qu’un simple bug casse tout |
| Gaspillage de ressources | Build et deploy exécutés pour rien si les tests échouent |
| Perte de contexte | Le développeur a commencé autre chose, il doit se replonger dans le code |
Comment corriger
Appliquer la pyramide des tests : exécuter les tests rapides en premier, les tests lents à la fin.
lint (1s) ──┬── tests unitaires (30s) ──┬── build (3min) │ │ └── Échec rapide ! └── tests e2e (seulement si tout passe)La pyramide des tests visualise cette stratégie :
/\ Tests e2e (lents, après merge) / \ → Peu nombreux, vérifient les parcours critiques /────\ / \ Tests d'intégration (sur PR) /────────\ → Vérifient que les composants fonctionnent ensemble / \ /────────────\ Tests unitaires (à chaque commit) → Nombreux, rapides, vérifient chaque fonctionLogique : les tests du bas (unitaires) sont rapides et nombreux. Si l’un échoue, on le sait en 30 secondes. Pas besoin de lancer les tests lourds.
La dépendance à l’environnement
L’analogie : C’est comme une recette qui dit « ajoutez de la farine » sans préciser la quantité ni le type. Chez vous ça marche, chez quelqu’un d’autre le résultat est différent.
Un runner, c’est la machine (physique ou virtuelle) qui exécute votre pipeline. Différents runners peuvent avoir des configurations différentes.
Symptôme
La pipeline fonctionne sur un runner spécifique mais échoue ailleurs. C’est le fameux « Ça marche sur ma machine », version CI.
Runner A (ubuntu-20.04): ✓ succèsRunner B (ubuntu-22.04): ✗ échec ← Même code, résultat différentRunner C (macos): ✗ échecCauses fréquentes
| Cause | Exemple concret |
|---|---|
| Outils non installés | La pipeline utilise jq qui est installé sur un runner mais pas les autres |
| Versions implicites | python appelle Python 3.8 sur un runner, Python 3.11 sur un autre |
| Chemins en dur | /home/runner/specific/path n’existe pas sur tous les runners |
| Variables d’environnement | Une variable est configurée sur un runner, absente sur les autres |
Comment corriger
| Solution | Pourquoi ça fonctionne |
|---|---|
| Conteneuriser | L’image Docker embarque tous les outils nécessaires, identiques partout |
| Expliciter les versions | python-version: "3.11" garantit la même version sur tous les runners |
| Éviter les chemins absolus | $GITHUB_WORKSPACE s’adapte automatiquement au runner |
| Installer les prérequis | Si un outil est nécessaire, l’installer explicitement dans la pipeline |
# ✓ Explicite et portablejobs: test: runs-on: ubuntu-latest container: python:3.11-slim # ← Environnement contrôlé steps: - uses: actions/checkout@v4 - run: pip install -r requirements.txtRègle d’or : si quelque chose n’est pas explicitement défini dans la pipeline, ça peut changer sans prévenir.
Les exceptions permanentes
L’analogie : C’est comme désactiver l’alarme incendie « juste pour cette fois » parce qu’elle se déclenche trop souvent. Un an plus tard, l’alarme est toujours désactivée, et vous avez oublié pourquoi.
Symptôme
Des projets sont exemptés des contrôles de sécurité « temporairement ». Deux ans plus tard, les exceptions sont toujours là, et personne ne sait si elles sont encore justifiées.
Exceptions au scan de sécurité:- projet-legacy (temporaire, ajouté en 2022) ← Toujours là- projet-urgence (temporaire, ajouté en 2023) ← Toujours là- projet-client-X (temporaire, ajouté en 2023) ← Toujours là- ...Pourquoi c’est problématique
| Problème | Risque concret |
|---|---|
| Fausse sécurité | Les tableaux de bord sont verts, mais des projets vulnérables passent sous le radar |
| Précédent dangereux | « Eux ont une exception, pourquoi pas nous ? » — les exceptions se multiplient |
| Dette invisible | Personne ne sait combien de projets sont exemptés ni pourquoi |
Comment corriger
| Mesure | Comment l’appliquer |
|---|---|
| Date d’expiration | Chaque exception expire automatiquement après 90 jours |
| Justification documentée | « Pourquoi cette exception ? Quel est le plan pour la supprimer ? » |
| Revue régulière | Audit trimestriel : les exceptions non renouvelées sont supprimées |
| Alternative au bypass total | Plutôt qu’ignorer le scan complet, ignorer uniquement la vulnérabilité spécifique |
La pipeline SPOF
SPOF signifie « Single Point of Failure » (point de défaillance unique). C’est un élément dont la panne bloque tout le système.
L’analogie : C’est comme avoir une seule clé pour toute l’entreprise, détenue par une seule personne. Si cette personne est absente, personne ne peut entrer.
Symptôme
Une seule pipeline fait tout, sur un seul runner, avec un seul maintainer. Si l’un de ces éléments tombe, tout s’arrête.
Comment corriger
| SPOF identifié | Solution |
|---|---|
| Runner unique | Créer un pool de runners (au moins 2-3) |
| Maintainer unique | Documenter la pipeline, former au moins une autre personne |
| Pas d’alertes | Configurer des notifications quand le runner est indisponible |
Checklist d’audit
Utilisez cette checklist pour évaluer la santé de vos pipelines :
Checklist d’audit
Utilisez cette checklist pour évaluer la santé de vos pipelines :
- Feedback rapide : Durée < 15 min pour le premier retour
- Déterminisme : Aucun « relance, ça va passer »
- Gouvernance : Templates centralisés (pas de copier-coller)
- Build once : Artefact construit une seule fois
- Pyramide des tests : Tests rapides exécutés en premier
- Exceptions traçables : Pas d’exception sans date d’expiration
- Pas de SPOF : Plus d’une personne connaît la pipeline
À retenir
- Un anti-pattern est une solution qui crée plus de problèmes qu’elle n’en résout
- La pipeline monolithe ralentit le feedback — découpez en pipelines indépendantes
- Une pipeline non-déterministe érode la confiance — épinglez les dépendances
- Le copier-coller multiplie la maintenance — centralisez dans des templates
- Build once, deploy many — un artefact unique pour tous les environnements
- Les tests rapides d’abord — échec en 30 secondes plutôt qu’en 20 minutes
- Les exceptions temporaires deviennent permanentes — ajoutez des dates d’expiration
- Éliminez les SPOF — redondance des runners et des connaissances
Liens utiles
- Bonnes pratiques CI/CD — Les invariants qui marchent
- Pourquoi les pipelines échouent-elles ? — Causes profondes organisationnelles
- Anatomie d’une pipeline — Comprendre pour mieux corriger