Por Performate
Cómo agregar pruebas de carga a CI/CD sin frenar los deploys
Separa gates de smoke de rendimiento en CI de suites programadas más pesadas, usa códigos de salida y umbrales de k6, y protege el presupuesto de latencia del pipeline.
El pipeline ya bloquea en lint, unit tests e integración—y alguien propone veinte minutos de prueba de carga en cada PR. Los dueños de CI/CD tienen razón en rechazarlo salvo que el rendimiento se comporte como cualquier otro gate de calidad: feedback rápido, entornos deterministas y umbrales objetivos que fallen el build cuando se rompe el SLO.
Las pruebas de carga en CI/CD no son ensayar escala de producción en cada commit. Son capas: smoke de rendimiento que atrapa regresiones catastróficas antes del merge, suites programadas que responden preguntas de capacidad, y campañas de release que siguen mereciendo revisión humana. En esta guía verás cómo separar esas capas, cablear el código de salida de k6 al éxito del pipeline y proteger el presupuesto de latencia sin saltarte validación.
Por qué CI amplifica los errores de rendimiento
Los pipelines premian velocidad y determinismo. Las pruebas que violan cualquiera de los dos se desactivan en dos sprints—y las regresiones salen en silencio.
- Colisiones en staging compartido: dos jobs de PR martillan el mismo sandbox; uno falla umbrales por razones ajenas al código.
- Datos flaky: usuarios sintéticos expiran a mitad de corrida; tormentas de refresh de auth enmascaran un pico real—or crean uno falso.
- Alcance todo-o-nada: soak en cada push entrena al equipo a ignorar builds rojos.
- Baselines ausentes: un umbral
p(95)<500sin contexto bloquea cambios buenos y deja pasar malos por igual.
k6 sale con código distinto de cero cuando fallan umbrales (thresholds), lo que encaja con éxito/fallo en CI. Grafana posiciona k6 como pruebas de carga scriptables pensadas para automatización (overview OSS de k6). Combínalo con errores comunes en pruebas de carga para no amplificar antipatrones que CI hace visibles.
Cuando las pruebas de integración pasan pero el rendimiento regresa
Las suites funcionales demuestran formas de request y happy paths. Rara vez demuestran que un refactor duplicó la latencia de checkout bajo arrival rate estable—or que el pool de conexiones se rompió con concurrencia modesta. Un gate de smoke de dos minutos con umbrales ajustados en rutas críticas cierra esa brecha sin poseer el pipeline.
Implementación práctica con k6: tres capas en un repo
Organiza el trabajo de rendimiento en capas que coincidan con el riesgo del pipeline, no con el organigrama. Un repositorio de scripts puede servir las tres si las variables de entorno controlan duración, tasa y escenarios activos.
Script de ejemplo (ilustrativo—no es una prueba lista para producción). URLs, tokens y SLO ficticios.
Qué demuestra este ejemplo:
- Capa 1 (smoke CI):
constant-arrival-ratecorto, RPS bajo, umbrales estrictos. - Capa 2 (nightly): activado con
TEST_PROFILE=nightly—más duración y colas de latencia. - Perfiles por entorno: mismo archivo, distintos
-e—sin scripts bifurcados que derivan. - Contrato de exit code: umbral roto = job roto; sin parsing custom.
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE = __ENV.BASE_URL || 'https://staging.example.com';
const PROFILE = __ENV.TEST_PROFILE || 'smoke'; // smoke | nightly
const profiles = {
smoke: {
duration: '45s',
rate: 5,
preAllocatedVUs: 5,
maxVUs: 20,
thresholds: {
http_req_failed: ['rate<0.005'],
'http_req_duration{route:health}': ['p(95)<400'],
'http_req_duration{route:checkout}': ['p(95)<900'],
},
},
nightly: {
duration: '8m',
rate: 25,
preAllocatedVUs: 30,
maxVUs: 120,
thresholds: {
http_req_failed: ['rate<0.01'],
'http_req_duration{route:checkout}': ['p(95)<800', 'p(99)<1400'],
},
},
};
const cfg = profiles[PROFILE] || profiles.smoke;
export const options = {
scenarios: {
api_smoke: {
executor: 'constant-arrival-rate',
rate: cfg.rate,
timeUnit: '1s',
duration: cfg.duration,
preAllocatedVUs: cfg.preAllocatedVUs,
maxVUs: cfg.maxVUs,
exec: 'hitCriticalRoutes',
},
},
thresholds: cfg.thresholds,
};
export function hitCriticalRoutes() {
const health = http.get(`${BASE}/health`, { tags: { route: 'health' } });
check(health, { 'health 2xx': (r) => r.status >= 200 && r.status < 300 });
const checkout = http.post(
`${BASE}/checkout`,
JSON.stringify({ sku: 'SKU-1', qty: 1 }),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${__ENV.API_TOKEN}`,
},
tags: { route: 'checkout' },
},
);
check(checkout, { 'checkout 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.2);
}
Patrones que funcionan
- Smoke después de checks baratos: corre k6 tras unit e integración—falla rápido en lógica antes de carga.
- Executors arrival-rate para RPS estable (referencia de executors).
- Rutas con tags para que un endpoint roto destaque en logs de CI.
- Subida de artefactos: JSON de resumen con
git_shayTEST_PROFILEpara regresión de baseline. - Cron o post-deploy para Capa 2 en ventanas de menor riesgo—not en cada push.
Anti-patrones a evitar
- Soak a escala completa en cada PR—el equipo pondrá
--skipal job. - API keys embebidas en scripts versionados en git.
- Staging compartido sin coordinación ni topes de RPS.
- Tratar smoke verde como sign-off de capacidad para launch day.
Pro tip (paso CI de ejemplo):
# Capa 1 — smoke en PR (~1–2 min incluyendo arranque de k6)
- name: k6 smoke performance gate
run: |
k6 run scripts/critical-path.js \
-e BASE_URL="${{ secrets.STAGING_URL }}" \
-e API_TOKEN="${{ secrets.STAGING_TOKEN }}" \
-e TEST_PROFILE=smoke \
--summary-export=summary-smoke.json
Qué demuestra este paso: secretos en runtime; perfil corto en PR; export para dashboards o diffs en workflows nightly.
Marco de decisión: qué capa cuándo
| Situación | Acción recomendada |
|---|---|
| Cada PR / pre-merge | Capa 1 smoke: 30–120s, rutas críticas, http_req_failed estricto |
| Nightly o post-deploy | Capa 2 programada: más duración, más rutas, comparar baseline |
| Launch / migración / pico marketing | Capa 3 manual: escenarios revisados, planificación de capacidad |
| Staging compartido | Tope de RPS; serializar jobs o previews efímeros |
| Umbrales que flappean | Ajustar smoke; mover p99 a nightly |
| APIs con auth pesada | Tokens en setup()—gestionar entornos y variables k6 |
Usa smoke en CI si necesitas gates deterministas de merge y puedes posponer señal de cola hasta nightly.
Usa suites programadas si soak, flujos multi-servicio o p99 exceden el presupuesto de latencia del PR.
Usa campañas manuales si la pregunta es capacidad o readiness de lanzamiento—not si un PR regresó una ruta.
Tácticas de latencia del pipeline
Protege el presupuesto de wall-clock explícitamente:
- Cachea dependencias y reutiliza artefactos para que el arranque de k6 domine poco.
- Paraleliza checks no relacionados; deja carga después de pasos más baratos.
- Fija duración de smoke en código (
45s, no5m) para que el costo sea visible en review. - Documenta variables obligatorias al inicio del script—falla fuerte si falta
BASE_URLoAPI_TOKEN. - Publica artefactos nightly ligados a metadata de build, no capturas sueltas en Slack.
Profundiza en stress vs carga vs spike y ejemplos de umbrales k6. Clasifica fallos con taxonomía de errores.
Cómo Performate simplifica pruebas de carga en CI/CD
Mantener scripts smoke y nightly separados garantiza drift. Flujo de ejemplo para el mismo API de camino crítico:
- Importa Postman u OpenAPI para health, checkout y refresh de auth. Problema resuelto: una fuente de verdad para local, CI y nightly.
- Crea escenario
ci_smokeen el editor visual—arrival rate bajo, 45–90s, umbrales estrictos enhttp_req_failedyp(95)de checkout. - Duplica a
nightly_regressioncon más duración y rutas; mismos tags para comparaciones justas. - Exporta el script k6 junto al YAML del pipeline; pasa
-e TEST_PROFILE=smokeonightlycomo en el ejemplo. - Adjunta
git_shay perfil en el export de reporte para diffs de baseline en corridas programadas. - Enlaza el reporte smoke en plantillas de PR para que revisores vean evidencia de rendimiento junto a checks funcionales.
Eso mapea al cta: estandarizar smoke rápido y suites profundas desde un workspace.
Cierre
Las pruebas de carga pertenecen al CI/CD cuando respetan presupuestos de latencia y fallan builds de forma determinista—no cuando reproducen producción en cada commit.
Añade este sprint un gate de smoke de un minuto en rutas críticas, programa las preguntas pesadas para nightly, y trata umbrales rotos como cualquier otro test fallido.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para estandarizar smoke checks rápidos en CI y suites de rendimiento programadas más profundas.