Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 422 minIntermédiaire

Matrice, cache et artefacts dans GitHub Actions

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • tester plusieurs versions en parallèle avec une matrice ;
  • accélérer un pipeline avec un cache et une bonne clé ;
  • transmettre des fichiers entre jobs avec des artefacts ;
  • factoriser un workflow avec une action composite ou un workflow réutilisable.

5 exercices corrigés1 schéma

Trois mécanismes de GitHub Actions rendent un pipeline plus complet et plus rapide : la matrice lance le même job sur plusieurs versions ou systèmes en parallèle, le cache garde des fichiers coûteux à télécharger d'une exécution à l'autre, et les artefacts conservent les fichiers produits par un job pour les transmettre à un autre job ou les télécharger. Ce chapitre explique chacun d'eux, quand choisir l'un ou l'autre, et comment factoriser un workflow qui se répète.

La matrice : tester plusieurs versions en parallèle

sorbier-frais tourne en production sur Node.js 24. L'équipe veut vérifier qu'il fonctionne aussi sur Node.js 22, encore installé sur un ancien serveur, et sur Node.js 26, la nouvelle version LTS, pour préparer la migration. Au lieu d'écrire trois jobs identiques, on déclare une matrice :

yaml
jobs:
  tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24, 26]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test -- --ci

GitHub crée trois jobs, tests (22), tests (24) et tests (26), qui tournent en parallèle. Dans chacun, ${{ matrix.node }} vaut la version correspondante.

Version de Node.jsStatut en novembre 2026Fin de maintenance
22LTS en maintenance (correctifs de sécurité seulement)avril 2027
24LTS en maintenance depuis octobre 2026avril 2028
26LTS active depuis le 28 octobre 2026avril 2029

Une matrice peut combiner plusieurs dimensions : GitHub crée un job par combinaison.

yaml
    strategy:
      fail-fast: false
      max-parallel: 4
      matrix:
        node: [22, 24, 26]
        os: [ubuntu-latest, windows-latest]
        exclude:
          - node: 22
            os: windows-latest
        include:
          - node: 26
            os: ubuntu-latest
            experimental: true
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental == true }}
CléEffet
Les listes (node, os)Produit cartésien : 3 × 2 = 6 combinaisons
excludeRetire les combinaisons qui correspondent : il en reste 5
includeAjoute des variables à une combinaison existante si c'est possible sans en écraser une valeur, sinon crée une nouvelle combinaison ; ici, experimental: true s'ajoute à la combinaison (26, ubuntu-latest) : toujours 5 jobs
fail-fasttrue par défaut : au premier job de la matrice en échec, les autres sont annulés. false les laisse finir, pour voir toutes les versions qui cassent
max-parallelNombre maximal de jobs simultanés de la matrice

Une matrice génère au maximum 256 jobs par exécution. En pratique, garde-la petite : trois versions de Node.js sur Linux suffisent pour une API ; ajoute Windows seulement si l'application y tourne vraiment.

Le cache : ne pas tout retélécharger

À chaque exécution, le runner est neuf : npm ci retélécharge toutes les dépendances depuis le registre npm, ce qui peut prendre une minute ou plus. Le cache sauvegarde un dossier à la fin d'un job et le restaure au début des suivants.

La méthode simple : le cache intégré aux actions setup-*. actions/setup-node avec cache: npm met en cache le dossier de téléchargement de npm (~/.npm, pas node_modules), avec une clé calculée à partir de package-lock.json. actions/setup-python fait de même avec cache: pip, actions/setup-java avec cache: maven.

yaml
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

La méthode générale : actions/cache. Pour tout autre dossier, on décrit soi-même le chemin et la clé. Exemple avec le cache de Composer pour une application PHP :

yaml
      - name: Trouver le dossier de cache de Composer
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

      - uses: actions/cache@v6
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: composer-${{ runner.os }}-${{ hashFiles('composer.lock') }}
          restore-keys: |
            composer-${{ runner.os }}-

      - run: composer install --no-interaction --no-progress

