Aller au contenu

Les formes modernes de pipelines CI/CD

Mise à jour :

Imaginez deux restaurants. Le premier est un petit bistrot familial : une cuisine, un chef, une carte simple. Le second est une chaîne avec 50 établissements : cuisines centrales, logistique complexe, menus standardisés. Les deux font de la restauration, mais leur organisation n’a rien à voir.

C’est pareil pour les pipelines CI/CD. Une startup avec trois développeurs et un seul service n’a pas besoin de la même architecture qu’une banque avec 200 équipes et des centaines de microservices. Il n’existe pas de « meilleure » pipeline — seulement celle qui correspond à votre contexte.

Ce guide présente les grandes familles d’architectures de pipelines et leurs compromis. L’objectif : vous donner les clés pour choisir (ou faire évoluer) la vôtre.

Avant de commencer : de quoi parle-t-on ?

Quand on parle d’« architecture de pipeline », on parle de comment organiser l’ensemble de vos pipelines — pas du contenu d’une seule pipeline. Les questions clés sont :

  • Combien de pipelines avez-vous ? Une seule pour tout ? Une par service ? Une par branche ?
  • Comment se déclenchent-elles ? À chaque commit ? Seulement sur certaines branches ?
  • Comment les artefacts circulent-ils ? Reconstruits à chaque étape ? Promus d’un environnement à l’autre ?

Ces choix dépendent de trois facteurs principaux :

FacteurQuestions à se poser
Structure d’équipeCombien de développeurs ? Combien d’équipes ? Travaillent-elles sur le même code ?
Stratégie de branchesUtilisez-vous GitFlow avec plusieurs branches longues ? Ou tout le monde travaille sur main ?
Modèle de déploiementDéployez-vous une fois par mois ou 50 fois par jour ? Avez-vous plusieurs versions en production ?

Stratégies de branches : comment organiser votre code

La façon dont vous organisez vos branches Git influence directement la structure de vos pipelines. Deux grandes philosophies s’opposent.

Pipeline par branche (approche GitFlow)

Le principe : vous maintenez plusieurs branches « longues » qui vivent en parallèle pendant des semaines ou des mois. Chaque branche a un rôle précis et sa propre pipeline.

Analogie : c’est comme une usine avec plusieurs chaînes de production parallèles. Une chaîne fabrique les prototypes (branche develop), une autre prépare la prochaine version (branche release/1.2), une dernière produit ce qui part en magasin (branche main).

Pipeline par branche

