Pruebas de Carga de Aplicación Web con k6
k6 es una herramienta moderna de pruebas de carga que permite a desarrolladores e ingenieros de DevOps probar aplicaciones web y APIs a escala usando scripts basados en JavaScript. Con soporte integrado para patrones de tráfico realistas, métricas personalizadas y reportes de rendimiento detallados, k6 transforma las pruebas de carga de una tarea especializada en un proceso accesible y amigable para desarrolladores. Esta guía cubre instalación de k6, desarrollo de scripts y estrategias de pruebas de rendimiento.
Tabla de Contenidos
- Instalación y Configuración de k6
- Pruebas de Carga Básicas
- Desarrollo de Scripts de k6
- Escenarios de Usuarios Virtuales
- Umbrales y Verificaciones
- Métricas de Rendimiento
- Reportes HTML
- Conclusión
Instalación y Configuración de k6
Instalación de k6
# Instalación en Ubuntu/Debian
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install -y k6
# Instalación en CentOS/RHEL
sudo yum install -y https://dl.k6.io/rpm/repo.rpm
sudo yum install -y k6
# O desde la fuente
wget https://github.com/grafana/k6/releases/download/v0.45.0/k6-v0.45.0-linux-amd64.tar.gz
tar xzf k6-v0.45.0-linux-amd64.tar.gz
sudo mv k6-v0.45.0-linux-amd64/k6 /usr/local/bin/
# Verificar instalación
k6 --version
Primera Prueba
# Ejecutar prueba simple de solicitud HTTP
k6 run --vus 1 --duration 10s - <<EOF
import http from 'k6/http';
export default function() {
http.get('http://example.com');
}
EOF
# Parámetros:
# --vus: Usuarios virtuales
# --duration: Duración de la prueba
Pruebas de Carga Básicas
Script de Prueba de Carga Simple
cat > example_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
export default function() {
http.get('http://example.com/api/users');
sleep(1);
}
EOF
# Ejecutar con 10 VUs durante 30 segundos
k6 run --vus 10 --duration 30s example_test.js
# La salida esperada muestra:
# http_reqs: Recuento de solicitudes
# http_req_duration: Tiempo de respuesta
# http_req_failed: Solicitudes fallidas
# Checks: Métricas de validación personalizadas
Patrón de Carga de Rampa
# Script con patrón de VU de rampa
cat > ramp_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 10 }, // Rampa a 10 VUs
{ duration: '1m30s', target: 50 }, // Rampa a 50 VUs
{ duration: '2m', target: 100 }, // Rampa a 100 VUs
{ duration: '1m', target: 0 }, // Rampa hacia abajo a 0 VUs
],
};
export default function() {
http.get('http://example.com/api/users');
sleep(1);
}
EOF
k6 run ramp_test.js
Prueba de Pico
# Escenario de pico de tráfico repentino
cat > spike_test.js <<'EOF'
import http from 'k6/http';
export let options = {
stages: [
{ duration: '10s', target: 100 }, // Carga normal
{ duration: '1s', target: 1000 }, // ¡Pico!
{ duration: '10s', target: 100 }, // Volver a normal
{ duration: '1s', target: 0 }, // Detener
],
};
export default function() {
http.get('http://example.com/api/data');
}
EOF
k6 run spike_test.js
Desarrollo de Scripts de k6
Realizar Solicitudes HTTP
cat > http_requests.js <<'EOF'
import http from 'k6/http';
export default function() {
// Solicitud GET
let res = http.get('http://example.com/api/users');
console.log(`Response status: ${res.status}`);
// Solicitud POST con carga útil
const payload = JSON.stringify({
name: 'Test User',
email: '[email protected]',
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
http.post('http://example.com/api/users', payload, params);
// Solicitud PUT/PATCH
http.put('http://example.com/api/users/1', payload, params);
// Solicitud DELETE
http.del('http://example.com/api/users/1');
}
EOF
k6 run http_requests.js
Validación de Solicitudes
cat > validate_requests.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export default function() {
const res = http.get('http://example.com/api/users');
// Validar respuesta
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'content type correct': (r) => r.headers['Content-Type'].includes('application/json'),
'response contains data': (r) => r.body.includes('users'),
});
sleep(1);
}
EOF
k6 run validate_requests.js
Variables Dinámicas y Parametrización
cat > parameterized_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
const BASE_URL = 'http://example.com';
const USER_IDS = ['1', '2', '3', '4', '5'];
export default function() {
// Selección de usuario aleatorio
const userId = USER_IDS[Math.floor(Math.random() * USER_IDS.length)];
const res = http.get(`${BASE_URL}/api/users/${userId}`);
console.log(`Fetched user: ${userId}`);
sleep(Math.random() * 3 + 1); // Espera aleatoria de 1-4 segundos
}
EOF
k6 run parameterized_test.js
Escenarios de Usuarios Virtuales
Prueba Multi-Escenario
cat > multi_scenario.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE_URL = 'http://example.com';
export let options = {
scenarios: {
// Escenario de búsqueda: Uso ligero
browse: {
executor: 'constant-vus',
vus: 10,
duration: '3m',
env: { SCENARIO: 'browse' },
},
// Escenario de búsqueda: Carga media
search: {
executor: 'ramping-vus',
stages: [
{ duration: '1m', target: 20 },
{ duration: '3m', target: 20 },
{ duration: '1m', target: 0 },
],
env: { SCENARIO: 'search' },
},
// Escenario de pico: Carga repentina
spike: {
executor: 'variable-looping-vus',
startVUs: 0,
stages: [
{ duration: '30s', target: 100 },
{ duration: '1m', target: 0 },
],
env: { SCENARIO: 'spike' },
},
},
};
export default function() {
const scenario = __ENV.SCENARIO;
if (scenario === 'browse') {
http.get(`${BASE_URL}/api/users`);
} else if (scenario === 'search') {
http.get(`${BASE_URL}/api/search?q=test`);
} else if (scenario === 'spike') {
http.post(`${BASE_URL}/api/orders`, JSON.stringify({ items: [] }));
}
sleep(1);
}
EOF
k6 run multi_scenario.js
Umbrales y Verificaciones
Establecer Umbrales de Rendimiento
cat > thresholds_test.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 10,
duration: '1m',
thresholds: {
// Umbrales de duración de solicitud HTTP
'http_req_duration': [
'p(95) < 500', // 95% de solicitudes < 500ms
'p(99) < 1000', // 99% de solicitudes < 1000ms
'max < 2000', // Ninguna solicitud > 2000ms
],
// Umbral de fallo de solicitud HTTP
'http_req_failed': [
'rate < 0.1', // Menos del 10% de tasa de fallo
],
// Umbral de verificación personalizada
'checks': [
'rate > 0.95', // 95%+ verificaciones pasan
],
},
};
export default function() {
const res = http.get('http://example.com/api/users');
check(res, {
'status 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
EOF
k6 run thresholds_test.js
# Los umbrales fallidos causan código de salida distinto de cero
echo "Exit code: $?"
Métricas de Rendimiento
Métricas Personalizadas
cat > custom_metrics.js <<'EOF'
import http from 'k6/http';
import { Counter, Trend, Rate, Gauge } from 'k6/metrics';
// Definir métricas personalizadas
const myCounter = new Counter('my_counter');
const myTrend = new Trend('my_trend');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');
export default function() {
const res = http.get('http://example.com/api/users');
// Registrar métricas
myCounter.add(1); // Incrementar contador
myTrend.add(res.timings.duration); // Rastrear tendencia de duración
myRate.add(res.status === 200); // Rastrear tasa de éxito
myGauge.add(Math.random() * 100); // Establecer valor de calibre
}
EOF
k6 run custom_metrics.js
# Ver métricas en salida
# my_counter.......................: X
# my_trend..........................avg=Yms
# my_rate...........................X%
# my_gauge..........................X
Reporte de Métricas Detallado
# Ejecutar prueba con salida JSON
k6 run --out json=results.json http_requests.js
# Analizar resultados con jq
cat results.json | jq '.data.samples[] | select(.metric=="http_req_duration") | .value' | \
jq -s 'add/length' # Calcular promedio
# Estadísticas de resumen
cat results.json | jq '.data.samples | group_by(.metric) | map({metric: .[0].metric, samples: length})'
Reportes HTML
Generar Reportes HTML
# Instalar k6-reporter
npm install -g @kevinsullivan/k6-reporter
# Ejecutar prueba y generar reporte
k6 run --out json=results.json multi_scenario.js
# Convertir a HTML
k6-reporter results.json --title "Load Test Report"
# Ver reporte
open summary.html
Integración con Grafana Cloud
cat > grafana_test.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 5,
duration: '5m',
ext: {
loadimpact: {
projectID: 12345, // Su ID de proyecto de Grafana Cloud
name: 'API Load Test',
},
},
};
export default function() {
const res = http.get('http://example.com/api/users');
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
EOF
# Ejecutar contra Grafana Cloud
export GRAFANA_CLOUD_API_TOKEN="your-token"
k6 cloud grafana_test.js
Conclusión
k6 moderniza las pruebas de carga a través de scripts amigables para desarrolladores, haciendo que la validación de rendimiento sea accesible para todos los equipos de desarrollo. Al implementar escenarios realistas, establecer umbrales significativos y validar continuamente el rendimiento de aplicaciones bajo carga, las organizaciones identifican cuellos de botella antes de que impacten a los usuarios. La integración con canalizaciones de CI/CD permite detección de regresión de rendimiento, mientras que la integración con Grafana Cloud proporciona capacidades de monitoreo persistente. Ya sea realizando validación previa al despliegue, planificación de capacidad o monitoreo de rendimiento continuo, la combinación de facilidad de uso y capacidades poderosas de k6 la convierten en el estándar moderno para pruebas de carga de aplicaciones web.


