StackPractices
intermediate Por Mathias Paulenko

Plantilla de Política de Auto-Scaling

Plantilla de política de auto-scaling lista para copiar, con umbrales de scale-up/down, cooldowns, controles de costo y ejemplos en Terraform y Kubernetes HPA.

Temas: devops

Visión General

El auto-scaling es el puente entre eficiencia de costos y disponibilidad. Escalar demasiado tarde y tu servicio se cae bajo carga; escalar demasiado temprano y quemas dinero en capacidad ociosa. Esta plantilla documenta las reglas exactas, umbrales y procedimientos que tu equipo de infraestructura usa para escalar cargas de trabajo automáticamente hacia arriba y hacia abajo. Combínala con una plantilla de capacity planning para el dimensionado y una plantilla de asignación de costos para que los controles de gasto de la sección 5 tengan dónde vivir.

Cuándo Usar

Usa este recurso cuando:

  • Defines reglas de escalamiento para un nuevo servicio desplegado en la nube
  • Auditas por qué un evento de auto-scaling causó una caída o costo excesivo
  • Migras de tamaños de instancia estáticos a escalamiento en vivo
  • Incorporas una carga de trabajo a Kubernetes HPA o a un autoscaler de nube por primera vez
  • Revisas una política de escalamiento tras un incidente en producción o un pico de costo

No la uses para cargas puramente serverless (la concurrencia la gestiona el proveedor — documenta los límites de concurrencia en su lugar) ni para servicios stateful que no pueden escalar horizontalmente.

Para alternativas, consulta la Plantilla de Asignación de Costos en la Nube.

Assets descargables de esta plantilla (config Terraform del ASG, manifiesto HPA, documento de política, formato de log de eventos): recursos companion.

Solución

# Política de Auto-Scaling: `<Nombre del Servicio>`

## 1. Metadatos del Servicio

| Campo | Valor |
|-------|-------|
| Servicio | `nombre` |
| Plataforma | `AWS / GCP / Azure / Kubernetes` |
| Equipo Responsable | `@team-name` |
| Última Revisión | `YYYY-MM-DD` |

## 2. Política de Scale-Up

### 2.1. Disparadores

| Métrica | Umbral | Duración | Acción de Escalamiento | Cooldown |
|---------|--------|----------|------------------------|----------|
| CPU utilization | > 60% | 2 minutos | Agregar 1 instancia | 3 minutos |
| Memory utilization | > 70% | 2 minutos | Agregar 1 instancia | 3 minutos |
| Request count | > 5,000 RPS | 1 minuto | Agregar 2 instancias | 5 minutos |
| Queue depth | > 100 mensajes | 3 minutos | Agregar 1 instancia | 3 minutos |
| Latency p95 | > 500ms | 2 minutos | Agregar 2 instancias | 5 minutos |

### 2.2. Límites

| Límite | Valor | Justificación |
|--------|-------|---------------|
| Máx instancias | 20 | Techo de costo, límite de conexiones DB |
| Máx scale-up por evento | 50% del actual | Prevenir thundering herd en cold start |
| Cooldown de scale-up | 3 minutos | Permitir estabilización de métricas |

## 3. Política de Scale-Down

### 3.1. Disparadores

| Métrica | Umbral | Duración | Acción de Escalamiento | Cooldown |
|---------|--------|----------|------------------------|----------|
| CPU utilization | < 30% | 10 minutos | Remover 1 instancia | 5 minutos |
| Memory utilization | < 30% | 10 minutos | Remover 1 instancia | 5 minutos |
| Request count | < 1,000 RPS | 10 minutos | Remover 1 instancia | 5 minutos |

### 3.2. Límites

| Límite | Valor | Justificación |
|--------|-------|---------------|
| Mín instancias | 3 | Redundancia, buffer para despliegue rolling |
| Máx scale-down por evento | 25% del actual | Evitar sobre-corrección |
| Cooldown de scale-down | 5 minutos | Permitir estabilización de métricas |

## 4. Requisitos de Instancia

### 4.1. Health Checks

- [ ] El health check del load balancer pasa antes de que la instancia reciba tráfico
- [ ] La instancia debe servir tráfico por mínimo 5 minutos antes de ser elegible para scale-down
- [ ] El connection draining permite completar requests en vuelo (30 segundos)

