KEDA: event-driven autoscaling voor Kubernetes

KEDA (Kubernetes Event-Driven Autoscaling) breidt de standaard HPA uit met meer dan 70 ingebouwde scalers en scale-to-zero-ondersteuning. In plaats van alleen op CPU en geheugen te schalen, kun je pods laten schalen op Kafka consumer lag, RabbitMQ queue depth, Prometheus-queryresultaten of een cron-schema. Deze tutorial loopt door het installeren van KEDA, het aanmaken van je eerste ScaledObject, het configureren van veelgebruikte scalers, en het begrijpen van het tweefasen-scalingmodel dat zero-to-many mogelijk maakt.

Inhoudsopgave

Leerdoel

Aan het einde van deze tutorial heb je KEDA draaiend op een Kubernetes-cluster, een werkend ScaledObject dat een Deployment schaalt op basis van een externe eventsource, en een helder mentaal model van hoe KEDA's tweefasen-activatie samenwerkt met de native HPA. Je weet ook hoe je de Kafka-, Prometheus- en Cron-scalers configureert, en hoe het scale-to-zero-mechanisme onder de motorkap werkt.

Voorwaarden

  • kubectl verbonden met een Kubernetes 1.30+ cluster
  • Helm 3.x geinstalleerd
  • Cluster-admin-rechten (KEDA installeert CRDs en admission webhooks)
  • Bekendheid met de Horizontal Pod Autoscaler. Je hoeft nog geen draaiende HPA te hebben; KEDA maakt er zelf een aan. Maar als je het HPA-schalingalgoritme begrijpt (de ratioformule, stabilisatievensters, behaviour policies), klikt alles hier sneller
  • Een Deployment om te schalen. De voorbeelden gebruiken een fictieve order-processor Deployment, maar elke workload werkt
  • Voor de Prometheus-scalersectie: Prometheus draaiend in het cluster met een bereikbaar query-endpoint

Waarom HPA alleen niet genoeg is

De standaard HPA dekt CPU en geheugen prima af. Voor stateless HTTP-services die netjes correleren met CPU-gebruik is het vaak genoeg. Het gat wordt zichtbaar zodra je schaalsignaal buiten de pod leeft.

Een Kafka-consumer die op 2% CPU zit te niksen terwijl er 50.000 berichten in een topic opstapelen, triggert nooit een HPA scale-out. CPU zegt immers dat alles in orde is. Hetzelfde geldt voor RabbitMQ queue depth, Prometheus-businessmetrics, SQS-berichtaantallen of welk extern signaal dan ook. Een custom metrics adapter bouwen om die signalen naar de HPA te bridgen kan wel, maar dat is een flinke operationele last die je zelf moet bouwen, deployen en onderhouden.

HPA kan ook niet naar nul replicas schalen. De minReplicas-vloer is 1, want HPA heeft een draaiende pod nodig om utilizationmetrics te genereren. Voor event-driven workloads die het grootste deel van de dag idle zijn, is die ene altijd-aan-pod weggegooid geld.

Mogelijkheid HPA alleen KEDA + HPA
Scale to zero Nee Ja
CPU/geheugenmetrics Ja Ja
Externe metrics (queues, streams) Vereist custom adapter 70+ ingebouwde scalers
Cron-gebaseerde scheduling Nee Ja
Event-driven job creation Nee Ja (ScaledJob)

KEDA vult die gaten zonder HPA te vervangen. Het bouwt er bovenop.

Hoe KEDA werkt

KEDA is een CNCF Graduated-project, oorspronkelijk gemaakt door Microsoft en Red Hat. Het installeert drie pods in de keda-namespace:

KEDA Operator. Een controller die ScaledObject- en ScaledJob-resources in de gaten houdt. Wanneer je een ScaledObject aanmaakt, maakt de operator automatisch een HPA (keda-hpa-{naam}) en beheert de hele lifecycle. De operator handelt ook de 0-naar-1-activatie en 1-naar-0-deactivatie rechtstreeks af, want HPA kan niet in dat bereik werken.

Metrics Server. Implementeert de Kubernetes External Metrics API (external.metrics.k8s.io). Vertaalt ruwe scaler-output naar metricwaarden die de HPA-controller kan consumeren. Zonder dit component heeft HPA geen manier om Kafka-lag of RabbitMQ queue depth te lezen.

