Aller au contenu
CI/CD & Automatisation medium

Lab 04 — Artifacts et cache

16 min de lecture

logo gitlab

À chaque run, votre pipeline réinstalle 15 paquets pip depuis zéro. Deux fois par job. Et quand les tests échouent, il faut aller chercher les logs dans l'interface GitLab pour comprendre ce qui s'est passé — rien n'est conservé. Dans ce lab, vous allez mettre les dépendances pip en cache (pour ne plus les télécharger à chaque fois) et publier les rapports de tests comme artifacts accessibles directement depuis GitLab.

  • Comprendre la différence entre un artifact et un cache dans GitLab CI/CD
  • Configurer un cache pip avec une clé basée sur le hash du fichier requirements-dev.txt
  • Publier un rapport JUnit en tant qu'artifact et le visualiser dans GitLab
  • Versionner les images Docker avec les variables prédéfinies de GitLab

Ce lab s'adresse surtout aux apprenants ayant complété Lab 03. Si vous êtes en débutant (L1), commencez par les labs 01-03. Si vous êtes intermédiaire (L2) ou avancé, vous pouvez commencer par Lab 04 directement depuis la branche starter/lab-04.

Un pipeline qui réinstalle toutes ses dépendances à chaque run est clairement sous-optimal. Sur un projet professionnel avec 50 dépendances et 20 runs par jour, ça représente des heures de temps machine perdues chaque semaine. Le cache est souvent la première optimisation qu'on ajoute après un pipeline fonctionnel.

Les artifacts, eux, servent à transmettre des fichiers entre jobs ou à conserver des données après le pipeline. Les rapports JUnit sont particulièrement utiles : GitLab les lit automatiquement et affiche un résumé des tests dans les Merge Requests, sans avoir à chercher dans les logs.

Situations réelles où ce lab vous aide :

  • Un collègue demande « est-ce que les tests passent sur ma MR ? » — les rapports JUnit répondent directement dans l'interface
  • Le pipeline prend 5 minutes pour lancer 30 secondes de tests à cause des pip install
  • Vous voulez retrouver les logs d'un run passé — les artifacts conservent les fichiers 7 jours par défaut
  • Le docker push écrase toujours latest sans versionnement — impossible de savoir quelle image correspond à quel commit
  1. Passez sur la branche de départ

    Fenêtre de terminal
    cd pipeline-craft
    git checkout starter/lab-04
  2. Poussez pour déclencher le pipeline

    Fenêtre de terminal
    git push origin starter/lab-04
  3. Observez le pipeline dans Build > Pipelines

    Le pipeline passe au vert — il hérite des corrections du Lab 03. Mais chaque job réinstalle ses dépendances depuis zéro, et le rapport de tests disparaît après le run.

Avant de coder, il faut clarifier deux concepts souvent confondus.

Un cache est conçu pour accélérer les builds. Il conserve des fichiers entre deux runs du même pipeline sur le même runner — typiquement un dossier de dépendances comme .pip-cache/. Si le cache n'existe pas encore, le job fonctionne quand même (il réinstalle juste tout). Le cache n'est pas garanti : si le runner change, si le cache expire, il sera vide.

Un artifact est un fichier que vous voulez conserver et partager. Il est téléversé sur GitLab après le job et reste accessible pendant la durée d'expiration configurée. Il peut être transmis d'un job à un autre dans le même pipeline (on l'utilise pour passer des binaires compilés d'un stage build à un stage deploy), ou conservé pour être consulté plus tard (rapports de tests, logs).

