
Un pipeline lent finit par coûter cher : attente développeur, review ralentie, files runners saturées. Dans ce lab, vous optimisez les points qui apportent le plus de gains sans complexifier inutilement le YAML : cache bien clés, images allégées et parallélisme contrôlé.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Choisir une clé de cache stable et pertinente
- Éviter les caches inutilisables (
$CI_COMMIT_SHA) - Utiliser
needspour lancer des jobs en parallèle - Mesurer l'impact réel des optimisations
Quel lab commencer ?
Section intitulée « Quel lab commencer ? »Ce lab s'appuie sur les acquis du Lab 11. Si vous maîtrisez déjà le diagnostic de pipeline, vous pouvez démarrer ici avec starter/lab-12.
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Les pipelines lents sont un problème de productivité avant d'être un problème technique. Si chaque push met 8 minutes, les développeurs poussent moins souvent et accumulent des changements plus gros, ce qui augmente le risque de conflit et d'erreur.
Ce lab répond à des cas fréquents :
- cache jamais réutilisé à cause d'une clé trop spécifique
- jobs indépendants bloqués par la logique de stage séquentielle
- images trop lourdes téléchargées à chaque run
Prérequis
Section intitulée « Prérequis »- Lab 11 — Débugger un job bloqué terminé
- Avoir lu DAG et parallélisme et Artifacts et cache
Point de départ
Section intitulée « Point de départ »-
Basculez sur la branche du lab
Fenêtre de terminal cd pipeline-craftgit checkout starter/lab-12 -
Lancez un run de référence
Fenêtre de terminal git push origin starter/lab-12 -
Notez la durée du pipeline
Cette valeur sert de baseline avant optimisation.
Le problème
Section intitulée « Le problème »La lenteur vient souvent de trois causes combinées :
- cache inexploitable
- exécution trop séquentielle
- images ou builds non optimisés
Vous allez corriger ces trois zones sans rendre le pipeline illisible.
L'exercice
Section intitulée « L'exercice »Étape 1 — À vous de corriger la stratégie de cache
Section intitulée « Étape 1 — À vous de corriger la stratégie de cache »-
Remplacez une clé de cache instable par une clé basée sur les fichiers de dépendances.
-
Vérifiez sur deux runs successifs : le second doit restaurer le cache.
Indice : une clé par commit empêche la réutilisation du cache.
👉 Vérifier votre solution (Étape 1)
1️⃣ Clé de cache basée sur les dépendances
Section intitulée « 1️⃣ Clé de cache basée sur les dépendances »Avant (clé instable, jamais réutilisée) :
cache: key: "$CI_COMMIT_SHA" # ❌ Change à chaque commit paths: - .pip-cache/Après (clé stable, réutilisée quand les dépendances ne changent pas) :
cache: key: files: - requirements-dev.txt paths: - .pip-cache/La clé change uniquement quand requirements-dev.txt est modifié.
Vérification dans les logs du job :
- 1er run :
Uploading cache.zip to https://...(création) - 2e run :
Downloading cache.zip from https://...(restauration ✅)
Étape 2 — À vous de paralléliser avec needs
Section intitulée « Étape 2 — À vous de paralléliser avec needs »-
Identifiez les jobs indépendants.
-
Ajoutez des
needsexplicites si nécessaire pour activer le DAG.Cette approche réduit l'attente artificielle liée aux stages.
👉 Vérifier votre solution (Étape 2)
1️⃣ Parallélisme avec needs
Section intitulée « 1️⃣ Parallélisme avec needs »Sans needs, les stages s'exécutent en séquence stricte. Avec needs, chaque job démarre dès que ses dépendances sont satisfaites :
pytest: stage: test needs: [] # ✅ Démarre dès que possible, sans attendre lintGraphe d'exécution :
- Avant :
ruff-lintfini →pytestdémarre →docker-builddémarre - Après :
ruff-lintetpytestdémarrent en parallèle grâce àneeds: []surpytest
Étape 3 — À vous de vérifier les images utilisées
Section intitulée « Étape 3 — À vous de vérifier les images utilisées »-
Confirmez l'usage de variantes allégées (
python:3.12-slim) -
Gardez
docker:27uniquement pour les jobs qui en ont besoinChaque job doit utiliser l'image minimale adaptée à son besoin.
👉 Vérifier votre solution (Étape 3)
1️⃣ Images recommandées par type de job
Section intitulée « 1️⃣ Images recommandées par type de job »| Type de job | Image recommandée | À éviter |
|---|---|---|
| Lint Python | python:3.12-slim | python:3.12 (+200 MB inutiles) |
| Tests Python | python:3.12-slim | ubuntu:22.04 (outils inutiles) |
| Build Docker | docker:27 + docker:27-dind | Image Docker + OS complet |
| Deploy simple | alpine:3.20 | Image Debian ou Ubuntu |
La variante slim d'une image Python coûte généralement 3 à 4 fois moins en temps de pull que la variante standard. alpine:3.20 pèse moins de 10 MB.
Étape 4 — À vous de mesurer le gain
Section intitulée « Étape 4 — À vous de mesurer le gain »-
Poussez votre optimisation
Fenêtre de terminal git add .gitlab-ci.ymlgit commit -m "ci: speed up pipeline with cache and dag"git push origin starter/lab-12 -
Comparez la durée avec la baseline
Vous devez observer une réduction tangible dès le second run optimisé.
👉 Vérifier votre solution (Étape 4)
1️⃣ Mesurer le gain réel
Section intitulée « 1️⃣ Mesurer le gain réel »Méthode : notez la colonne duration dans la liste des pipelines avant et après optimisation.
Gains typiques :
| Optimisation | Gain attendu |
|---|---|
| Cache restauré | -30 à 60 sec |
needs sur jobs indépendants | -durée du stage le plus court |
Image slim vs standard | -10 à 30 sec de pull |
Mesurez sur 2 à 3 runs consécutifs : le premier run alimente le cache, les suivants le réutilisent. C'est à partir du deuxième run que le gain est visible.
Le fichier complet
Section intitulée « Le fichier complet »📄 Voir le fichier .gitlab-ci.yml complet
stages: - lint - test - build - deploy
workflow: rules: - if: $CI_PIPELINE_SOURCE == "push" - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG
variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
ruff-lint: stage: lint image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/ policy: pull before_script: - pip install ruff script: - ruff check app/ tests/
pytest: stage: test image: python:3.12-slim needs: [] cache: key: files: - requirements-dev.txt paths: - .pip-cache/ before_script: - pip install -r requirements-dev.txt script: - pytest -v --junitxml=report.xml --cov=app --cov-report=term-missing coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' artifacts: when: always paths: - report.xml reports: junit: report.xml expire_in: 7 days
docker-build: stage: build image: docker:27 services: - docker:27-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker build -t $CI_REGISTRY_IMAGE:latest . - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $CI_REGISTRY_IMAGE:latest
deploy-staging: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to staging..." - ./scripts/deploy-demo.sh staging rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-production: stage: deploy image: alpine:3.20 script: - echo "Deploying $CI_COMMIT_SHORT_SHA to production..." - ./scripts/deploy-demo.sh production rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manualVérification
Section intitulée « Vérification »- Le cache est restauré sur un run suivant
- Les jobs indépendants ne s'attendent plus inutilement
- Le pipeline total est sensiblement plus court
- Le YAML reste lisible et maintenable
Pièges fréquents
Section intitulée « Pièges fréquents »Un pipeline très optimisé mais illisible devient vite un problème de maintenance. Cherchez un équilibre entre performance et clarté.
Autre piège : annoncer un gain après un seul run. Le premier run sert souvent à alimenter le cache. Mesurez au minimum sur deux à trois exécutions comparables.
À retenir
Section intitulée « À retenir »- Une bonne clé de cache change quand les dépendances changent, pas à chaque commit
- Le DAG (
needs) réduit l'attente artificielle des stages - Les images allégées accélèrent le pull et donc le pipeline
- Mesurer avant/après est indispensable pour valider une optimisation