Aller au contenu

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.

Pipeline monolithe

Pourquoi c’est problématique

ProblèmeImpact concret
Feedback lent47 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élisationLes étapes s’exécutent une par une, même si elles pourraient tourner en parallèle
Debugging difficileQuand ç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 marchent

Bé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ès
Run 2: ✗ échec (test timeout) ← Pourquoi maintenant ?
Run 3: ✓ succès
Run 4: ✗ échec (dépendance introuvable)
Run 5: ✓ succès ← Rien n'a changé pourtant...

Causes fréquentes

CauseExplication simple
Dépendances non épingléesnpm install télécharge la dernière version, qui peut avoir changé depuis hier
Tests flakyTests instables qui dépendent de l’ordre d’exécution, du timing, ou de données externes
Ressources partagéesPlusieurs tests utilisent la même base de données et se marchent dessus
Rate limitingLes API externes (GitHub, npm) bloquent parfois les requêtes trop fréquentes

Comment corriger

ActionPourquoi ça aide
Épingler toutes les dépendancesLe lockfile garantit les mêmes versions à chaque exécution
Isoler les testsChaque test crée ses propres données, pas de conflit possible
Mocker les API externesSimuler les réponses au lieu d’appeler les vrais services
Corriger les tests flakyLes 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.json

Le 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èmeConséquence concrète
IncohérenceUn projet scanne les vulnérabilités, un autre non — sans raison valable
Maintenance impossibleUne faille de sécurité dans la pipeline = 50 fichiers à corriger manuellement
DriftLes copies divergent au fil du temps, personne ne sait plus quelle version est « la bonne »
Pas de capitalisationUne é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 production

Pourquoi c’est problématique

ProblèmeExemple concret
Non-reproductibilitéL’artefact testé en staging ≠ l’artefact déployé en production
Bugs fantômesUn bug apparaît en production alors que « ça marchait en staging »
Temps perduChaque 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 change

Garantie : 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èmeImpact sur le développeur
Feedback tardif20 minutes pour découvrir qu’un simple bug casse tout
Gaspillage de ressourcesBuild et deploy exécutés pour rien si les tests échouent
Perte de contexteLe 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 fonction

Logique : 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ès
Runner B (ubuntu-22.04): ✗ échec ← Même code, résultat différent
Runner C (macos): ✗ échec

Causes fréquentes

CauseExemple concret
Outils non installésLa pipeline utilise jq qui est installé sur un runner mais pas les autres
Versions implicitespython 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’environnementUne variable est configurée sur un runner, absente sur les autres

Comment corriger

SolutionPourquoi ça fonctionne
ConteneuriserL’image Docker embarque tous les outils nécessaires, identiques partout
Expliciter les versionspython-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érequisSi un outil est nécessaire, l’installer explicitement dans la pipeline
# ✓ Explicite et portable
jobs:
test:
runs-on: ubuntu-latest
container: python:3.11-slim # ← Environnement contrôlé
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt

Rè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èmeRisque 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 invisiblePersonne ne sait combien de projets sont exemptés ni pourquoi

Comment corriger

MesureComment l’appliquer
Date d’expirationChaque exception expire automatiquement après 90 jours
Justification documentée« Pourquoi cette exception ? Quel est le plan pour la supprimer ? »
Revue régulièreAudit trimestriel : les exceptions non renouvelées sont supprimées
Alternative au bypass totalPlutô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.

Pipeline SPOF

Comment corriger

SPOF identifiéSolution
Runner uniqueCréer un pool de runners (au moins 2-3)
Maintainer uniqueDocumenter la pipeline, former au moins une autre personne
Pas d’alertesConfigurer 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