### 4.2. Warm-Up

- [ ] Las nuevas instancias completan inicialización (inicio de app, warm-up de caché) antes de unirse al pool
- [ ] Tiempo de warm-up documentado: `60 segundos`
- [ ] Startup probe / readiness probe configurado en el orquestador

## 5. Controles de Costo

| Control | Valor | Notas |
|---------|-------|-------|
| Gasto máximo por hora | $500 | Alertar si se excede |
| Tipo de instancia | `c5.large` | Optimizada para CPU para workload de API |
| Spot / Preemptible | 50% de instancias | Usar solo para procesamiento batch no crítico |
| Capacidad reservada | Línea base de 3 instancias | Descuento de compromiso para el mínimo |

## 6. Respuesta a Incidentes

| Escenario | Acción | Responsable |
|-----------|--------|-------------|
| Scale-up falla (cuota excedida) | Página on-call, escalar a admin de cloud | SRE |
| Scale-down causa errores | Pausar auto-scaling, investigar | Plataforma |
| Pico de costo > 2x línea base | Revisar política, verificar jobs desbocados | Finanzas + SRE |
| Latencia sube a pesar del escalamiento | Alerta: probable cuello de botella de DB, no compute | DBA + App Team |

Explicación

La plantilla separa scale-up (rápido, agresivo) de scale-down (lento, conservador). Los disparadores de scale-up usan duraciones más cortas porque necesitas capacidad antes de que ocurran fallas. El scale-down usa duraciones más largas para evitar fluctuar instancias entrando y saliendo durante jitter normal de tráfico. El cooldown previene que el auto-scaler reaccione al ruido de métricas causado por el propio evento de escalamiento. Las instancias mínimas existen por redundancia: incluso con tráfico cero, necesitas suficientes instancias para sobrevivir un despliegue rolling sin downtime.

Mermaid flowchart TD diagram

Configuración de Auto Scaling en AWS con Terraform

resource "aws_autoscaling_group" "app" {
  name                = "app-asg"
  vpc_zone_identifier = data.aws_subnets.private.ids
  min_size            = 3
  max_size            = 20
  desired_capacity    = 5

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }

  tag {
    key                 = "Team"
    value               = "platform"
    propagate_at_launch = true
  }
}

resource "aws_autoscaling_policy" "scale_up" {
  name                   = "scale-up"
  autoscaling_group_name = aws_autoscaling_group.app.name
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = 2
  cooldown               = 300
}

resource "aws_autoscaling_policy" "scale_down" {
  name                   = "scale-down"
  autoscaling_group_name = aws_autoscaling_group.app.name
  adjustment_type        = "ChangeInCapacity"
  scaling_adjustment     = -1
  cooldown               = 600
}

resource "aws_cloudwatch_metric_alarm" "high_cpu" {
  alarm_name          = "high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 60
  statistic           = "Average"
  threshold           = 70
  alarm_actions       = [aws_autoscaling_policy.scale_up.arn]
}

resource "aws_cloudwatch_metric_alarm" "low_cpu" {
  alarm_name          = "low-cpu"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 10
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 60
  statistic           = "Average"
  threshold           = 30
  alarm_actions       = [aws_autoscaling_policy.scale_down.arn]
}

HPA de Kubernetes con Métricas Personalizadas

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 30
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
        - type: Pods
          value: 4
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 120
      selectPolicy: Min

Consulta para Dashboard de Eventos de Scaling

=== Log de Eventos de Scaling (Ultimas 24h) ===

Hora         Direccion   Trigger              De -> A      Duracion
02:15 UTC    Scale UP    CPU > 70% (2 min)    5 -> 7       45s warm-up
02:45 UTC    Scale UP    CPU > 70% (2 min)    7 -> 9       42s warm-up
04:30 UTC    Scale DOWN  CPU < 30% (10 min)   9 -> 8       15s drain
08:00 UTC    Scale UP    RPS > 1000 (1 min)   8 -> 12      38s warm-up
08:30 UTC    Scale UP    RPS > 1000 (1 min)   12 -> 16     41s warm-up
10:00 UTC    Scale DOWN  CPU < 30% (10 min)   16 -> 14     12s drain
14:00 UTC    Scale DOWN  CPU < 30% (10 min)   14 -> 10     18s drain
18:00 UTC    Scale DOWN  CPU < 30% (10 min)   10 -> 8      15s drain
22:00 UTC    Scale DOWN  CPU < 30% (10 min)   8 -> 5       20s drain

