
Votre pipeline est devenu plus rapide, mais il est aussi devenu plus long. Quand le même bloc cache ou before_script est copié dans 3 ou 4 jobs, la moindre modification devient risquée. Dans ce lab, vous gardez le même résultat fonctionnel, mais vous réduisez la duplication pour rendre votre YAML plus maintenable.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Détecter les duplications qui doivent être factorisées dans un pipeline GitLab
- Construire un job caché réutilisable avec
extends: - Utiliser correctement les anchors YAML (
&,*,<<:) - Expliquer la différence entre
extendset anchor YAML
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Ce type de refactoring intervient quand un pipeline commence à vivre dans la durée. Un ajout simple, comme une option pip ou une nouvelle clé de cache, doit alors être répliqué partout. C'est à ce moment que les incohérences apparaissent : un job est à jour, un autre non.
Ce lab vous aide dans des situations réelles :
- votre
.gitlab-ci.ymldépasse la centaine de lignes ; - plusieurs jobs Python répètent les mêmes sections ;
- l'équipe hésite à modifier le pipeline par peur de casser un job secondaire.
Prérequis
Section intitulée « Prérequis »- Lab 12 — Accélérer le pipeline terminé
- Avoir lu Extends et anchors
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-13 -
Ouvrez
.gitlab-ci.ymlVous devez y voir des blocs dupliqués, notamment sur les jobs Python.
-
Lancez un pipeline de référence
Fenêtre de terminal git push origin starter/lab-13Conservez ce run comme baseline fonctionnelle avant refactoring.
Le problème
Section intitulée « Le problème »Le pipeline fonctionne, mais il a un problème de maintenabilité : mêmes clés répétées, même before_script, même stratégie de cache, mêmes variables Docker.
Sur le court terme, cela passe. Sur le moyen terme, chaque correction coûte plus de temps et augmente le risque d'oubli.
L'exercice
Section intitulée « L'exercice »Étape 1 — Créer un job caché de base Python
Section intitulée « Étape 1 — Créer un job caché de base Python »-
Ajoutez un job caché (préfixe
.)Indice : ce job doit centraliser la partie Python partagée entre plusieurs jobs.
-
Déplacez les éléments communs Python
Selon votre starter, vous pouvez aussi y mettre un
before_scriptcommun. -
Appliquez
extendssurruff-lintetpytestIndice : gardez les différences métier (
script, installation spécifique) dans chaque job final.
👉 Vérifier votre solution (Étape 1)
1️⃣ Job caché .python-base + extends
Section intitulée « 1️⃣ Job caché .python-base + extends »Le but est de déplacer les éléments communs Python dans un job caché, puis de les réutiliser dans ruff-lint et pytest.
.python-base: image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/
ruff-lint: extends: .python-base stage: lint cache: policy: pull before_script: - pip install ruff
pytest: extends: .python-base stage: test needs: [] before_script: - pip install -r requirements-dev.txtruff-lint conserve policy: pull pour restaurer le cache sans tenter de le republier.
Étape 2 — Introduire une anchor pour Docker-in-Docker
Section intitulée « Étape 2 — Introduire une anchor pour Docker-in-Docker »-
Déclarez une anchor de variables Docker
Indice : l'anchor doit vivre en haut du fichier pour rester visible et réutilisable.
-
Réutilisez-la dans
docker-buildIndice : utilisez la fusion YAML (
<<:) pour injecter le bloc ancré.Cette approche est utile quand plusieurs jobs Docker partagent les mêmes variables.
👉 Vérifier votre solution (Étape 2)
1️⃣ Anchor YAML pour les variables Docker
Section intitulée « 1️⃣ Anchor YAML pour les variables Docker »Déclarez une anchor YAML réutilisable, puis injectez-la dans docker-build.
.docker-vars: &docker_vars DOCKER_TLS_CERTDIR: "/certs"
docker-build: stage: build image: docker:27 services: - docker:27-dind variables: <<: *docker_varsCette factorisation est utile si plusieurs jobs Docker partagent les mêmes variables d'environnement.
Étape 3 — Utiliser default pour le commun global
Section intitulée « Étape 3 — Utiliser default pour le commun global »-
Si pertinent, ajoutez un bloc
default:Indice : commencez avec une clé sûre et globale, sans impacter la logique métier des jobs.
-
Gardez uniquement les clés vraiment globales
N'y mettez pas des options qui ne concernent qu'un sous-ensemble de jobs.
👉 Vérifier votre solution (Étape 3)
1️⃣ Bloc default global
Section intitulée « 1️⃣ Bloc default global »Dans la solution, le pipeline ajoute un default minimal :
default: interruptible: trueCe réglage permet d'annuler les jobs obsolètes quand un nouveau pipeline démarre sur la même branche.
Étape 4 — Valider que le comportement n'a pas changé
Section intitulée « Étape 4 — Valider que le comportement n'a pas changé »-
Validez le YAML
Fenêtre de terminal glab ci lint .gitlab-ci.yml -
Poussez le refactoring
Fenêtre de terminal git add .gitlab-ci.ymlgit commit -m "ci: refactor pipeline with extends and anchors"git push origin starter/lab-13 -
Comparez avec le run baseline
Les jobs doivent rester équivalents en résultat, ordre logique et artefacts.
👉 Vérifier votre solution (Étape 4)
1️⃣ Validation de cohérence fonctionnelle
Section intitulée « 1️⃣ Validation de cohérence fonctionnelle »Après refactoring, les jobs et résultats doivent rester identiques :
- même ordre logique des stages (
lint,test,build,deploy) ; - mêmes scripts exécutés dans chaque job ;
- mêmes artefacts (
report.xml) et même regex decoverage.
Vérifiez avec CI Lint, puis en comparant un pipeline avant/après.
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
default: interruptible: true
variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
.python-base: image: python:3.12-slim cache: key: files: - requirements-dev.txt paths: - .pip-cache/
.docker-vars: &docker_vars DOCKER_TLS_CERTDIR: "/certs"
ruff-lint: extends: .python-base stage: lint cache: policy: pull before_script: - pip install ruff script: - ruff check app/ tests/
pytest: extends: .python-base stage: test needs: [] 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_vars 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 »- Les duplications
cacheetbefore_scriptont été réduites -
ruff-lintetpytestutilisentextends: .python-base - Une anchor YAML est utilisée pour les variables Docker récurrentes
- Le pipeline passe sans régression fonctionnelle
Pièges fréquents
Section intitulée « Pièges fréquents »extends fait un merge par clé, pas un deep merge universel. Si vous redéfinissez une clé composite dans un job, vous pouvez écraser une partie de la base sans le vouloir. Vérifiez le rendu final dans CI Lint.
Les anchors YAML sont locales au fichier courant. Si vous externalisez plus tard votre pipeline avec include, une anchor définie dans un autre fichier ne sera pas forcément disponible comme vous l'imaginez.
À retenir
Section intitulée « À retenir »- Un pipeline lisible est plus sûr qu'un pipeline simplement "qui passe"
extendsfactorise la structure métier des jobs- Les anchors YAML factorisent des blocs purement YAML
- Le refactoring CI doit préserver le comportement observable