Load testing k6 dans le pipeline CI

À la fin de ce tutoriel, vous saurez poser un load test Grafana k6 comme quality gate : script JS versionné, checks, thresholds qui font échouer le job, GitHub Actions (ou Docker), artefact JSON, et un lab en ca-central-1 sur votre staging — jamais un tiers, jamais la prod ouverte. Angle DevOps Elastic Hayway (DEH) : perf mesurable, pas un PDF JMeter mort.

Niveau : Intermédiaire · Temps estimé : 50–70 min · Versions cibles : Grafana k6 OSS (v0.57 / 1.x) · Docker · GitHub Actions · Dernière vérification : 2026-09-11 · Region : ca-central-1

Slug : wow-load-testing-k6 · Série : WOW (30/50) · Mot-clé SEO : load testing k6 · Publish : HOLD

← Précédent : Chaos Engineering Litmus · → Suivant : Trivy scan CI · Aussi : GitHub Actions reusable · SLO / error budgets · Prometheus Grafana Helm

Prérequis

Coût : 0 € en local ; 0–quelques € si cible AWS jetable. k6 OSS est gratuit. Interdit de bombarder un SaaS public « pour voir ».

export AWS_PROFILE=lab
export AWS_DEFAULT_REGION=ca-central-1
docker --version

Ce que nous allons construire

Load testing k6 CI (WOW 30/50) — ca-central-1
  ├── Pourquoi k6 + contrat éthique
  ├── Script : HTTP, checks, thresholds, BASE_URL
  ├── Scénarios : ramp VU vs arrival-rate
  ├── Lab nginx Docker + option AWS
  ├── GitHub Actions : gate + artefacts
  └── Anti-patterns, quiz, FAQ

(Schéma — alt : « k6 dans le CI DEH : script versionné, seuils, job Actions, cible staging ca-central-1 ».)

Étape 1 — Pourquoi k6 dans le CI

Un load test hors pipeline pourrit : personne ne le relance. DEH : même repo, même PR, exit ≠ 0 si les seuils cassent.

Outil Force Limite CI
JMeter GUI, héritage XML lourd, peu « as code »
Locust Python Agents à scaler
Artillery YAML/JS Moins d’écosystème Grafana
k6 JS, CLI, Docker, thresholds = tests Pas un e2e navigateur

k6 (Grafana) : binaire / image, JS, métriques natives. Grafana Cloud existe ; ce WOW = OSS dans le CI.

Éthique : uniquement vos URLs. Interdit : prod sans runbook, sites tiers, flood httpbin. Chaos ≠ flood HTTP — Litmus.

Étape 2 — Script : checks + thresholds

Fichier tests/load/health.js. BASE_URL vient de l’env — jamais un hostname en dur.

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 5,
  duration: '30s',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
    checks: ['rate>0.99'],
  },
};

export default function () {
  const base = __ENV.BASE_URL;
  if (!base) throw new Error('BASE_URL manquant');
  const res = http.get(`${base}/health`, {
    tags: { name: 'health' },
    timeout: '10s',
  });
  check(res, {
    'status 200': (r) => r.status === 200,
  });
  sleep(1);
}

checks = assertions par itération (seuls, ils ne rougissent pas le process). thresholds = critères globaux : un seuil raté → exit souvent 99job CI rouge. Tags name: évitent d’exploser les séries d’URL. Timeout explicite : un hung n’est pas un p95 cosmétique.

Étape 3 — Scénarios : ramp vs arrival-rate

VUs fixes = smoke. Un test « comme la prod » décrit des arrivées, pas seulement des boucles sleep.

export const options = {
  scenarios: {
    smoke: {
      executor: 'constant-vus',
      vus: 2,
      duration: '20s',
      tags: { test_type: 'smoke' },
    },
    ramp: {
      executor: 'ramping-vus',
      startTime: '20s',
      stages: [
        { duration: '30s', target: 10 },
        { duration: '1m', target: 10 },
        { duration: '20s', target: 0 },
      ],
      tags: { test_type: 'ramp' },
    },
  },
  thresholds: {
    http_req_failed: ['rate<0.01'],
    'http_req_duration{test_type:ramp}': ['p(95)<800'],
  },
};
Executor Idée Quand DEH
constant-vus N utilisateurs Smoke PR
ramping-vus Montée / palier Soak court
constant-arrival-rate N req/s visées SLO débit