Admission Webhooks. Valideren ScaledObject- en ScaledJob-configuraties bij het aanmaken. Ze voorkomen conflicten, zoals twee ScaledObjects die dezelfde Deployment targeten.

Elke scaler implementeert twee methodes: IsActive() (voor de 0-naar-1-beslissing) en GetMetrics() (voor 1-naar-N-schaling via HPA). Per KEDA v2.19 zijn er meer dan 70 ingebouwde scalers voor Kafka, RabbitMQ, Prometheus, SQS, Pub/Sub, Redis Streams, PostgreSQL, Datadog, Cron en meer.

KEDA installeren met Helm

Stap 1: voeg de KEDA Helm-repository toe

helm repo add kedacore https://kedacore.github.io/charts
helm repo update

Stap 2: installeer KEDA

Pin de chartversie. Ongepinde installs in productie zijn een recept voor onverwachte breaking changes.

helm install keda kedacore/keda \
  --version 2.19.0 \
  --namespace keda \
  --create-namespace

Stap 3: controleer de installatie

kubectl get pods -n keda

Je zou drie pods in Running-status moeten zien:

NAME                                      READY   STATUS    RESTARTS   AGE
keda-admission-webhooks-...               1/1     Running   0          45s
keda-operator-...                         1/1     Running   0          45s
keda-operator-metrics-apiserver-...       1/1     Running   0          45s

Bevestig dat de CRDs geregistreerd zijn:

kubectl get crd | grep keda

Verwachte output bevat scaledobjects.keda.sh, scaledjobs.keda.sh, triggerauthentications.keda.sh en clustertriggerauthentications.keda.sh.

Checkpoint: als een pod niet Running is, bekijk de logs met kubectl logs -n keda -l app.kubernetes.io/instance=keda. De meest voorkomende oorzaak is een admission webhook timeout in clusters met strikte network policies.

Je eerste ScaledObject aanmaken

Een ScaledObject koppelt een Deployment aan een of meer eventsources. Hier is een minimaal voorbeeld dat een order-processor Deployment schaalt op basis van een RabbitMQ-queue:

# scaledobject-orders.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-processor-scaler
  namespace: default
spec:
  scaleTargetRef:
    name: order-processor         # moet overeenkomen met de Deployment-naam
  pollingInterval: 15              # controleer de queue elke 15 seconden
  cooldownPeriod: 120              # wacht 2 min idle voordat er naar nul geschaald wordt
  minReplicaCount: 0               # schakel scale-to-zero in
  maxReplicaCount: 30
  triggers:
    - type: rabbitmq
      metadata:
        queueName: incoming-orders
        mode: QueueLength
        value: "10"                # doel: 1 replica per 10 berichten
        activationValue: "1"       # activeer vanuit nul bij >= 1 bericht
      authenticationRef:
        name: rabbitmq-trigger-auth

Toepassen en verifiëren:

kubectl apply -f scaledobject-orders.yaml

# Controleer dat KEDA een HPA heeft aangemaakt
kubectl get hpa
# NAME                              REFERENCE                    TARGETS          MINPODS   MAXPODS   REPLICAS
# keda-hpa-order-processor-scaler   Deployment/order-processor   0/10 (avg)       1         30        0

De TARGETS-kolom toont de huidige metricwaarde vs. het doel. Wanneer berichten in de queue binnenkomen, activeert KEDA de Deployment vanuit nul en schaalt de HPA van 1 naar N op basis van queue depth.

Checkpoint: draai kubectl describe scaledobject order-processor-scaler en kijk naar Conditions. Ready: True en Active: True (of False als de queue leeg is) bevestigen dat KEDA succesvol pollt.

Veelgebruikte scalers

Kafka: schalen op consumer lag

De Kafka-scaler leest consumer group lag (het verschil tussen de laatste offset en de gecommitte offset). Schaalberekening: als de totale lag 500 is en lagThreshold is 50, target KEDA 500 / 50 = 10 replicas. Standaard worden replicas begrensd op het aantal partities, tenzij je allowIdleConsumers: "true" instelt.

triggers:
  - type: kafka
    metadata:
      bootstrapServers: kafka-broker-1.kafka:9092,kafka-broker-2.kafka:9092
      consumerGroup: order-consumer-group
      topic: orders
      lagThreshold: "50"               # 1 replica per 50 lag
      activationLagThreshold: "1"      # activeer vanuit nul bij elke lag
      offsetResetPolicy: latest
    authenticationRef:
      name: kafka-trigger-auth         # TriggerAuthentication met SASL-credentials

Waarom activationLagThreshold ertoe doet: zonder die instelling (standaard 0) activeert KEDA vanuit nul bij het allereerste bericht. Als je topic sporadische single-event-pieken ontvangt die het cold-starten van een consumer niet rechtvaardigen, zet deze waarde dan hoger.

Prometheus: schalen op elke PromQL-query

De Prometheus-scaler laat je schalen op alles wat Prometheus kan meten: HTTP request rate, p95-latency, custom businessmetrics, queue depth via exporters.

triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus-operated.monitoring.svc:9090
      query: sum(rate(http_requests_total{deployment="api-gateway"}[2m]))
      threshold: "100"                 # 1 replica per 100 req/s
      activationThreshold: "5"         # activeer vanuit nul boven 5 req/s
      ignoreNullValues: "true"         # geen error bij lege Prometheus-response

De query moet een enkele scalar of single-element vector teruggeven. Multi-element results veroorzaken een scaler-error. Test je PromQL eerst even in de Prometheus-UI.

Cron: gegarandeerde capaciteit tijdens geplande vensters

De Cron-scaler stelt een replicavloer in tijdens een tijdvenster. Combineert goed met eventscalers: Cron garandeert een warme baseline tijdens kantooruren, terwijl Kafka of Prometheus de burstschaling erboven afhandelt.

triggers:
  - type: cron
    metadata:
      timezone: Europe/Amsterdam
      start: "0 8 * * 1-5"            # werkdagen 08:00
      end: "0 18 * * 1-5"             # werkdagen 18:00
      desiredReplicas: "5"             # vloer van 5 tijdens kantooruren
  - type: kafka
    metadata:
      bootstrapServers: kafka:9092
      consumerGroup: order-consumer-group
      topic: orders
      lagThreshold: "50"

Tijdens kantooruren zorgt KEDA voor minimaal 5 replicas. Kafka-lag kan daar bovenuit duwen. Buiten het Cron-venster regeert de Kafka-scaler alleen, inclusief scale to zero wanneer het topic idle is.

Scale to zero

Dit is de feature die KEDA het duidelijkst onderscheidt van gewone HPA. Het mechanisme werkt in twee fasen.

Activatie (0 naar 1). Wanneer minReplicaCount: 0 en het huidige aantal replicas 0 is, pollt de KEDA-operator (niet de HPA) de eventsource elke pollingInterval seconden en roept IsActive() aan. Zodra de metricwaarde strikt boven activationThreshold uitkomt, zet de operator de Deployment op 1 replica. Vanaf daar neemt de HPA het over.

Deactivatie (1 naar 0). Wanneer alle triggers inactief melden (metric op of onder de drempel), start de operator de cooldownPeriod-countdown. Na cooldownPeriod seconden aanhoudende inactiviteit zet KEDA replicas op 0, rechtstreeks, want HPA kan niet onder minReplicas: 1 gaan.

Een belangrijke valkuil: activationThreshold heeft voorrang op threshold. Als je activationThreshold: 50 en threshold: 10 instelt, en er staan 40 berichten in de queue, dan blijft de scaler inactief. De workload activeert niet, ook al zou de HPA-berekening 4 replicas opleveren. Dit is bewust ontworpen. Het voorkomt cold-starts bij vluchtige pieken. Maar het bijt als je het instelt zonder deze voorrangsregel te kennen.

Cold-start-kosten. Pods die vanuit nul geactiveerd worden, moeten eerst opstarten voordat ze events kunnen verwerken. Voor workloads waar cold-start-latency onacceptabel is, zet je minReplicaCount: 1 om altijd een warme replica te behouden. Je verliest de kostenbesparing van echte nul, maar vermijdt de opstartvertraging.

Hoe KEDA en HPA samenwerken