Le fonctionnement de la clé :

  1. Au début du job, GitHub cherche un cache dont la clé est exactement composer-Linux-<empreinte de composer.lock>. S'il le trouve (cache hit), il le restaure.
  2. Sinon (cache miss), il essaie les préfixes de restore-keys et restaure le cache le plus récent qui commence par composer-Linux- : une version un peu ancienne, que Composer complète.
  3. À la fin du job, s'il n'y a pas eu de correspondance exacte, il enregistre le dossier sous la clé exacte.

Comme la clé contient l'empreinte de composer.lock, toute modification des dépendances produit une nouvelle clé : le cache ne peut pas servir des versions périmées.

RègleDétail
Quota10 Go de cache par dépôt ; au-delà, les plus anciens sont supprimés
ExpirationUn cache inutilisé pendant 7 jours est supprimé
PortéeUn job peut lire les caches de sa branche et de la branche par défaut (et, pour une pull request, ceux de la branche cible), pas ceux d'une autre branche de travail
ContenuUniquement des fichiers reconstructibles : dépendances téléchargées, outils, résultats de compilation intermédiaires

Les artefacts : conserver et transmettre des fichiers

Un artefact est un fichier ou un dossier produit par un job et conservé après la fin de l'exécution : le dossier dist/ construit, un rapport de couverture des tests, un journal d'erreur, une archive à livrer. On l'envoie avec actions/upload-artifact et on le récupère avec actions/download-artifact, dans un autre job ou depuis la page de l'exécution.

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v7
        with:
          name: site-statique
          path: dist/
          retention-days: 14
          if-no-files-found: error

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: site-statique
          path: dist/
      - run: ls -R dist/
ParamètreRôle
nameLe nom de l'artefact ; il doit être unique dans une exécution
pathLe fichier, le dossier ou le motif à envoyer (dist/, coverage/**/*.xml)
retention-daysDurée de conservation ; 90 jours par défaut, à réduire pour économiser le stockage
if-no-files-foundwarn par défaut ; error fait échouer l'étape si rien n'a été produit, ce qui évite de livrer un dossier vide

Le job deploy ne refait ni checkout, ni installation, ni construction : il récupère exactement les fichiers construits et testés par build. C'est la règle « construire une fois, déployer partout » du chapitre 2.

Pour un rapport de tests, envoie l'artefact même en cas d'échec, puisque c'est justement là qu'il sert :

yaml
      - run: npm test -- --ci --coverage
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: couverture-node-${{ matrix.node }}
          path: coverage/

Les artefacts se téléchargent en bas de la page de l'exécution, ou en ligne de commande avec gh run download. Ils comptent dans le quota de stockage des dépôts privés (500 Mo sur l'offre gratuite).

Cache ou artefact ?

Cache : entre des exécutions différentesexécutiondu lundicache ~/.npmclé : hash du lockexécution dumardiexécution dumercredienregistrerestaurePerdu ? Le job est plus lent, mais il marche.Artefact : dans la même exécutionjobbuildartefactdist/jobdeploytéléchargementpar une personneenvoieAbsent ? Le job suivant échoue.
Le cache accélère les exécutions suivantes ; l'artefact transmet un résultat dans l'exécution en cours
CacheArtefact
Sert àGagner du tempsTransmettre ou conserver un résultat
EntreDes exécutions différentesLes jobs d'une même exécution, ou vers une personne
Contenu typiqueDépendances téléchargées, outilsdist/, image exportée, rapport de tests, journaux
S'il disparaîtLe job est plus lent, mais il marcheLe job suivant échoue
Durée de vie7 jours sans utilisation90 jours par défaut, réglable

Ne pas se répéter : workflows réutilisables et actions composites

Les jobs lint, tests et build répètent les mêmes trois étapes : checkout, setup-node, npm ci. Deux mécanismes évitent ces copies.