CacheArtifact
ObjectifAccélérer le buildConserver / transmettre des fichiers
Partagé entre jobsNon (même runner)Oui (pipeline entier)
Garanti ?NonOui (jusqu'à expiration)
Visible dans GitLab UINonOui

Étape 1 — À vous de configurer le cache pip global

Section intitulée « Étape 1 — À vous de configurer le cache pip global »

En haut du .gitlab-ci.yml, avant les définitions de jobs, vous devez ajouter une variable globale qui configure où pip stocke ses paquets téléchargés. Cette variable sera ensuite utilisée par le job pytest.

À vous de proposer :

  1. Quel est le nom de la variable que vous allez créer pour indiquer le chemin du cache pip ?

    • 🔍 Indice : Les variables de cache pip dans les outils CI/CD suivent généralement la convention PIP_*
  2. Quelle est la valeur que vous allez attribuer à cette variable ?

    • 🔍 Indice : Utilisez une variable prédéfinie de GitLab qui représente le répertoire du projet. Elle commence par $CI_...
  3. Comment allez-vous ensuite référencer cette variable dans la configuration du cache du job pytest ?

    • 🔍 Indice : La clé du cache devrait changer automatiquement quand les dépendances changent
👉 Vérifier votre solution

Ajoutez en haut du fichier (avant les définitions de jobs) :

variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"

$CI_PROJECT_DIR est une variable prédéfinie de GitLab qui pointe vers le répertoire racine du projet sur le runner.

Modifiez le job pytest pour ajouter une configuration de cache :

pytest:
stage: test
image: python:3.12-slim
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
before_script:
- pip install -r requirements-dev.txt
script:
- pytest -v --junitxml=report.xml
artifacts:
when: always
paths:
- report.xml
reports:
junit: report.xml
expire_in: 7 days

Précisions essentielles :

  • key.files: - requirements-dev.txt : la clé du cache est calculée à partir du hash de ce fichier. Si les dépendances changent, le cache est invalidé automatiquement.
  • paths: - .pip-cache/ : ce dossier sera sauvegardé et restauré entre les runs du même pipeline sur le même runner.

3️⃣ Cache en lecture seule sur le job ruff-lint

Section intitulée « 3️⃣ Cache en lecture seule sur le job ruff-lint »

Le job ruff-lint n'installe qu'un seul paquet, mais il peut bénéficier du cache après pytest. Ajoutez la même configuration de cache avec policy: pull pour indiquer qu'il est en lecture seule :

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/

La politique pull évite les conflits d'écriture simultanée dans le cache.

Après avoir configuré le cache, vous allez faire en sorte que pytest génère un rapport au format JUnit, puis vous indiquerez à GitLab :

  1. Où trouver ce rapport (pour l'afficher dans l'UI)
  2. Qu'il doit être conservé comme artifact (même si le job échoue)
  3. Pendant combien de temps le conserver

À vous de proposer :

  1. Quelle option pytest doivent vous ajouter pour générer un rapport XML ?

    • 🔍 Indice : Elle commence par --junit...
  2. Quelle clé YAML vous indique à GitLab que ce fichier doit être conservé après le job ?

    • 🔍 Indice : Elle s'appelle artifacts:
  3. Comment configurez-vous pour que l'artifact soit conservé même si le job échoue ?

    • 🔍 Indice : Cherchez la clé when: avec valeur always
  4. Comment indiquez à GitLab que ce fichier est un rapport JUnit pour qu'il l'affiche dans les Merge Requests ?

    • 🔍 Indice : Sous-clé reports: avec junit:
👉 Vérifier votre solution

Modifiez le job pytest pour générer le rapport et le déclarer comme artifact :

pytest:
stage: test
image: python:3.12-slim
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
before_script:
- pip install -r requirements-dev.txt
script:
- pytest -v --junitxml=report.xml
artifacts:
when: always
paths:
- report.xml
reports:
junit: report.xml
expire_in: 7 days

Explication ligne par ligne :

  • --junitxml=report.xml : pytest écrit les résultats en format JUnit dans report.xml
  • when: always : conserve l'artifact même si le job échoue. C'est crucial pour les tests — c'est précisément quand les tests échouent qu'on veut le rapport !
  • paths: - report.xml : le fichier sera téléchargeable depuis la page du job
  • reports.junit: report.xml : indique à GitLab que c'est un rapport JUnit. GitLab le parse et affiche un résumé dans les Merge Requests
  • expire_in: 7 days : après 7 jours, GitLab supprime l'artifact pour économiser l'espace

Pour l'instant, le job docker-build pousse l'image avec le tag latest, ce qui écrase à chaque fois l'image précédente. Vous ne savez plus quelle image correspond à quel commit.

Vous allez ajouter un second tag basé sur le SHA du commit, pour que chaque push crée une image unique et traçable.

À vous de proposer :

  1. Où trouvez-vous le hash du commit pour l'utiliser comme tag Docker ?

    • 🔍 Indice : GitLab injecte une variable $CI_COMMIT_* que vous pouvez utiliser
  2. Quelles sont les deux lignes docker build que vous allez utiliser ?

    • 🔍 Indice : Une avec le SHA du commit court, une avec latest
  3. Lequel des deux tags doit être créé et poussé en premier ?

    • 🔍 Indice : Le tag versionnée (SHA) pour traçabilité
  4. Comment référencez-vous le Container Registry de votre projet GitLab ?

    • 🔍 Indice : Variable prédéfinie $CI_REGISTRY*
👉 Vérifier votre solution

Modifiez le job docker-build :

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

Variables prédéfinies en action :

  • $CI_REGISTRY_IMAGE : l'adresse du Container Registry de votre projet (ex : registry.gitlab.com/votre-pseudo/pipeline-craft)
  • $CI_COMMIT_SHORT_SHA : les 8 premiers caractères du hash du commit (ex : a1b2c3d4). Chaque push produit un tag unique et traçable.
  • $CI_REGISTRY_USER et $CI_REGISTRY_PASSWORD : identifiants pour le login — GitLab les injecte automatiquement

Avantage : maintenant, chaque version de votre code a sa propre image Docker dans le registry. Vous pouvez revenir à une version passée directement en re-pullant $CI_REGISTRY_IMAGE:a1b2c3d4, même 6 mois après.

📄 Voir le fichier .gitlab-ci.yml complet
stages:
- lint
- test
- build
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
cache:
key:
files:
- requirements-dev.txt
paths:
- .pip-cache/
before_script:
- pip install -r requirements-dev.txt
script:
- pytest -v --junitxml=report.xml
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
  1. Committez et poussez

    Fenêtre de terminal
    git add .gitlab-ci.yml
    git commit -m "ci: add pip cache, junit artifact and docker versioning"
    git push origin starter/lab-04
  2. Observez le premier run

    Le premier run ne bénéficie pas encore du cache (il n'existe pas encore). C'est normal. Regardez le job pytest — à la fin, GitLab affiche Uploading artifacts... puis report.xml.

  3. Déclenchez un deuxième run (faites un commit vide par exemple)

    Fenêtre de terminal
    git commit --allow-empty -m "ci: test cache hit"
    git push origin starter/lab-04

    Cette fois, les jobs doivent afficher Checking cache for ... suivi de Successfully extracted cache. Le pip install va beaucoup plus vite.

  4. Vérifiez le rapport de tests

    Après le run, cliquez sur le job pytest dans GitLab. En bas de la page, vous verrez une section Artifacts avec un lien pour télécharger report.xml. Si vous créez une Merge Request sur ce projet, l'onglet Tests affichera automatiquement les résultats.

  • Le pipeline passe au vert
  • Le deuxième run est plus rapide que le premier (cache hit)
  • Le fichier report.xml est visible comme artifact dans le job pytest
  • Le job docker-build utilise $CI_COMMIT_SHORT_SHA pour taguer l'image
SymptômeCauseSolution
Cache jamais utiliséClé de cache différente entre les runsVérifier que les clés (key:) sont identiques entre le job qui écrit et les jobs qui lisent
report.xml absent des artifacts--junitxml=report.xml absent du script pytestAjouter l'option à la commande pytest
Tests absents dans les MRreports.junit: manquant dans artifacts:Le chemin dans reports.junit: doit correspondre exactement au fichier généré
Push registry échoueContainer Registry désactivé dans le projetActiver dans Settings > General > Visibility
  • Les variables prédéfinies de GitLab : GitLab injecte des dizaines de variables dans chaque job ($CI_COMMIT_REF_NAME, $CI_PROJECT_PATH, $CI_PIPELINE_ID…). Ajoutez env | grep CI_ dans un script: pour les voir toutes. Voir Artifacts et cache.
  • Comparez avec solution/lab-04 : git diff origin/starter/lab-04..origin/solution/lab-04 montre les corrections complètes.
  • Un cache accélère les builds en réutilisant des fichiers entre les runs (pas garanti), un artifact conserve des fichiers après un job (garanti jusqu'à expiration)
  • La clé de cache basée sur key.files invalide automatiquement le cache quand les dépendances changent — c'est la bonne pratique
  • when: always sur les artifacts de tests est essentiel : c'est quand les tests échouent qu'on a le plus besoin du rapport
  • Les variables prédéfinies ($CI_REGISTRY_IMAGE, $CI_COMMIT_SHORT_SHA…) évitent de coder en dur des valeurs dans le pipeline

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn