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

  1. Instalación y Configuración de k6
  2. Pruebas de Carga Básicas
  3. Desarrollo de Scripts de k6
  4. Escenarios de Usuarios Virtuales
  5. Umbrales y Verificaciones
  6. Métricas de Rendimiento
  7. Reportes HTML
  8. 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.