L'action composite regroupe des étapes dans une action locale. On crée .github/actions/preparer-node/action.yml :

yaml
name: Préparer Node.js
description: Installe Node.js et les dépendances du projet
inputs:
  node-version:
    description: Version de Node.js à installer
    default: '24'
runs:
  using: composite
  steps:
    - uses: actions/setup-node@v7
      with:
        node-version: ${{ inputs.node-version }}
        cache: npm
    - run: npm ci
      shell: bash

Dans une action composite, chaque étape run doit préciser son shell. Les jobs l'utilisent après le checkout (l'action est un fichier du dépôt, il faut d'abord le récupérer) :

yaml
    steps:
      - uses: actions/checkout@v7
      - uses: ./.github/actions/preparer-node
        with:
          node-version: 26
      - run: npm test -- --ci

Le workflow réutilisable factorise des jobs entiers. Le fichier .github/workflows/tests-node.yml se déclare avec workflow_call :

yaml
name: Tests Node.js

on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '24'

permissions:
  contents: read

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: ${{ inputs.node-version }}
          cache: npm
      - run: npm ci
      - run: npm test -- --ci

Un autre workflow l'appelle au niveau d'un job, avec uses à la place de runs-on et steps :

yaml
jobs:
  tests-26:
    uses: ./.github/workflows/tests-node.yml
    with:
      node-version: '26'

S'il a besoin de secrets, l'appelant les transmet un par un sous secrets:, ou tous avec secrets: inherit. Une organisation peut ainsi écrire une fois son workflow de tests et l'appeler depuis tous ses dépôts, avec uses: organisation/depot/.github/workflows/tests-node.yml@v1.

Action compositeWorkflow réutilisable
RegroupeDes étapesDes jobs entiers
S'utilise dansLa liste steps d'un jobUn job, à la place de runs-on
Choisit la machineNon, elle tourne dans le job appelantOui, chacun de ses jobs a son runs-on
Idéal pour« Préparer l'environnement »« Le pipeline de tests standard de l'équipe »

Exercices

Exercice 1 sur 5 Compter les jobs d'une matrice Réussi À revoir

yaml
    strategy:
      matrix:
        node: [22, 24, 26]
        os: [ubuntu-latest, windows-latest]
        exclude:
          - node: 22
            os: windows-latest
        include:
          - node: 24
            os: macos-latest
  1. Combien de jobs cette matrice crée-t-elle ? Liste-les.
  2. Combien en créerait-elle si l'include était - node: 26 avec os: ubuntu-latest et experimental: true ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. Le produit cartésien donne 3 × 2 = 6 combinaisons ; exclude retire (22, windows-latest), il en reste 5. L'include (24, macos-latest) ne peut s'ajouter à aucune combinaison sans écraser la valeur de os : il crée une nouvelle combinaison. Total : 6 jobs : (22, ubuntu-latest), (24, ubuntu-latest), (24, windows-latest), (26, ubuntu-latest), (26, windows-latest), (24, macos-latest).
  2. (26, ubuntu-latest) existe déjà et experimental est une nouvelle variable : elle s'ajoute à cette combinaison sans en créer. Total : 5 jobs.

Tu avais trouvé ?

Exercice 2 sur 5 Écrire une matrice Réussi À revoir

Le script Python inventaire.py de l'équipe d'exploitation doit être testé avec Python 3.13 et 3.14, sous Linux et sous Windows. Toutes les combinaisons doivent aller au bout, même si l'une échoue. Écris le job tests : installation des dépendances listées dans requirements.txt, avec le cache de pip, puis lancement de pytest.

Voir la réponseMasquer la réponse de l'exercice 2
yaml
jobs:
  tests:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        python: ['3.13', '3.14']
        os: [ubuntu-latest, windows-latest]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: ${{ matrix.python }}
          cache: pip
      - run: pip install -r requirements.txt
      - run: pytest

