Guides

Test de charge dans GitHub Actions : guide pratique

7 min de lecture

Pour un test de charge dans GitHub Actions, un seul fichier de workflow suffit : installer l’outil, lancer un test court contre une URL de préproduction et laisser le code de sortie de l’outil faire échouer le job quand un seuil n’est pas tenu. Envoyez les résultats comme artefact pour que chaque exécution laisse une trace. Gardez un test modeste, car un runner standard hébergé par GitHub est une seule machine virtuelle avec quelques cœurs de CPU.

Les informations sur les outils et sur GitHub viennent de la documentation de chaque éditeur, vérifiées le 9 octobre 2026, et les workflows suivent cet usage documenté.

OutilInstallé parCe qui fait échouer le jobRésultats à conserver
k6setup-k6-action de GrafanaUn seuil non tenuSortie JSON
Artilleryartilleryio/action-cliUne vérification ensure en échecreport.json
Locustpip install locustToute requête en échec, ou votre propre règleFichiers CSV et HTML
JMeterL’archive de la versionRien d’intégré : ajouter une étape de contrôleFichier .jtl et rapport HTML

De quoi un workflow de test de charge a-t-il besoin ?

D’un déclencheur, d’un runner, d’une étape qui installe l’outil, d’une étape qui lance le test avec des seuils et d’une étape qui envoie les résultats. Tout le reste est facultatif.

Gardez le script de test dans le dépôt, par exemple sous tests/load/. Lisez l’URL de préproduction depuis une variable du dépôt via le contexte vars, et fixez timeout-minutes sur le job : la valeur par défaut est de 360 minutes (vérifié le 9 octobre 2026).

Quels événements doivent déclencher un test de charge ?

Lancez un test court sur les pull requests et un test plus long selon un planning ou à la main.

Utilisez pull_request pour repérer une régression avant la fusion, workflow_dispatch pour les exécutions manuelles et schedule pour des exécutions cron (en UTC, sauf si vous indiquez un fuseau horaire). La référence des événements de GitHub ajoute quelques limites (vérifié le 9 octobre 2026) : les exécutions planifiées ne tournent que sur la branche par défaut, au plus toutes les cinq minutes, et peuvent être retardés en début d’heure. Les pull requests venant de forks ne reçoivent aucun secret hormis GITHUB_TOKEN.

Comment lancer k6 dans GitHub Actions ?

Utilisez les deux actions officielles de Grafana, l’une pour installer k6, l’autre pour lancer le script. L’étape échoue dès que k6 se termine avec un code de sortie non nul, ce que provoque un seuil non tenu.

setup-k6-action et run-k6-action sont toutes deux en v1 (vérifié le 9 octobre 2026) :

name: load-test
on:
  pull_request:
  workflow_dispatch:

jobs:
  k6:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      BASE_URL: ${{ vars.STAGING_URL }}
    steps:
      - uses: actions/checkout@v7
      - uses: grafana/setup-k6-action@v1
      - uses: grafana/run-k6-action@v1
        with:
          path: tests/load/smoke.js
          flags: --out json=results.json
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: k6-results
          path: results.json
          retention-days: 14

Les seuils se trouvent dans le script, qui lit BASE_URL dans l’environnement :

import http from "k6/http";

export const options = {
  vus: 10,
  duration: "1m",
  thresholds: {
    http_req_failed: ["rate<0.01"],
    http_req_duration: ["p(95)<500"],
  },
};

export default function () {
  http.get(`${__ENV.BASE_URL}/health`);
}

La documentation de k6 précise que les checks n’ont pas d’effet sur le code de sortie, si bien que la décision de réussite ou d’échec doit venir des seuils.

Comment lancer Artillery, Locust ou JMeter à la place ?

Remplacez les étapes d’installation et d’exécution, gardez le reste du workflow et servez-vous du mécanisme propre à chaque outil pour échouer quand un objectif est manqué.

Artillery dispose d’une GitHub Action officielle dont le README indique que @v2 suit la dernière version 2.x (vérifié le 9 octobre 2026). L’étape devient uses: artilleryio/action-cli@v2 avec command: run --target ${{ vars.STAGING_URL }} tests/load/api.yml --output report.json, et le plugin ensure fait sortir Artillery avec un code non nul quand un objectif est manqué. La section config du script :

config:
  target: "https://staging.example.com"
  phases:
    - duration: 60
      arrivalRate: 5
  plugins:
    ensure:
      thresholds:
        - "http.response_time.p95": 500

Locust se termine avec le code 1 dès qu’une requête a échoué, et pour des règles de latence, vous fixez environment.process_exit_code dans un écouteur de l’événement quitting, comme le montre la même page (vérifié le 9 octobre 2026). Après actions/setup-python@v7 avec python-version: "3.13" :

      - run: pip install locust
      - run: >
          locust -f tests/load/locustfile.py --headless
          -u 20 -r 5 -t 2m -H "$BASE_URL"
          --csv results --html report.html

JMeter tourne sans interface grâce à -n, et les options de ligne de commande de son manuel ne prévoient aucun seuil de réussite ou d’échec (vérifié le 9 octobre 2026). Une courte étape peut lire le fichier de résultats, au format CSV avec une colonne success par défaut, et faire échouer le job elle-même :

      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: "21"
      - name: Install JMeter 5.6.3
        env:
          JM_BASE: https://archive.apache.org/dist/jmeter/binaries
        run: curl -sSL "$JM_BASE/apache-jmeter-5.6.3.tgz" | tar -xzf -
      - name: Run the test plan
        run: >
          apache-jmeter-5.6.3/bin/jmeter -n -t tests/load/plan.jmx
          -l results.jtl -e -o report
      - name: Fail above 1% errors
        run: |
          python3 - <<'EOF'
          import csv, sys
          rows = list(csv.DictReader(open("results.jtl")))
          bad = sum(r["success"] != "true" for r in rows)
          print(f"{bad} of {len(rows)} samples failed")
          sys.exit(1 if not rows or bad > 0.01 * len(rows) else 0)
          EOF

