--- title: "Cómo usar KEDA para autoescalado por eventos en Kubernetes" description: "Instala KEDA con Helm y escala un worker según los mensajes de una cola RabbitMQ, de cero réplicas y de vuelta a cero, con ejemplos de cron y Prometheus." category: "Ecosistema Kubernetes" language: es source: https://cubepath.com/es/docs/ecosistema-kubernetes/keda-autoescalado-por-eventos-en-kubernetes updated: 2026-09-25 --- # Cómo usar KEDA para autoescalado por eventos en Kubernetes 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. - `kubectl` con permisos de administrador del clúster y Helm 3 en tu equipo. - Ningún otro adaptador de métricas externas instalado (como `prometheus-adapter` sirviendo `external.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 `pollingInterval` segundos. - Mientras no hay eventos, mantiene el Deployment en `minReplicaCount`, que puede ser `0`. 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-` 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 `cooldownPeriod` segundos, KEDA baja el Deployment a cero. ## Paso 1: Instalar KEDA Añade el repositorio oficial de charts de KEDA: ```bash helm repo add kedacore https://kedacore.github.io/charts helm repo update ``` Instala KEDA en su propio namespace: ```bash 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: ```bash kubectl -n keda get pods ``` ```output 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: ```bash kubectl get apiservice v1beta1.external.metrics.k8s.io ``` ```output 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: ```bash kubectl create namespace keda-demo ``` ```bash nano rabbitmq.yaml ``` ```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 ``` ```bash 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: ```bash 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 ``` > Nota: este 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`: ```bash nano worker.yaml ``` ```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 ``` ```bash 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. ```bash nano scaledobject-rabbitmq.yaml ``` ```yaml apiVersion: v1 kind: Secret metadata: name: rabbitmq-keda namespace: keda-demo type: Opaque stringData: host: amqp://keda:tu_password@rabbitmq.keda-demo.svc.cluster.local: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: 0` permite escalar a cero. `maxReplicaCount` es el techo de réplicas. - `pollingInterval` es cada cuántos segundos KEDA consulta la cola, y `cooldownPeriod` cuánto espera con la cola vacía antes de volver a cero. - `mode: QueueLength` con `value: "5"` pide una réplica por cada 5 mensajes pendientes. Con 30 mensajes, el HPA calculará 6 réplicas. ```bash 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: ```bash kubectl -n keda-demo get hpa ``` ```output NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE keda-hpa-worker Deployment/worker /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: ```bash 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: ```bash kubectl -n keda-demo get deployment worker -w ``` ```output 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: ```bash kubectl -n keda-demo get hpa keda-hpa-worker ``` ```output 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: ```bash 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: ```bash 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`: ```yaml 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`. > Importante: no 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: ```yaml 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: ```yaml 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: ```bash 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.