Eventos totales: 9
Scale-up: 4 (warm-up promedio: 41s)
Scale-down: 5 (drain promedio: 16s)
Thrashing detectado: No (min 2h entre eventos opuestos)

Cómo elegir umbrales y cooldowns

Los números de la plantilla son puntos de partida, no valores para copiar a ciegas. Tres relaciones guían la elección:

Umbral vs. warm-up. El umbral de scale-up debe dispararse con la suficiente antelación para que las instancias nuevas terminen de arrancar antes de que el pool actual se sature. Si el arranque tarda 60 s y el tráfico crece un 20% por minuto, un umbral de CPU al 60% deja unos dos minutos de margen. El ejemplo de Terraform usa 70 en lugar del 60 de la plantilla — deliberado para un servicio con warm-up rápido. En servicios de arranque lento (JVM, carga de modelos, precalentado de caché), baja el umbral o acelera el warm-up primero.

Duración como filtro de ruido. Una ventana de evaluación de 2 minutos ignora el ruido de picos aislados, pero añade latencia a la respuesta. Ventanas más cortas reaccionan antes y generan más thrashing: 1–3 min para scale-up de cara al usuario, 5 min para consumidores batch.

Cooldown >= warm-up + tiempo de estabilización. El cooldown evita que el autoscaler reaccione al ruido que su propia acción genera — una instancia recién creada reporta CPU alta mientras arranca y dispararía otro scale-up. Configura el cooldown de scale-up en al menos el warm-up más un período de métricas, y el de scale-down aún más largo, porque quitar capacidad es más arriesgado que añadirla.

DecisiónGuía
Umbral de CPUMargen para un evento completo de scale-up: warm-up x tasa de crecimiento
Duración de scale-up1–3 min de cara al usuario; 5 min para batch
Duración de scale-down10 min como mínimo; más para servicios pesados en JVM
Cooldown de scale-up>= warm-up + 1 período de métricas
Cooldown de scale-down>= 2x el cooldown de scale-up
Máx scale-up por evento<= 50% de la capacidad actual

Métricas que importan para el auto-scaling

No toda métrica sirve como disparador de escalamiento. Las útiles comparten tres rasgos: se saturan antes de que los usuarios lo noten, son baratas de recolectar y no mienten durante fallos parciales.

  • Utilización de CPU es la opción por defecto por una razón — correlaciona con la capacidad en servicios limitados por cómputo. Falla en servicios I/O-bound que se quedan esperando llamadas downstream mientras las requests se acumulan.
  • Memoria detecta fugas y working sets que crecen, pero rara vez baja lo bastante rápido como para gobernar el scale-down; úsala para scale-up y prevención de out-of-memory únicamente.
  • Tasa de requests (RPS) refleja la demanda de usuarios directamente en servicios sin estado. Combínala con la concurrencia por instancia para que el disparo ocurra por saturación real y no por volumen bruto.
  • Profundidad de cola es el disparador correcto para consumidores — el número de mensajes pendientes es el backlog. Escala por mensajes-por-worker, no por CPU.
  • Latencia p95 mide el resultado que sienten los usuarios, pero es una señal tardía: cuando el p95 se incumple, algunas requests ya fallaron. Úsala como alarma, no como disparador principal.

Métricas a evitar como disparadores: promedios sobre ventanas largas (suavizan los picos), KPIs de negocio como registros-por-minuto (no correlacionan con nada que el autoscaler pueda arreglar), y cualquier métrica que un fallo parcial pueda distorsionar — un nodo que reporta cero RPS porque está caído parece “baja demanda” para una política ingenua.

En la práctica, las políticas sólidas casi nunca dependen de una sola métrica. Combinan dos disparadores con lógica OR: una señal adelantada (profundidad de cola, RPS) que salta pronto, y una señal de confirmación (CPU o latencia) que atrapa lo que la primera se pierde. La tabla de cinco métricas de la plantilla modela exactamente eso — rellénala con tus dos mejores señales y borra las filas que no necesites en lugar de cablear las cinco.

Variantes

