KEDA: Event-Driven Pod Scaling
Scale Ziba's blog workers from real queue demand while keeping memory limits, node capacity, and credentials explicit.
The enterprise problem and today’s slice
Enterprise problem: Ziba’s blog was OOM-killed during a publish surge, yet keeping idle workers running all night wastes money; scaling on CPU alone cannot see queued posts and may add more memory-hungry pods than any node can hold. Whole-course context: The incoming evidence separates a container limit breach from a Pending pod and node pressure; today turns external work demand into a bounded replica decision. Today’s slice: We configure Kubernetes Event-driven Autoscaling (KEDA), its ScaledObject, authentication boundary, and generated HorizontalPodAutoscaler (HPA), while leaving nodes to a separate node autoscaler. End-of-day evidence: A queue-depth test records scale 0→N→0, the generated HPA, pod placements, node headroom, denied credentials, and an immutable run ID. Still unsolved: AWS-specific just-in-time node provisioning and the final platform decision are deferred.
The thesis is that KEDA does not add RAM or create virtual machines: it converts an external event signal into a desired pod count, after which Kubernetes still has to create and place those pods. Start with three boxes and expand each boundary only when its responsibility is clear.
Customer use cases
Customers experience backlog and failed publishing, not scaler internals, so the use cases must make both latency and safety visible. These two cases distinguish serving legitimate work from refusing an unauthorised or unsafe scaling path.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D66-UC-01 | Ziba publishing operator | Drain a burst of queued image-render jobs without permanently running idle workers | Queue age falls within the service objective, replicas rise within the approved maximum, and return to zero after cooldown | A failed event-source query leaves scaling bounded; alert and unchanged queue evidence identify the failure |
| D66-UC-02 | Platform security engineer | Let KEDA read queue depth without exposing the credential to the blog container | Only the KEDA identity or referenced Secret can authenticate; the workload processes jobs without receiving the scaler password | Invalid credentials produce an observed authentication error while the existing blog frontend remains healthy |
Actor-centred user stories
Scaling can look successful while silently violating cost or credential boundaries, so acceptance must include negative evidence. Each story ties the customer result to a measurable control-plane and runtime outcome.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D66-US-01 | D66-UC-01 | As a Ziba publishing operator, I want worker replicas to follow Redis list depth, so that publish bursts drain without paying for idle pods | A timestamped run shows list length, KEDA metric, generated HPA target, Deployment replicas, pod states, drain time, and scale-to-zero after cooldown |
| D66-US-02 | D66-UC-02 | As a platform security engineer, I want scaler authentication separated from the worker image, so that revoking queue-read authority does not require rebuilding the application | A valid Secret reference reads the metric, an invalid password is denied, the worker pod environment contains no scaler password, and both observations share an immutable test ID |
End-to-end product flows
An event-driven scale path crosses the queue, KEDA, Kubernetes controllers, and node placement, so skipping a hop makes diagnosis guesswork. The flow starts at Ziba’s publish action and ends with queue, pod, and denial evidence.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D66-FLOW-01 | D66-UC-01 | Happy | Ziba submits 100 image-render jobs | 1. Redis records list depth. 2. KEDA polls the scaler using TriggerAuthentication.3. KEDA activates from zero and exposes an external metric. 4. The generated HPA calculates desired replicas. 5. The Deployment and ReplicaSet create pods. 6. The scheduler binds each pod to a node with enough requested memory. 7. Workers drain jobs and KEDA scales down after cooldown. | Actor, queue, precondition, expected and observed depth, HPA target, pod UIDs, node UIDs, environment, timestamps, and immutable load-test ID |
| D66-FLOW-02 | D66-UC-02 | Denied | Security engineer rotates the queue password to a deliberately invalid test value | 1. KEDA reads the referenced Secret. 2. Redis rejects authentication. 3. KEDA records scaler error and does not invent a healthy metric. 4. Alert fires. 5. Existing frontend receives a positive-control request. 6. Valid authority is restored and backlog drain is observed. | KEDA error, denied actor and resource, Secret resource version, queue depth, bounded replica state, positive-control HTTP result, recovery timestamp, and test ID |
System design derived from the flows
KEDA, HPA, and the scheduler solve different layers, so treating them as rival autoscalers hides the physical bottleneck. KEDA produces demand metrics and activation, HPA changes replica intent, and the scheduler plus a node autoscaler determine whether those pods get machines.
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D66-UC-01 | Publish API and Redis work list | Redis, KEDA operator, KEDA metrics API server, Kubernetes HPA controller, Deployment controller, scheduler, kubelet | Redis list for pending work; Kubernetes API for ScaledObject, generated HPA, Deployment, Pods, and Nodes | Scaler query error, stale external metric, HPA condition, Pending pod, Insufficient memory, or worker OOM event |
| D66-UC-02 | Secret rotation and TriggerAuthentication change | Kubernetes API server, admission policy, Secret store, KEDA operator, Redis authentication, observability pipeline | Kubernetes Secret and TriggerAuthentication desired state plus KEDA events/logs | Secret reference denial, Redis authentication failure, policy rejection, or credential exposure scan failure |
The official KEDA architecture states that its operator manages activation and the HPA handles one-to-many replica scaling; its metrics API serves external metrics to Kubernetes. CPU and memory triggers are different because the HPA reads Metrics Server directly, and they cannot activate from zero when no pod exists to emit that metric (KEDA concepts).
Data model and ownership
Scaling becomes unauditable when event demand, credentials, desired replicas, and physical placement are collapsed into one record. The ownership model keeps the external queue, Kubernetes control plane, and node runtime independently inspectable and revocable.
Generated-application database: Not created in this slice — the blog’s existing queue plus Kubernetes desired state, Secrets, status, metrics, and evidence store are sufficient; KEDA does not require a new application database.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| QueueDemandObservation | Redis plus metrics evidence store, owned by publishing platform | queue name plus observed timestamp | Opaque load-test run ID | Ziba environment identifier | Depth and age must be captured from the same queue and time window | Created on poll, short-lived in Redis metrics, retained with incident/test evidence, expires by policy | D66-UC-01 |
| ScaledObjectRevision | Kubernetes API store, owned by KEDA operator and platform team | ScaledObject UID plus generation | Local reference to Deployment name and TriggerAuthentication name | Namespace | Exactly one scaler policy owns the target workload; min and max replicas remain within platform policy | Created on apply, revised declaratively, deleted before workload retirement; audit retained | D66-UC-01, D66-UC-02 |
| ScalerCredentialRevision | Kubernetes Secret store, owned by security platform | Secret UID plus resource version | Local reference from TriggerAuthentication | Namespace | Queue-read scope only; never mounted into the blog worker merely for KEDA | Rotated and revoked independently; old value destroyed while metadata and audit are retained | D66-UC-02 |
| ReplicaAndPlacementEvidence | Kubernetes API status/events plus evidence store, owned by controllers and SRE | Pod UID plus observation timestamp | Local Deployment UID and opaque node infrastructure ID | Namespace | Every pod must respect max replicas, requests, limits, affinity, taints, and node allocatable memory | Pod status ends on deletion; events and metrics expire; test evidence retained immutably | D66-UC-01, D66-UC-02 |
Build the smallest runnable policy
A scaler that lacks explicit bounds can turn a healthy queue spike into node exhaustion, so begin with one target, one trigger, and a hard maximum. This Redis example is runnable once Redis, KEDA, the ziba-render-worker Deployment, and the named Secret exist in namespace ziba.
apiVersion: v1
kind: Secret
metadata:
name: redis-scaler-auth
namespace: ziba
type: Opaque
stringData:
password: replace-with-a-secret-manager-delivered-value
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: redis-scaler-auth
namespace: ziba
spec:
secretTargetRef:
- parameter: password
name: redis-scaler-auth
key: password
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: ziba-render-worker
namespace: ziba
spec:
scaleTargetRef:
name: ziba-render-worker
pollingInterval: 15
cooldownPeriod: 120
minReplicaCount: 0
maxReplicaCount: 12
triggers:
- type: redis
metadata:
address: redis.ziba.svc.cluster.local:6379
listName: render-jobs
listLength: "10"
activationListLength: "0"
authenticationRef:
name: redis-scaler-auth
TriggerAuthentication separates authentication parameters from the ScaledObject and can use Secrets or cloud workload identity; a namespaced TriggerAuthentication must be in the same namespace as its ScaledObject (KEDA authentication). The Redis scaler divides queue work by the configured target to derive the external metric; use the exact scaler fields supported by the installed KEDA version (KEDA Redis Lists scaler).
| Code declaration | Kubernetes/software effect | VM or hardware effect |
|---|---|---|
minReplicaCount: 0 | KEDA may set the target Deployment to zero when the trigger is inactive | Worker cgroups disappear; nodes are unchanged until a node autoscaler later consolidates them |
maxReplicaCount: 12 | The generated HPA cannot request more than 12 replicas | Caps potential CPU/RAM consumers but does not reserve or create machine capacity |
listLength: "10" | Roughly one desired worker per ten queued items, subject to HPA behavior and bounds | No direct hardware action; desired pods may become Pending if nodes cannot fit their requests |
secretTargetRef | KEDA reads the credential from the Kubernetes Secret | No credential is needed in the worker process; the queue still runs on real servers and storage |
scaleTargetRef | KEDA/HPA write the Deployment scale subresource | ReplicaSet creates or deletes pod sandboxes and container cgroups on nodes |
Do not commit plaintext production credentials: deliver the Secret through the organisation’s secret-management path, encrypt it at rest, restrict RBAC, and prefer a supported workload identity where available.
Follow the signal into physical memory
A rising replica count is not proof of added service capacity because new pods can remain Pending or repeat the original OOM. Follow one queue observation through each controller and then down to a node’s CPU and DRAM.
KEDA complements rather than replaces HPA and node autoscaling. Kubernetes documentation describes node autoscaling as reacting to pod scheduling constraints and cloud/node configuration, while horizontal workload scaling changes replica count; combining them creates the chain load → pods → nodes (Kubernetes node autoscaling).
Observe, falsify, and recover
A dashboard that shows “replicas = 10” can conceal an authentication error, Pending pods, or OOM restarts, so collect evidence at every boundary. The test is complete only when queue age improves and the physical placement remains safe.
kubectl -n ziba apply -f keda-render-scaling.yaml
kubectl -n ziba get scaledobject,triggerauthentication,hpa
kubectl -n ziba describe scaledobject ziba-render-worker
kubectl -n ziba get pods -o wide --watch
kubectl -n ziba get events --sort-by=.lastTimestamp
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEMORY:.status.allocatable.memory
| Command | What it reads in software | What it proves about machines |
|---|---|---|
get scaledobject,...,hpa | Desired KEDA objects and generated HPA | Nothing yet about running processes or extra VMs |
describe scaledobject | Trigger readiness, conditions, target and events | A ready scaler can still request more pods than present nodes fit |
get pods -o wide | Replica phase and node binding | Running plus a node name means a kubelet created a pod sandbox on one machine |
get events | Authentication, HPA, scheduling, image, eviction and OOM chronology | Insufficient memory means node supply, not KEDA math, blocked placement |
get nodes ... allocatable | Kubelet-reported schedulable CPU and memory | Shows the machine budget after system reservations, not unused cluster-wide RAM |
Recovery order is: restore metric authority, verify the generated HPA, verify pod creation, verify scheduling, then verify per-container memory. Pausing or deleting a ScaledObject changes the scaling control loop; it does not remove queued work or repair a leaking worker.
Decision rules and trade-offs
Choosing KEDA for every workload creates needless external dependencies, while refusing it leaves event backlog invisible to CPU-based policies. Use the demand signal closest to the customer’s waiting work, then keep replica and machine controls separate.
| Need | Prefer | Avoid when |
|---|---|---|
| Scale HTTP or compute replicas from CPU/memory | Native HPA | The real bottleneck is an external backlog that CPU does not reveal early enough |
| Scale workers from queue length, lag, or another external event | KEDA ScaledObject plus HPA | The scaler cannot observe a trustworthy, bounded signal |
| Create one Job per unit or bounded batch | KEDA ScaledJob | A long-running Deployment with connection reuse and ordered processing is required |
| Right-size requests from historical usage | Vertical Pod Autoscaler (VPA) recommendation/application | Simultaneous automatic changes would conflict with an HPA using the same resource signal |
| Add machines for Pending pods | Node autoscaler | The pod is OOM-killed by its own unchanged cgroup limit |
KEDA supports scaling Deployments and StatefulSets through a ScaledObject and delegates one-to-many scaling to HPA (KEDA scaling deployments). The reusable rule is: KEDA answers “how many pods does external work justify?”; requests and limits answer “what does each pod need?”; a node autoscaler answers “which machines must exist?”
Key takeaways
Event-driven scaling fails when teams mistake a desired replica count for delivered physical capacity. Keep the signal, pod, and machine loops observable as separate systems.
- KEDA turns external events into activation and external metrics; HPA changes target replicas.
- KEDA can scale an eligible workload to zero, but CPU/memory-only triggers cannot wake it from zero without a live metric source.
TriggerAuthenticationseparates scaler authority from the application container and is independently revocable.- More replicas mean more container cgroups competing for per-node CPU and RAM; they do not add hardware.
- Pending pods require compatible node capacity, while OOM-killed pods require a per-container memory diagnosis.
Checklist
A KEDA rollout is incomplete until the queue, controllers, pods, nodes, and denial path agree. Use this list for Ziba’s production readiness review.
- [ ] The event signal measures customer work rather than a convenient proxy.
- [ ]
minReplicaCount,maxReplicaCount, polling, cooldown, and HPA behavior are bounded. - [ ] The scaler credential is least-privilege, not embedded in the worker image, and can be revoked independently.
- [ ] Requests and limits come from representative worker evidence, including the prior OOM.
- [ ] Nodes can fit the maximum practical burst or a node autoscaler is tested.
- [ ] A zero-to-N-to-zero test records queue age, HPA target, pod UIDs, node UIDs, timestamps, and run ID.
- [ ] Invalid authentication produces an alert and does not harm the frontend positive control.
- [ ] The rollback path preserves queued work and restores a known-good scaler revision.