Blog - Jorge Rodriguez Flores
← Volver al blog

Kubernetes Autoscaling: Estrategias para Responder a la Demanda en 2025

kubernetes autoscaling hpa vpa keda infraestructura
Compartir: X / Twitter LinkedIn
Kubernetes Autoscaling: Estrategias para Responder a la Demanda en 2025

En un mundo donde los patrones de tráfico son impredecibles y las cargas de trabajo pueden pasar de cero a miles de peticiones por segundo en minutos, la capacidad de escalar automáticamente se ha convertido en un requisito no negociable para los equipos de infraestructura. Kubernetes ofrece un ecosistema maduro de herramientas de autoscaling que, bien configuradas, permiten responder a la demanda de forma eficiente y rentable. En este artículo exploramos en profundidad las cuatro dimensiones del autoscaling en Kubernetes y cómo combinarlas.

Las Cuatro Dimensiones del Autoscaling en Kubernetes

Kubernetes divide el escalado automático en cuatro mecanismos complementarios, cada uno actuando en una capa diferente del sistema:

  • HPA (Horizontal Pod Autoscaler): Ajusta el número de réplicas de un Deployment o StatefulSet según métricas de CPU, memoria o métricas personalizadas.
  • VPA (Vertical Pod Autoscaler): Ajusta los recursos (requests y limits) de los contenedores de forma dinámica, sin cambiar el número de réplicas.
  • KEDA (Kubernetes Event-Driven Autoscaling): Escala basándose en eventos externos — longitud de colas SQS, mensajes en Kafka, métricas de Prometheus, etc.
  • Cluster Autoscaler / Karpenter: Agrega o elimina nodos del clúster cuando los pods no pueden ser schedulados por falta de recursos.
Capas del autoscaling en Kubernetes:

┌─────────────────────────────────────────────────────────┐
│              CLUSTER AUTOSCALER / KARPENTER              │
│         (Escala nodos del clúster)                       │
├─────────────────────────────────────────────────────────┤
│   HPA / KEDA           │         VPA                    │
│   (Escala réplicas)    │   (Escala recursos por pod)    │
├─────────────────────────────────────────────────────────┤
│              PODS (contenedores de aplicación)           │
└─────────────────────────────────────────────────────────┘

Horizontal Pod Autoscaler (HPA): Escalado por Métricas

El HPA es el mecanismo de autoscaling más usado en Kubernetes. Actúa periódicamente (por defecto cada 15 segundos) consultando el Metrics Server y ajusta el número de réplicas para mantener las métricas objetivo dentro de los umbrales configurados.

HPA basado en CPU (configuración clásica)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70   # Escalar si CPU supera el 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30   # Esperar 30s antes de escalar hacia arriba
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60            # Máximo 4 pods nuevos por minuto
    scaleDown:
      stabilizationWindowSeconds: 300  # Esperar 5 min antes de escalar hacia abajo
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60            # Reducir máximo 25% de pods por minuto

La sección behavior es crítica y a menudo ignorada. Define las ventanas de estabilización para evitar el flapping (escalado y desescalado rápido alternante) y limita la velocidad de cambio para proteger la base de datos y servicios downstream ante picos de tráfico repentinos.

HPA con métricas personalizadas

A partir de la versión autoscaling/v2, el HPA puede escalar basándose en métricas personalizadas expuestas a través del Custom Metrics API. Esto requiere instalar un adaptador como prometheus-adapter:

# HPA basado en métrica personalizada de Prometheus
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa-custom
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  minReplicas: 2
  maxReplicas: 50
  metrics:
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second   # Métrica expuesta en Prometheus
        target:
          type: AverageValue
          averageValue: "100"              # 100 req/s por pod como objetivo
    - type: External
      external:
        metric:
          name: sqs_queue_depth
          selector:
            matchLabels:
              queue: orders-queue
        target:
          type: AverageValue
          averageValue: "30"               # 30 mensajes por pod en cola

Vertical Pod Autoscaler (VPA): Ajuste Dinámico de Recursos

El VPA resuelve un problema diferente: ¿cómo dimensionar correctamente los requests y limits de CPU y memoria de cada pod? Un dimensionamiento incorrecto tiene consecuencias graves — recursos subestimados causan OOMKills y throttling de CPU, mientras que recursos sobredimensionados desperdician dinero y bloquean la utilización del clúster.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  updatePolicy:
    updateMode: "Auto"   # Off | Initial | Recreate | Auto
  resourcePolicy:
    containerPolicies:
      - containerName: api
        minAllowed:
          cpu: 100m
          memory: 128Mi
        maxAllowed:
          cpu: 4
          memory: 4Gi
        controlledResources: ["cpu", "memory"]
        controlledValues: RequestsAndLimits

Los modos de actualización del VPA tienen implicaciones importantes:

  • Off: Solo recomienda valores, no aplica cambios. Ideal para observar sin impacto.
  • Initial: Aplica recomendaciones solo en pods nuevos (en la creación).
  • Recreate: Termina y recrea pods para aplicar nuevas recomendaciones.
  • Auto: Modo inteligente que puede actualizar in-place si el node lo soporta (Kubernetes 1.29+).

