¿Qué es k6?
k6 es una herramienta moderna y open-source de pruebas de carga construida por Grafana Labs. A diferencia del enfoque basado en GUI de JMeter, k6 usa scripts en JavaScript que escribes en tu editor de código y ejecutas desde la línea de comandos. Esto lo convierte en una opción natural para desarrolladores e ingenieros de automatización que prefieren código sobre configuración.
k6 está escrito en Go, lo que le otorga excelentes características de rendimiento. Una sola máquina ejecutando k6 puede simular miles de usuarios virtuales con bajo consumo de recursos comparado con JMeter. La herramienta se integra naturalmente en pipelines de CI/CD, haciéndola ideal para performance testing shift-left.
k6 vs JMeter: Cuándo Usar Cada Uno
Antes de profundizar en k6, es útil entender dónde destaca comparado con JMeter.
| Característica | k6 | JMeter |
|---|---|---|
| Scripting | JavaScript (código) | GUI + XML |
| Uso de recursos | Bajo (runtime Go) | Alto (Java/JVM) |
| Soporte de protocolos | HTTP, WebSocket, gRPC | HTTP, JDBC, FTP, SMTP, LDAP, JMS |
| Integración CI/CD | Nativa (CLI-first) | Requiere plugins |
| Testing distribuido | k6 Cloud o xk6-distributed | Master/slave integrado |
| Testing de navegador | Módulo k6 browser | No soportado |
| Curva de aprendizaje | Fácil si conoces JS | Moderada (basado en GUI) |
| Comunidad | Crecimiento rápido | Muy grande, madura |
Elige k6 cuando: Tu equipo conoce JavaScript, necesitas integración CI/CD, pruebas APIs HTTP/gRPC/WebSocket, o quieres ejecución local ligera.
Elige JMeter cuando: Necesitas soporte de protocolos más allá de HTTP (JDBC, FTP, JMS), tu equipo prefiere una GUI, o necesitas un ecosistema establecido con extensos plugins.
Instalación de k6
# macOS
brew install k6
# Windows
choco install k6
# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D68
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
# Docker
docker run --rm -i grafana/k6 run - <script.js
Verifica la instalación:
k6 version
Tu Primer Script de k6
Aquí tienes un script básico de k6 que envía solicitudes HTTP GET:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // 10 usuarios virtuales
duration: '30s', // ejecutar por 30 segundos
};
export default function () {
const res = http.get('https://test-api.k6.io/public/crocodiles/');
check(res, {
'status es 200': (r) => r.status === 200,
'tiempo de respuesta < 500ms': (r) => r.timings.duration < 500,
'body contiene crocodiles': (r) => r.body.includes('crocodiles'),
});
sleep(1); // esperar 1 segundo entre iteraciones
}
Ejecútalo:
k6 run script.js
Estructura del Script
Todo script de k6 tiene tres partes clave:
- Imports: Cargar módulos de k6 (
k6/http,k6,k6/metrics) - Options: Configurar VUs, duración, stages, thresholds
- Función default: El código que cada VU ejecuta en un bucle
La función default se ejecuta una vez por iteración para cada VU. Con 10 VUs y un sleep de 1 segundo, obtienes aproximadamente 10 solicitudes por segundo.
Virtual Users (VUs) e Iteraciones
Un Virtual User (VU) es un hilo de ejecución concurrente. Cada VU ejecuta la función default en un bucle hasta que expira la duración del test o se alcanza el número de iteraciones.
- VUs: Número de usuarios concurrentes
- Iterations: Número total de veces que se ejecuta la función default (entre todos los VUs)
- Duration: Cuánto tiempo se ejecuta la prueba
Puedes especificar iterations o duration, pero no ambos con conteos fijos de VUs.
// Iteraciones fijas: 100 iteraciones totales distribuidas entre 10 VUs
export const options = {
vus: 10,
iterations: 100,
};
// Basado en duración: 10 VUs ejecutándose por 1 minuto
export const options = {
vus: 10,
duration: '1m',
};
Stages: Ramp-Up, Estado Estable, Ramp-Down
Para patrones de carga realistas, usa stages para cambiar el número de VUs a lo largo del tiempo:
export const options = {
stages: [
{ duration: '2m', target: 50 }, // subir a 50 VUs en 2 minutos
{ duration: '5m', target: 50 }, // mantener 50 VUs por 5 minutos
{ duration: '2m', target: 0 }, // bajar a 0 en 2 minutos
],
};
Esto crea un perfil de prueba de carga clásico: aumento gradual, carga sostenida y disminución gradual.
Thresholds: Criterios de Aprobación/Rechazo
Los thresholds definen criterios de éxito para tu prueba. Si algún threshold se viola, k6 termina con un código de salida no-cero — perfecto para pipelines de CI/CD.
export const options = {
thresholds: {
http_req_duration: ['p(95)<500'], // 95% de solicitudes bajo 500ms
http_req_failed: ['rate<0.01'], // menos de 1% de tasa de fallo
http_reqs: ['rate>100'], // al menos 100 solicitudes/segundo
checks: ['rate>0.99'], // 99% de checks pasan
},
};
Métricas comunes para thresholds:
http_req_duration— Tiempo de respuesta (p50, p90, p95, p99, avg, max)http_req_failed— Porcentaje de solicitudes fallidashttp_reqs— Tasa de solicitudes (solicitudes por segundo)checks— Porcentaje de checks aprobados
Checks: Aserciones en Línea
Los checks verifican datos de respuesta sin detener la prueba (a diferencia de los assertions en JMeter que marcan solicitudes como fallidas):
check(res, {
'status es 200': (r) => r.status === 200,
'body tiene campo esperado': (r) => JSON.parse(r.body).hasOwnProperty('id'),
'tiempo de respuesta OK': (r) => r.timings.duration < 300,
});
Los checks se registran como métricas. Puedes establecer thresholds sobre las tasas de aprobación de checks.
Trabajando con Métodos HTTP
import http from 'k6/http';
// GET
const getRes = http.get('https://api.example.com/users');
// POST con body JSON
const payload = JSON.stringify({ name: 'Test User', email: 'test@example.com' });
const params = { headers: { 'Content-Type': 'application/json' } };
const postRes = http.post('https://api.example.com/users', payload, params);
// PUT
const putRes = http.put('https://api.example.com/users/1', payload, params);
// DELETE
const delRes = http.del('https://api.example.com/users/1');
// Solicitudes batch (paralelas)
const responses = http.batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
Ejercicio: Prueba de Carga Multi-Endpoint con k6
Escribe un script de k6 que pruebe una API de e-commerce con autenticación, múltiples endpoints, thresholds y checks.
Escenario
Prueba una API de e-commerce con el siguiente flujo:
- Login para obtener un token de autenticación
- Explorar el catálogo de productos
- Ver un producto específico
- Agregar producto al carrito
Requisitos
- Usa stages: subir a 25 VUs en 1 minuto, mantener por 3 minutos, bajar en 1 minuto
- Establece thresholds: p(95) < 800ms, tasa de error < 1%, tasa de checks > 99%
- Agrega checks para códigos de estado y contenido del body
- Usa el token de autenticación en solicitudes posteriores
- Agrega think time realista entre solicitudes
Pista: Estructura del Script
import http from 'k6/http';
import { check, sleep, group } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 25 },
{ duration: '3m', target: 25 },
{ duration: '1m', target: 0 },
],
thresholds: {
// define tus thresholds aquí
},
};
export default function () {
// Grupo 1: Login
// Grupo 2: Explorar catálogo
// Grupo 3: Ver producto
// Grupo 4: Agregar al carrito
}
Usa group() para organizar solicitudes en secciones lógicas. Esto te da métricas por grupo en los resultados.
Solución: Script Completo de k6
import http from 'k6/http';
import { check, sleep, group } from 'k6';
const BASE_URL = 'https://api.ecommerce.example.com';
export const options = {
stages: [
{ duration: '1m', target: 25 },
{ duration: '3m', target: 25 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01'],
checks: ['rate>0.99'],
},
};
export default function () {
let token;
// Paso 1: Login
group('Login', function () {
const loginPayload = JSON.stringify({
username: `user${__VU}@example.com`,
password: 'testpass123',
});
const loginRes = http.post(`${BASE_URL}/api/auth/login`, loginPayload, {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, {
'login status es 200': (r) => r.status === 200,
'login retorna token': (r) => JSON.parse(r.body).token !== undefined,
});
token = JSON.parse(loginRes.body).token;
});
sleep(1);
const authHeaders = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
};
// Paso 2: Explorar catálogo
let productId;
group('Explorar Catálogo', function () {
const catalogRes = http.get(`${BASE_URL}/api/products`, authHeaders);
check(catalogRes, {
'catálogo status es 200': (r) => r.status === 200,
'catálogo retorna productos': (r) => JSON.parse(r.body).products.length > 0,
});
const products = JSON.parse(catalogRes.body).products;
productId = products[Math.floor(Math.random() * products.length)].id;
});
sleep(2);
// Paso 3: Ver detalles del producto
group('Ver Producto', function () {
const productRes = http.get(`${BASE_URL}/api/products/${productId}`, authHeaders);
check(productRes, {
'producto status es 200': (r) => r.status === 200,
'producto tiene nombre': (r) => JSON.parse(r.body).name !== undefined,
'producto tiene precio': (r) => JSON.parse(r.body).price > 0,
});
});
sleep(1);
// Paso 4: Agregar al carrito
group('Agregar al Carrito', function () {
const cartPayload = JSON.stringify({
productId: productId,
quantity: 1,
});
const cartRes = http.post(`${BASE_URL}/api/cart`, cartPayload, authHeaders);
check(cartRes, {
'carrito status es 200 o 201': (r) => r.status === 200 || r.status === 201,
'carrito confirma item agregado': (r) => JSON.parse(r.body).success === true,
});
});
sleep(1);
}
Ejecutar la prueba:
k6 run ecommerce-load-test.js
Qué buscar en los resultados:
http_req_duration— p95 debe estar bajo 800mshttp_req_failed— debe estar cerca de 0%checks— tasa de aprobación debe ser superior al 99%http_reqs— tasa total de solicitudes- Métricas por grupo muestran qué endpoint es más lento
Exportar resultados para análisis:
# Salida JSON
k6 run --out json=results.json ecommerce-load-test.js
# Salida CSV
k6 run --out csv=results.csv ecommerce-load-test.js
# InfluxDB (para dashboards de Grafana)
k6 run --out influxdb=http://localhost:8086/k6 ecommerce-load-test.js
Tips Profesionales
- Usa
__VUy__ITER: Las variables integradas__VU(número de VU actual) y__ITER(número de iteración actual) ayudan a crear datos únicos por usuario sin archivos CSV externos. - SharedArray para datos de prueba: Carga datasets grandes una vez y compártelos entre VUs usando
SharedArraydek6/data. Esto evita que cada VU cargue su propia copia. - Métricas personalizadas: Crea métricas personalizadas con
Counter,Gauge,RateyTrenddek6/metricspara rastrear KPIs específicos del negocio. - Scenarios para patrones complejos: Usa
scenariosen lugar destagescuando necesites múltiples tipos de ejecutores (constant-vus, ramping-vus, per-vu-iterations, constant-arrival-rate) ejecutándose simultáneamente. - Módulo k6 browser: Para pruebas de carga basadas en navegador, k6 ahora incluye un módulo de navegador basado en Chromium que puede medir Core Web Vitals bajo carga.