Comment ça fonctionne concrètement :

  1. Les développeurs travaillent sur des branches feature/* (nouvelles fonctionnalités)
  2. Ces branches sont mergées dans develop — une branche d’intégration
  3. Quand on prépare une release, on crée une branche release/X.Y
  4. Après validation, la release est mergée dans main (production)
  5. Chaque branche longue a sa propre pipeline avec des comportements différents

Exemple de configuration :

BrancheCe que fait la pipeline
feature/*Tests unitaires, lint, analyse statique
developTests + déploiement en environnement de développement
release/*Tests complets + déploiement en staging + tests d’acceptance
mainDéploiement en production (souvent avec approbation manuelle)

Avantages :

  • Séparation claire : chaque environnement correspond à une branche, c’est facile à comprendre
  • Contrôle fin : vous décidez exactement ce qui va en production et quand
  • Versions parallèles : vous pouvez maintenir plusieurs versions en production (1.0, 1.1, 2.0)

Inconvénients :

  • Complexité : plusieurs pipelines à maintenir, qui peuvent diverger
  • Merges douloureux : plus les branches vivent longtemps, plus les conflits sont nombreux
  • Délai : entre l’écriture du code et sa mise en production, il peut se passer des semaines

Contexte idéal : équipes avec des cycles de release planifiés (tous les mois, tous les trimestres), produits avec plusieurs versions supportées en parallèle (logiciels installés chez les clients, applications mobiles).

Trunk-based development : tout sur une seule branche

Le principe : tout le monde travaille directement sur la branche principale (main ou trunk). Les branches de fonctionnalité existent, mais elles sont très courtes — quelques heures, pas quelques jours.

Analogie : c’est comme un journal quotidien. Chaque journaliste écrit son article, le fait relire rapidement, et il part à l’impression le jour même. Pas de « numéro spécial » préparé pendant des semaines.

Trunk-based development

Comment ça fonctionne concrètement :

  1. Un développeur crée une branche feature/xxx depuis main
  2. Il travaille quelques heures (rarement plus d’une journée)
  3. Il ouvre une Pull Request, qui est revue rapidement
  4. Dès que c’est mergé dans main, la pipeline se déclenche
  5. Si tous les tests passent, le code peut partir en production automatiquement

La clé : les feature flags

Comment déployer du code en production s’il n’est pas terminé ? Grâce aux feature flags (ou « feature toggles ») — des interrupteurs dans le code qui activent ou désactivent une fonctionnalité.

# Exemple simplifié de feature flag
if feature_flags.is_enabled("nouvelle_interface_paiement"):
afficher_nouvelle_interface()
else:
afficher_ancienne_interface()

Le code est en production, mais la fonctionnalité est « éteinte ». Quand elle est prête, on l’active pour quelques utilisateurs (canary), puis pour tout le monde.

Avantages :

  • Intégration continue vraie : le code est intégré plusieurs fois par jour, pas une fois par semaine
  • Pas de « merge hell » : les branches courtes = peu de conflits
  • Feedback rapide : vous savez en quelques heures si votre code fonctionne avec celui des autres

Inconvénients :

  • Discipline stricte : chaque commit sur main doit être de qualité production
  • Feature flags obligatoires : infrastructure supplémentaire à gérer
  • Couverture de tests excellente requise : sans tests fiables, c’est le chaos

Contexte idéal : équipes matures pratiquant le continuous deployment, produits SaaS où une seule version est en production.

Comparaison : quelle approche choisir ?

AspectPipeline par branche (GitFlow)Trunk-based
Fréquence de mergeJours ou semainesHeures
Nombre de pipelinesMultiple (une par type de branche)Une seule
Feature flagsOptionnelsObligatoires
Risque de conflit GitÉlevé (branches longues)Faible (branches courtes)
Temps jusqu’à la productionJours ou semainesMinutes ou heures
Complexité de la pipelineMoyenne à élevéeSimple
Besoin en testsImportantCritique

Comment choisir ?

  • Vous livrez une fois par mois ou moins → GitFlow est acceptable
  • Vous livrez plusieurs fois par semaine → considérez trunk-based
  • Vous avez plusieurs versions en production → GitFlow est souvent nécessaire
  • Vous faites du SaaS avec une seule version → trunk-based est plus adapté

Monorepo vs polyrepo : où mettre votre code

Au-delà des branches, une autre question structure vos pipelines : combien de dépôts Git avez-vous ?

Monorepo : tout dans un seul dépôt

Le principe : tous vos services, bibliothèques et outils partagent le même dépôt Git. Google, Meta et Microsoft utilisent cette approche pour une grande partie de leur code.

Analogie : c’est comme un grand entrepôt unique où tous les rayons (API, frontend, outils) sont sous le même toit. Facile de voir l’ensemble, mais il faut une bonne organisation.

monorepo/
├── services/
│ ├── api/ # Service backend
│ ├── frontend/ # Application web
│ └── worker/ # Traitement asynchrone
├── libs/
│ ├── auth/ # Bibliothèque d'authentification
│ └── utils/ # Utilitaires partagés
└── infra/
├── terraform/ # Infrastructure as Code
└── k8s/ # Manifests Kubernetes

Le défi : ne pas tout reconstruire à chaque commit

Si quelqu’un modifie le README du frontend, inutile de reconstruire l’API. La pipeline doit être « intelligente » et détecter ce qui a changé.

Commit modifie : services/api/src/users.py
Pipeline analyse les dépendances :
✓ services/api → rebuild (fichier modifié)
✓ libs/auth → vérifier (l'API l'utilise peut-être)
✗ services/frontend → skip (pas impacté)
✗ services/worker → skip (pas impacté)

Avantages :

  • Refactoring atomique : une seule PR peut modifier l’API, le frontend et les libs — tout reste cohérent
  • Partage de code naturel : les bibliothèques communes sont dans le même repo, pas besoin de les publier
  • Cohérence des versions : tous les services utilisent la même version des libs

Inconvénients :

  • Pipeline complexe : détecter ce qui a changé demande de l’outillage (Nx, Turborepo, Bazel)
  • Temps de clone élevé : le repo peut devenir très gros
  • Permissions compliquées : difficile de restreindre l’accès à certaines parties

Outils courants pour monorepo :

OutilLangage/ÉcosystèmePoint fort
NxJavaScript/TypeScriptExcellent pour les projets frontend
TurborepoJavaScript/TypeScriptSimple, rapide, cache distribué
BazelPolyglotteTrès puissant, mais courbe d’apprentissage raide
PantsPython, Go, JavaBonne alternative à Bazel

Polyrepo : un dépôt par service

Le principe : chaque service, chaque bibliothèque a son propre dépôt Git et sa propre pipeline.

Analogie : c’est comme des boutiques indépendantes dans une rue commerçante. Chacune gère ses stocks, ses horaires, sa décoration. Autonome, mais la coordination demande des efforts.

github.com/acme/api # Son propre repo, sa propre pipeline
github.com/acme/frontend # Idem
github.com/acme/worker # Idem
github.com/acme/libs-auth # Publié comme package (npm, PyPI...)
github.com/acme/infra # Terraform séparé

Avantages :

  • Pipeline simple : chaque repo a une pipeline qui fait toujours la même chose
  • Équipes autonomes : l’équipe API peut déployer sans attendre l’équipe frontend
  • Permissions claires : facile de donner accès à un repo sans exposer le reste

Inconvénients :

  • Coordination des versions : l’API v2.3 est-elle compatible avec le frontend v1.8 ?
  • Duplication : la configuration de pipeline est souvent copiée-collée entre repos
  • Refactoring pénible : modifier une interface partagée = plusieurs PRs dans plusieurs repos

Le problème du « dependency hell » :

En polyrepo, les bibliothèques partagées sont publiées comme des packages (npm, PyPI, Maven…). Cela crée des questions :

  • Quand libs-auth publie une nouvelle version, qui la met à jour dans chaque service ?
  • Comment s’assurer que tous les services sont compatibles ?
  • Que faire si un service est bloqué sur une vieille version ?

Stratégie hybride : le meilleur des deux mondes ?

Beaucoup d’organisations adoptent un compromis pragmatique :

Type de codeStratégiePourquoi
Services métierMonorepoRefactoring atomique, cohérence
InfrastructureRepo séparéCycle de vie différent, permissions distinctes
Libs très partagéesRepos séparés + versioningRéutilisation hors de l’organisation

Exemple concret :

github.com/acme/platform # Monorepo : API + frontend + workers
github.com/acme/infra # Séparé : Terraform, secrets
github.com/acme/design-system # Séparé : composants UI réutilisables, versionnés

Promotion vs rebuild : que déployer exactement ?

Une question cruciale que beaucoup d’équipes négligent : l’artefact que vous déployez en production est-il le même que celui que vous avez testé ?

Le problème du rebuild

Situation courante (et risquée) :

# Pipeline staging
staging:
script:
- npm install
- npm run build
- docker build -t app:staging .
- docker push registry/app:staging
- kubectl apply -f k8s/staging/
# Pipeline production (quelques jours plus tard)
production:
script:
- npm install # ⚠️ Versions des dépendances peuvent avoir changé
- npm run build # ⚠️ Résultat potentiellement différent
- docker build -t app:production .
- docker push registry/app:production
- kubectl apply -f k8s/production/

Pourquoi c’est un problème ?

Entre le test en staging et le déploiement en production :

  • Une dépendance npm a peut-être été mise à jour (même en patch)
  • L’image de base Docker a peut-être changé
  • Un outil de build a peut-être évolué

Résultat : ce que vous déployez en production n’est pas exactement ce que vous avez testé. Et quand ça casse, impossible de savoir pourquoi.

La solution : promotion d’artefact (build once, deploy many)

Le principe : construire l’artefact une seule fois, puis le promouvoir d’environnement en environnement sans le reconstruire.

Promotion d'artefact

Comment ça fonctionne :

  1. Build : l’artefact est construit avec un tag unique (ex: app:abc123)
  2. Test en dev : on déploie app:abc123 en développement
  3. Test en staging : on déploie le même app:abc123 en staging
  4. Production : on déploie toujours le même app:abc123 en production

La seule chose qui change entre environnements : la configuration (variables d’environnement, secrets).

Exemple concret :

# Étape 1 : Build unique
build:
script:
- docker build -t app:$CI_COMMIT_SHA .
- docker push registry/app:$CI_COMMIT_SHA
# Étape 2 : Déploiement en staging (même image)
staging:
script:
- helm upgrade app ./chart \
--set image.tag=$CI_COMMIT_SHA \
--set env=staging \
-f values-staging.yaml
# Étape 3 : Déploiement en production (toujours la même image)
production:
script:
- helm upgrade app ./chart \
--set image.tag=$CI_COMMIT_SHA \
--set env=production \
-f values-production.yaml
when: manual # Approbation requise

Avantages de la promotion :

AspectRebuildPromotion
Reproductibilité❌ Incertaine✅ Garantie
Temps de déploiementLong (rebuild)Rapide (juste déployer)
RollbackRebuild l’ancienne versionRedéployer l’ancien tag
Confiance« Ça marchait en staging… »« C’est le même artefact »

Prérequis pour la promotion :

Pour que ça fonctionne, votre application doit :

  1. Séparer le build de la config : pas de valeurs en dur (URLs, credentials)
  2. Accepter la configuration au runtime : variables d’environnement, fichiers de config montés
  3. Avoir un registre d’artefacts fiable : où stocker les images entre les étapes

Pipeline applicative vs pipeline infrastructure

Jusqu’ici, on a surtout parlé des pipelines qui déploient du code applicatif (API, frontend). Mais l’infrastructure aussi peut (et devrait) être gérée par des pipelines.

Pipeline applicative

Ce qu’elle déploie : le code de votre application — API, frontend, workers, jobs.

Pipeline applicative

Caractéristiques :

  • Déclenchée par des commits sur le code applicatif
  • Fréquence élevée (plusieurs fois par jour possible)
  • Impact limité (un service à la fois, généralement)

Pipeline infrastructure

Ce qu’elle déploie : les ressources sur lesquelles tourne votre application — réseaux, bases de données, clusters, DNS.

Pipeline infrastructure

Caractéristiques :

  • Déclenchée par des modifications d’Infrastructure as Code (Terraform, Pulumi)
  • Fréquence plus faible (quelques fois par semaine)
  • Impact potentiellement large (peut affecter plusieurs services)

Faut-il séparer ou intégrer ?

ApprocheAvantagesInconvénients
Pipelines séparéesCycles indépendants, permissions distinctes, blast radius limitéCoordination manuelle parfois nécessaire
Pipeline uniqueDéploiement atomique (infra + app ensemble), cohérence garantieComplexité, risque élevé si échec

Recommandation : séparez les pipelines, mais coordonnez-les.

L’infrastructure change moins souvent que le code. Mélanger les deux crée des pipelines lourdes où un changement de couleur de bouton déclenche une vérification Terraform.

Comment coordonner ?

  • Triggers : la pipeline infra déclenche la pipeline app après succès
  • GitOps : des outils comme ArgoCD ou Flux détectent les changements et déploient
  • Dépendances explicites : la pipeline app vérifie que l’infra est à jour avant de déployer

Architectures avancées

Une fois les bases maîtrisées, certaines équipes adoptent des patterns plus sophistiqués.

Pipeline en diamant : parallélisation maximale

Le principe : exécuter un maximum de jobs en parallèle, puis reconverger avant l’étape suivante.

Pourquoi « diamant » ? La forme du graphe ressemble à un diamant : un point de départ, une divergence (parallélisation), une convergence, puis à nouveau divergence/convergence.

Pipeline en diamant

Avantages :

  • Temps total réduit : lint, tests et scan s’exécutent en même temps
  • Feedback rapide : si le lint échoue, on le sait sans attendre la fin des tests
  • Ressources optimisées : les runners travaillent en parallèle

Attention : tous les jobs parallèles doivent réussir pour passer à l’étape suivante. Un échec = pipeline bloquée.

Pipeline avec matrix : tester toutes les combinaisons

Le problème : votre bibliothèque doit fonctionner avec Python 3.9, 3.10, 3.11 et 3.12, sur Ubuntu, macOS et Windows. Ça fait 12 combinaisons à tester.

La solution : la matrix génère automatiquement tous les jobs.

test:
strategy:
matrix:
python: [3.9, 3.10, 3.11, 3.12]
os: [ubuntu-latest, macos-latest, windows-latest]
# Résultat : 4 versions × 3 OS = 12 jobs parallèles

Cas d’usage :

  • Bibliothèques open source (compatibilité multi-versions)
  • Applications desktop (multi-OS)
  • Validation de compatibilité avant une mise à jour de dépendance

Pipeline conditionnelle : adapter selon le contexte

Le principe : certains jobs ne s’exécutent que si certaines conditions sont remplies.

Exemples courants :

ConditionComportement
Branche = mainDéployer en staging, puis production (avec approbation)
Branche = feature/*Déployer en environnement éphémère, détruire après 24h
Fichiers modifiés dans docs/Skipper les tests, rebuilder uniquement la documentation
Tag créé (v*)Créer une release, publier les packages

Pourquoi c’est utile ?

  • Économie de ressources : pas besoin de tout tester si seule la doc a changé
  • Feedback adapté : une feature branch n’a pas besoin de déployer en production
  • Flexibilité : comportements différents selon le contexte sans multiplier les pipelines

Choisir son architecture : guide de décision

Face à toutes ces options, comment choisir ? Voici un guide basé sur des questions concrètes.

Question 1 : Combien d’équipes travaillent sur le code ?

SituationRecommandation
1 équipe (< 10 personnes)Monorepo simple, trunk-based possible
2-5 équipesMonorepo avec outillage (Nx, Turborepo) ou polyrepo léger
10+ équipesPolyrepo probable, ou monorepo avec infrastructure dédiée

Question 2 : Quelle fréquence de déploiement ?

FréquenceRecommandation
Mensuelle ou moinsGitFlow acceptable, promotion d’artefact recommandée
HebdomadaireTrunk-based à considérer, promotion obligatoire
Quotidienne ou plusTrunk-based + feature flags + continuous deployment

Question 3 : Avez-vous plusieurs versions en production ?

SituationRecommandation
Non (SaaS, une seule version)Trunk-based, pipeline unique
Oui (logiciel installé, app mobile)Branches de release, pipelines par branche

Question 4 : Quelle est votre maturité en tests ?

NiveauRecommandation
Peu ou pas de tests automatisésRestez sur des pipelines simples, investissez dans les tests d’abord
Couverture correcte (> 60%)Trunk-based envisageable avec prudence
Excellente couverture + tests d’intégrationContinuous deployment possible

L’anti-pattern : copier sans comprendre

« Netflix fait du trunk-based avec continuous deployment, on devrait faire pareil ! »

Non. Netflix a :

  • Des centaines d’ingénieurs dédiés à l’outillage
  • Des années d’itération sur leurs pratiques
  • Une infrastructure de tests et de feature flags mature
  • Un contexte SaaS avec une seule version en production

Copier leur architecture sans leur contexte, c’est comme copier l’entraînement d’un athlète olympique sans avoir sa condition physique.

La bonne approche :

  1. Commencez simple : une pipeline linéaire qui build, teste, déploie
  2. Identifiez les douleurs : qu’est-ce qui vous ralentit vraiment ?
  3. Évoluez progressivement : ajoutez de la complexité quand vous en avez besoin
  4. Mesurez : temps de pipeline, fréquence de déploiement, taux d’échec

À retenir

  1. L’architecture de pipeline reflète votre contexte — structure d’équipe, stratégie de branches, modèle de déploiement. Il n’y a pas de solution universelle.

  2. Trunk-based vs GitFlow — branches courtes = moins de conflits et feedback rapide, mais demande discipline et feature flags. Branches longues = plus de contrôle, mais merges douloureux.

  3. Monorepo vs polyrepo — monorepo facilite le refactoring et la cohérence, mais demande de l’outillage. Polyrepo donne de l’autonomie, mais complique la coordination.

  4. Promotion > rebuild — construisez l’artefact une fois, déployez-le partout. Ce que vous testez doit être exactement ce que vous déployez.

  5. Séparez app et infra — des cycles différents, des permissions différentes, des risques différents. Coordonnez, mais ne mélangez pas.

  6. Commencez simple, complexifiez quand nécessaire — la pipeline parfaite n’existe pas au jour 1. Itérez en fonction des problèmes réels.

Pour aller plus loin

  • Qu’est-ce qu’une pipeline CI/CD ? — Les fondamentaux avant d’aller plus loin
  • CI vs CD vs CD — Comprendre les différences entre intégration, livraison et déploiement continus
  • Bonnes pratiques CI/CD — Les invariants qui marchent partout
  • Anti-patterns CI/CD — Les erreurs classiques à éviter