Importante: El VPA y el HPA no deben gestionar el mismo recurso simultáneamente. Si el HPA usa CPU, el VPA debe configurarse solo para memoria, o se producirán conflictos de escalado.

KEDA: Autoscaling Basado en Eventos

KEDA (Kubernetes Event-Driven Autoscaling) extiende el HPA nativo con soporte para más de 60 fuentes de eventos externas: AWS SQS, Azure Service Bus, Apache Kafka, RabbitMQ, Prometheus, métricas de bases de datos y muchas más. KEDA también soporta escalar a cero réplicas cuando no hay eventos, algo que el HPA nativo no puede hacer.

Instalación de KEDA

# Instalar KEDA via Helm
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda \
  --namespace keda \
  --create-namespace \
  --version 2.13.0

ScaledObject: escalar con cola SQS

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-processor-scaler
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-processor
  pollingInterval: 15          # Revisar la fuente cada 15 segundos
  cooldownPeriod: 120          # Esperar 2 min antes de escalar a 0
  minReplicaCount: 0           # ¡Permite escalar a cero!
  maxReplicaCount: 30
  triggers:
    - type: aws-sqs-queue
      authenticationRef:
        name: aws-credentials
      metadata:
        queueURL: https://sqs.us-east-1.amazonaws.com/123456789/orders
        awsRegion: us-east-1
        queueLength: "10"       # 1 pod por cada 10 mensajes en cola
        activationQueueLength: "1"  # Activar desde 0 réplicas si hay ≥1 mensaje
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: aws-credentials
  namespace: production
spec:
  podIdentity:
    provider: aws   # Usar IRSA (IAM Roles for Service Accounts)

ScaledJob: procesar ráfagas con Jobs

Para cargas de trabajo de tipo batch (procesamiento de imágenes, reportes, ML inference), KEDA soporta el escalado de Jobs en lugar de Deployments — cada mensaje crea un Job efímero que procesa y termina:

apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
  name: image-processor-job
  namespace: production
spec:
  jobTargetRef:
    template:
      spec:
        containers:
          - name: processor
            image: company/image-processor:latest
            resources:
              requests:
                cpu: 500m
                memory: 1Gi
        restartPolicy: Never
  pollingInterval: 10
  maxReplicaCount: 50
  triggers:
    - type: rabbitmq
      metadata:
        protocol: amqp
        queueName: image-processing
        host: amqp://rabbitmq.production.svc.cluster.local
        queueLength: "1"   # 1 Job por mensaje

Cluster Autoscaler y Karpenter: Escalado de Nodos

Cuando los pods no pueden ser schedulados porque no hay nodos con suficiente capacidad disponible, el Cluster Autoscaler agrega nodos automáticamente. Cuando los nodos están subutilizados, los drena y los elimina. Sin escalado de nodos, el HPA y KEDA tienen un techo fijo de capacidad.

Karpenter: la evolución del Cluster Autoscaler

Karpenter es el reemplazo moderno del Cluster Autoscaler, especialmente para AWS EKS. A diferencia del CA (que gestiona Auto Scaling Groups predefinidos), Karpenter aprovisiona nodos directamente en EC2, eligiendo el tipo de instancia óptimo para cada pod pendiente en tiempo real.

# NodePool de Karpenter: define los tipos de nodos permitidos
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    metadata:
      labels:
        workload-type: general
    spec:
      nodeClassRef:
        apiVersion: karpenter.k8s.aws/v1beta1
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]   # Preferir Spot para reducir costos
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]        # Compute, Memory, General
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["3"]
  limits:
    cpu: 1000                            # Máximo 1000 vCPUs en este NodePool
    memory: 2000Gi
  disruption:
    consolidationPolicy: WhenUnderutilized  # Consolidar nodos infrautilizados
    consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2
  role: KarpenterNodeRole
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster

Patrones Avanzados: Escalado Predictivo y Programado

Escalado programado con KEDA CronScaler

Cuando los patrones de tráfico son predecibles (picos cada mañana, campañas de marketing programadas), el escalado reactivo siempre llega tarde. El CronScaler de KEDA permite pre-escalar antes de que llegue la demanda:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: api-cron-scaler
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  minReplicaCount: 2
  maxReplicaCount: 40
  triggers:
    - type: cron
      metadata:
        timezone: America/Lima
        start: "0 8 * * 1-5"    # Lunes a viernes a las 8am
        end:   "0 20 * * 1-5"   # Lunes a viernes a las 8pm
        desiredReplicas: "15"   # Pre-escalar a 15 réplicas en horario laboral
    - type: cron
      metadata:
        timezone: America/Lima
        start: "0 20 * * 1-5"   # Fuera de horario laboral
        end:   "0 8 * * 1-5"
        desiredReplicas: "2"    # Reducir a mínimo fuera de horario

Prometheus Adapter para métricas de negocio

Una de las estrategias más potentes es escalar basándose en métricas de negocio reales en lugar de métricas de infraestructura. Por ejemplo, escalar el servicio de pagos según el número de transacciones pendientes, o escalar el servicio de notificaciones según la longitud de la cola de emails:

