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 (
requestsylimits) 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
FinOps en AWS: Optimización y Gestión de Costos en la Nube
Introducción a Terraform para Infraestructura como Código