KEDA vervangt HPA niet. Het maakt er een aan en voedt die.

Wanneer je een ScaledObject aanmaakt, creëert KEDA's operator een HPA genaamd keda-hpa-{scaledobject-naam}. De metrics-sectie van de HPA verwijst naar externe metrics die KEDA's metricsserver levert. Elke 15 seconden (de standaard --horizontal-pod-autoscaler-sync-period) bevraagt de HPA-controller de metricsserver en past de standaardformule toe: desiredReplicas = ceil(currentReplicas * (currentValue / targetValue)).

Bewerk de HPA die KEDA aanmaakt niet handmatig. Wijzigingen worden overschreven bij de volgende reconciliatie. Om HPA behaviour policies te configureren (stabilisatievensters, scale-down-snelheidslimieten), gebruik je het horizontalPodAutoscalerConfig.behavior-veld in het ScaledObject:

horizontalPodAutoscalerConfig:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 30                    # verwijder max 30% per periode
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0    # reageer direct

Een belangrijk verschil: cooldownPeriod regelt alleen de 1-naar-0-transitie. Voor N-naar-1 scale-down-gedrag is de scaleDown.stabilizationWindowSeconds van de HPA aan zet. Dit zijn gescheiden mechanismen met gescheiden timers.

Valkuilen in productie

Scale-down doodt pods tijdens verwerking. KEDA monitort queue depth. Als een bericht gedequeued maar nog niet geacknowledged is, lijkt de queue korter. KEDA kan dan scale-down triggeren en de pod beëindigen voordat die klaar is. Oplossingen: voor RabbitMQ, gebruik excludeUnacknowledged: "true" (alleen in HTTP-protocolmodus). Voor Kafka, gebruik excludePersistentLag: "true". Configureer altijd terminationGracePeriodSeconds en preStop lifecycle hooks. Voor langlopende batchtaken gebruik je beter ScaledJob in plaats van ScaledObject; dat maakt een Kubernetes Job per event met een eigen completionlifecycle.

KEDA-polling + HPA-polling = dubbele latency. KEDA pollt elke pollingInterval seconden. De HPA-controller evalueert elke 15 seconden. In het slechtste geval zit er pollingInterval + 15 seconden tussen het binnenkomen van een event en het starten van de schaling. Zet pollingInterval: 10 of lager voor latencygevoelige workloads, maar hou de belasting op je eventsource in de gaten.

Admission webhook blokkeert ScaledObject-creatie. In clusters met strikte network policies is de webhook-pod mogelijk niet bereikbaar. Als kubectl apply 30 seconden hangt en dan faalt met een webhook timeout, controleer dan of de keda-admission-webhooks pod draait en bereikbaar is vanuit de API-server.

Standaard RBAC is breed. De KEDA Helm-chart installeert permissieve RBAC standaard (get/list/watch/scale op alle resources). Voor productie beperk je dit met de rbac.scaledRefKinds-waarde in de Helm-chart om te limiteren welke resource-types KEDA mag targeten.

Wat je geleerd hebt

Je hebt KEDA geinstalleerd via de Helm-chart en de drie componenten geverifieerd (operator, metricsserver, admission webhooks). Je hebt een ScaledObject aangemaakt dat een Deployment koppelt aan een eventsource en gezien dat KEDA automatisch een HPA achter de schermen aanmaakt. Je hebt drie scalertypen geconfigureerd: Kafka voor consumer lag, Prometheus voor willekeurige PromQL-queries, en Cron voor geplande capaciteitsvloeren. En je begrijpt het tweefasen-scalingmodel: KEDA handelt de 0-naar-1-activatie en 1-naar-0-deactivatie af; de HPA doet alles daartussenin.

Verder lezen

  • Horizontal Pod Autoscaler deep-dive behandelt de HPA-laag die KEDA onder de motorkap aanmaakt: het schalingalgoritme, stabilisatievensters en het combineren van CPU met custom metrics
  • Kubernetes Jobs en CronJobs legt de Job-resource uit waarop KEDA's ScaledJob bouwt voor batchverwerking
  • Resource requests en limits behandelt het juist dimensioneren van de pods die KEDA schaalt, zodat je geen OOMKills of schedulingfouten krijgt wanneer replicas pieken

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.