Séparez smoke PR (2–5 VUs, 20–30 s) et soak nightly (staging dimensionné). 50 VUs sur un nginx laptop saturent le laptop. Seuils = SLOerror budgets.

Étape 4 — Lab local + option ca-central-1

4.1 — nginx (0 €)

mkdir -p lab-k6 && echo ok > lab-k6/index.html
docker run --rm -d --name deh-wow30-nginx -p 8080:80 
  -v "$PWD/lab-k6:/usr/share/nginx/html:ro" nginx:1.27-alpine

Alignez le check : GET / (body ok) ou un location /health { return 200 'ok'; }.

docker run --rm -i --network host 
  -e BASE_URL=http://127.0.0.1:8080 
  grafana/k6:latest run - < tests/load/health.js

--network host (Linux). macOS/Windows : host.docker.internal.

4.2 — Cible AWS lab (optionnel)

Uniquement votre ALB / ECS en ca-central-1ECS Fargate. Pas us-east-1 par défaut.

aws sts get-caller-identity
aws elbv2 describe-load-balancers --query 'LoadBalancers[].DNSName'
export BASE_URL="https://<votre-alb-lab>.ca-central-1.elb.amazonaws.com"
# 2–10 VUs. Cleanup après le lab.

Pas de NAT/RDS « pour k6 ». Pas de 1000 VUs. SG : source = runner / IP lab, pas 0.0.0.0/0.

docker rm -f deh-wow30-nginx

Étape 5 — GitHub Actions : quality gate

Smoke sur PR, même script, BASE_URL en variable (staging). Image officielle : reproductible.

# .github/workflows/k6-smoke.yml
name: k6-smoke
on:
  pull_request:
    paths: ['tests/load/**', 'app/**', '.github/workflows/k6-smoke.yml']
jobs:
  k6:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: k6 smoke
        env:
          BASE_URL: ${{ vars.STAGING_BASE_URL }}
        run: |
          docker run --rm -e BASE_URL 
            -v "$PWD:/work" 
            grafana/k6:latest run 
            --summary-export=/work/k6-summary.json 
            /work/tests/load/health.js
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: k6-summary
          path: k6-summary.json

Les thresholds restent la source rouge/vert. L’action grafana/k6-action existe ; Docker marche aussi sur GitLab / Buildkite. OIDC vers AWS si ALB interne : Actions OIDC AWS — pas de clés longues.

Nightly : schedule + workflow_dispatch, scénario ramp, VUs plus hauts seulement si staging isolé. Ne collez pas le soak dans le smoke PR.

Étape 6 — Lire les résultats sans se mentir

Métriques utiles : http_reqs, http_req_duration (p90/p95/p99), http_req_failed, vus.

Lecture naïve Lecture DEH
avg 80 ms = OK p95 / p99 + erreurs
0 erreur / 2 VUs Smoke ≠ capacité
« ça passe en local » Runner ≠ prod (CPU, TLS)
10k VUs tout de suite D’abord le bottleneck (app, DB, ALB)

k6 mesure côté client (runner, DNS, WAF possibles). Corrélez la cibleOpenTelemetry, Prometheus. Cloud Grafana = add-on, pas un prérequis.

Étape 7 — Anti-patterns + checklist DEH

Anti-patterns : load prod sans fenêtre ; viser un tiers ; 500 VUs sur un micro ; seuils p(95)<10000 ; URL en dur ; un scénario fourre-tout ; ignorer http_req_failed ; SG 0.0.0.0/0 ; confondre k6 et e2e.

