Aller au contenu
CI/CD & Automatisation medium

Lab 04 — Artifacts et cache

14 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

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
  1. Ajoutez une variable globale pour le répertoire de cache pip

    En haut du .gitlab-ci.yml, avant les définitions de jobs, ajoutez :

    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 du projet sur le runner — c’est là que votre code est cloné. On va y créer un dossier .pip-cache pour stocker les paquets pip téléchargés.

    En configurant PIP_CACHE_DIR, on dit à pip : « au lieu de mettre les paquets dans le cache système par défaut, mets-les dans ce dossier du projet ». Ça permet à GitLab de mettre ce dossier en cache entre les runs.

  2. Ajoutez la configuration de cache au job pytest (c’est lui qui installe le plus de dépendances)

    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

    Décortiquons la clé de cache :

    • key.files: - requirements-dev.txt : la clé du cache est calculée à partir du hash du fichier requirements-dev.txt. Si vous ajoutez ou supprimez une dépendance (ce qui modifie requirements-dev.txt), la clé change → GitLab crée un nouveau cache. Si requirements-dev.txt n’a pas changé, la même clé est réutilisée → les dépendances sont déjà là.
    • paths: - .pip-cache/ : ce dossier sera sauvegardé et restauré automatiquement autour du job.
  3. Ajoutez le cache en lecture seule au job ruff-lint

    Le job ruff-lint n’installe qu’un seul paquet (ruff), mais si vous y mettez aussi le cache, il bénéficiera de ce qui a été mis en cache par pytest lors du run précédent. Ajoutez la clé policy: pull pour indiquer que ce job ne fait que lire le cache (il ne le met pas à jour) :

    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 que deux jobs essaient d’écrire dans le cache en même temps.

  1. Modifiez le script pytest pour générer un fichier de rapport XML

    pytest:
    ...
    script:
    - pytest -v --junitxml=report.xml

    L’option --junitxml=report.xml demande à pytest d’écrire les résultats dans un fichier XML au format JUnit. Ce format est un standard reconnu par GitLab, Jenkins, et de nombreux autres outils CI.

  2. Déclarez l’artifact juste après le script

    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

    Quelques précisions :

    • when: always : conserve l’artifact même si le job échoue. C’est essentiel pour les rapports de 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 dans GitLab.
    • reports.junit: report.xml : indique à GitLab que ce fichier est un rapport JUnit. GitLab le parse et affiche un résumé des tests dans les Merge Requests.
    • expire_in: 7 days : après 7 jours, GitLab supprime automatiquement l’artifact pour économiser de l’espace.
  1. Modifiez le script du job docker-build pour utiliser le SHA du commit comme tag

    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

    Ces lignes utilisent des variables prédéfinies de GitLab injectées automatiquement dans chaque job :

    • $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.
    • $CI_REGISTRY_USER et $CI_REGISTRY_PASSWORD : identifiants pour se connecter au registry — GitLab les injecte automatiquement, vous n’avez rien à configurer.
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 starter/lab-04..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