La matrice crée 2 × 2 = 4 jobs. runs-on lit le système dans la matrice. Les versions sont entre guillemets : ce n'est pas indispensable pour 3.13 et 3.14, mais c'est une bonne habitude, car 3.10 sans guillemets deviendrait le nombre 3.1. fail-fast: false laisse les trois autres jobs se terminer si l'un échoue, pour voir d'un coup toutes les combinaisons qui cassent. Les commandes pip et pytest sont les mêmes sous Windows : le shell par défaut y est PowerShell, qui les exécute sans changement.

Tu avais trouvé ?

Exercice 3 sur 5 Cache ou artefact ? Réussi À revoir

Pour chaque besoin, choisis entre un cache et un artefact, et justifie.

  1. Garder le dossier de téléchargement de pip d'une exécution à l'autre.
  2. Récupérer, dans le job deploy, l'archive .zip construite par le job build.
  3. Permettre à la professeure de télécharger le rapport de couverture des tests.
  4. Éviter de retélécharger à chaque exécution le navigateur utilisé par les tests de bout en bout.
Voir la réponseMasquer la réponse de l'exercice 3
  1. Cache (cache: pip dans actions/setup-python) : on gagne du temps, et s'il disparaît, pip retélécharge.
  2. Artefact : c'est un résultat à transmettre entre deux jobs de la même exécution ; s'il manquait, le déploiement serait impossible.
  3. Artefact : il reste téléchargeable depuis la page de l'exécution pendant sa durée de conservation.
  4. Cache : le navigateur est un outil reconstructible ; la clé peut contenir la version de l'outil de test, pour être renouvelée quand il change.

Tu avais trouvé ?

Exercice 4 sur 5 Un artefact en conflit Réussi À revoir

Le job tests tourne en matrice sur Node.js 22, 24 et 26. Il se termine par :

yaml
      - uses: actions/upload-artifact@v7
        with:
          name: rapport-tests
          path: reports/junit.xml

Le job tests (22) réussit, mais tests (24) et tests (26) échouent sur cette étape. Explique pourquoi et corrige.

Voir la réponseMasquer la réponse de l'exercice 4

Les trois jobs envoient un artefact nommé rapport-tests. Le premier arrivé le crée ; les suivants échouent, car un artefact est immuable et son nom unique dans l'exécution (Conflict: an artifact with this name already exists on the workflow run). On ajoute la variable de matrice au nom :

yaml
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: rapport-tests-node-${{ matrix.node }}
          path: reports/junit.xml

Le if: always() n'est pas demandé, mais il est utile : le rapport est surtout précieux quand des tests échouent.

Tu avais trouvé ?

Exercice 5 sur 5 Lire une clé de cache Réussi À revoir

Un job utilise key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }} et restore-keys: npm-${{ runner.os }}-. Que se passe-t-il au début et à la fin du job dans chacun de ces cas ?

  1. Aucun fichier n'a changé depuis l'exécution d'hier.
  2. Lina vient d'ajouter une dépendance (le package-lock.json a changé).
  3. Le dépôt n'a pas été utilisé depuis dix jours.
Voir la réponseMasquer la réponse de l'exercice 5
  1. Correspondance exacte (cache hit) : le cache est restauré, et rien n'est enregistré à la fin puisque la clé existe déjà.
  2. L'empreinte change, la clé exacte n'existe pas (cache miss). Le préfixe npm-Linux- restaure le cache le plus récent ; npm ci ne télécharge que la nouvelle dépendance. À la fin, le dossier est enregistré sous la nouvelle clé.
  3. Les caches inutilisés pendant 7 jours ont été supprimés : ni la clé exacte ni le préfixe ne trouvent rien. Tout est téléchargé, puis enregistré à la fin. Le job est plus lent, mais il réussit.

Tu avais trouvé ?

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : GitLab CI : le fichier .gitlab-ci.ymlRevoir le sommaire du cours