Aller au contenu principal

Anatomie d'un pipeline CI/CD

Résumé : un pipeline est la traduction technique du CI/CD — une suite d'étapes automatisées déclenchées par un événement Git. Cette leçon décompose chaque étape (checkout, build, test, analyse, packaging, déploiement), clarifie la différence entre stage et job, explique le rôle des runners, des artefacts et des caches, et se termine par un exemple complet commenté.


1. Ce qu'est un pipeline

Le pipeline est du code. Il vit dans un fichier versionné avec votre application : .github/workflows/ci.yml pour GitHub Actions, .gitlab-ci.yml pour GitLab, Jenkinsfile pour Jenkins. C'est ce qu'on appelle le pipeline as code, et c'est un acquis majeur des années 2015-2018.


2. Les étapes classiques d'un pipeline

Règle d'or de l'ordonnancement : placez les étapes qui échouent vite et coûtent peu en premier. Un linter qui prend 10 secondes doit tourner avant une suite de tests d'intégration de 8 minutes. Cela s'appelle le principe fail fast — échouer tôt pour rendre le feedback plus rapide.


3. Stage, job, step — le vocabulaire à maîtriser

Ces trois mots reviennent constamment et désignent des niveaux d'organisation différents.

TermeDéfinitionParallélisme
PipelineL'exécution complète, déclenchée par un événement
Stage (ou phase)Un groupe logique de jobs. Le stage suivant attend la réussite du précédentLes stages sont séquentiels
JobUne unité d'exécution qui tourne sur un runner, avec son propre environnementLes jobs d'un même stage sont parallèles
Step (ou étape)Une commande ou une action dans un jobLes steps sont séquentiels dans leur job

Conséquence pratique importante : deux jobs d'un même stage ne partagent pas leur système de fichiers. Si le job build produit un binaire dont le job deploy a besoin, il faut passer par un artefact — c'est l'objet de la section suivante.


4. Les runners — où le pipeline s'exécute réellement

Un runner (ou agent, ou executor) est la machine qui exécute un job.

Point de sécurité crucial : un runner auto-hébergé qui exécute des pipelines de pull requests provenant de l'extérieur est une faille béante — un contributeur malveillant peut exécuter n'importe quel code sur votre machine, avec accès à votre réseau interne. C'est pourquoi les projets open source utilisent quasi exclusivement des runners hébergés et éphémères pour les contributions externes.


5. Artefacts et caches — deux notions à ne pas confondre

C'est une confusion très fréquente chez les débutants, avec des conséquences réelles.

Le test qui tranche : « si je supprime ça, mon pipeline échoue-t-il ou devient-il juste plus lent ? ». Échoue → c'est un artefact. Plus lent → c'est un cache.

Erreur classique à éviter : mettre un artefact de build dans le cache. Si le cache expire ou est invalidé, le déploiement échoue de façon mystérieuse et intermittente — l'un des bugs les plus pénibles à diagnostiquer en CI.


6. Le principe de l'artefact unique

Il s'agit de la règle la plus importante de la livraison continue, énoncée par Humble et Farley.

Corollaire : la configuration doit être externe à l'artefact. La même image de conteneur tourne en recette et en production, seules les variables d'environnement changent. C'est un des principes du Twelve-Factor App.


7. Exemple complet expliqué

Voici un pipeline GitHub Actions réaliste pour une application Node.js conteneurisée.

name: CI/CD

# Déclencheurs : sur push vers main, et sur toute pull request
on:
push:
branches: [main]
pull_request:

# Annule les exécutions précédentes sur la même branche
# Évite de gaspiller des minutes de runner sur du code obsolète
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# ---------- STAGE 1 : validation (jobs en parallèle) ----------
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm' # cache automatique de ~/.npm
- run: npm ci
- run: npm run lint

test:
runs-on: ubuntu-latest
# Base de données réelle pour les tests d'intégration
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
# Publication d'un artefact : le rapport de couverture
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/

# ---------- STAGE 2 : build (attend la réussite du stage 1) ----------
build:
needs: [lint, test] # dépendance explicite
if: github.ref == 'refs/heads/main' # uniquement sur main
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # droit de pousser dans le registre
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
# Étiquette avec le hash du commit : artefact traçable
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha # cache des couches Docker
cache-to: type=gha,mode=max

# ---------- STAGE 3 : déploiement (environnement protégé) ----------
deploy:
needs: build
runs-on: ubuntu-latest
environment: production # peut exiger une approbation manuelle
steps:
- name: Déployer l'image sur le cluster
run: |
echo "Déploiement de ghcr.io/${{ github.repository }}:${{ github.sha }}"
# kubectl set image deployment/mon-app app=ghcr.io/...:${{ github.sha }}

Les points remarquables de cet exemple :

ÉlémentPourquoi c'est important
concurrency avec cancel-in-progressAnnule les builds obsolètes, économise des minutes de runner
lint et test sans needsIls tournent en parallèle, le pipeline est plus rapide
services: postgresUne vraie base de données éphémère pour les tests d'intégration
needs: [lint, test]Le build n'a lieu que si la validation a réussi
if: github.ref == 'refs/heads/main'On ne publie pas d'image pour une simple pull request
tags: ...:${{ github.sha }}L'artefact est traçable jusqu'au commit exact
cache-from: type=ghaRéutilise les couches Docker, build 3 à 10 fois plus rapide
environment: productionPermet d'exiger une approbation humaine — c'est le passage de déploiement continu à livraison continue
permissions explicitesPrincipe du moindre privilège pour le jeton du pipeline

Notez ce détail décisif : la ligne environment: production est exactement ce qui fait basculer ce pipeline entre livraison continue (si l'environnement exige une approbation) et déploiement continu (s'il n'en exige pas). Une seule ligne de configuration sépare les deux pratiques vues en leçon 2.


8. Ce qui rend un pipeline bon ou mauvais

Le pire ennemi d'un pipeline : les tests instables (flaky tests). Un test qui échoue une fois sur dix sans raison apprend à l'équipe à relancer sans réfléchir. À partir de ce moment, le pipeline ne protège plus rien : les vrais échecs sont ignorés au même titre que les faux. Un test instable doit être corrigé ou supprimé, jamais toléré.


Retenir en 30 secondes

  • Un pipeline est une suite d'étapes automatisées déclenchée par un événement Git, définie dans un fichier versionné.
  • Étapes types : checkout, cache, dépendances, lint, build, tests unitaires, tests d'intégration, scan sécurité, packaging, publication, déploiement, surveillance.
  • Fail fast : placez ce qui échoue vite et coûte peu en premier.
  • Stage (séquentiels) > Job (parallèles dans un stage) > Step (séquentiels dans un job).
  • Un runner est la machine qui exécute un job. Hébergé, auto-hébergé, ou éphémère en conteneur.
  • Artefact = ce qu'on produit et conserve. Cache = ce qu'on réutilise pour aller vite. Ne jamais confondre.
  • Un seul build : le même artefact traverse recette, préproduction et production.
  • Un bon pipeline est rapide (moins de 10 min), déterministe, lisible, informatif et sécurisé.

Suivant : Les stratégies de déploiement : rolling, blue-green, canary, feature flags →