Checklist : cible = staging à vous ; BASE_URL injecté ; smoke petit + nightly séparé ; thresholds = SLO ; tags name ; timeout HTTP ; timeout-minutes job ; artefact ; APM/métriques ; region ca-central-1 ; teardown ; qui a le droit de lancer le soak.

Erreurs fréquentes

Erreur Impact Correction
Tester httpbin / un site public Abuse, éthique Staging votre
Thresholds trop lâches Gate cosmétique p95 + http_req_failed réels
Checks sans thresholds CI toujours vert Thresholds checks + HTTP
localhost dans Actions Connexion refusée BASE_URL staging
Soak 15 min sur chaque PR File CI Smoke court / soak schedule
Region us-east-1 Incohérence DEH Forcer ca-central-1
Oublier teardown Bruit / facture docker rm / scale-in
k6 = chaos Mauvais outil Litmus = fautes ; k6 = charge

Quiz (5 questions)

1. Ce qui fait échouer le job CI k6 :
– A. Un check isolé à 99 %
– B. Un threshold global raté (exit ≠ 0)
– C. L’absence de Grafana Cloud

2. Où pointer BASE_URL en CI ?
– A. La homepage d’un SaaS célèbre
– B. Votre staging (variable CI)
– C. http://127.0.0.1 sur le runner (sans service)

3. Region lab de ce tuto :
– A. us-east-1
– B. ca-central-1
– C. eu-west-3

4. Le smoke PR DEH doit être :
– A. 10 000 VUs pendant 30 min
– B. Court, peu de VUs, chaque PR pertinente
– C. Uniquement manuel

5. constant-arrival-rate sert surtout à :
– A. Remplacer les probes K8s
– B. Viser un débit (req/s), pas N boucles VU
– C. Signer les images (Cosign)

Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑B

FAQ

Pourquoi ca-central-1 ?

Standard lab DEH : IAM, ALB, ECS alignés. Le runner GitHub n’est pas dans cette region : c’est la cible qui l’est.

k6 vs JMeter ?

JMeter = GUI héritée. k6 = CI as code (JS, Docker, thresholds). Gates PR DEH = k6.

Faut-il Grafana Cloud k6 ?

Non pour démarrer. OSS + summary + job rouge/vert suffisent. Cloud = géo / UI équipe.

GraphQL / gRPC ?

Oui (http, k6/net/grpc). Commencez par health + 1 parcours métier.

Load test = chaos ?

Non. k6 = charge. Chaos = fautes — Litmus. Les deux, sur staging.

Combien de VUs ?

Smoke : 2–10. Soak : calé sur le budget staging, par paliers — pas un chiffre LinkedIn.

k6 dans Kubernetes ?

Job / opérateur possible ; DEH PR = Actions + Docker d’abord.

Navigateur / Web Vitals ?

k6 browser ≠ ce smoke CLI. E2E perf = autre job (Playwright). Ne mélangez pas dans le même p95 HTTP.

Pour aller plus loin

Maillage série WOW

← Précédent Chaos Engineering Litmus
→ Suivant Trivy scan CI
Aussi GitHub Actions reusable · SLO / error budgets · Prometheus Grafana

Meta publication (SEO)

  • Title SEO : Load testing k6 dans le pipeline CI : seuils, gates, lab FR
  • Meta description : Intégrez Grafana k6 dans le CI : script JS, checks, thresholds, GitHub Actions, lab ca-central-1, FAQ, quiz et check-list DEH.
  • Focus keyword : load testing k6
  • Secondary : Grafana k6 CI, k6 GitHub Actions, load test thresholds, quality gate performance
  • Image : assets/web/devopelastichayway/cover-wow-load-testing-k6-1200x630.webp (à générer)
  • Catégorie : WOW / SRE · Niveau : Intermédiaire
  • URL cible : https://devopelastichayway.com/tutoriels/wow-load-testing-k6/
  • Post live : N/A (nouveau) · slug wow-load-testing-k6 · Publish : HOLD (draft — push WP-CLI sur feu vert Maître)

← Catalogue Tutoriels

Pour aller plus loin — hubs live

Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.