KEDA: Autoescalado por Eventos en Kubernetes
KEDA (Kubernetes Event-Driven Autoscaling) es un operador que permite escalar workloads de Kubernetes basándose en eventos externos: longitud de una cola de mensajes, métricas de Prometheus, cron schedules o cualquier fuente de eventos personalizada. A diferencia del HPA estándar que solo escala por CPU y memoria, KEDA puede escalar a cero réplicas cuando no hay trabajo pendiente y reactivar los pods automáticamente al llegar nuevos eventos.
Requisitos Previos
- Kubernetes 1.24+
kubectlconfiguradohelmv3 instalado- Acceso a las fuentes de eventos (Kafka, RabbitMQ, Prometheus, etc.)
Instalación de KEDA
# Instalar KEDA con Helm (método recomendado)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
# Instalación en el namespace keda
helm install keda kedacore/keda \
--namespace keda \
--create-namespace \
--set prometheus.metricServer.enabled=true \
--set prometheus.operator.enabled=true
# Verificar la instalación
kubectl get pods -n keda
kubectl get crd | grep keda
# Verificar que el Metrics Server de KEDA está registrado
kubectl get apiservice | grep external.metrics
ScaledObject y ScaledJob
KEDA usa dos recursos principales: ScaledObject para Deployments/StatefulSets y ScaledJob para Jobs de Kubernetes.
# Estructura básica de un ScaledObject
cat > scaled-object-ejemplo.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: mi-worker-scaler
namespace: produccion
spec:
scaleTargetRef:
# El Deployment o StatefulSet que KEDA va a escalar
name: mi-worker
kind: Deployment
apiVersion: apps/v1
# Número mínimo de réplicas (0 = escalar a cero)
minReplicaCount: 0
# Número máximo de réplicas
maxReplicaCount: 50
# Tiempo en segundos antes de escalar hacia abajo
cooldownPeriod: 60
# Intervalo de polling de las métricas (segundos)
pollingInterval: 15
# Umbral para escalar a cero (segundos de inactividad)
idleReplicaCount: 0
triggers:
# Aquí van los triggers (ejemplos en las secciones siguientes)
- type: kafka
metadata:
topic: mi-topico
bootstrapServers: kafka:9092
consumerGroup: mi-consumer-group
lagThreshold: "100"
EOF
# ScaledJob: para procesamiento de tareas de una sola vez
cat > scaled-job-ejemplo.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: procesador-imagenes
namespace: produccion
spec:
jobTargetRef:
parallelism: 1
completions: 1
template:
spec:
containers:
- name: procesador
image: procesador-imagenes:v1.0
env:
- name: QUEUE_URL
value: "amqp://rabbitmq:5672"
restartPolicy: OnFailure
# Número máximo de Jobs en paralelo
maxReplicaCount: 20
# Política de escalado
scalingStrategy:
strategy: "accurate" # accurate, default o custom
# Cuántos Jobs completados mantener
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
triggers:
- type: rabbitmq
metadata:
host: amqp://user:pass@rabbitmq:5672/
queueName: imagenes-a-procesar
queueLength: "1"
EOF
kubectl apply -f scaled-job-ejemplo.yaml
Scaler de Kafka
# Escalar un consumer de Kafka basado en el lag del consumer group
cat > keda-kafka.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer-scaler
namespace: produccion
spec:
scaleTargetRef:
name: kafka-consumer-worker
minReplicaCount: 1
maxReplicaCount: 30
cooldownPeriod: 120
pollingInterval: 10
triggers:
- type: kafka
metadata:
# Servidores de Kafka
bootstrapServers: kafka-broker-1:9092,kafka-broker-2:9092,kafka-broker-3:9092
# Tópico a monitorear
topic: pedidos-nuevos
# Consumer group que se quiere escalar
consumerGroup: procesador-pedidos
# Número de mensajes por réplica antes de escalar
lagThreshold: "50"
# Desactivar SSL
saslType: plaintext
tls: disable
EOF
kubectl apply -f keda-kafka.yaml
# Con autenticación SASL/TLS
cat > keda-kafka-auth.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: kafka-credentials
namespace: produccion
type: Opaque
stringData:
sasl: "plaintext"
username: "kafka-user"
password: "kafka-password"
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: kafka-trigger-auth
namespace: produccion
spec:
secretTargetRef:
- parameter: sasl
name: kafka-credentials
key: sasl
- parameter: username
name: kafka-credentials
key: username
- parameter: password
name: kafka-credentials
key: password
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer-seguro
namespace: produccion
spec:
scaleTargetRef:
name: kafka-consumer-worker
minReplicaCount: 1
maxReplicaCount: 20
triggers:
- type: kafka
metadata:
bootstrapServers: kafka:9093
topic: pedidos-nuevos
consumerGroup: procesador-pedidos
lagThreshold: "100"
saslType: plaintext
tls: enable
authenticationRef:
name: kafka-trigger-auth
EOF
kubectl apply -f keda-kafka-auth.yaml
Scaler de RabbitMQ
# Escalar basado en el número de mensajes en una cola de RabbitMQ
cat > keda-rabbitmq.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-secret
namespace: produccion
type: Opaque
stringData:
# URL de conexión a la Management API de RabbitMQ
host: "http://user:password@rabbitmq:15672/vhost"
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-trigger-auth
namespace: produccion
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-secret
key: host
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: rabbitmq-consumer-scaler
namespace: produccion
spec:
scaleTargetRef:
name: worker-procesador
minReplicaCount: 0
maxReplicaCount: 25
cooldownPeriod: 30
pollingInterval: 5
triggers:
- type: rabbitmq
metadata:
# Nombre de la cola a monitorear
queueName: tareas-pendientes
# Escalar 1 réplica por cada N mensajes
queueLength: "10"
# Protocolo: amqp o http
protocol: http
authenticationRef:
name: rabbitmq-trigger-auth
EOF
kubectl apply -f keda-rabbitmq.yaml
Scaler de Prometheus
# Escalar basado en métricas personalizadas de Prometheus
cat > keda-prometheus.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: prometheus-scaler
namespace: produccion
spec:
scaleTargetRef:
name: api-backend
minReplicaCount: 2
maxReplicaCount: 20
cooldownPeriod: 60
pollingInterval: 30
triggers:
- type: prometheus
metadata:
# URL del servidor de Prometheus
serverAddress: http://prometheus.monitoring.svc.cluster.local:9090
# Query de Prometheus para obtener la métrica de escala
# Ejemplo: peticiones HTTP por segundo promedio de los últimos 2 minutos
query: sum(rate(http_requests_total{service="api-backend"}[2m]))
# Umbral: escalar para mantener esta cantidad de peticiones por réplica
threshold: "100"
# Valor mínimo de la métrica para activar el escaler
activationThreshold: "10"
# Namespace de la métrica (opcional)
namespace: produccion
EOF
kubectl apply -f keda-prometheus.yaml
# Scaler de Prometheus con autenticación bearer token
cat > keda-prometheus-auth.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: prometheus-bearer-token
namespace: produccion
type: Opaque
stringData:
bearerToken: "mi-token-secreto"
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: prometheus-auth
namespace: produccion
spec:
secretTargetRef:
- parameter: bearerToken
name: prometheus-bearer-token
key: bearerToken
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-scaler-con-auth
namespace: produccion
spec:
scaleTargetRef:
name: api-backend
triggers:
- type: prometheus
metadata:
serverAddress: https://prometheus.midominio.com
query: avg(rate(http_requests_total[1m]))
threshold: "50"
authenticationRef:
name: prometheus-auth
EOF
Scaler Cron para Escalado Programado
# Escalar según un horario predefinido (útil para picos de tráfico conocidos)
cat > keda-cron.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: cron-scaler
namespace: produccion
spec:
scaleTargetRef:
name: api-backend
minReplicaCount: 2
maxReplicaCount: 50
triggers:
- type: cron
metadata:
# Zona horaria (usar nombres de zona IANA)
timezone: "Europe/Madrid"
# Inicio del período de alta carga: lunes a viernes a las 8:00 AM
start: "0 8 * * 1-5"
# Fin del período: lunes a viernes a las 10:00 PM
end: "0 22 * * 1-5"
# Número de réplicas durante el período de alta carga
desiredReplicas: "15"
# Segundo trigger cron: fin de semana
- type: cron
metadata:
timezone: "Europe/Madrid"
start: "0 10 * * 0,6" # Sábado y domingo a las 10:00 AM
end: "0 20 * * 0,6" # Sábado y domingo a las 8:00 PM
desiredReplicas: "5"
EOF
kubectl apply -f keda-cron.yaml
Escalado a Cero
# Configurar un Deployment para que escale a cero fuera del horario de trabajo
cat > escalado-a-cero.yaml << 'EOF'
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: ambiente-dev-scaler
namespace: desarrollo
spec:
scaleTargetRef:
name: app-desarrollo
# minReplicaCount: 0 permite escalar a cero réplicas
minReplicaCount: 0
maxReplicaCount: 5
# Tiempo en segundos sin eventos antes de escalar a cero
cooldownPeriod: 300
triggers:
# Solo escalar durante horario laboral
- type: cron
metadata:
timezone: "Europe/Madrid"
start: "0 9 * * 1-5"
end: "0 18 * * 1-5"
desiredReplicas: "3"
EOF
kubectl apply -f escalado-a-cero.yaml
# Verificar el estado del ScaledObject
kubectl get scaledobjects -n desarrollo
kubectl describe scaledobject ambiente-dev-scaler -n desarrollo
# Ver las métricas actuales de KEDA
kubectl get hpa -n desarrollo # KEDA crea un HPA internamente
Solución de Problemas
El ScaledObject no escala:
# Ver los logs del operador de KEDA
kubectl logs -n keda -l app=keda-operator -f
# Verificar el estado del ScaledObject
kubectl describe scaledobject <nombre> -n <namespace>
# Ver las métricas que KEDA está obteniendo
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq .
# Verificar el HPA creado por KEDA
kubectl get hpa -n <namespace>
kubectl describe hpa keda-hpa-<nombre-scaledobject> -n <namespace>
Error de conexión al scaler externo:
# Para Kafka: verificar conectividad desde el pod del operador KEDA
kubectl exec -n keda -it <keda-operator-pod> -- \
nc -zv kafka-broker:9092
# Para RabbitMQ: verificar credenciales y URL
kubectl exec -n keda -it <keda-operator-pod> -- \
curl -s http://user:pass@rabbitmq:15672/api/overview | jq .rabbitmq_version
Escalado agresivo que afecta la estabilidad:
# Ajustar los parámetros de estabilización en el HPA
# Editar el ScaledObject para agregar configuración de comportamiento
kubectl edit scaledobject <nombre> -n <namespace>
# Agregar:
# spec:
# advanced:
# horizontalPodAutoscalerConfig:
# behavior:
# scaleDown:
# stabilizationWindowSeconds: 300
# scaleUp:
# stabilizationWindowSeconds: 30
Conclusión
KEDA transforma el autoescalado de Kubernetes de reactivo a proactivo al conectar directamente con las fuentes de eventos que generan carga de trabajo. La capacidad de escalar a cero réplicas ahorra recursos en entornos de desarrollo y para cargas de trabajo intermitentes, mientras que los scalers para Kafka, RabbitMQ y Prometheus cubren la gran mayoría de patrones de procesamiento asíncrono en producción. La combinación de KEDA con escalado programado mediante cron es especialmente útil para gestionar picos de tráfico predecibles de forma eficiente.


