Deux équipes peuvent utiliser le même outil de CI et obtenir des résultats opposés. Chez l'une, chaque exécution part du même état, un échec se localise en quelques secondes et le résultat ne dépend pas de la machine qui a joué le job. Chez l'autre, les exécutions divergent sans raison apparente, les erreurs remontent des heures plus tard et personne ne sait reproduire la panne.
L'écart ne vient pas de l'outil mais des principes de conception appliqués au pipeline : reproductibilité, rapidité du retour, isolation des étapes.
Certaines chaînes fonctionnent pendant des années sans intervention. D'autres s'effondrent au moindre changement. La différence tient rarement aux outils (GitHub Actions, GitLab CI, Jenkins...), elle tient aux principes appliqués.
Ce guide présente les bonnes pratiques qui distinguent une pipeline robuste d'une pipeline fragile. Ces principes sont universels : ils s'appliquent quel que soit l'outil que vous utilisez.
Pourquoi ces pratiques sont-elles importantes ?
Section intitulée « Pourquoi ces pratiques sont-elles importantes ? »Une pipeline CI/CD n'est pas un script qu'on écrit une fois et qu'on oublie. C'est un système critique qui :
- Valide chaque modification de code avant qu'elle n'atteigne les utilisateurs
- Automatise les déploiements, parfois des dizaines par jour
- Détient des secrets (clés d'API, credentials de production)
- Impacte la productivité de toute l'équipe quand elle dysfonctionne
Une mauvaise pipeline, c'est :
- Des heures perdues à attendre des résultats
- Des bugs qui passent en production parce que « les tests étaient verts »
- Des déploiements qui échouent le vendredi soir
- Des développeurs qui contournent la pipeline parce qu'elle est trop lente
Une bonne pipeline, c'est :
- Un feedback en minutes, pas en heures
- La certitude que ce qui est testé = ce qui est déployé
- Des déploiements prévisibles et reproductibles
- Une équipe qui fait confiance à son outillage
En résumé : Une bonne pipeline est prévisible, rapide et autonome. Elle produit le même résultat à chaque exécution, donne un feedback en minutes, et ne dépend pas d'un humain pour fonctionner.
Build once, deploy many : construire une fois, déployer partout
Section intitulée « Build once, deploy many : construire une fois, déployer partout »Le principe : l'artefact (image Docker, binaire, bundle JS...) est construit une seule fois, puis déployé tel quel dans chaque environnement (dev, staging, production).
Le problème du rebuild
Section intitulée « Le problème du rebuild »Voici un pattern très courant... et très risqué :
# ❌ PROBLÈME : on reconstruit pour chaque environnement
staging: script: - npm install # Télécharge les dépendances - npm run build # Build n°1 - docker build -t app:staging . - docker push registry/app:staging
production: script: - npm install # Re-télécharge (versions peut-être différentes !) - npm run build # Build n°2 (potentiellement différent !) - docker build -t app:production . - docker push registry/app:productionQu'est-ce qui peut mal tourner ?
Entre le build staging et le build production (quelques jours plus tard) :
- Une dépendance npm a été mise à jour (même en patch version)
- L'image de base Docker a changé
- Un outil de build a évolué
- Une variable d'environnement système est différente
Résultat : l'artefact en production n'est pas celui que vous avez testé en staging. Et quand ça casse, vous entendez : « Mais ça marchait en staging ! »
La solution : promotion d'artefact
Section intitulée « La solution : promotion d'artefact »# ✅ SOLUTION : un seul build, promu d'environnement en environnement
build: script: - npm install - npm run build - docker build -t registry/app:$COMMIT_SHA . # Tag unique par commit - docker push registry/app:$COMMIT_SHA
staging: script: - docker pull registry/app:$COMMIT_SHA # Même artefact - helm upgrade app ./chart --set image.tag=$COMMIT_SHA -f values-staging.yaml
production: script: - docker pull registry/app:$COMMIT_SHA # Toujours le même artefact - helm upgrade app ./chart --set image.tag=$COMMIT_SHA -f values-production.yamlCe qui change entre les environnements : uniquement la configuration (variables d'environnement, secrets, URLs de base de données).
Ce qui ne change jamais : l'artefact lui-même.
Pourquoi c'est important
Section intitulée « Pourquoi c'est important »Le tableau oppose les deux approches sur les critères qui pèsent en incident. Lisez d'abord la ligne Rollback : avec un rebuild, revenir en arrière suppose de reconstruire une ancienne version, donc de rejouer un build qui peut échouer au pire moment. Avec la promotion d'artefact, le rollback se résume à redéployer un tag déjà présent dans le registre, opération quasi instantanée.
| Aspect | Rebuild par environnement | Promotion d'artefact |
|---|---|---|
| Reproductibilité | ❌ Incertaine | ✅ Garantie |
| Confiance | « Ça marchait en staging... » | « C'est le même artefact » |
| Temps de déploiement | Long (rebuild complet) | Rapide (juste déployer) |
| Rollback | Rebuild l'ancienne version (risqué) | Redéployer l'ancien tag (instantané) |
Comment l'implémenter
Section intitulée « Comment l'implémenter »Pour que la promotion fonctionne, votre application doit :
-
Séparer le code de la configuration
- ❌
const API_URL = "https://prod.example.com"en dur dans le code - ✅
const API_URL = process.env.API_URLlu au démarrage
- ❌
-
Accepter la configuration au runtime
- Variables d'environnement
- Fichiers de configuration montés
- Secrets injectés par le système d'orchestration
-
Tagger les artefacts de manière unique
- ❌
app:staging,app:production(on ne sait pas quel code c'est) - ✅
app:abc123def456(SHA du commit = traçabilité totale)
- ❌
Immutabilité : ce qui est créé ne change jamais
Section intitulée « Immutabilité : ce qui est créé ne change jamais »Le principe : un artefact, une fois créé, ne doit jamais être modifié. Si un changement est nécessaire, on crée un nouvel artefact.
Pourquoi l'immutabilité est critique
Section intitulée « Pourquoi l'immutabilité est critique »Imaginez ce scénario :
- Lundi : vous déployez l'image
app:latesten production, tout fonctionne - Mardi : un collègue rebuild
app:latestavec une correction - Mercredi : un autre service redémarre et pull
app:latest - Jeudi : ce service ne fonctionne plus, mais personne ne comprend pourquoi
Le problème : app:latest de lundi ≠ app:latest de mardi. Le tag est le
même, mais le contenu a changé. C'est l'absence d'immutabilité.
Ce qui casse l'immutabilité
Section intitulée « Ce qui casse l'immutabilité »Ces quatre pratiques ont un point commun : elles laissent le contenu d'un artefact évoluer alors que son identifiant reste figé. Le cas le plus fréquent est le premier, les tags mutables comme latest : deux docker pull latest à une semaine d'écart peuvent ramener des images différentes, et plus rien ne relie ce qui tourne au code qui l'a produit.
| Pratique | Problème |
|---|---|
Tags mutables (latest, stable, main) | Le contenu change, le nom reste |
| Modification d'un artefact après création | Impossible de savoir ce qui tourne vraiment |
| Scripts qui patchent en production | État différent de ce qui est versionné |
| Déploiement sans tag explicite | « Quelle version est en prod ? », « Euh... » |
Ce qui préserve l'immutabilité
Section intitulée « Ce qui préserve l'immutabilité »À l'inverse, ces mécanismes garantissent qu'un identifiant pointe pour toujours vers le même contenu. Le schéma illustre le principe : chaque SHA de commit ouvre une image distincte, une correction produit un nouvel artefact plutôt que d'écraser l'ancien. Le tableau qui suit décline ce principe en pratiques concrètes, du tag par empreinte à la protection des tags côté registre.
Registre d'images avec tags immutables
abc123 ─────▶ image v1.2.3 (ne change jamais)def456 ─────▶ image v1.2.4 (nouvel artefact)ghi789 ─────▶ image v1.2.5 (nouvel artefact)| Pratique | Bénéfice |
|---|---|
Tags par SHA de commit (app:abc123def) | Traçabilité totale : code → artefact |
| Versioning sémantique avec releases figées | v1.2.3 pointe toujours vers le même contenu |
| Infrastructure as Code | La config est versionnée, pas modifiée à la main |
| Registre avec protection des tags | Impossible d'écraser un tag existant |
En pratique
Section intitulée « En pratique »La règle se ramène à une question : si je rejoue ce pipeline dans six mois, obtiendrai-je le même artefact ? Un tag mobile répond non, une empreinte répond oui.
# ❌ Tag mutable : dangereuxdocker build -t app:latest .docker push app:latest
# ✅ Tag immutable : sûrdocker build -t app:$CI_COMMIT_SHA .docker push app:$CI_COMMIT_SHA
# Pour la lisibilité, on peut ajouter un tag sémantique# MAIS le SHA reste la référence de véritédocker tag app:$CI_COMMIT_SHA app:v1.2.3docker push app:v1.2.3Idempotence : même input, même output, toujours
Section intitulée « Idempotence : même input, même output, toujours »Le principe : exécuter la même pipeline sur le même commit doit produire exactement le même résultat, que ce soit la première ou la dixième fois.
Même commit (abc123), trois exécutions différentes
Run 1 (lundi) : commit abc123 → artefact xyz789Run 2 (mercredi) : commit abc123 → artefact xyz789 ✅ identiqueRun 3 (vendredi) : commit abc123 → artefact xyz789 ✅ identiquePourquoi c'est important
Section intitulée « Pourquoi c'est important »Sans idempotence :
- Debugging impossible : « Ça marchait hier, pourquoi ça casse aujourd'hui ? »
- Rollback incertain : rebuilder l'ancienne version ne donne pas le même résultat
- Tests non fiables : le même test passe ou échoue aléatoirement
Ce qui casse l'idempotence
Section intitulée « Ce qui casse l'idempotence »Chaque ligne du tableau introduit une source de non-déterminisme : quelque chose qui varie d'une exécution à l'autre alors que le commit, lui, n'a pas bougé. Repérez la première cause, les dépendances non épinglées : c'est la plus courante et la plus vicieuse, car un npm install sans lockfile peut ramener une version différente sans qu'aucun de vos fichiers n'ait changé.
| Cause | Exemple | Problème |
|---|---|---|
| Dépendances non épinglées | npm install sans lockfile | Une dépendance est mise à jour entre deux builds |
| Timestamps dans l'artefact | Date de build dans le binaire | L'artefact change à chaque build |
| Données externes | Tests qui appellent une vraie API | L'API peut répondre différemment |
| État global modifié | Script qui modifie une variable globale | Le deuxième run voit un état différent |
| Ordre non déterministe | Tests parallèles mal isolés | Les tests s'influencent mutuellement |
Ce qui préserve l'idempotence
Section intitulée « Ce qui préserve l'idempotence »1. Épingler toutes les dépendances
# ❌ Non déterministe : peut changer demainnpm installpip install requests
# ✅ Déterministe : toujours les mêmes versionsnpm ci # Utilise package-lock.jsonpip install -r requirements.txt --no-deps # Versions exactesLes fichiers de lock à committer :
| Écosystème | Fichier de lock |
|---|---|
| Node.js (npm) | package-lock.json |
| Node.js (yarn) | yarn.lock |
| Python (pip) | requirements.txt avec versions exactes |
| Python (poetry) | poetry.lock |
| Go | go.sum |
| Rust | Cargo.lock |
2. Éviter les timestamps et données variables
# ❌ La date change à chaque buildRUN echo "Built on $(date)" > /app/version.txt
# ✅ Utiliser des informations déterministesARG COMMIT_SHARUN echo "Commit: $COMMIT_SHA" > /app/version.txt3. Isoler les tests
# ❌ Test qui dépend d'un état globaldef test_user_count(): assert User.count() == 5 # Et si un autre test a créé des users ?
# ✅ Test qui crée son propre contextedef test_user_count(): with fresh_database(): create_users(5) assert User.count() == 54. Mocker les dépendances externes
# ❌ Appelle une vraie API (peut changer, être lente, être down)def test_weather(): response = requests.get("https://api.weather.com/paris") assert response.status_code == 200
# ✅ Mock l'APIdef test_weather(mock_weather_api): mock_weather_api.return_value = {"temp": 20} result = get_weather("paris") assert result["temp"] == 20Feedback rapide : savoir en minutes si le code est valide
Section intitulée « Feedback rapide : savoir en minutes si le code est valide »Le principe : un développeur doit savoir en quelques minutes, pas en heures, si son code est valide.
Pourquoi c'est fondamental ?
La valeur d'un test ne tient pas seulement à ce qu'il détecte, mais au délai au bout duquel il le signale. Un échec remonté après quarante minutes arrive alors que le développeur a déjà ouvert un autre sujet.
Plus le feedback est tardif :
- Plus le développeur a changé de contexte (il travaille sur autre chose)
- Plus il est difficile de se souvenir de ce qu'on a fait
- Plus la correction prend du temps
Objectifs de temps réalistes :
| Étape | Objectif | Pourquoi cette durée |
|---|---|---|
| Lint + format | < 1 min | Vérifications syntaxiques, très rapides |
| Tests unitaires | < 3 min | Tests isolés, pas de dépendances externes |
| Build | < 5 min | Compilation, bundling |
| Tests d'intégration | < 10 min | Tests avec base de données, services |
| Tests end-to-end | < 15 min | Tests navigateur, scénarios complets |
Si votre pipeline dépasse 30 minutes, les développeurs commencent à la contourner (« je merge, on verra bien ») ou à faire du multi-tâche inefficace.
Comment accélérer votre pipeline
Section intitulée « Comment accélérer votre pipeline »1. Paralléliser les jobs indépendants
Au lieu d'exécuter lint, tests et scan de sécurité l'un après l'autre, exécutez-les en parallèle :
2. Fail fast, s'arrêter dès qu'un check rapide échoue
Si le lint échoue en 3 secondes, inutile de lancer les tests qui durent 30 secondes :
lint (3s) │ ├── échec → STOP (on a gagné 30s) │ └── succès → tests (30s) │ └── build (2min)3. Utiliser le cache de manière agressive
Télécharger les dépendances à chaque exécution est du gaspillage :
| Sans cache | Avec cache |
|---|---|
npm install : 3 min | npm install : 10 sec |
pip install : 2 min | pip install : 5 sec |
| Téléchargement images Docker : 1 min | Déjà en cache : 0 sec |
4. Organiser les tests en pyramide
L'idée est d'avoir beaucoup de tests rapides (unitaires) et peu de tests lents (end-to-end) :
Les tests unitaires détectent 80% des bugs en quelques secondes. Les tests E2E vérifient les scénarios critiques, mais sont réservés aux parcours essentiels.
Séparation build / deploy : deux responsabilités distinctes
Section intitulée « Séparation build / deploy : deux responsabilités distinctes »Le principe : le build crée un artefact générique. Le deploy l'installe avec une configuration spécifique. Les deux ne doivent pas être mélangés.
Le problème du couplage
Section intitulée « Le problème du couplage »# ❌ COUPLAGE : le build connaît la destination
build-staging: script: - API_URL=https://staging.example.com npm run build - docker build -t app:staging . - deploy-to-staging
build-production: script: - API_URL=https://api.example.com npm run build - docker build -t app:production . - deploy-to-productionProblèmes :
- Duplication de code (le build est répété deux fois)
- Impossible de promouvoir l'artefact (staging ≠ production)
- Si on ajoute un environnement (preprod), il faut un nouveau job
La solution : séparation claire
Section intitulée « La solution : séparation claire »Le build produit un artefact indépendant de sa destination, le déploiement l'injecte dans un environnement donné. Cette frontière permet de promouvoir en production exactement le binaire validé en recette, sans le reconstruire.
# ✅ SÉPARATION : build générique, deploy configuré
build: script: - npm run build # Pas de config d'environnement - docker build -t app:$COMMIT_SHA . - docker push registry/app:$COMMIT_SHA
deploy-staging: needs: [build] script: - docker pull registry/app:$COMMIT_SHA - helm upgrade app ./chart \ --set image.tag=$COMMIT_SHA \ --set apiUrl=https://staging.example.com \ -f values-staging.yaml
deploy-production: needs: [build] script: - docker pull registry/app:$COMMIT_SHA # Même artefact ! - helm upgrade app ./chart \ --set image.tag=$COMMIT_SHA \ --set apiUrl=https://api.example.com \ -f values-production.yamlCe que chaque étape doit faire
Section intitulée « Ce que chaque étape doit faire »La colonne qui protège la promotion d'artefact est la dernière, Ce qu'elle NE fait PAS. Tant que le build n'injecte aucune URL ni secret, et que le deploy ne recompile rien, l'artefact validé en test est bien celui qui part en production. Dès qu'une étape déborde de son rôle, la garantie « ce qui est testé égale ce qui est déployé » tombe.
| Étape | Responsabilité | Ce qu'elle NE fait PAS |
|---|---|---|
| Build | Compiler, bundler, créer l'artefact | Injecter des URLs, secrets, configs d'env |
| Test | Valider que l'artefact fonctionne | Modifier l'artefact |
| Deploy | Installer l'artefact + injecter la config | Recompiler, modifier le code |
Comment gérer la configuration
Section intitulée « Comment gérer la configuration »La configuration spécifique à l'environnement doit être injectée au runtime, pas au build :
// ❌ Configuration au build (en dur dans le code)const API_URL = "https://api.example.com";
// ✅ Configuration au runtimeconst API_URL = process.env.API_URL || "http://localhost:3000";apiUrl: https://staging.example.comlogLevel: debugreplicas: 1
# values-production.yamlapiUrl: https://api.example.comlogLevel: warnreplicas: 3Pipeline déclarative : décrire quoi, pas comment
Section intitulée « Pipeline déclarative : décrire quoi, pas comment »Le principe : votre fichier de pipeline doit décrire ce que vous voulez obtenir (quoi), pas les détails d'implémentation (comment). C'est la plateforme qui gère l'orchestration.
Le problème de l'impératif
Section intitulée « Le problème de l'impératif »# ❌ IMPÉRATIF : le script gère tout lui-même
steps: - run: | if [ "$BRANCH" = "main" ]; then npm run build docker build -t app . docker push registry/app:latest if [ "$DEPLOY" = "true" ]; then kubectl config use-context production kubectl apply -f k8s/ kubectl rollout status deployment/app fi elif [ "$BRANCH" = "develop" ]; then npm run build docker build -t app . docker push registry/app:dev # ... encore du code fiProblèmes :
- Difficile à lire : il faut suivre la logique du script
- Difficile à maintenir : un changement peut casser autre chose
- Pas de réutilisation : tout est dans un gros script
- Pas de parallélisation : la plateforme ne peut pas optimiser
La solution déclarative
Section intitulée « La solution déclarative »Vous décrivez l'état attendu, la plateforme choisit la séquence d'opérations pour l'atteindre. L'intérêt pratique est la reprise : un job interrompu se rejoue sans effet de bord, là où une suite d'ordres impératifs laisse le système à mi-chemin.
# ✅ DÉCLARATIF : intention claire, plateforme orchestre
permissions: {}
jobs: build: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20' - run: npm ci - run: npm run build - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: push: true tags: registry/app:${{ github.sha }}
deploy-staging: needs: build if: github.ref == 'refs/heads/main' environment: staging steps: - uses: azure/k8s-deploy@51ca02a8b7225fbd0924aac359c5b336a5f1e5b4 # v7.0.0 with: manifests: k8s/staging/ images: registry/app:${{ github.sha }}
deploy-production: needs: deploy-staging if: github.ref == 'refs/heads/main' environment: production # Approbation requise steps: - uses: azure/k8s-deploy@51ca02a8b7225fbd0924aac359c5b336a5f1e5b4 # v7.0.0 with: manifests: k8s/production/ images: registry/app:${{ github.sha }}Avantages du déclaratif
Section intitulée « Avantages du déclaratif »Au-delà de la lisibilité, la ligne décisive est Optimisation. Un script impératif impose sa séquence, la plateforme ne peut rien paralléliser toute seule. Une description déclarative expose au contraire un graphe de dépendances que la plateforme lit pour lancer en parallèle tout ce qui est indépendant, sans que vous ayez à l'orchestrer à la main.
| Aspect | Impératif | Déclaratif |
|---|---|---|
| Lisibilité | Suivre la logique du script | Structure visible immédiatement |
| Réutilisation | Copier-coller | Actions/plugins partagés |
| Maintenance | Modifier des scripts complexes | Changer des paramètres |
| Optimisation | Manuelle (parallélisation) | Automatique par la plateforme |
| Visualisation | Pas de graphe | Graphe de dépendances généré |
Les concepts déclaratifs clés
Section intitulée « Les concepts déclaratifs clés »Ces cinq mots-clés forment le vocabulaire commun à la plupart des plateformes, même si la syntaxe exacte varie. Retenez surtout needs, qui déclare les dépendances entre jobs : c'est lui qui construit le graphe d'exécution et permet à la plateforme de savoir quoi lancer en parallèle et quoi attendre. Les exemples ci-dessous utilisent la syntaxe GitHub Actions, transposable ailleurs.
| Concept | Signification | Exemple |
|---|---|---|
| needs | « Ce job dépend de celui-là » | needs: [build, test] |
| if | « Exécuter seulement si... » | if: github.ref == 'refs/heads/main' |
| environment | « Déployer dans cet environnement » | environment: production |
| matrix | « Exécuter pour chaque combinaison » | matrix: { node: [18, 20] } |
| uses | « Utiliser cette action réutilisable » | uses: actions/checkout@v4 |
Fail fast, fail loud : échouer vite et clairement
Section intitulée « Fail fast, fail loud : échouer vite et clairement »Le principe : quand quelque chose ne va pas, la pipeline doit s'arrêter immédiatement et le signaler clairement.
Pourquoi c'est important ?
Une erreur silencieuse ou tardive est bien pire qu'une erreur bruyante et immédiate :
- Silencieuse : le bug passe en production, les utilisateurs le découvrent
- Tardive : vous avez déjà mergé 10 autres commits, impossible de savoir lequel a cassé
- Bruyante et immédiate : vous corrigez en 5 minutes, personne n'est impacté
Les trois règles de l'échec
Section intitulée « Les trois règles de l'échec »1. S'arrêter immédiatement
Ne jamais continuer avec un état invalide. Si une étape échoue, les suivantes n'ont pas de sens.
# ❌ Script qui ignore les erreursresult=$(command_that_might_fail)# Continue même si la commande a échoué...next_commanddeploy # Déploie du code potentiellement cassé !# ✅ Script qui s'arrête au premier problèmeset -euo pipefail # Arrêt automatique en cas d'erreur
result=$(command_that_might_fail)# Si la commande échoue, le script s'arrête icinext_command # N'est jamais atteint si échecExplication de set -euo pipefail :
| Option | Signification |
|---|---|
-e | Arrêter le script si une commande retourne une erreur |
-u | Arrêter si une variable non définie est utilisée |
-o pipefail | Considérer un pipeline comme échoué si n'importe quelle commande échoue |
2. Notifier clairement
Une notification d'échec doit être :
| Critère | ❌ Mauvais | ✅ Bon |
|---|---|---|
| Timing | 30 minutes après l'échec | Immédiatement |
| Destinataire | Toute l'équipe (spam) | L'auteur du commit |
| Contenu | « Pipeline failed » | « Test user_login_test failed: expected 200, got 401 » |
| Action | Chercher dans les logs | Lien direct vers la ligne qui a échoué |
3. Faciliter le debugging
Quand une pipeline échoue, le développeur doit pouvoir comprendre pourquoi sans fouiller 500 lignes de logs :
- Logs structurés : sections claires pour chaque étape
- Contexte préservé : variables d'environnement, versions utilisées
- Artefacts de debug : screenshots pour les tests E2E, rapports de couverture
- Reproduction locale possible : « Pour reproduire :
make test-integration»
Observabilité : voir ce qui se passe sans fouiller
Section intitulée « Observabilité : voir ce qui se passe sans fouiller »Le principe : la pipeline doit exposer son état de santé de manière proactive. Vous ne devriez pas avoir à fouiller les logs pour savoir si tout va bien.
Ce qu'il faut mesurer
Section intitulée « Ce qu'il faut mesurer »Ces métriques répondent chacune à une question de santé différente, mais deux méritent une attention particulière. Le taux de succès trahit une pipeline qui perd la confiance de l'équipe, et les tests flaky rongent cette confiance à bas bruit. La colonne des seuils donne des repères de départ à ajuster selon votre contexte, ce ne sont pas des valeurs universelles.
| Métrique | Ce qu'elle révèle | Seuil d'alerte typique |
|---|---|---|
| Durée totale | Régression de performance | > 15 min (selon contexte) |
| Durée par job | Goulot d'étranglement | Job qui double de durée |
| Taux de succès | Santé globale | < 90% sur 24h |
| Tests flaky | Tests non fiables | Même test échoue > 2x/semaine aléatoirement |
| Temps de queue | Manque de runners | > 5 min d'attente |
| Fréquence de déploiement | Vélocité de l'équipe | Baisse soudaine |
Les tests flaky : un cancer silencieux
Section intitulée « Les tests flaky : un cancer silencieux »Un test flaky est un test qui échoue parfois sans raison apparente, puis passe à la relance.
Pourquoi c'est grave :
- Les développeurs perdent confiance dans la pipeline (« oh, ça a échoué, je relance »)
- Les vrais problèmes passent inaperçus (noyés dans le bruit)
- Temps perdu à relancer des pipelines
Comment les détecter :
- Suivre les tests qui échouent puis passent sur le même commit
- Identifier les tests avec un taux de succès < 99%
- Alerter quand un test spécifique échoue plus de X fois par semaine
Que faire :
- Marquer le test comme flaky (pour ne pas bloquer les autres)
- Créer un ticket prioritaire pour le corriger
- Corriger la cause racine (souvent : dépendance au timing, état partagé, ressource externe)
Alertes recommandées
Section intitulée « Alertes recommandées »Ne vous noyez pas dans les alertes. Concentrez-vous sur ce qui est actionnable :
| Condition | Action requise |
|---|---|
| Pipeline principale > 20 min | Investiguer la régression de performance |
| Taux de succès < 85% sur 24h | Problème systémique, prioriser la correction |
| Test X échoue > 3 fois cette semaine | Marquer comme flaky, créer ticket |
| Temps de queue > 10 min | Ajouter des runners ou optimiser le parallélisme |
| Déploiement production échoue | Alerte immédiate à l'équipe on-call |
Dashboard minimum viable
Section intitulée « Dashboard minimum viable »Un bon dashboard de pipeline montre en un coup d'œil :
À retenir
Section intitulée « À retenir »-
Feedback rapide, Le développeur doit savoir en minutes si son code est valide. Parallélisez, cachez, fail fast.
-
Fail fast, fail loud, Une erreur silencieuse est pire qu'une erreur bruyante. Arrêtez-vous immédiatement, notifiez clairement.
-
Build once, deploy many, Construisez l'artefact une fois, déployez-le partout. Ce que vous testez = ce que vous déployez.
-
Immutabilité, Un artefact créé ne change jamais. Si changement nécessaire, créez un nouvel artefact. Évitez les tags mutables (
latest). -
Idempotence, Même commit = même résultat. Épinglez les dépendances, isolez les tests, évitez les états globaux.
-
Séparation build/deploy, Le build crée un artefact générique. Le deploy injecte la configuration. Ne mélangez pas.
-
Pipeline déclarative, Décrivez le quoi, pas le comment. Laissez la plateforme orchestrer.
-
Observabilité, Mesurez la durée, le taux de succès, les tests flaky. Alertez sur ce qui est actionnable.
Checklist avant de modifier une pipeline
Section intitulée « Checklist avant de modifier une pipeline »Avant de merger une modification de pipeline, vérifiez :
- Build once : L'artefact est construit une seule fois
- Immutabilité : Les tags sont immutables (SHA, pas
latest) - Idempotence : Les dépendances sont épinglées (lockfiles committés)
- Feedback rapide : La pipeline complète en moins de 15 minutes
- Fail fast : Un échec arrête la pipeline immédiatement
- Notifications : Les alertes sont actionnables (lien direct, contexte)
- Séparation : Build et deploy sont des jobs distincts
- Déclaratif : La logique est dans la structure, pas dans des scripts
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Qu'est-ce qu'une pipeline CI/CD ?, Les fondamentaux
- Les formes de pipelines, Choisir son architecture selon le contexte
- Anti-patterns CI/CD, Les erreurs classiques à éviter
- Sécuriser une pipeline CI/CD, Modèle de sécurisation approfondi