Chapitre 214 minIntermédiaire
Les étapes d'un pipeline CI/CD
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- expliquer comment un pipeline décide qu'une étape a échoué ;
- ordonner les étapes d'un pipeline, de la récupération du code à la production ;
- installer des dépendances verrouillées avec la bonne commande ;
- appliquer l'échec rapide et la règle « construire une fois, déployer partout ».
4 exercices corrigés1 schéma
Un pipeline CI/CD est la suite automatisée d'étapes qui transforme un commit en version vérifiée, puis livrée : récupérer le code, installer les dépendances, analyser, tester, construire, publier, déployer. Chaque étape doit réussir pour que la suivante démarre. Ce chapitre décrit ces étapes dans l'ordre, explique comment une machine sait qu'une étape a échoué, et donne les règles qui font un bon pipeline : rapide, reproductible et digne de confiance.
Le principe : des commandes et des codes de retour
Un pipeline ne fait rien de magique : il exécute des commandes, les mêmes que tu tapes dans ton terminal. Pour savoir si une commande a réussi, il lit son code de retour (exit code) :
0: la commande a réussi, l'étape est verte ;- tout autre nombre : la commande a échoué, l'étape est rouge et le pipeline s'arrête.
npm test
echo $? # affiche le code de retour de la dernière commande : 0 si tous les tests passent, 1 sinon
Jest, ESLint, PHPUnit, dotnet test, pytest : tous les outils de qualité renvoient un code différent de 0 quand ils trouvent un problème. C'est ce qui permet de les brancher dans n'importe quel pipeline.
Les étapes, dans l'ordre
| N° | Étape | Exemple pour sorbier-frais | Elle répond à la question |
|---|---|---|---|
| 1 | Déclenchement | Un push, une pull request, un tag, un horaire | Quand vérifier ? |
| 2 | Récupération du code | Copie du dépôt au commit exact sur une machine neuve | Quel code vérifier ? |
| 3 | Installation | Installer Node.js 24, puis npm ci | Les dépendances déclarées suffisent-elles ? |
| 4 | Vérifications statiques | npx prettier --check ., npm run lint | Le code respecte-t-il les règles de l'équipe ? |
| 5 | Tests unitaires | npm test | Chaque fonction fait-elle ce qu'elle doit ? |
| 6 | Construction (build) | Compiler, transpiler, minifier : npm run build | Le code se construit-il ? |
| 7 | Tests d'intégration | L'API testée contre une vraie base MySQL | Les composants fonctionnent-ils ensemble ? |
| 8 | Analyse de sécurité | npm audit, analyse du code, recherche de secrets | Le code ou ses dépendances ont-ils une faille connue ? |
| 9 | Empaquetage | Construire l'image Docker sorbier-frais:3f9c2e1 | Quel objet va-t-on livrer ? |
| 10 | Publication | Pousser l'image dans un registre | Où l'objet est-il rangé ? |
| 11 | Déploiement en recette | Lancer l'image sur le serveur de recette | La version tourne-t-elle dans un environnement réaliste ? |
| 12 | Tests de fumée et d'acceptation | curl -f https://recette.sorbier.example/health, scénarios de recette | Le service répond-il vraiment ? |
| 13 | Déploiement en production | Même image, après approbation ou automatiquement | On livre ? |
| 14 | Vérification après déploiement | Surveillance des erreurs et du temps de réponse | La production va-t-elle bien ? |
Les étapes 1 à 8 forment l'intégration continue ; les étapes 9 à 14 la livraison ou le déploiement continu. Un projet de BTS commence par les étapes 1 à 5 et ajoute les autres au fil des besoins.
Installer avec des dépendances verrouillées
La machine qui exécute le pipeline démarre vide. Elle doit installer exactement les mêmes versions de bibliothèques que sur le poste du développeur, sinon un test peut passer chez l'un et échouer chez l'autre. Le fichier de verrouillage (lockfile) fige ces versions : il doit être versionné dans Git.
| Écosystème | Fichier de verrouillage | Commande de CI | À éviter dans la CI |
|---|---|---|---|
| Node.js (npm) | package-lock.json | npm ci | npm install, qui peut modifier le verrouillage |
| PHP (Composer) | composer.lock | composer install --no-interaction --no-progress | composer update |
| Python (pip) | requirements.txt avec versions exactes | pip install -r requirements.txt | pip install sans version |
| .NET (NuGet) | packages.lock.json | dotnet restore --locked-mode | dotnet restore sans verrouillage, si le projet en a un |
| Java (Maven) | versions dans pom.xml | mvn -B verify | les versions LATEST ou les plages |
npm ci (clean install) supprime node_modules, installe exactement ce que dit package-lock.json, et échoue si ce fichier ne correspond plus à package.json. C'est voulu : une dépendance ajoutée sans mettre à jour le verrouillage est détectée tout de suite.
Échouer vite : l'ordre des étapes
Un pipeline s'arrête au premier échec. On place donc en tête les étapes rapides et souvent en échec, et en fin les étapes lentes. C'est le principe d'échec rapide (fail fast) : le développeur apprend le problème en trente secondes plutôt qu'au bout de dix minutes, et la machine ne calcule pas pour rien.
| Étape | Durée typique pour une petite API | Pourquoi à cette place |
|---|---|---|
| Formatage et lint | 5 à 20 secondes | Très rapide, échoue souvent (une virgule, une variable inutilisée) |
| Tests unitaires | 10 secondes à 2 minutes | Rapides, ils couvrent la logique |
| Construction | 30 secondes à 3 minutes | Inutile de construire un code qui ne passe pas les tests |
| Tests d'intégration | 1 à 5 minutes | Ils démarrent une base, une API : plus lents |
| Tests de bout en bout | 5 à 20 minutes | Un navigateur piloté, très lents : peu nombreux, souvent seulement avant la production |
Deuxième levier : paralléliser. Le lint, les tests unitaires et l'analyse de sécurité ne dépendent pas les uns des autres. Placés dans trois jobs distincts, ils tournent en même temps sur trois machines, et la durée totale devient celle du plus long (les jobs parallèles).
Les vérifications statiques : formatage, lint, typage
Elles examinent le code sans l'exécuter.
- Le formatage vérifie la mise en page : indentation, guillemets, longueur des lignes. Dans la CI, on vérifie sans modifier :
npx prettier --check .. Le développeur corrige chez lui avecnpx prettier --write .. - Le lint cherche les erreurs probables et les mauvaises pratiques : variable déclarée mais jamais utilisée,
==au lieu de===,awaitoublié. ESLint pour JavaScript, PHPStan ou Psalm pour PHP, les analyseurs Roslyn pour C#, Ruff pour Python. - Le typage vérifie la cohérence des types :
npx tsc --noEmitpour TypeScript, PHPStan à un niveau élevé pour PHP, le compilateur pour C# et Java.
| Langage | Formatage (CI) | Lint ou analyse statique |
|---|---|---|
| JavaScript, TypeScript | npx prettier --check . | npx eslint . (configuration eslint.config.js), npx tsc --noEmit |
| PHP | vendor/bin/php-cs-fixer fix --dry-run --diff | vendor/bin/phpstan analyse |
| C# | dotnet format --verify-no-changes | Avertissements du compilateur traités en erreurs (-warnaserror) |
| Python | ruff format --check . | ruff check . |
Construire et empaqueter : l'artefact
L'étape de construction produit un artefact : l'objet qui sera livré. Selon le projet, c'est un dossier dist/ de fichiers statiques, une archive .zip, un exécutable, un .jar, ou de plus en plus souvent une image Docker qui contient l'application et tout ce dont elle a besoin pour tourner.
Deux règles rendent la livraison fiable :
- Construire une seule fois, déployer partout (build once, deploy many). L'artefact testé en recette est exactement celui qui part en production. On ne reconstruit pas pour la production : une reconstruction pourrait tirer une dépendance différente, et l'on déploierait un objet jamais testé.
- Identifier chaque artefact sans ambiguïté. On l'étiquette avec l'empreinte du commit (
3f9c2e1) ou un numéro de version (2.4.0), jamais seulement aveclatest. Ainsi, on sait toujours quel code tourne en production, et on peut redéployer la version précédente en une commande (chapitre 12).
Déployer, puis vérifier
Le déploiement installe l'artefact dans un environnement : recette d'abord, production ensuite (chapitre 10). Un déploiement n'est pas terminé quand la commande rend la main, mais quand on a vérifié que le service répond. Le plus simple est un test de fumée (smoke test) : une requête sur une route de santé.
curl --fail --silent --show-error --retry 5 --retry-delay 3 --retry-all-errors \
https://recette.sorbier.example/health
--fail fait renvoyer à curl un code d'erreur si le serveur répond 4xx ou 5xx ; sans cette option, une page d'erreur 500 serait considérée comme un succès. --retry laisse au service le temps de démarrer.
Où tourne le pipeline : les runners
La machine qui exécute un pipeline s'appelle un runner (GitHub, GitLab) ou un agent (Jenkins, Azure Pipelines).
| Type | Description | Avantages | Inconvénients |
|---|---|---|---|
| Hébergé par la forge | Une machine virtuelle neuve fournie par GitHub ou GitLab, détruite après le job | Rien à installer, toujours propre, sûre | Quota de minutes sur les dépôts privés, pas d'accès direct à ton réseau interne |
| Auto-hébergé (self-hosted) | Une machine à toi qui exécute les jobs | Accès au réseau interne, matériel choisi, pas de quota | À maintenir et à sécuriser ; un job peut laisser des traces pour le suivant |
Une machine neuve à chaque exécution est une force : aucun reste du job précédent, aucune configuration cachée. C'est aussi pourquoi chaque job commence par récupérer le code et installer ses outils.
Ce qu'on attend d'un bon pipeline
- Reproductible : mêmes entrées, même résultat ; versions verrouillées, environnement neuf.
- Rapide : moins de dix minutes pour une pull request.
- Fiable : un test qui échoue une fois sur dix au hasard (flaky test) apprend à l'équipe à ignorer le rouge ; on le corrige ou on le met en quarantaine (les tests instables).
- Bloquant : un échec empêche vraiment la fusion ou le déploiement (protection de branche).
- Versionné : la description du pipeline vit dans le dépôt, avec le code, et passe en revue comme lui.
- Sûr : droits minimaux, secrets protégés, actions tierces maîtrisées (chapitre 7).
Exercices
Exercice 1 sur 4 Remettre les étapes dans l'ordre Réussi À revoir
Voici les étapes du pipeline d'une application PHP, dans le désordre : vendor/bin/phpunit (tests unitaires), déploiement en production, composer install --no-interaction, récupération du code, vendor/bin/phpstan analyse, construction de l'image Docker, test de fumée en recette, publication de l'image dans le registre, déploiement en recette.
- Remets-les dans l'ordre.
- Lesquelles relèvent de l'intégration continue, lesquelles de la livraison ?
Voir la réponseMasquer la réponse de l'exercice 1
- Récupération du code ;
composer install --no-interaction;vendor/bin/phpstan analyse;vendor/bin/phpunit; construction de l'image Docker ; publication de l'image dans le registre ; déploiement en recette ; test de fumée en recette ; déploiement en production. - Intégration continue : récupération, installation, analyse statique, tests. Livraison : construction de l'image, publication, déploiements et test de fumée.
PHPStan passe avant PHPUnit : l'analyse est plus rapide et détecte des erreurs qui feraient de toute façon échouer les tests.
Tu avais trouvé ?
Exercice 2 sur 4 Un pipeline toujours vert Réussi À revoir
Le pipeline de Hugo est toujours vert, même quand des tests échouent. Voici l'étape de test :
npm test || echo "Des tests ont échoué, à regarder plus tard"
- Pourquoi l'étape est-elle verte ?
- Corrige-la.
Voir la réponseMasquer la réponse de l'exercice 2
- Quand
npm testéchoue (code 1), l'opérateur||exécuteecho, qui réussit (code 0). Le code de retour de la ligne est celui de la dernière commande exécutée : 0, donc l'étape est verte. - Il suffit d'écrire
npm test. Si Hugo veut afficher un message, il doit conserver l'échec :npm test || { echo "Des tests ont échoué"; exit 1; }.
Tu avais trouvé ?
Exercice 3 sur 4 Gagner du temps Réussi À revoir
Le pipeline de sorbier-frais enchaîne quatre étapes dans un seul job : lint (1 min), tests unitaires (3 min), tests d'intégration (6 min), construction de l'image (2 min). L'installation des dépendances prend 1 minute au début du job.
- Quelle est la durée totale ?
- On sépare le lint, les tests unitaires et les tests d'intégration en trois jobs parallèles, chacun avec sa propre installation (1 min). La construction de l'image, dans un quatrième job qui attend les trois autres, a aussi besoin de l'installation. Quelle est la nouvelle durée ?
- Quel est l'intérêt de cette organisation quand le lint échoue ?
Voir la réponseMasquer la réponse de l'exercice 3
- 1 + 1 + 3 + 6 + 2 = 13 minutes.
- Les trois jobs parallèles durent 1 + 1 = 2 min, 1 + 3 = 4 min et 1 + 6 = 7 min : ils sont tous terminés au bout de 7 minutes. Le job de construction démarre alors et dure 1 + 2 = 3 min. Total : 7 + 3 = 10 minutes.
- Avec un seul job, un lint en échec arrête tout au bout de 2 minutes, mais on ne sait rien des tests. En parallèle, le lint échoue au bout de 2 minutes et les tests continuent : le développeur reçoit tous les problèmes en une seule exécution. La construction, elle, ne démarre pas, puisqu'elle attend les trois jobs.
Tu avais trouvé ?
Exercice 4 sur 4 Les bonnes commandes Réussi À revoir
Pour chaque projet, donne la commande d'installation des dépendances adaptée à la CI et la commande de vérification du formatage.
- Une API Node.js qui utilise Prettier.
- Une application Symfony qui utilise PHP-CS-Fixer.
- Une application WinForms en .NET 10.
Voir la réponseMasquer la réponse de l'exercice 4
npm ci, puisnpx prettier --check ..composer install --no-interaction --no-progress, puisvendor/bin/php-cs-fixer fix --dry-run --diff.dotnet restore(avec--locked-modesi le projet a unpackages.lock.json), puisdotnet format --verify-no-changes. Une application WinForms ne se construit que sous Windows : le job tourne sur un runner Windows.
Tu avais trouvé ?