ContextoEnfoqueNotas
Kubernetes HPAMétricas via custom metrics APIEscalar en métricas custom (profundidad de cola, latencia de request)
AWS EC2 Auto ScalingAlarmas de CloudWatchUsar predictive scaling para patrones conocidos
Serverless (Lambda)Límites de concurrenciaNo hay escalamiento tradicional; gestionar max concurrency y reserved concurrency
Workloads GPUEscalar en GPU utilizationWarm-up más largo, costo más alto; evitar instancias spot

Lo que funciona

  1. Siempre establecer límites máximos de instancias para prevenir escalamiento desbocado por loops infinitos o DDoS
  2. Usar predictive scaling para patrones de tráfico predecibles (batch nocturno, horario comercial)
  3. Testear eventos de scale-up y scale-down en staging antes de producción
  4. Monitorear frecuencia de eventos de escalamiento; eventos frecuentes indican mala configuración de umbrales
  5. Documentar por qué se eligió cada umbral para que equipos futuros puedan ajustar inteligentemente

Errores Comunes

  1. Establecer umbral de CPU al 80% o más, dejando sin margen para picos
  2. Usar la misma política para todos los servicios sin importar sus patrones de workload
  3. Olvidar el connection draining, causando requests dropeadas durante scale-down
  4. Escalar solo en CPU e ignorar memoria, red o métricas custom
  5. Permitir scale-down a cero para servicios stateful que necesitan conexiones persistentes

Adopción de esta política

Trata la plantilla como un documento que validas, no como un formulario que rellenas:

  1. Baseline primero. Extrae cuatro semanas de datos de CPU, memoria, RPS y p95. Los umbrales elegidos sin baseline son adivinanzas — fíjalos justo por encima del p99 normal de cada métrica, no en números redondos.
  2. Despliega en modo observación. Tanto HPA (scaleTargetRef apuntando a un deployment en sombra) como los autoscalers de nube soportan configuraciones dry-run; recoge lo que la política habría hecho durante una semana.
  3. Prueba la política con carga. Genera tráfico con k6 o Locust atravesando los umbrales reales y mide la latencia de scale-up de punta a punta: recolección de métricas, ventana de evaluación, lanzamiento de instancia, warm-up, health check. Ese total — no el umbral — es tu verdadera capacidad de respuesta.
  4. Conecta las filas de respuesta a incidentes con responsables reales antes de producción. Una política que avisa a “SRE” sin entrada en el runbook es un hueco de monitorización.
  5. Activa en producción con límites conservadores (máximo más bajo, cooldowns más largos) y ajústalos cuando tengas datos reales de eventos de escalamiento.

Revisión de la política

Las políticas de escalamiento se degradan en silencio: los patrones de tráfico cambian, los tipos de instancia evolucionan y el warm-up crece a medida que la app acumula dependencias. Revísalas con una cadencia fija — trimestral para servicios estables, mensual en fases de crecimiento, y tras cualquier incidente donde el escalamiento haya jugado un papel. En cada revisión comprueba: frecuencia de eventos de escalamiento (la señal de thrashing), deriva del warm-up, si el tope de instancias sigue casando con el límite de conexiones de la base de datos, y si el techo de costo sigue reflejando el presupuesto. Actualiza Última Revisión en la plantilla y registra por qué cambió cada umbral — la columna de justificación existe para que el siguiente revisor no tenga que adivinar.

Solución de Problemas

  • El escalamiento oscila arriba y abajo: el umbral de scale-down está demasiado cerca del de scale-up, o el cooldown es menor que el warm-up. Amplía la separación entre disparadores y configura cooldown >= warm-up + un período de métricas.
  • El scale-up nunca se dispara: confirma que la métrica llega al evaluador — HPA necesita metrics-server (o la custom metrics API para escalar por RPS), y las alarmas de CloudWatch necesitan datos en el namespace consultado. kubectl describe hpa muestra “unable to fetch metrics” cuando el pipeline de métricas está roto.
  • Las instancias nuevas fallan los health checks: el warm-up es menor que el arranque real. Mide el cold start en staging y configura stabilizationWindowSeconds / instance warmup por encima.
  • El scale-down corta requests en vuelo: falta connection draining. Activa el deregistration delay del ALB o terminationGracePeriodSeconds, y deja de aceptar trabajo nuevo antes de salir.
  • Los costos se duplicaron tras activar auto-scaling: falta un tope máximo o mal uso de spot. Configura max replicas, añade una alerta de presupuesto y restringe las instancias spot a trabajo interrumpible.
  • Los eventos de escalamiento se disparan pero la capacidad no cambia: revisa las cuotas de nube y la disponibilidad del tipo de instancia en la AZ; un ASG que no puede lanzar instancias se queda en la capacidad deseada con solo un aviso en el activity log.

