TL;DR

  • Valida políticas de auto-scaling con pruebas de carga reales antes de producción—K6 para equipos JavaScript/TypeScript, Locust para equipos Python
  • Usa Terraform para provisionar infraestructura efímera de pruebas de carga: levanta, prueba, destruye—paga solo por la duración del test
  • Prueba tres escenarios como mínimo: carga sostenida (baseline), pico de carga (disparo de auto-scaling) y recuperación (comportamiento de scale-down)

Ideal para: Equipos con infraestructura auto-escalable que necesitan validar políticas de scaling y entender límites de capacidad No recomendado si: Ejecutas infraestructura de capacidad fija sin auto-scaling (enfócate en planificación de capacidad) Tiempo de lectura: 14 minutos

Las políticas de auto-scaling que funcionan en teoría frecuentemente fallan bajo carga real. Una política que se activa al 70% de CPU puede escalar muy lentamente, dejando usuarios esperando. O puede escalar muy agresivamente, desperdiciando presupuesto. La única forma de saber que tu infraestructura maneja carga correctamente es probarla.

Para testing de infraestructura relacionado, consulta Estrategias de Testing de Terraform y Testing de Configuración de Red.

Enfoques Asistidos por IA

Las herramientas de IA destacan generando scripts de pruebas de carga y analizando patrones de rendimiento.

Generando escenarios de prueba de carga con K6:

Escribe un script de prueba de carga K6 que valide comportamiento de auto-scaling:

Aplicación objetivo: API REST con endpoints /api/users, /api/orders
Infraestructura: AWS ALB + Auto Scaling Group (min: 2, max: 10, CPU objetivo: 70%)

Incluye tres etapas de prueba:

1. Calentamiento: Incremento gradual a 100 VUs en 2 minutos
2. Carga sostenida: Mantener 500 VUs por 10 minutos (debe disparar scale-up)
3. Pico: Ráfaga a 2000 VUs por 1 minuto, luego volver a 500
4. Enfriamiento: Disminuir gradualmente a 0 en 5 minutos (debe disparar scale-down)

Agrega thresholds para:

- Tiempo de respuesta p95 < 500ms
- Tasa de error < 1%
- Métricas personalizadas para rastrear eventos de scaling

Incluye integración con CloudWatch para correlacionar carga con conteo de instancias ASG.

Analizando comportamiento de auto-scaling:

Analiza estos resultados de prueba de carga y métricas de auto-scaling:

Timeline de prueba de carga:

- 0-2min: Rampa a 100 VUs, p95=120ms
- 2-12min: 500 VUs sostenidos, p95 comenzó en 150ms, creció a 800ms al minuto 8
- ASG escaló de 2 a 4 instancias al minuto 6, a 6 instancias al minuto 10
- 12-13min: Pico a 2000 VUs, p95=2500ms, tasa de error 15%

Preguntas:

1. ¿Es la política de scaling demasiado lenta? ¿Cuál debería ser el valor de target tracking?
2. ¿Por qué la latencia creció antes de que ocurriera el scaling?
3. ¿Qué explica la alta tasa de error durante el pico?
4. Recomienda cambios específicos a la configuración de auto-scaling.

Creando pruebas de carga distribuidas con Locust:

Crea una prueba de carga Locust para flujo de checkout de e-commerce:

1. Navegar productos (70% del tráfico)
2. Agregar al carrito (20% del tráfico)
3. Checkout (10% del tráfico)

Incluye:

- Tiempos de espera realistas entre acciones
- Manejo de sesión para estado del carrito
- Métricas personalizadas para cada etapa del flujo
- Configuración de setup distribuido para ejecutar en Kubernetes

Muestra cómo ejecutar esto con 10 pods worker para generar 50,000 usuarios concurrentes.

Cuándo Usar Diferentes Enfoques de Testing

Framework de Decisión de Estrategia de Testing

Tipo de TestHerramientaPropósitoCuándo Ejecutar
Smoke testK6/LocustVerificar que sistema funciona bajo carga mínimaCada deployment
Load testK6/LocustValidar rendimiento bajo carga esperadaSemanalmente, antes de releases
Stress testK6/LocustEncontrar puntos de quiebreMensualmente, después de cambios de infra
Spike testK6/LocustValidar comportamiento de auto-scalingDespués de cambios de políticas de scaling
Soak testK6/LocustEncontrar memory leaks, agotamiento de conexionesTrimestralmente

Checklist de Validación de Auto-Scaling