Comment faire échouer le job quand le test manque un objectif ?

Inscrivez les objectifs dans le test sous forme de seuils et laissez le code de sortie faire le reste. GitHub marque une étape en échec quand sa commande se termine avec un code non nul, et le job échoue avec elle.

Commencez par un taux d’erreur strict et un percentile de latence généreux, puis resserrez la limite de latence une fois que vous savez de combien vos chiffres varient d’une exécution à l’autre. Le guide pas à pas du test de charge explique comment choisir ces objectifs.

Où ranger les résultats de chaque exécution ?

Envoyez-les comme artefact du workflow, avec if: always() pour qu’un test en échec garde quand même son rapport. Les artefacts sont conservés 90 jours par défaut, et retention-days raccourcit cette durée.

Le README d’upload-artifact documente les 90 jours par défaut (vérifié le 9 octobre 2026). Réglez if-no-files-found: error pour qu’un rapport manquant fasse nettement échouer l’étape.

Quelle charge un runner hébergé par GitHub peut-il générer ?

Assez pour un smoke test ou un petit contrôle de régression. Un runner Linux standard dispose de 4 CPU et 16 Go de RAM dans les dépôts publics, de 2 CPU et 8 Go dans les dépôts privés, et un job s’arrête au bout de 6 heures.

Ces chiffres viennent de la référence des runners et des limites d’Actions de GitHub (vérifié le 9 octobre 2026), qui ne donnent aucun chiffre de bande passante réseau.

Un job tourne sur une seule machine et forme donc un seul générateur de charge : si son CPU sature, les temps de réponse incluent le temps d’attente sur le runner lui-même. Chaque requête part aussi de cette machine, si bien que les limiteurs de débit qui comptent par adresse client voient un seul client très actif. GitHub déconseille de mettre en liste d’autorisation les plages d’adresses IP Azure de ses runners Ubuntu ; les runners plus puissants (larger runners) des offres Team et Enterprise Cloud ajoutent du CPU, de la mémoire et des adresses IP statiques.

Quand la charge doit-elle venir d’ailleurs ?

Quand il vous faut plus de charge qu’une machine n’en produit, du trafic depuis plusieurs régions ou des exécutions plus longues que la limite d’un job.

Avec k6, run-k6-action exécute le test sur Grafana Cloud k6 si vous réglez cloud-run-locally: false et fournissez K6_CLOUD_TOKEN, K6_CLOUD_PROJECT_ID et, avec k6 v2, K6_CLOUD_STACK_ID. Une exécution cloud ignore par défaut l’environnement du runner : ajoutez donc -e BASE_URL=${{ vars.STAGING_URL }} à flags (vérifié le 10 octobre 2026). L’action d’Artillery peut appeler run-fargate ou run-lambda pour générer la charge depuis votre propre compte AWS (les deux vérifiés le 9 octobre 2026). La comparaison k6 vs Artillery couvre les deux.

Pour JMeter, LoadFocus documente une intégration avec GitHub Actions fondée sur son client npm. Vous enregistrez le test dans votre compte LoadFocus, stockez la clé d’API et l’ID d’équipe comme secrets, puis appelez run-test. Les options ci-dessous suivent l’aide du client :

      - run: npm install -g @loadfocus/loadfocus-api-client
      - env:
          LF_KEY: ${{ secrets.LOADFOCUS_API_KEY }}
          LF_TEAM: ${{ secrets.LOADFOCUS_TEAM_ID }}
        run: |
          test -n "$LF_KEY"
          test -n "$LF_TEAM"
          loadfocus-api config set \
            --apikey "$LF_KEY" --teamid "$LF_TEAM"
          loadfocus-api jmeter run-test --name checkout-api \
            --thresholds "avgresponse<=200,errors==0,p95<=250"

Le client attend jusqu’à 1 800 secondes par défaut (--wait-timeout change cette durée) : donnez donc au job un timeout-minutes supérieur à 30, par exemple 40. Il se termine avec le code 1 quand un seuil n’est pas tenu et avec le code 0 quand la clé d’API ou l’ID d’équipe manque, d’où les lignes test -n, qui font échouer l’étape dès qu’un secret est vide. La charge vient de LoadFocus, qui exécute des scripts JMeter depuis 25 régions cloud.

FAQ

Combien de temps doit durer un test de charge dans une pull request ? Quelques minutes : assez pour dépasser la montée en charge et obtenir un percentile stable, assez peu pour que personne ne l’attende.

A-t-on le droit de lancer des tests de charge depuis les runners hébergés par GitHub ? Les conditions de GitHub pour Actions interdisent de perturber un service et réservent les runners hébergés au travail sur le projet du dépôt (vérifié le 9 octobre 2026). Tester la préproduction de ce même projet, sans surcharger les serveurs de GitHub, correspond au type de test que décrivent ces conditions.

Puis-je utiliser un runner auto-hébergé à la place ? Oui. Il tourne sur votre propre matériel et votre propre réseau, et la page des limites de GitHub autorise un job jusqu’à 5 jours sur ce type de runner. Il reste un seul générateur de charge.

À lire aussi


Les affirmations techniques de cet article n'ont pas été relues de façon indépendante.

LoadFocus team
Écrit par l'équipe LoadFocus, qui fait tourner un service de test de charge dans le cloud.