CaenHackYouAcademy
DevSecOps

Anti-patrons CI/CD

Les erreurs récurrentes, et pourquoi elles se répètent.

Un anti-patron est une solution qui semble bonne sur le moment mais qui crée des problèmes à long terme. Le gain est immédiat et visible, le coût est différé et diffus : c'est cette asymétrie qui le rend piégeant. On l'adopte pour livrer plus vite aujourd'hui, on le paie en maintenance et en incidents pendant des mois. Appliqués aux pipelines, les anti-patrons ralentissent le retour aux développeurs, compliquent la maintenance, créent des bugs difficiles à reproduire et accumulent de la dette technique.

En bref

Les pipelines les plus problématiques ne sont pas celles qui échouent bruyamment, mais celles qui semblent fonctionner tout en accumulant une dette invisible : temps de construction qui s'allonge, tests instables qu'on ignore, exceptions qui s'empilent. L'objectif de ce cours n'est pas de faire honte, mais de reconnaître des situations vécues.

La pipeline monolithe

Une pipeline monolithe enchaîne toutes les étapes du cycle dans une seule définition, du style au déploiement en production. Chaque exécution paie le coût de l'ensemble, même quand la modification ne concerne qu'une étape, et une défaillance sur n'importe quel maillon bloque tout le reste.

Symptôme

Une seule pipeline fait tout : style, tests, construction, analyse de sécurité, déploiement sur tous les environnements. Elle dure 45 minutes. Personne n'ose y toucher.

ProblèmeImpact concret
Retour lent47 minutes pour apprendre qu'une faute de frappe casse la construction
Fragilitéun changement dans une étape peut casser toutes les autres
Pas de parallélisationles étapes s'exécutent une par une, même quand elles sont indépendantes
Débogage difficileen cas d'échec, il faut chercher dans 500 lignes de configuration

Quatre symptômes, une seule cause : l'absence de découpage.

La correction consiste à découper en pipelines indépendantes, chacune avec un objectif unique, capable d'échouer sans bloquer les autres.

Découpée, la chaîne signale un défaut de style en deux minutes au lieu de quarante-sept.

Pipeline CI (5 min)             vérifie que le code est correct
├── lint                        style du code
├── tests unitaires             fonctions prises isolément
└── build                       compilation de l'application

Pipeline CD-staging (3 min)     déploie pour les tests
├── deploy staging
└── tests e2e                   exerce l'application via son interface

Pipeline CD-prod (2 min)        déploie pour les vrais utilisateurs
├── deploy production
└── smoke tests                 vérifie les fonctions essentielles

La pipeline non déterministe

Une pipeline déterministe donne toujours le même résultat avec les mêmes entrées. La phrase révélatrice de l'inverse : « relance, ça va passer ».

Exécution 1 : succès
Exécution 2 : échec (délai de test dépassé)     pourquoi maintenant ?
Exécution 3 : succès
Exécution 4 : échec (dépendance introuvable)
Exécution 5 : succès                            rien n'a changé pourtant
CauseExplication
Dépendances non épingléesnpm install récupère la dernière version, qui a pu changer depuis hier
Tests instablestests dépendant de l'ordre d'exécution, du minutage ou de données externes
Ressources partagéesplusieurs tests utilisent la même base de données et se marchent dessus
Limitation de débitles API externes bloquent les requêtes trop fréquentes

Le message d'échec oriente : « module not found » pointe les dépendances, un délai dépassé intermittent les tests instables.

ActionPourquoi elle aide
Épingler toutes les dépendancesle fichier de verrouillage garantit les mêmes versions à chaque exécution
Isoler les testschaque test crée ses propres données, aucun conflit possible
Simuler les API externeson n'appelle plus de vrais services
Corriger les tests instablesles ignorer ne fait que repousser le problème

La première action rapporte le plus vite : elle tient en un changement de commande.

{Une commande de différence, tout le déterminisme en jeu.}
# À ÉVITER : recupere les dernieres versions disponibles
- run: npm install

# CORRECT : installe exactement les versions de package-lock.json
- run: npm ci

Relancer un test instable n'est pas le réparer