Lectura Adicional

Preguntas frecuentes

¿Debería escalar por CPU o por requests por segundo?

CPU funciona para workloads compute-bound (procesamiento de imágenes, inferencia de ML). RPS funciona para workloads I/O-bound (APIs, proxies). Usa métricas custom (profundidad de cola, latencia) cuando ni CPU ni RPS correlacionan con la experiencia de usuario. Las mejores políticas usan múltiples métricas con lógica OR.

¿Qué es predictive scaling y cuándo debo usarlo?

El predictive scaling (AWS, GCP) usa tráfico histórico para precalentar instancias antes de que llegue el pico. Úsalo para patrones predecibles: picos diarios, jobs batch semanales o campañas de marketing. No lo uses para tráfico viral impredecible.

¿Cómo evito explosiones de costo por auto-scaling?

Establece un conteo máximo hard de instancias. Usa alertas de presupuesto. Revisa tipos de instancia trimestralmente (una generación más nueva puede ser más barata y rápida). Usa instancias reservadas para capacidad de línea base y auto-scaling para overflow. Etiqueta instancias por servicio para que finanzas pueda atribuir costos con precisión.

¿Cómo manejamos el scaling para servicios con estado?

Los servicios con estado (bases de datos, caches con persistencia) no deben usar auto-scaling estándar. En su lugar, usa read replicas para escalar lecturas y scaling vertical (instancias más grandes) para capacidad de escritura. Si debes escalar servicios con estado horizontalmente, usa sticky sessions o consistent hashing para distribuir la carga. Nunca permitas scale-down a cero para servicios con estado. Documenta la estrategia de scaling por separado de los servicios sin estado.

¿Qué es step scaling vs target tracking?

Target tracking mantiene un valor objetivo de métrica (ej. 70% CPU) ajustando capacidad automáticamente. Es más simple y requiere menos ajuste. Step scaling usa alarmas de CloudWatch con ajustes específicos (ej. +2 instancias cuando CPU > 70%, +4 cuando > 85%). Ofrece más control pero requiere más configuración. Usa target tracking para la mayoría de los workloads. Usa step scaling cuando necesitas respuestas diferentes en umbrales diferentes.

¿Cómo probamos las políticas de auto-scaling?
  1. Despliega la política en staging. 2. Genera carga con herramientas como k6, Locust o Artillery. 3. Verifica que el scale-up se dispare en el umbral y ventana de tiempo esperados. 4. Detén la carga y observa el scale-down después del cooldown. 5. Comprueba que las nuevas instancias pasen health checks antes de recibir tráfico. 6. Verifica que el connection draining funcione durante el scale-down. 7. Monitorea el impacto en costos. 8. Documenta los resultados y ajusta umbrales si es necesario.
¿Qué es el warm-up time y por qué importa?

El warm-up time es el retraso entre el inicio de una instancia y su disponibilidad para servir tráfico. Durante el warm-up, la instancia consume recursos pero no atiende requests. Para aplicaciones JVM, el warm-up puede ser de 60-120 segundos por la compilación JIT. Para servicios en contenedores, 10-30 segundos es lo típico. Configura el warm-up time en tu política de auto-scaling para no contar instancias que aún no están listas. Si el warm-up configurado es muy corto, las nuevas instancias pueden fallar health checks y disparar eventos innecesarios de scale-up.

¿Cómo manejamos el scaling durante despliegues?

Usa despliegues rolling con max surge y max unavailable para controlar cuántas instancias nuevas se crean. Para despliegues blue-green, escala el entorno green antes de cortar tráfico. Para despliegues canary, escala incrementalmente a medida que el tráfico se desplaza. Pausa el auto-scaling durante despliegues si el despliegue cambia los patrones de uso de recursos. Reanuda el auto-scaling después de que el despliegue se estabilice. Documenta el comportamiento de scaling específico del despliegue en el runbook de despliegue.