đ k6 â Test de Charge "Developer-Centric"
Guide complet IDEO-Lab sur l'outil de test de performance (Go/JS) de Grafana Labs.
Concept : "Tests as Code"
Go (backend), JS (scripts). Open source (Grafana Labs).
k6 Test de Charge Govs. JMeter / Gatling
Code (JS) vs GUI (XML). Event-Loop vs Thread-per-User.
JMeter Gatling Event-LoopInstallation
Binaire natif (apt, brew), Docker (grafana/k6).
Le Script (script.js)
Structure : Contexte init vs Contexte VU.
VUs (Virtual Users)
Utilisateurs virtuels. Goroutines (légers).
VU Goroutineoptions (Configuration)
Définition de la charge (vus, duration) dans le script.
http.get() / http.post()
Le "Sampler" HTTP (Params, Body, Headers).
http Samplercheck() (Assertion)
Vérification de la réponse (Status 200, Body JSON).
check() Assertionthresholds (SLO/SLA)
CritÚres de réussite (Pass/Fail) du test (p(95), rate).
scenarios (Exécuteurs)
options.scenarios. Gestion avancée des VUs.
Exécuteur : ramping-vus
stages (Ramp-up, Hold, Ramp-down).
Cycle de Vie (Setup/Teardown)
setup() (1 fois, avant), teardown() (1 fois, aprĂšs).
Métriques (Built-in)
http_req_duration, http_req_failed, checks.
Métriques (Custom)
Counter, Gauge, Rate, Trend (le plus utile).
Custom Metrics Trendgroup() (Transactions)
Grouper des requĂȘtes (ex: "Login").
group() TransactionExécution (CLI)
k6 run, -u, -d, -e (Variables d'env).
Sorties (Outputs)
--out json, Prometheus, InfluxDB, Datadog.
k6 Cloud (SaaS)
k6 cloud script.js. Scaling distribué, Dashboards.
k6-browser
Test Front-End (Chromium, Playwright). Core Web Vitals.
k6-browser FrontendExtensions (xk6)
Builder (Go). k6-sql (BDD), k6-kafka (Events).
Script ModĂšle
Structure d'un bon script (Options, Thresholds, Check).
Cheatsheet ScriptQu'est-ce que k6 ?
k6 est un outil de test de charge (load testing) open-source, moderne, acquis par Grafana Labs. Il est conçu pour ĂȘtre "Developer-Centric" (axĂ© sur les dĂ©veloppeurs) et s'intĂ©grer parfaitement dans les pipelines CI/CD.
Philosophie "Tests as Code"
Contrairement à JMeter (basé GUI), k6 adopte une approche "Tests as Code" :
- Les scénarios de test sont écrits en JavaScript (JS) (ES2015+), ce qui les rend faciles à lire, à versionner (Git) et à intégrer.
- Le moteur (le "cĆur") de k6 est Ă©crit en Go (Golang), le rendant extrĂȘmement performant (binaire unique, faible consommation mĂ©moire).
- Il est conçu pour le CI/CD (Intégration Continue). L'échec d'un test (basé sur des seuils/Thresholds) retourne un code d'erreur non-zéro, faisant échouer le pipeline.
Haute Performance (Event-Loop)
k6 (comme Gatling ou Node.js) utilise un modÚle asynchrone (event-loop). Un seul thread peut gérer des centaines d'utilisateurs virtuels (VUs). (JMeter, à l'inverse, utilise un modÚle "thread-per-user", beaucoup plus lourd).
| CritĂšre | k6 (Grafana Labs) | Apache JMeter |
|---|---|---|
| Langage (CĆur) | Go (Golang) | Java (JVM) |
| Langage (Script) | JavaScript (ES2015+) | GUI (Drag & Drop) -> XML (.jmx) |
| Philosophie | Tests as Code (CI/CD natif) | GUI-driven (Focus sur l'UI pour la création) |
| ModĂšle (VUs) | Event-Loop / Asynchrone (Goroutines) | Thread-per-User (Bloquant) |
| Performance (Injecteur) | TrĂšs ĂlevĂ©e (Faible conso RAM/CPU) | LimitĂ©e (Haute conso RAM/CPU) |
| Protocoles | HTTP/S, WebSocket, gRPC, (Browser, SQL via xk6) | ExtrĂȘme (HTTP, JDBC, FTP, TCP, LDAP, JMS...) |
| Pass/Fail (SLA) | Thresholds (IntĂ©grĂ©, puissant) | Assertions (par requĂȘte), Listeners (basique) |
| Rapports | Résumé CLI, JSON/CSV, Intégration (Prometheus, Cloud) | Rapport HTML (-e -o), Listeners GUI (lourds) |
Installation Native (Binaire)
k6 est un binaire unique sans dépendance (sauf glibc).
# macOS (via Homebrew) brew install k6 # Linux (Debian/Ubuntu) sudo gpg -k ... (Ajouter la clé GPG k6) echo "deb https://dl.k6.io/deb stable main" | sudo tee ... sudo apt-get update sudo apt-get install k6 # Windows (via winget ou Chocolatey) winget install k6 # Vérifier l'installation k6 version
Docker (Utilisation CI/CD)
C'est la méthode préférée pour l'intégration continue (GitLab CI, Jenkins, GitHub Actions).
# Syntaxe docker run -i --rm -v [DOSSIER_LOCAL]:/scripts grafana/k6 [commande] # Exemple d'exécution # 1. 'pwd' (Linux/Mac) monte le dossier courant # 2. '--rm' détruit le conteneur aprÚs l'exécution # 3. 'run /scripts/mon_test.js' exécute le script docker run -i --rm -v "$(pwd):/scripts" grafana/k6 run /scripts/mon_test.js
init vs VU)Le script (.js) est le cĆur de k6. Il a deux "contextes" d'exĂ©cution :
import http from 'k6/http';
import { sleep } from 'k6';
// ---------------------------------------------
// 1. CONTEXTE "INIT"
// ---------------------------------------------
// (Exécuté 1 SEULE fois, au tout début, par 1 seul thread)
// (Utilisé pour importer les modules,
// charger les fichiers de test (open()),
// définir les 'options' globales)
console.log("Contexte INIT (1 fois)");
export const options = {
vus: 10,
duration: '30s',
};
// Chargement des données (payload) depuis le disque
const data = open('./payload.json');
// ---------------------------------------------
// 2. CONTEXTE "VU" (Default Function)
// ---------------------------------------------
// (Exécuté en BOUCLE par chaque VU (Utilisateur Virtuel))
// (C'est le scénario de test)
export default function () {
console.log("Contexte VU (en boucle)");
// La requĂȘte (Sampler)
http.post('https://api.test.com/login', data, {
headers: { 'Content-Type': 'application/json' },
});
// "Think time" (Pause)
sleep(1); // Pause de 1 seconde
}Les VUs (Utilisateurs Virtuels) sont l'équivalent des "Threads" dans JMeter. C'est l'unité de concurrence.
Chaque VU est une goroutine (un thread "léger" Go) qui exécute votre export default function () en boucle, de maniÚre indépendante et parallÚle.
ModĂšle Asynchrone (Event-Loop)
k6 étant basé sur un event-loop Go (similaire à Node.js), les VUs sont trÚs légers en RAM/CPU. Un seul thread physique peut gérer des centaines, voire des milliers, de VUs (connexions parallÚles).
Différence JMeter : JMeter utilise 1 Thread Java (lourd) par VU. 1000 VUs = 1000 Threads Java (consomme beaucoup de RAM). k6 (1000 VUs) n'utilisera qu'une fraction des ressources.
options (Configuration)L'objet options (exportĂ© depuis le contexte init) dĂ©finit la configuration du test de charge (le "comment"). Ces valeurs peuvent ĂȘtre surchargĂ©es en CLI (-u, -d).
Options Simples (vus / duration)
Pour un test simple (charge fixe).
// Fait un "Ramp-up" (montée) de 10 VUs en 5 sec
// Maintient 10 VUs (Hold) pendant 30 sec
// Fait un "Ramp-down" (descente) Ă 0 en 5 sec
export const options = {
vus: 10,
duration: '30s',
// (Optionnel) ContrÎle de la montée/descente
rampUp: '5s',
rampDown: '5s',
};Options Avancées (stages)
Pour un test par paliers (voir 4.2). (Si stages est défini, vus/duration/ramp* sont ignorés).
Options de CritĂšres (thresholds)
Pour définir les SLA (voir 3.3).
export const options = {
thresholds: {
'http_req_duration': ['p(95)<500'], // P95 < 500ms
'http_req_failed': ['rate<0.01'], // Erreurs < 1%
},
};k6/httpLe module k6/http est le "Sampler" principal pour les tests web.
http.get(url, [params])
import http from 'k6/http';
export default function () {
const params = {
headers: {
'Authorization': 'Bearer 12345',
'X-My-Header': 'MyValue',
},
tags: {
name: 'GetUsers', // Tag la métrique (pour filtrer les résultats)
},
};
const res = http.get('https://api.test.com/users', params);
}http.post(url, [body], [params])
import http from 'k6/http';
export default function () {
const url = 'https://api.test.com/login';
// Le 'body' (payload)
const payload = JSON.stringify({
email: 'user@example.com',
password: '123',
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const res = http.post(url, payload, params);
}check() (Assertion)check() est l'équivalent des "Assertions" (JMeter). C'est ce qui vérifie si la réponse reçue est correcte (validation fonctionnelle).
Important : Un check() qui Ă©choue n'arrĂȘte pas l'exĂ©cution du VU. Il incrĂ©mente simplement la mĂ©trique checks (en Ă©chec). C'est une vĂ©rification "soft".
Exemple de check()
import http from 'k6/http';
import { check } from 'k6';
export default function () {
const res = http.get('https://api.test.com/users/1');
// 'check' prend la réponse (res) et un objet
// de vérifications (Titre -> Fonction booléenne)
check(res, {
'1. Statut est 200': (r) => r.status === 200,
'2. Le corps contient du texte': (r) => r.body.includes('Alice'),
'3. Le JSON est correct': (r) => {
// (Test coûteux, uniquement si nécessaire)
const j = r.json();
return j.id === 1;
},
});
}Résultat (CLI) :
â 1. Statut est 200
â 2. Le corps contient du texte
â 3. Le JSON est correct
âł (r.json().id === 1) returned false
checks..................: 66.67% â 2 â 1thresholds (SLO/SLA)Les Thresholds (Seuils) sont la fonctionnalitĂ© la plus importante de k6. C'est ce qui dĂ©finit les critĂšres de rĂ©ussite (Pass/Fail) (SLA/SLO) de l'ensemble du test.
Si check() (3.2) vérifie la *correction fonctionnelle*, thresholds vérifie la *performance*.
Syntaxe (dans options)
[métrique]: [conditions (array de strings)]
Exemple Complet
export const options = {
thresholds: {
// 1. Métrique Standard (Durée)
// "p(95)" = 95e percentile. (95% des requĂȘtes
// doivent ĂȘtre < 500ms).
'http_req_duration': ['p(95)<500', 'p(99)<1500'],
// 2. Métrique Standard (Erreurs)
// "rate" = Taux. (Moins de 1% des requĂȘtes
// doivent ĂȘtre en Ă©chec (non-2xx/3xx)).
'http_req_failed': ['rate<0.01'],
// 3. Métrique Standard (Checks)
// (Plus de 98% de nos "checks" (3.2)
// doivent réussir).
'checks': ['rate>0.98'],
// 4. Métrique Custom (voir 5.2)
// (P90 de notre "Trend" custom < 100ms)
'my_custom_trend': ['p(90)<100'],
},
};Si un seul de ces seuils n'est pas atteint Ă la fin du test, k6 s'arrĂȘtera avec un code d'erreur (non-zĂ©ro), ce qui fera Ă©chouer le pipeline CI/CD.
scenarios (Exécuteurs)L'objet options simple (vus/duration) est un raccourci. Pour des tests complexes (plusieurs "Thread Groups"), on utilise l'objet scenarios.
Un "Scénario" lie un Exécuteur (Executor) (comment la charge est appliquée) à une fonction JS (ce qu'il faut exécuter).
Types d'Exécuteurs (Executors)
| Exécuteur | Description |
|---|---|
ramping-vus | (Le plus courant) Montée/Palier/Descente basé sur le nombre de VUs. (Voir 4.2). |
ramping-arrival-rate | (RPS) MontĂ©e/Palier/Descente basĂ© sur le nombre de requĂȘtes/sec (RPS). |
constant-vus | Nombre fixe de VUs (ex: 10 VUs) pendant X temps. |
constant-arrival-rate | (RPS) Nombre fixe de requĂȘtes/sec. |
per-vu-iterations | Chaque VU exécute le scénario N fois (ex: 10 VUs, 5 itérations = 50 runs). |
Exemple (2 Scénarios)
export function adminLogin() { /* ... */ }
export function userSearch() { /* ... */ }
export const options = {
scenarios: {
// Scénario 1 (Charge de base)
search_users: {
executor: 'ramping-vus',
exec: 'userSearch', // Fonction Ă appeler
stages: [
{ duration: '1m', target: 100 },
],
},
// Scénario 2 (Spike test)
login_admin: {
executor: 'per-vu-iterations',
exec: 'adminLogin',
vus: 5,
iterations: 10,
},
},
};ramping-vus (Stages)L'exécuteur ramping-vus est le plus utilisé pour simuler un test de charge réaliste (montée en charge, palier, descente).
Il est défini par un tableau (array) stages.
Exemple de Scénario (30 min)
export const options = {
scenarios: {
my_load_test: {
executor: 'ramping-vus',
startTime: '0s',
stages: [
// 1. Ramp-up (Montée en charge)
// (De 0 Ă 100 VUs en 5 minutes)
{ duration: '5m', target: 100 },
// 2. Hold (Palier)
// (Maintient 100 VUs pendant 20 minutes)
{ duration: '20m', target: 100 },
// 3. Ramp-down (Descente)
// (De 100 Ă 0 VUs en 5 minutes)
{ duration: '5m', target: 0 },
],
},
},
};En plus du code init (1 fois, 1 thread) et VU (N fois, N threads), k6 offre deux fonctions spéciales pour la préparation et le nettoyage.
export function setup()
S'exécute 1 seule fois au tout début du test (aprÚs init, avant VU).
Usage : Préparer l'environnement de test (ex: insérer des données en BDD) ou récupérer un token d'authentification global.
La valeur retournée (return) par setup() est passée en argument à la fonction default (VU).
export function teardown(data)
S'exécute 1 seule fois à la toute fin du test (aprÚs que tous les VUs aient terminé).
Usage : Nettoyer l'environnement (ex: supprimer les données de test en BDD).
L'argument data (optionnel) est la valeur qui a été retournée par setup().
Exemple de Cycle de Vie
export function setup() {
// 1. (Exécuté 1 fois)
const res = http.post('https://api.test.com/auth', { ... });
const token = res.json().token;
// 2. Passe le token aux VUs
return { authToken: token };
}
export default function (data) {
// 3. (Exécuté N fois)
// 'data' est { authToken: "..." }
const params = { headers: { 'Authorization': `Bearer ${data.authToken}` } };
http.get('https://api.test.com/profile', params);
}
export function teardown(data) {
// 4. (Exécuté 1 fois)
// 'data' est { authToken: "..." }
http.post('https://api.test.com/cleanup', ...);
}k6 collecte automatiquement un ensemble de métriques systÚme, utilisées pour les thresholds et le résumé final.
| Nom de la Métrique | Type | Description |
|---|---|---|
vus | Gauge | Nombre d'utilisateurs virtuels (VUs) actifs. |
http_reqs | Counter | Nombre total de requĂȘtes HTTP effectuĂ©es. |
http_req_duration | Trend | (La plus importante) Temps de réponse total (ms) (sans le DNS/Connect). |
http_req_waiting | Trend | (TTFB - Time To First Byte). Temps d'attente de la réponse. |
http_req_failed | Rate | Taux de requĂȘtes Ă©chouĂ©es (non-2xx/3xx). |
checks | Rate | Taux de check() (assertions) réussis. |
data_sent | Counter | Octets envoyés. |
data_received | Counter | Octets reçus. |
Vous pouvez (et devriez) créer vos propres métriques pour mesurer des aspects spécifiques de votre application.
| Type | Description | Usage |
|---|---|---|
| Counter (Compteur) | Une valeur qui ne fait qu'augmenter (ex: total de logins). | myCounter.add(1) |
| Gauge (Jauge) | Une valeur qui peut monter ou descendre (ex: nb items dans le panier). | myGauge.add(5) |
| Rate (Taux) | Un pourcentage de "vrai" (ex: % de logins réussis). | myRate.add(true), myRate.add(false) |
| Trend (Tendance) | (Le plus utile) Stocke des valeurs de temps (ms). Calcule automatiquement Min, Max, Moy, P90, P95. | myTrend.add(res.timings.waiting) |
Exemple (Trend)
import { Trend } from 'k6/metrics';
// 1. (Contexte INIT) Définir la métrique (Trend)
const loginTrend = new Trend('login_response_time');
export default function () {
// 2. (Contexte VU) Ajouter une valeur
const res = http.post('/login', ...);
loginTrend.add(res.timings.duration);
}
// 3. (Contexte INIT) Définir un seuil
export const options = {
thresholds: {
'login_response_time': ['p(95)<800'], // P95 des logins < 800ms
},
};group() (Transactions)group() est l'Ă©quivalent du "Transaction Controller" de JMeter. Il permet de grouper logiquement plusieurs requĂȘtes (Samplers) en une seule action mĂ©tier.
Fonctionnement
Les métriques (ex: http_req_duration) collectées à l'intérieur du group() sont aussi taguées avec le nom du groupe.
import http from 'k6/http';
import { group, sleep } from 'k6';
export default function () {
// Action 1: Login
group('01_Login_Process', function () {
http.get('https://test.k6.io/login');
sleep(1);
const res = http.post('https://test.k6.io/login', ...);
// (Les mĂ©triques de ces 2 requĂȘtes sont taguĂ©es
// "group:01_Login_Process")
});
sleep(5);
// Action 2: Search
group('02_Search_Users', function () {
http.get('https://test.k6.io/search?q=test');
// (Tagué "group:02_Search_Users")
});
}Résultat : Permet de filtrer les résultats (ex: "Quel est le P95 de http_req_duration uniquement pour le groupe '01_Login_Process' ?").
L'exécution se fait via la commande k6 run.
Commandes Courantes
# Exécuter un script (utilise les 'options' du .js) $ k6 run script.js # Surcharger les 'options' (Test rapide) # -u (vus) # -d (duration) $ k6 run -u 10 -d 30s script.js # Passer des variables d'environnement # (-e ou --env) $ k6 run -e MY_API_KEY="12345" script.js // (Dans le script : __ENV.MY_API_KEY)
Exemple de Sortie (Résumé CLI)
â 1. Statut est 200
checks..................: 100.00% â 180 â 0
data_received...........: 1.5 MB 50 kB/s
data_sent...............: 22 kB 725 B/s
http_req_duration.......: avg=300ms min=150ms med=280ms p(90)=450ms p(95)=490ms
http_req_failed.........: 0.00% â 0 â 180
http_reqs...............: 180 5.9/s
vus.....................: 10 min=10 max=10
La force de k6 (pour l'observabilité) est sa capacité à "streamer" (envoyer) les résultats en temps réel vers d'autres plateformes (via l'option --out).
Fichiers Locaux
# 1. JSON (format structuré) k6 run script.js --out json=results.json # 2. CSV k6 run script.js --out csv=results.csv
Plateformes de Monitoring (Temps Réel)
Permet de corréler le test de charge (k6) avec la performance de l'infra (Prometheus/Grafana).
# 1. InfluxDB (Push) k6 run script.js --out influxdb=http://influx-server:8086/k6 # 2. Prometheus (Remote Write - Push) k6 run script.js --out prometheusremotewrite=http://prom-server/api/v1/write # 3. Datadog / New Relic (Agent) # (Nécessite l'agent StatsD (Datadog) ou l'API (New Relic)) k6 run script.js --out datadog k6 run script.js --out newrelic
k6 Cloud est la plateforme SaaS (payante) de Grafana Labs, conçue pour gérer et scaler les tests k6.
Fonctionnement
Au lieu de k6 run, vous utilisez k6 cloud :
# Authentification (1 fois) k6 login cloud # Exécuter le script sur le Cloud k6 cloud script.js
Avantages
- Scaling (DistribuĂ©) : (L'avantage n°1) Vous n'ĂȘtes plus limitĂ© par votre injecteur local. Vous pouvez demander Ă k6 Cloud de simuler 1 Million de VUs depuis 10 rĂ©gions du monde (AWS, GCP...).
- Dashboards : Fournit une interface web (Grafana) pour analyser les résultats (Pass/Fail, Graphes).
- Historique : Stocke l'historique de tous vos tests (comparaison).
- Intégration CI/CD : Facilite l'intégration dans GitLab/GitHub Actions (ex: "Fail si p95 > 500ms").
k6-browser (Test Front-End)k6 (par défaut) est un testeur de protocole (Back-end). Il ne charge pas le HTML, n'exécute pas le JS, ne rend pas le CSS.
Le module k6-browser (inclus dans k6) ajoute le "Real Browser Testing" (Front-end). Il utilise Chromium (via Playwright) pour piloter un vrai navigateur.
Usage : Test Hybride
Permet de mesurer la performance perçue par l'utilisateur (Core Web Vitals) et de combiner des tests L7 (API) et des tests Browser (Front) dans le mĂȘme scĂ©nario.
import { browser } from 'k6/browser';
import { check } from 'k6';
export const options = {
scenarios: {
browser_test: {
executor: 'constant-vus',
vus: 1,
duration: '30s',
options: {
browser: {
type: 'chromium',
},
},
},
},
};
export default async function () {
const page = await browser.newPage();
await page.goto('https://test.k6.io/');
check(page, {
'Page title': p => p.locator('h1').textContent() == 'Welcome!',
});
// Collecte automatiquement les métriques Core Web Vitals
// (browser_web_vital_lcp, browser_web_vital_cls...)
await page.close();
}xk6)k6 (par défaut) ne supporte pas tout (ex: SQL, Kafka). Pour ajouter des protocoles, il faut recompiler le binaire k6 avec des extensions (écrites en Go).
xk6 est l'outil (le "builder") utilisé pour compiler k6 avec ces extensions.
Exemple (k6-sql)
Objectif : Tester une base de données PostgreSQL.
# 1. (Prérequis) Installer Go, Git # 2. Installer xk6 $ go install go.k6.io/xk6/cmd/xk6@latest # 3. Compiler un binaire k6 "personnalisé" (avec l'extension sql) $ xk6 build --with github.com/loadimpact/k6-extension-sql # (Résultat : un nouveau binaire "k6" est créé)
Script (sql.js)
import sql from 'k6/x/sql';
const db = sql.open('postgres', 'postgres://user:pass@host/db');
export default function () {
sql.query(db, 'SELECT * FROM users WHERE id=1;');
}
// 4. Exécuter (avec le binaire personnalisé)
$ ./k6 run sql.jsUn script de test de charge "propre" (ex: load-test.js) :
import http from 'k6/http';
import { sleep, check, group } from 'k6';
import { Trend } from 'k6/metrics';
// --- 1. CONTEXTE INIT ---
// (Charger les données, définir les métriques custom)
const loginData = JSON.parse(open('./data/users.json'));
const myCustomTrend = new Trend('custom_api_time');
// --- 2. OPTIONS (La Charge) ---
export const options = {
// Scénario par paliers
scenarios: {
my_scenario: {
executor: 'ramping-vus',
stages: [
{ duration: '2m', target: 50 }, // Ramp-up
{ duration: '5m', target: 50 }, // Hold
{ duration: '1m', target: 0 }, // Ramp-down
],
},
},
// CritÚres de réussite (SLA/SLO)
thresholds: {
'http_req_duration': ['p(95)<1000'], // P95 < 1s
'http_req_failed': ['rate<0.02'], // Erreurs < 2%
'checks': ['rate>0.95'], // Checks > 95%
'custom_api_time': ['p(90)<800'],
},
};
// --- 3. SETUP / TEARDOWN (Optionnel) ---
export function setup() {
// (Ex: Récupérer un token admin)
return { token: 'abc123' };
}
// --- 4. VU CODE (Le Scénario) ---
export default function (data) {
// Utiliser la variable d'environnement (passée par -e)
const host = __ENV.TARGET_HOST || 'https://test.k6.io';
const params = {
headers: { 'Authorization': `Bearer ${data.token}` },
};
group('01_Homepage', function () {
const res = http.get(`${host}/`, params);
check(res, {
'Homepage status 200': (r) => r.status === 200,
});
myCustomTrend.add(res.timings.duration);
sleep(2); // Think time 2 sec
});
group('02_Profile_Page', function () {
// (Autre requĂȘte...)
});
}