Beaucoup d'équipes « résolvent » les tests instables en les relançant automatiquement jusqu'à ce qu'ils passent. Le test finit vert, mais le défaut qu'il signalait est toujours là : vous avez désactivé l'alerte, pas corrigé la cause. Le jour où le même aléa frappe en production, plus aucun signal ne prévient.

Le copier-coller sans gouvernance

Une pipeline qui fonctionne se duplique de projet en projet, puis diverge au gré des ajustements locaux. Chaque copie devient un exemplaire unique, sans source commune : une correction ne se propage plus, et un défaut recopié se répand avec le modèle.

projet-a/.github/workflows/ci.yml    (version d'origine)
projet-b/.github/workflows/ci.yml    (copiée, modifiée)
projet-c/.github/workflows/ci.yml    (copiée de B, re-modifiée)
projet-d/.github/workflows/ci.yml    (copiée de C, avec les défauts de C)
ProblèmeConséquence concrète
Incohérenceun projet scanne les vulnérabilités, un autre non, sans raison valable
Maintenance impossibleune faille dans la pipeline : cinquante fichiers à corriger à la main
Dériveles copies divergent, personne ne sait plus laquelle fait référence
Pas de capitalisationune équipe améliore sa pipeline, les autres n'en profitent pas

La dérive est le mécanisme silencieux qui rend la correction si risquée.

La correction : centraliser la logique dans un workflow réutilisable (workflow_call sur GitHub Actions) ou un modèle partagé. Une seule définition fait autorité, chaque projet la référence par version, et une correction s'y propage sans copier-coller.

{Deux cents lignes copiées deviennent cinq lignes d'appel.}
jobs:
  ci:
    uses: org/shared-workflows/.github/workflows/ci.yml@v1
    with:
      language: python
      python-version: "3.11"

La reconstruction à chaque environnement

L'artefact est reconstruit pour chaque environnement. La phrase révélatrice : « ça marchait en staging ! ». Le principe et sa correction sont détaillés au chapitre ce qui compte ici, c'est de savoir reconnaître le symptôme.

ProblèmeExemple concret
Non-reproductibilitél'artefact testé en staging n'est pas celui déployé en production
Bugs fantômesun défaut apparaît en production alors que « ça marchait en staging »
Temps perduchaque déploiement recompile tout, même quand rien n'a changé

Le « ça marchait en staging » est littéralement vrai : vous avez testé un artefact, vous en déployez un autre.

{Un artefact unique, identifié par son empreinte, promu tel quel.}
build:
  npm run build
  docker push app:abc123     # artefact unique

staging:
  docker pull app:abc123     # exactement le meme artefact
  ENV=staging deploy         # seule la configuration change

production:
  docker pull app:abc123     # toujours le meme artefact
  ENV=production deploy      # seule la configuration change

Les tests en fin de chaîne

Placer les tests les plus coûteux à la toute fin retarde le signal d'échec jusqu'après la construction et le déploiement. Le développeur apprend la régression longtemps après avoir changé de sujet, et la remise en contexte coûte souvent plus cher que la correction.

lint (1 s) -> build (3 min) -> deploy (2 min) -> tests e2e (15 min)
                                                        |
                                                        +-- échec détecté
                                                            après 20 minutes
ProblèmeImpact sur le développeur
Retour tardifvingt minutes pour découvrir qu'un défaut simple casse tout
Gaspillage de ressourcesconstruction et déploiement exécutés pour rien
Perte de contexteil a commencé autre chose, il doit se replonger dans le code

Placer les tests lents en dernier ne gêne pas quand ils passent — seulement quand ils échouent.

La correction est la pyramide des tests, vue au chapitre : les tests rapides d'abord, les lents ensuite, et seulement si les premiers passent.

lint (1 s) --+-- tests unitaires (30 s) --+-- build (3 min)
             |                            |
             +-- échec rapide             +-- tests e2e (seulement si tout passe)

La dépendance à l'environnement

La pipeline fonctionne sur un runner et échoue ailleurs : c'est le « ça marche sur ma machine », version intégration continue.

Runner A (ubuntu-20.04) : succès
Runner B (ubuntu-22.04) : échec      même code, résultat différent
Runner C (macOS)        : échec
CauseExemple concret
Outils non installésla pipeline utilise jq, installé sur un runner et pas sur les autres
Versions implicitespython lance 3.8 sur un runner, 3.11 sur un autre
Chemins en dur/home/runner/specific/path n'existe pas partout
Variables d'environnementdéfinies sur un runner, absentes ailleurs

Quatre choses que la pipeline suppose au lieu de les déclarer — les versions implicites sont les plus sournoises.

SolutionPourquoi elle fonctionne
Conteneuriserl'image embarque tous les outils, 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 au runner
Installer les prérequissi un outil est nécessaire, l'installer explicitement

Règle d'or : ce qui n'est pas explicitement défini dans la pipeline peut changer sans prévenir.

{Environnement contrôlé, permissions minimales, action épinglée par empreinte.}
permissions: {}                    # aucun droit par defaut
jobs:
  test:
    runs-on: ubuntu-24.04
    permissions:
      contents: read               # le strict necessaire
    container: python:3.11-slim    # environnement controle
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
        with:
          persist-credentials: false
      - run: pip install -r requirements.txt

Les exceptions permanentes

Une dérogation accordée pour débloquer une livraison n'a presque jamais de date de fin. Faute d'échéance et de réexamen, elle survit à l'urgence qui l'a justifiée, et le périmètre réellement contrôlé se réduit sans que personne ne l'ait décidé.

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à
ProblèmeRisque concret
Fausse sécuritéles tableaux de bord sont verts, mais les projets exemptés passent sous le radar
Précédent dangereux« eux ont une exception, pourquoi pas nous ? »
Dette invisiblepersonne ne sait combien de projets sont exemptés, ni pourquoi

Vous croyez couvrir cent pour cent du parc vous en couvrez peut-être soixante.

MesureComment l'appliquer
Date d'expirationchaque exception expire automatiquement après 90 jours
Justification documentéepourquoi cette exception, et quel est le plan pour la supprimer ?
Revue régulièreaudit trimestriel : les exceptions non renouvelées disparaissent
Alternative au contournement totalignorer une vulnérabilité précise plutôt que le scan entier

La date d'expiration inverse la charge de la preuve : il faut désormais justifier le renouvellement.

Plutôt que d'exempter un projet

Configurez le scan pour ignorer une vulnérabilité identifiée, avec sa justification : « CVE-2023-XXXX ignorée car non applicable à notre usage — aucune entrée utilisateur sur ce chemin ».

La pipeline à point unique de défaillance

Une seule pipeline, sur un seul runner, avec une seule personne qui la comprend. Si l'un de ces éléments tombe, tout s'arrête.

Point uniqueParade
Runner uniqueconstituer un groupe de runners, deux ou trois au minimum
Mainteneur uniquedocumenter la pipeline, former au moins une autre personne
Absence d'alertenotifier quand un runner devient indisponible

Le point unique le plus souvent oublié n'est pas une machine : c'est une personne.

Audit et synthèse

Liste d'audit, un anti-patron par ligne

  • Retour rapide — moins de 15 min pour le premier signal.

  • Déterminisme — plus aucun « relance, ça va passer ».

  • Gouvernance — des modèles centralisés, pas du copier-coller.

  • Construction unique — un artefact construit une seule fois.

  • Pyramide des tests — les tests rapides s'exécutent en premier.

  • Exceptions traçables — aucune exception sans date d'expiration.

  • Pas de point unique — plus d'une personne connaît la pipeline.

    Traitez d'abord le retour rapide et le déterminisme : ce sont les deux points qui libèrent le plus de temps pour l'équipe. Refaites cet audit chaque trimestre — les anti-patrons s'installent progressivement.

À retenir de cette séance

Un anti-patron crée plus de problèmes qu'il n'en résout. La pipeline monolithe ralentit le retour : découpez. La pipeline non déterministe érode la confiance : épinglez. Le copier-coller multiplie la maintenance : centralisez. Construisez une fois, déployez partout. Les tests rapides d'abord : échouer en trente secondes plutôt qu'en vingt minutes. Les exceptions temporaires deviennent permanentes : datez-les. Éliminez les points uniques, machines comme personnes.

VérificationQuestion 1 / 3

Réponse unique

Une pipeline échoue une fois sur cinq sans qu'aucun code n'ait changé. De quel anti-patron s'agit-il ?

Indice · Même entrée, résultat différent : comment appelle-t-on cela ?

On this page