// blog · 04 Sept 2024 · draft
Cutting over-provisioning with KEDA: cron, external triggers, and a Selenium Grid queue
How I used KEDA cron scalers, external triggers, and Selenium Grid queue scalers to stop paying for idle replicas in production.
A lot of Kubernetes capacity waste isn’t dramatic — it’s workloads sized for their busiest hour and left there for the other twenty-three. During my time as a Performance, Scalability & Reliability Engineer at Blibli, I went after that idle capacity with KEDA, and the wins came from matching the scaler to the actual shape of the traffic.
Cron scalers for predictable rhythms
Some workloads have a schedule you can set a watch to: batch-ish services that ramp during business hours and flatline overnight. Horizontal Pod Autoscaler with CPU metrics reacts to load that already happened; a KEDA cron scaler moves replicas before the window opens and pulls them back after it closes. The scale-down hours are where the money is. Replicas you don’t run between midnight and 6am are pure savings.
The discipline that made this safe: cron scalers got floors, not ceilings-only. A minReplicaCount that reflects the true minimum viable capacity, so a mistyped schedule degrades to “slightly expensive” instead of “down.”
External triggers for demand you can’t schedule
For workloads driven by events rather than clocks, cron is the wrong tool. KEDA’s external scalers let you scale on a metric that actually represents demand — queue depth, lag, a Prometheus query — instead of hoping CPU correlates with work. The shift in mindset is scaling on backlog rather than utilization: if there’s a queue, its length is the actual signal.
The Selenium Grid case
The clearest win was our Selenium Grid for automated testing. Test sessions arrive in bursts — a pipeline triggers, a queue of sessions builds, and browser nodes either exist or engineers wait. Static sizing meant either idle browser nodes all night or queued test runs at peak.
KEDA’s Selenium Grid scaler scales browser node deployments on the actual session queue: queued sessions scale nodes up, drained queue scales them to zero-ish. Combined with the migration of the grid itself from VMs to Kubernetes, test infrastructure went from a fixed cost to a function of demand.
What stuck
Scale on the signal that represents demand, not the one that’s easiest to collect. And every scaler gets a floor and a ceiling — autoscaling without bounds is just a new failure mode.
Running KEDA in production also pushed me past being just a user. We stored scaler credentials in HashiCorp Vault, which at the time meant carrying an internal patch on top of upstream KEDA. That patch never got upstreamed, but debugging the setup surfaced a real bug: the operator panicked when a TriggerAuthentication authenticated to Vault with a root token, because root tokens come back without the renewable field KEDA tried to parse. I fixed the parsing and sent it upstream as kedacore/keda#5193. Using a tool in anger is the best bug-finder there is.