KEDA (Kubernetes Event-Driven Autoscaling) es un proyecto de la CNCF que escala Deployments, StatefulSets y Jobs en función de fuentes externas: mensajes pendientes en una cola, el lag de un consumer group de Kafka, el resultado de una consulta de Prometheus o un horario. A diferencia del HorizontalPodAutoscaler estándar, que solo mira CPU y memoria, KEDA puede dejar una carga en cero réplicas mientras no hay trabajo y arrancarla cuando llega. En este tutorial instalarás KEDA, escalarás un worker a partir de una cola de RabbitMQ, comprobarás el escalado desde y hacia cero, y verás cómo escalar por horario y por métricas de Prometheus.
Requisitos previos
Para seguir esta guía necesitas:
- Un clúster Kubernetes en una versión soportada por la release actual de KEDA (la tabla de compatibilidad está en la documentación de KEDA), por ejemplo sobre VPS de CubePath con Ubuntu 24.04.
kubectlcon permisos de administrador del clúster y Helm 3 en tu equipo.- Ningún otro adaptador de métricas externas instalado (como
prometheus-adaptersirviendoexternal.metrics.k8s.io), ya que solo puede haber uno registrado para esa API.
Cómo funciona KEDA
KEDA no sustituye al HPA, lo alimenta. Cuando creas un ScaledObject para un Deployment:
- El operador de KEDA consulta la fuente de eventos cada
pollingIntervalsegundos. - Mientras no hay eventos, mantiene el Deployment en
minReplicaCount, que puede ser0. Al detectar el primer evento, lo activa y pasa a una réplica. - De una réplica en adelante, KEDA crea un HPA llamado
keda-hpa-<nombre>y le sirve la métrica a través de su propio servidor de métricas. El HPA calcula las réplicas como el valor de la métrica dividido por el objetivo de cada trigger. - Cuando la fuente vuelve a estar a cero durante
cooldownPeriodsegundos, KEDA baja el Deployment a cero.
Paso 1: Instalar KEDA
Añade el repositorio oficial de charts de KEDA:
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
Instala KEDA en su propio namespace:
helm install keda kedacore/keda --namespace keda --create-namespace
Comprueba que los tres componentes están en marcha: el operador, el servidor de métricas y el webhook de admisión que valida los recursos:
kubectl -n keda get pods
NAME READY STATUS RESTARTS AGE
keda-admission-webhooks-6d7b8c9f5d-kx2mz 1/1 Running 0 50s
keda-operator-7c9d6b8f4-q7lp2 1/1 Running 1 50s
keda-operator-metrics-apiserver-5f8d7c6b9-rt4wz 1/1 Running 0 50s
Verifica que el servidor de métricas de KEDA está registrado en la API de Kubernetes:
kubectl get apiservice v1beta1.external.metrics.k8s.io
NAME SERVICE AVAILABLE AGE
v1beta1.external.metrics.k8s.io keda/keda-operator-metrics-apiserver True 60s
Paso 2: Desplegar RabbitMQ y un worker de prueba
Para ver KEDA en acción necesitas una fuente de eventos. Despliega un RabbitMQ de prueba, sin persistencia, en un namespace propio. Sustituye tu_password por una contraseña real:
kubectl create namespace keda-demo
nano rabbitmq.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
namespace: keda-demo
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:4-management
env:
- name: RABBITMQ_DEFAULT_USER
value: keda
- name: RABBITMQ_DEFAULT_PASS
value: tu_password
ports:
- containerPort: 5672
- containerPort: 15672
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
namespace: keda-demo
spec:
selector:
app: rabbitmq
ports:
- name: amqp
port: 5672
- name: management
port: 15672
kubectl apply -f rabbitmq.yaml
kubectl -n keda-demo rollout status deployment/rabbitmq
Cuando RabbitMQ esté listo (espera unos segundos tras el rollout para que arranque la API de gestión), crea la cola tareas que usará el worker con la API HTTP de gestión de RabbitMQ desde un pod temporal. La respuesta vacía indica que se ha creado:
kubectl -n keda-demo run crear-cola --rm -i --restart=Never --image=curlimages/curl --command -- \
curl -s -u keda:tu_password -X PUT -H "content-type: application/json" -d '{"durable":true}' \
http://rabbitmq:15672/api/queues/%2F/tareas
Notaeste RabbitMQ solo sirve para la prueba. En producción usa el RabbitMQ Cluster Operator o un servicio gestionado, y guarda las credenciales en un Secret.
Ahora crea el worker que KEDA va a escalar. En un caso real sería tu consumidor de la cola; aquí basta con un contenedor que espera, porque lo que interesa es ver cómo cambia el número de réplicas. Empieza con replicas: 0:
nano worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: keda-demo
spec:
replicas: 0
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "sleep infinity"]
resources:
requests:
cpu: 10m
memory: 16Mi
kubectl apply -f worker.yaml
Paso 3: Crear el ScaledObject para la cola
Las credenciales de la fuente de eventos no van en el ScaledObject, sino en un Secret referenciado desde un TriggerAuthentication. Así varios ScaledObjects pueden reutilizarlas y no quedan en texto plano en el manifiesto de escalado.
nano scaledobject-rabbitmq.yaml
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-keda
namespace: keda-demo
type: Opaque
stringData:
host: amqp://keda:[email protected]:5672
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-auth
namespace: keda-demo
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-keda
key: host
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: worker
namespace: keda-demo
spec:
scaleTargetRef:
name: worker
minReplicaCount: 0
maxReplicaCount: 10
pollingInterval: 10
cooldownPeriod: 60
triggers:
- type: rabbitmq
metadata:
protocol: amqp
queueName: tareas
mode: QueueLength
value: "5"
authenticationRef:
name: rabbitmq-auth
Los campos principales:
minReplicaCount: 0permite escalar a cero.maxReplicaCountes el techo de réplicas.pollingIntervales cada cuántos segundos KEDA consulta la cola, ycooldownPeriodcuánto espera con la cola vacía antes de volver a cero.mode: QueueLengthconvalue: "5"pide una réplica por cada 5 mensajes pendientes. Con 30 mensajes, el HPA calculará 6 réplicas.
kubectl apply -f scaledobject-rabbitmq.yaml
kubectl -n keda-demo get scaledobject worker
La columna READY debe mostrar True, lo que significa que KEDA ha podido conectar con RabbitMQ, y ACTIVE debe mostrar False, porque la cola está vacía. Comprueba también que KEDA ha creado el HPA:
kubectl -n keda-demo get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
keda-hpa-worker Deployment/worker <unknown>/5 (avg) 1 10 0 20s
El HPA muestra un mínimo de 1 porque solo actúa de una réplica en adelante; el paso entre cero y uno lo gestiona el operador de KEDA.
Paso 4: Probar el escalado desde cero
Publica 30 mensajes en la cola tareas desde un pod temporal, de nuevo con la API HTTP de gestión:
kubectl -n keda-demo run publicador --rm -i --restart=Never --image=curlimages/curl --command -- sh -c '
API=http://rabbitmq:15672/api
for i in $(seq 1 30); do
curl -s -o /dev/null -u keda:tu_password -X POST -H "content-type: application/json" \
-d "{\"properties\":{},\"routing_key\":\"tareas\",\"payload\":\"trabajo $i\",\"payload_encoding\":\"string\"}" \
"$API/exchanges/%2F/amq.default/publish"
done
echo "30 mensajes publicados"'
Observa el Deployment. En unos segundos pasará de 0 a 1 réplica por la activación de KEDA, y después el HPA lo subirá a 6:
kubectl -n keda-demo get deployment worker -w
NAME READY UP-TO-DATE AVAILABLE AGE
worker 0/0 0 0 5m
worker 0/1 0 0 5m
worker 1/1 1 1 5m
worker 1/4 1 1 5m
worker 6/6 6 6 5m
Pulsa Ctrl+C. El ScaledObject aparece ahora con ACTIVE en True, y el HPA muestra la métrica real:
kubectl -n keda-demo get hpa keda-hpa-worker
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
keda-hpa-worker Deployment/worker 5/5 (avg) 1 10 6 3m
Paso 5: Probar el escalado a cero
Como el worker de prueba no consume mensajes, vacía la cola a mano para simular que el trabajo ha terminado:
kubectl -n keda-demo run purga --rm -i --restart=Never --image=curlimages/curl --command -- \
curl -s -u keda:tu_password -X DELETE http://rabbitmq:15672/api/queues/%2F/tareas/contents
Cuando la cola lleve cooldownPeriod segundos (60 en este ejemplo) vacía, KEDA bajará el Deployment directamente a 0:
kubectl -n keda-demo get deployment worker -w
Al final verás 0/0 réplicas y el ScaledObject con ACTIVE en False.
Paso 6: Ajustar la velocidad de escalado
Si el escalado hacia abajo es demasiado lento o demasiado agresivo para tu carga, ajusta el comportamiento del HPA que genera KEDA desde el propio ScaledObject, en la sección advanced. Añade este bloque dentro de spec en scaledobject-rabbitmq.yaml, al mismo nivel que triggers:
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 30
scaleUp:
stabilizationWindowSeconds: 0
Con esta configuración, el HPA sube en cuanto hay más mensajes y baja como mucho la mitad de las réplicas cada 30 segundos, tras un minuto de estabilidad. Aplica el cambio con kubectl apply -f scaledobject-rabbitmq.yaml.
Importanteno crees un HPA propio para un Deployment gestionado por un ScaledObject. Los dos lucharían por el número de réplicas, y el webhook de KEDA rechaza el ScaledObject si detecta el conflicto.
Otros scalers habituales
El patrón es siempre el mismo: cambiar el bloque triggers. Si un ScaledObject tiene varios triggers, el HPA usa el que pida más réplicas.
Escalado por horario con cron
El scaler cron fija un número de réplicas durante una franja horaria. Es útil para adelantarse a picos conocidos. Este ejemplo mantiene 10 réplicas de api-backend en horario laboral de Madrid y 2 el resto del tiempo:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-backend-horario
namespace: produccion
spec:
scaleTargetRef:
name: api-backend
minReplicaCount: 2
maxReplicaCount: 20
triggers:
- type: cron
metadata:
timezone: Europe/Madrid
start: "0 8 * * 1-5"
end: "0 20 * * 1-5"
desiredReplicas: "10"
Fuera de la franja, el Deployment vuelve a minReplicaCount. Puedes combinarlo con un trigger de carga para que el horario marque un mínimo y la carga real decida por encima.
Escalado por métricas de Prometheus
El scaler prometheus ejecuta una consulta PromQL y usa su resultado como métrica. Este ejemplo escala para que cada réplica atienda unas 100 peticiones por segundo, y no sale de minReplicaCount mientras el tráfico total no supere 10 peticiones por segundo:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-backend-rps
namespace: produccion
spec:
scaleTargetRef:
name: api-backend
minReplicaCount: 2
maxReplicaCount: 20
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-operated.monitoring.svc.cluster.local:9090
query: sum(rate(http_requests_total{service="api-backend"}[2m]))
threshold: "100"
activationThreshold: "10"
La consulta debe devolver un único valor. Pruébala antes en la interfaz de Prometheus y ajusta serverAddress al Service de tu instalación.
Jobs en lugar de Deployments
Para tareas largas en las que cada mensaje es un trabajo independiente (transcodificar un vídeo, generar un informe), usa un ScaledJob en lugar de un ScaledObject. KEDA crea un Job de Kubernetes por cada mensaje pendiente, hasta maxReplicaCount, y evita que el HPA mate un pod a mitad de tarea al reducir réplicas.
Solución de problemas
El ScaledObject tiene READY en False. KEDA no puede consultar la fuente. Los eventos del recurso y los logs del operador indican el motivo, normalmente credenciales, un nombre de host o un puerto incorrectos:
kubectl -n keda-demo describe scaledobject worker
kubectl -n keda logs deploy/keda-operator --tail=50
ACTIVE está en True pero no escala por encima de 1. Revisa el HPA con kubectl -n keda-demo describe hpa keda-hpa-worker. Si muestra errores al obtener la métrica externa, comprueba que el APIService v1beta1.external.metrics.k8s.io está True y apunta a keda/keda-operator-metrics-apiserver.
No vuelve a cero. El Deployment solo baja a cero cuando la métrica está por debajo del umbral de activación durante cooldownPeriod. Comprueba que la cola está realmente vacía y que minReplicaCount es 0. Con el protocolo amqp KEDA cuenta los mensajes listos para entregar; si tus consumidores retienen muchos mensajes sin confirmar, usa protocol: http para que también se tengan en cuenta.
El ScaledObject se rechaza al crearlo. El webhook de admisión de KEDA lo bloquea si el Deployment ya tiene un HPA o si otro ScaledObject apunta al mismo destino. El mensaje de error de kubectl apply indica cuál.
Conclusión
Has instalado KEDA, has escalado un worker de 0 a 6 réplicas según los mensajes de una cola RabbitMQ y lo has devuelto a cero cuando la cola se vació, además de ver cómo escalar por horario y por métricas de Prometheus. Como siguientes pasos, sustituye el worker de prueba por tu consumidor real, define un bloque fallback para que el Deployment mantenga un número fijo de réplicas si la fuente de eventos no responde, y combina KEDA con el Cluster Autoscaler para que los nodos también crezcan con la carga.
