K6 es una herramienta moderna de load testing amigable para desarrolladores, diseñada para probar el rendimiento y confiabilidad de APIs, microservicios y sitios web. Construido con Go y scriptable en JavaScript, K6 lleva las pruebas de rendimiento al flujo de trabajo DevOps con integración perfecta de CI/CD y capacidades cloud-native.
¿Por Qué K6 para Load Testing Moderno?
K6 aborda las limitaciones de las herramientas tradicionales de pruebas de rendimiento enfocándose en la experiencia del desarrollador y la infraestructura moderna:
- API JavaScript: Scripting familiar para desarrolladores frontend y backend
- Diseño CLI-first: Perfecto para automatización y pipelines CI/CD
- Rendimiento: Uso eficiente de recursos, puede generar alta carga desde una sola máquina
- Cloud & Local: Ejecutar localmente durante desarrollo, escalar a cloud para pruebas a nivel producción
- Protocolos modernos: Soporte HTTP/1.1, HTTP/2, WebSockets, gRPC
- Observabilidad: Métricas en tiempo real, integración Prometheus, dashboards Grafana
K6 vs Herramientas Tradicionales de Load Testing
Característica | K6 | JMeter | Gatling | Locust |
---|---|---|---|---|
Lenguaje de Scripting | JavaScript | GUI/XML | Scala | Python |
Eficiencia de Recursos | Excelente | Media | Buena | Media |
Integración CI/CD | Nativa | Plugins | Buena | Buena |
Curva de Aprendizaje | Baja | Media | Alta | Baja |
Soporte Cloud | Nativo (K6 Cloud) | Plugins | Gatling Enterprise | No nativo |
Soporte de Protocolos | HTTP/2, WS, gRPC | Extenso | HTTP/2, WS | HTTP |
Resultados en Tiempo Real | Excelente | Limitado | Bueno | Bueno |
Comenzando con K6
Instalación
# macOS (Homebrew)
brew install k6
# Windows (Chocolatey)
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 C5AD17C747E3415A3642D57D77C6C491D6AC1D69
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 pull grafana/k6
Tu Primera Prueba K6
// simple-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // Virtual users (usuarios virtuales)
duration: '30s', // Duración de la prueba
};
export default function () {
const response = http.get('https://test-api.k6.io');
check(response, {
'status es 200': (r) => r.status === 200,
'tiempo de respuesta < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Ejecutar la prueba:
k6 run simple-test.js
Configuración de Pruebas y Perfiles de Carga
Usuarios Virtuales y Duración
export const options = {
// Configuración simple
vus: 50,
duration: '5m',
// O usar stages para ramping
stages: [
{ duration: '2m', target: 100 }, // Subir a 100 usuarios
{ duration: '5m', target: 100 }, // Mantener 100 usuarios
{ duration: '2m', target: 0 }, // Bajar a 0 usuarios
],
};
Escenarios de Load Testing
export const options = {
scenarios: {
// Carga constante
constant_load: {
executor: 'constant-vus',
vus: 50,
duration: '5m',
},
// Ramping VUs
ramping_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
},
// Tasa de solicitudes constante
constant_rps: {
executor: 'constant-arrival-rate',
rate: 1000, // 1000 solicitudes por segundo
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 50,
maxVUs: 200,
},
// Prueba de spike
spike_test: {
executor: 'ramping-arrival-rate',
startRate: 100,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '30s', target: 100 }, // Carga normal
{ duration: '1m', target: 1000 }, // Spike
{ duration: '30s', target: 100 }, // Volver a normal
],
},
},
};
Thresholds para Criterios Pass/Fail
export const options = {
thresholds: {
// Errores HTTP deben ser menos del 1%
http_req_failed: ['rate<0.01'],
// 95% de solicitudes deben estar bajo 500ms
http_req_duration: ['p(95)<500'],
// Percentil 99 debe estar bajo 1s
'http_req_duration{type:api}': ['p(99)<1000'],
// Thresholds de métricas personalizadas
checks: ['rate>0.99'], // 99% de checks deben pasar
},
};
Pruebas HTTP Avanzadas
Métodos HTTP y Request Bodies
import http from 'k6/http';
import { check } from 'k6';
export default function () {
// Solicitud GET
const getResponse = http.get('https://api.example.com/users');
// POST con JSON
const payload = JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
});
const params = {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
},
};
const postResponse = http.post(
'https://api.example.com/users',
payload,
params
);
check(postResponse, {
'creado exitosamente': (r) => r.status === 201,
'tiene user id': (r) => r.json('id') !== undefined,
});
// Solicitud PUT
const updatePayload = JSON.stringify({ name: 'Jane Doe' });
http.put(`https://api.example.com/users/${postResponse.json('id')}`, updatePayload, params);
// Solicitud DELETE
http.del(`https://api.example.com/users/${postResponse.json('id')}`, null, params);
}
Batch Requests
import http from 'k6/http';
export default function () {
// Ejecutar solicitudes en paralelo
const responses = http.batch([
['GET', 'https://api.example.com/users'],
['GET', 'https://api.example.com/products'],
['GET', 'https://api.example.com/orders'],
]);
// Verificar todas las respuestas
responses.forEach((response, index) => {
check(response, {
[`solicitud ${index} status es 200`]: (r) => r.status === 200,
});
});
}
Autenticación y Gestión de Sesiones
import http from 'k6/http';
import { check } from 'k6';
export function setup() {
// Login una vez por ejecución de prueba
const loginResponse = http.post('https://api.example.com/auth/login', {
username: 'testuser',
password: 'testpass',
});
return { token: loginResponse.json('token') };
}
export default function (data) {
// Usar token desde setup
const params = {
headers: {
'Authorization': `Bearer ${data.token}`,
},
};
const response = http.get('https://api.example.com/protected', params);
check(response, {
'autenticado': (r) => r.status === 200,
});
}
Parametrización de Datos
Usando Datos CSV
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
const csvData = new SharedArray('users', function () {
return papaparse.parse(open('./users.csv'), { header: true }).data;
});
export default function () {
// Obtener usuario aleatorio
const user = csvData[Math.floor(Math.random() * csvData.length)];
const response = http.post('https://api.example.com/login', {
username: user.username,
password: user.password,
});
check(response, {
'login exitoso': (r) => r.status === 200,
});
}
Usando Datos JSON
import { SharedArray } from 'k6/data';
const testData = new SharedArray('products', function () {
return JSON.parse(open('./products.json'));
});
export default function () {
const product = testData[__VU % testData.length]; // Selección round-robin
http.post('https://api.example.com/cart/add', JSON.stringify({
productId: product.id,
quantity: 1,
}), {
headers: { 'Content-Type': 'application/json' },
});
}
Métricas Personalizadas y Tags
Métricas Personalizadas
import http from 'k6/http';
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';
const myTrend = new Trend('waiting_time');
const myCounter = new Counter('my_counter');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');
export default function () {
const response = http.get('https://api.example.com');
// Agregar métricas personalizadas
myTrend.add(response.timings.waiting);
myCounter.add(1);
myRate.add(response.status === 200);
myGauge.add(response.timings.duration);
}
Tags para Organizar Métricas
import http from 'k6/http';
import { check } from 'k6';
export default function () {
// Etiquetar solicitudes individuales
const response = http.get('https://api.example.com/users', {
tags: {
type: 'api',
endpoint: 'users',
},
});
check(response, {
'status 200': (r) => r.status === 200,
}, { type: 'api' }); // Etiquetar checks
}
export const options = {
thresholds: {
// Threshold para tags específicos
'http_req_duration{type:api}': ['p(95)<500'],
'http_req_duration{endpoint:users}': ['p(99)<1000'],
},
};
Escenarios de Prueba del Mundo Real
Flujo de Usuario E-commerce
import http from 'k6/http';
import { check, group, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500'],
'http_req_duration{page:home}': ['p(95)<300'],
'http_req_duration{page:product}': ['p(95)<400'],
},
};
export default function () {
let response;
group('Página de Inicio', function () {
response = http.get('https://ecommerce.example.com', {
tags: { page: 'home' },
});
check(response, { 'homepage cargada': (r) => r.status === 200 });
sleep(1);
});
group('Buscar Productos', function () {
response = http.get('https://ecommerce.example.com/search?q=laptop', {
tags: { page: 'search' },
});
check(response, { 'resultados de búsqueda': (r) => r.status === 200 });
sleep(2);
});
group('Ver Producto', function () {
response = http.get('https://ecommerce.example.com/products/123', {
tags: { page: 'product' },
});
check(response, { 'detalles del producto': (r) => r.status === 200 });
sleep(3);
});
group('Agregar al Carrito', function () {
response = http.post(
'https://ecommerce.example.com/cart',
JSON.stringify({ productId: 123, quantity: 1 }),
{
headers: { 'Content-Type': 'application/json' },
tags: { page: 'cart' },
}
);
check(response, { 'agregado al carrito': (r) => r.status === 200 });
sleep(1);
});
group('Checkout', function () {
response = http.post(
'https://ecommerce.example.com/checkout',
JSON.stringify({
payment: 'card',
address: '123 Main St',
}),
{
headers: { 'Content-Type': 'application/json' },
tags: { page: 'checkout' },
}
);
check(response, { 'checkout exitoso': (r) => r.status === 200 });
});
}
Prueba de Carga API con Manejo de Errores
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('errors');
export const options = {
scenarios: {
stress_test: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '1m', target: 10 },
{ duration: '2m', target: 50 },
{ duration: '2m', target: 100 },
{ duration: '1m', target: 10 },
],
},
},
thresholds: {
errors: ['rate<0.1'], // Tasa de error debe ser menor al 10%
http_req_duration: ['p(95)<2000', 'p(99)<5000'],
},
};
export default function () {
const response = http.get('https://api.example.com/data');
const success = check(response, {
'status es 200': (r) => r.status === 200,
'tiempo de respuesta OK': (r) => r.timings.duration < 1000,
'sin errores en body': (r) => !r.body.includes('error'),
});
errorRate.add(!success);
if (response.status !== 200) {
console.error(`Solicitud falló: ${response.status} ${response.body}`);
}
sleep(1);
}
Integración CI/CD
GitHub Actions
name: Load Test
on:
push:
branches: [main]
schedule:
- cron: '0 2 * * *' # Diariamente a las 2 AM
jobs:
k6_load_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run K6 Load Test
uses: grafana/k6-action@v0.3.1
with:
filename: tests/load-test.js
cloud: true
token: ${{ secrets.K6_CLOUD_TOKEN }}
- name: Upload Results
uses: actions/upload-artifact@v3
if: always()
with:
name: k6-results
path: results/
Jenkins Pipeline
pipeline {
agent any
stages {
stage('Load Test') {
steps {
sh '''
k6 run --out json=results.json \
--out influxdb=http://influxdb:8086/k6 \
tests/load-test.js
'''
}
}
stage('Check Thresholds') {
steps {
script {
def exitCode = sh(
script: 'k6 run --quiet tests/load-test.js',
returnStatus: true
)
if (exitCode != 0) {
error("Los thresholds de load test fallaron")
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'results.json', fingerprint: true
}
}
}
GitLab CI
k6-test:
stage: test
image: grafana/k6:latest
script:
- k6 run --summary-export=summary.json tests/load-test.js
artifacts:
when: always
paths:
- summary.json
reports:
junit: summary.json
Observabilidad y Análisis de Resultados
Opciones de Output
# Salida JSON
k6 run --out json=results.json script.js
# Salida CSV
k6 run --out csv=results.csv script.js
# InfluxDB + Grafana
k6 run --out influxdb=http://localhost:8086/k6 script.js
# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js
# K6 Cloud
k6 run --out cloud script.js
# Múltiples salidas
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js
Lectura de Resultados
# Estadísticas resumen
k6 run script.js
# Salida detallada
k6 run --verbose script.js
# Modo silencioso (solo errores)
k6 run --quiet script.js
# Guardar resumen a archivo
k6 run --summary-export=summary.json script.js
Resumen Personalizado
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
export function handleSummary(data) {
return {
"summary.html": htmlReport(data),
"summary.json": JSON.stringify(data),
stdout: textSummary(data, { indent: " ", enableColors: true }),
};
}
Mejores Prácticas
Eficiencia de Recursos
// ❌ Mal: Crea nuevo objeto cada iteración
export default function () {
const headers = { 'Content-Type': 'application/json' };
http.get('https://api.example.com', { headers });
}
// ✓ Bien: Reutilizar objetos
const params = {
headers: { 'Content-Type': 'application/json' },
};
export default function () {
http.get('https://api.example.com', params);
}
Think Time y Pacing
import { sleep } from 'k6';
export default function () {
// Simular tiempo de pensamiento del usuario
http.get('https://api.example.com/page1');
sleep(Math.random() * 3 + 2); // 2-5 segundos
http.get('https://api.example.com/page2');
sleep(Math.random() * 2 + 1); // 1-3 segundos
}
Scripts de Prueba Modulares
// modules/auth.js
export function login(username, password) {
const response = http.post('https://api.example.com/login', {
username,
password,
});
return response.json('token');
}
// modules/users.js
export function getUser(token, userId) {
return http.get(`https://api.example.com/users/${userId}`, {
headers: { Authorization: `Bearer ${token}` },
});
}
// main-test.js
import { login } from './modules/auth.js';
import { getUser } from './modules/users.js';
export default function () {
const token = login('user', 'pass');
getUser(token, 123);
}
Conclusión
K6 representa un cambio de paradigma en pruebas de rendimiento, trayendo herramientas amigables para desarrolladores y prácticas modernas al load testing. Su API JavaScript, uso eficiente de recursos e integración perfecta con pipelines CI/CD lo hacen ideal para equipos DevOps que practican pruebas continuas.
Ventajas clave:
- API JavaScript amigable para desarrolladores
- Uso eficiente de recursos para generación de alta carga
- Integración nativa CI/CD
- Soporte de protocolos modernos (HTTP/2, WebSockets, gRPC)
- Modos de ejecución flexibles (local, cloud, híbrido)
- Rico ecosistema y opciones de observabilidad
Ya sea que estés probando microservicios, APIs o aplicaciones web completas, K6 proporciona las herramientas y flexibilidad para asegurar que tus sistemas funcionen confiablemente bajo carga. Al integrar pruebas de rendimiento temprano en el ciclo de desarrollo, los equipos pueden detectar problemas antes de que lleguen a producción y mantener aplicaciones de alta calidad y rendimiento.