ValidaciónQué VerificarCriterio de Éxito
Disparo de scale-upTiempo desde breach de threshold a nueva instancia< 3 minutos
Capacidad de scale-upNuevas instancias manejan tráfico inmediatamenteSin fallos de requests
Disparo de scale-downInstancias removidas cuando carga disminuyeDentro de período de cooldown
Seguridad de scale-downSin terminación prematura durante tráficoCero requests perdidos
Capacidad máximaSistema maneja carga de máximo de instanciasCumple SLA a máxima escala

K6 para Testing de Escalabilidad

Test Básico de Validación de Auto-Scaling

// tests/autoscaling-validation.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Métricas personalizadas
const errorRate = new Rate('errors');
const scalingLatency = new Trend('scaling_latency');

export const options = {
  scenarios: {
    // Etapa 1: Calentamiento
    warmup: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },
      ],
      gracefulRampDown: '0s',
      exec: 'defaultScenario',
    },
    // Etapa 2: Carga sostenida (debe disparar scale-up)
    sustained: {
      executor: 'constant-vus',
      vus: 500,
      duration: '10m',
      startTime: '2m',
      exec: 'defaultScenario',
    },
    // Etapa 3: Pico (stress test de auto-scaling)
    spike: {
      executor: 'ramping-vus',
      startVUs: 500,
      stages: [
        { duration: '30s', target: 2000 },
        { duration: '1m', target: 2000 },
        { duration: '30s', target: 500 },
      ],
      startTime: '12m',
      exec: 'defaultScenario',
    },
    // Etapa 4: Enfriamiento (debe disparar scale-down)
    cooldown: {
      executor: 'ramping-vus',
      startVUs: 500,
      stages: [
        { duration: '5m', target: 0 },
      ],
      startTime: '14m',
      gracefulRampDown: '30s',
      exec: 'defaultScenario',
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% de requests bajo 500ms
    errors: ['rate<0.01'],              // Tasa de error bajo 1%
    http_req_failed: ['rate<0.01'],     // Requests fallidos bajo 1%
  },
};

const BASE_URL = __ENV.TARGET_URL || 'https://api.example.com';

export function defaultScenario() {
  // Simular uso realista de API
  const endpoints = [
    { path: '/api/users', weight: 0.5 },
    { path: '/api/orders', weight: 0.3 },
    { path: '/api/products', weight: 0.2 },
  ];

  const random = Math.random();
  let cumulative = 0;
  let selectedEndpoint = endpoints[0].path;

  for (const endpoint of endpoints) {
    cumulative += endpoint.weight;
    if (random <= cumulative) {
      selectedEndpoint = endpoint.path;
      break;
    }
  }

  const response = http.get(`${BASE_URL}${selectedEndpoint}`);

  const success = check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  errorRate.add(!success);

  // Tiempo de espera realista
  sleep(Math.random() * 2 + 1);
}

Locust para Testing de Escalabilidad

Configuración de Prueba de Carga Distribuida

# locustfile.py
from locust import HttpUser, task, between, events
from locust.runners import MasterRunner
import time
import logging

class WebsiteUser(HttpUser):
    wait_time = between(1, 3)

    def on_start(self):
        """Inicializar sesión de usuario."""
        self.client.headers = {'Content-Type': 'application/json'}

    @task(5)
    def browse_products(self):
        """70% del tráfico - Navegar productos."""
        with self.client.get("/api/products", catch_response=True) as response:
            if response.status_code == 200:
                response.success()
            else:
                response.failure(f"Got status {response.status_code}")

    @task(2)
    def view_product_detail(self):
        """20% del tráfico - Ver detalles de producto."""
        product_id = self.get_random_product_id()
        self.client.get(f"/api/products/{product_id}")

    @task(1)
    def checkout_flow(self):
        """10% del tráfico - Flujo completo de checkout."""
        # Agregar al carrito
        self.client.post("/api/cart", json={
            "product_id": self.get_random_product_id(),
            "quantity": 1
        })

        # Checkout
        with self.client.post("/api/checkout", json={
            "payment_method": "card"
        }, catch_response=True) as response:
            if response.status_code in [200, 201]:
                response.success()
            elif response.status_code == 503:
                response.failure("Servicio no disponible - ¿problema de scaling?")

    def get_random_product_id(self):
        import random
        return random.randint(1, 1000)


# Métricas personalizadas para análisis de scaling
@events.request.add_listener
def track_response_time(request_type, name, response_time, response_length, **kwargs):
    if response_time > 1000:  # Registrar requests lentos
        logging.warning(f"Request lento: {name} tomó {response_time}ms")

Deployment en Kubernetes para Locust Distribuido