# Configuración del Prometheus Adapter
apiVersion: v1
kind: ConfigMap
metadata:
  name: adapter-config
  namespace: monitoring
data:
  config.yaml: |
    rules:
      - seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: "namespace"}
            pod: {resource: "pod"}
        name:
          matches: "^(.*)_total"
          as: "${1}_per_second"
        metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'

      - seriesQuery: 'payments_pending_total'
        resources:
          template: '<<.Resource>>'
        name:
          as: payments_pending
        metricsQuery: 'sum(<<.Series>>)'

Observabilidad del Autoscaling: Detectar y Depurar Problemas

El autoscaling es invisible hasta que falla. Monitorizar activamente los eventos de escalado es esencial para detectar problemas como flapping, escalado insuficiente o nodos que no se aprovisionan a tiempo.

Comandos esenciales de diagnóstico

# Ver el estado y las métricas actuales del HPA
kubectl get hpa -n production
kubectl describe hpa api-hpa -n production

# Ver el historial de eventos de escalado
kubectl get events -n production \
  --field-selector reason=SuccessfulRescale \
  --sort-by='.lastTimestamp'

# Ver recomendaciones del VPA
kubectl describe vpa api-vpa -n production

# Ver pods pendientes (síntoma de que el Cluster Autoscaler no reaccionó)
kubectl get pods -n production --field-selector=status.phase=Pending

# Ver logs del Cluster Autoscaler
kubectl logs -n kube-system \
  -l app=cluster-autoscaler \
  --tail=50 | grep -E 'scale|node'

# Ver logs de Karpenter
kubectl logs -n karpenter \
  -l app.kubernetes.io/name=karpenter \
  --tail=100 | grep -E 'launched|terminated|disrupted'

Alertas recomendadas en Prometheus

groups:
  - name: autoscaling-alerts
    rules:
      - alert: HPAAtMaxReplicas
        expr: |
          kube_horizontalpodautoscaler_status_current_replicas
          ==
          kube_horizontalpodautoscaler_spec_max_replicas
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "HPA {{ $labels.horizontalpodautoscaler }} alcanzó el máximo de réplicas"
          description: "Considera aumentar maxReplicas o el Cluster Autoscaler puede estar atascado"

      - alert: PodsPendingTooLong
        expr: |
          sum by (namespace) (
            kube_pod_status_phase{phase="Pending"} == 1
          ) > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Pods en estado Pending por más de 5 minutos en {{ $labels.namespace }}"

      - alert: HPAScaleDownBlocked
        expr: |
          kube_horizontalpodautoscaler_status_current_replicas
          > kube_horizontalpodautoscaler_status_desired_replicas
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "HPA no puede escalar hacia abajo — posible PodDisruptionBudget bloqueando"

Mejores Prácticas y Recomendaciones para 2025

1. Siempre define requests de CPU y memoria

El HPA y el VPA requieren que los pods tengan requests definidos. Sin requests, el scheduler no puede tomar decisiones informadas de placement y el autoscaling basado en porcentaje de utilización no funciona correctamente.

2. Usa PodDisruptionBudgets para proteger la disponibilidad

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
  namespace: production
spec:
  minAvailable: 2    # Mínimo 2 pods siempre disponibles durante disrupciones
  selector:
    matchLabels:
      app: api

3. Configura topologySpreadConstraints

Al escalar horizontalmente, distribuir los pods entre zonas de disponibilidad garantiza resiliencia:

spec:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: api

4. KEDA para cargas event-driven, HPA para servicios HTTP

La elección entre KEDA y HPA depende de la naturaleza de la carga. Los servicios HTTP que responden a peticiones síncronas se escalan bien con HPA basado en CPU o RPS. Los workers que procesan mensajes de colas deben usar KEDA, que puede escalar a cero y reacciona directamente a la longitud de la cola.

5. Prueba el escalado en staging con carga realista

Herramientas como k6, Locust o Artillery permiten simular picos de tráfico controlados en entornos de staging. Antes de confiar en la configuración de autoscaling en producción, es esencial validar que los tiempos de respuesta del escalado son adecuados para los SLOs del servicio.

Conclusión

El autoscaling en Kubernetes en 2025 va mucho más allá del simple HPA basado en CPU. La combinación de HPA con métricas personalizadas, VPA para el dimensionamiento correcto, KEDA para cargas event-driven y Karpenter para el aprovisionamiento eficiente de nodos crea un sistema de escalado multi-capa que puede responder a prácticamente cualquier patrón de demanda.

La clave está en entender las características de cada carga de trabajo: sus métricas de saturación, patrones temporales, tiempos de arranque del pod, y dependencias downstream. Un sistema de autoscaling bien diseñado no solo responde a la demanda — lo hace de forma predecible, eficiente en costos y sin degradar la disponibilidad del servicio durante las transiciones.

Invertir tiempo en configurar correctamente el autoscaling, combinado con una buena estrategia de observabilidad, es una de las mejores optimizaciones que un equipo de plataforma puede realizar para mejorar tanto la experiencia del usuario final como la factura de infraestructura en la nube.

Artículos relacionados