# locust-master.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: locust-master
spec:
  replicas: 1
  selector:
    matchLabels:
      app: locust
      role: master
  template:
    metadata:
      labels:
        app: locust
        role: master
    spec:
      containers:

        - name: locust
          image: locustio/locust:2.20.0
          args:

            - --master
            - -f
            - /mnt/locust/locustfile.py
            - --host
            - $(TARGET_HOST)
          env:

            - name: TARGET_HOST
              valueFrom:
                configMapKeyRef:
                  name: locust-config
                  key: target_host
          ports:

            - containerPort: 8089
            - containerPort: 5557
          volumeMounts:

            - name: locust-scripts
              mountPath: /mnt/locust
      volumes:

        - name: locust-scripts
          configMap:
            name: locust-scripts
---
# locust-worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: locust-worker
spec:
  replicas: 10  # 10 workers para testing distribuido
  selector:
    matchLabels:
      app: locust
      role: worker
  template:
    metadata:
      labels:
        app: locust
        role: worker
    spec:
      containers:

        - name: locust
          image: locustio/locust:2.20.0
          args:

            - --worker
            - --master-host=locust-master
            - -f
            - /mnt/locust/locustfile.py
          volumeMounts:

            - name: locust-scripts
              mountPath: /mnt/locust
      volumes:

        - name: locust-scripts
          configMap:
            name: locust-scripts

Terraform para Infraestructura de Pruebas de Carga

Entorno de Prueba de Carga Efímero

# modules/load-test-infra/main.tf

variable "run_load_test" {
  description = "Establecer a true para provisionar infraestructura de pruebas"
  type        = bool
  default     = false
}

variable "worker_count" {
  description = "Número de workers K6/Locust"
  type        = number
  default     = 5
}

# Cluster ECS para generadores de carga
resource "aws_ecs_cluster" "load_test" {
  count = var.run_load_test ? 1 : 0
  name  = "load-test-cluster"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  tags = {
    Purpose   = "LoadTesting"
    AutoClean = "true"
  }
}

Integración CI/CD

Workflow GitHub Actions para Pruebas de Carga

name: Testing de Escalabilidad

on:
  schedule:

    - cron: '0 4 * * 1'  # Semanalmente Lunes 4 AM
  workflow_dispatch:
    inputs:
      test_duration:
        description: 'Duración del test (ej., 10m, 1h)'
        default: '20m'
      max_vus:
        description: 'Máximo usuarios virtuales'
        default: '1000'

jobs:
  load-test:
    runs-on: ubuntu-latest
    environment: load-test

    steps:

      - uses: actions/checkout@v4

      - name: Configurar credenciales AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.LOAD_TEST_ROLE_ARN }}
          aws-region: us-east-1

      - name: Setup K6
        run: |
          sudo apt-get update && sudo apt-get install k6

      - name: Ejecutar prueba de carga
        run: |
          k6 run tests/autoscaling-validation.js \
            --env TARGET_URL=${{ vars.TARGET_URL }} \
            --duration ${{ inputs.test_duration || '20m' }} \
            --vus ${{ inputs.max_vus || '1000' }} \
            --out json=results/k6-results.json

Midiendo el Éxito

MétricaObjetivoCómo Rastrear
Latencia de scale-up< 3 minutos desde triggerMétricas CloudWatch ASG
Latencia P95 durante scale< 500msResultados K6/Locust
Tasa de error durante pico< 1%Resultados K6/Locust
Precisión de scale-downDentro de 2x período de cooldownLogs de actividad ASG
Eficiencia de costosSin sobre-provisionamientoAWS Cost Explorer

Señales de que tu testing de escalabilidad no funciona:

  • Tests pasan pero producción aún tiene problemas de scaling
  • Políticas de scaling nunca se disparan durante tests (carga muy baja)
  • Entorno de prueba no coincide con producción (diferentes tipos de instancia, límites)
  • Resultados varían mucho entre ejecuciones de test (baseline inconsistente)

Conclusión

El testing efectivo de escalabilidad de infraestructura requiere escenarios realistas y herramientas apropiadas:

  1. Prueba tres escenarios como mínimo: carga sostenida, pico y recuperación
  2. Usa infraestructura efímera con Terraform para testing rentable
  3. Integra con CI/CD para validación regular
  4. Correlaciona métricas entre resultados de pruebas de carga y scaling de infraestructura
  5. Documenta thresholds y alerta cuando tests fallen

La idea clave: las políticas de auto-scaling necesitan validación bajo condiciones de carga reales. Los cálculos teóricos no son suficientes—prueba tu infraestructura antes de que lo hagan tus usuarios.

Ver También

Recursos Oficiales