5 · Scale to zero
Watch ACME sleep, then wake it — and understand what the first request actually paid for.
This is the chapter worth doing slowly. Everything else is deployment mechanics; this is the behaviour you came for.
Watch it sleep
Leave ACME alone and watch its pods:
kubectl get pods -l serving.knative.dev/service=acme -wGive it about 90 seconds. Knative waits out a 60-second stable window, then a 30-second grace period, before terminating the pod. People watching for "about a minute" conclude it is broken and stop — this is the single most common false alarm.
Then the list empties. No pod. No container compute for ACME.
Your nodes keep running and billing. Scale-to-zero reclaims the pod, not the machine. On a managed cluster the nodes only go away if a cluster autoscaler independently reclaims them. The saving is real but it is per-app, not per-cluster.
Wake it
In another terminal:
curl -sSf <the-URL-status-printed>/ > /dev/null && echo "awake"Watch the pod reappear in the first terminal.
What happened: Knative's activator held your request, started a pod, waited for the readiness probe, and forwarded the request. You got a slower response rather than an error.
It is not a guarantee. A cold wake can still return 503 if the revision or the readiness probe
does not come up in time — and the -sSf above will exit non-zero if that happens. Knowing that in
advance is better than discovering it under load.
What the first request actually cost
That request paid a cold start, and how long depends on your cluster, node sizing, whether the image is already cached on the node, and the app itself.
knext publishes measured distributions rather than a headline number, because on a contended cluster the spread is wide enough that a median misleads. One measured example: on a two-node cluster, cold starts came out bimodal — a cluster of samples around ~2.5s and another around ~10.5s, with a clean gap between them. A single "average" of those two groups would describe neither.
See Scale to zero for the numbers and how they were taken.
The knob, and when to turn it
If cold starts matter more than idle cost for ACME:
scaling: { minScale: 1, maxScale: 10 }One pod stays warm. No cold start, and you pay for it continuously. That is the whole trade, and it is one line either way.
There is a middle setting worth knowing about: keeping a warm floor only during hours you expect traffic. That is a scheduling question rather than a config flag — see Scale to zero.
Keep warm after traffic stops
There is a second middle setting, and a new app already has it:
scaling: {
minScale: 0,
maxScale: 10,
containerConcurrency: 100,
scaleDownDelay: "5m",
}scaleDownDelay keeps the last pod routable for a window after traffic stops, instead of
letting it go straight after the stable window. Inside that window there is nothing to wake:
measured responses land under 100 ms, against seconds for the same request from zero. It
also removes the worst case entirely — a request that arrives while a pod is being taken away can
otherwise sit in the routing layer for several seconds, and inside the delay window there is no
such handover in flight.
After the window, the pod goes away and the next request is a normal cold start. Scale-to-zero is still on; it just starts counting later.
The cost, plainly: one pod stays up for five minutes after every burst — along with the database connections that pod is holding. For an app that gets a request every few minutes, that is close to always-on. That is why it lives in your config file rather than in the platform:
- delete the
scaleDownDelayline to go back to scaling to zero immediately; - shorten it (
"30s") if you want the transition smoothed over but not much idle time; - lengthen it if bursty traffic matters more to you than an idle pod.
This one is enforced by your cluster. knext asks Knative to hold the pod; a Knative
installation that does not honour that request will simply scale down as it always did. kn-next doctor reports the version installed on your cluster. The knext operator has to know the setting
too: on a cluster whose operator predates it, kn-next deploy stops at its preflight check and
names the field — update the operator first. Note too that the delay keeps your app
warm — a database that sleeps when idle has its own wake time, and the first query after that can
still be slow on an otherwise warm pod. If your database runs on a scale-to-zero engine, its idle
window must be at least as long as this delay, or the warm pod answers instantly and then
waits for the database to wake anyway. That idle window is a platform setting your cluster
operator owns — it is not configured per app.
Check your understanding
- Why did the pod take ~90 seconds to disappear rather than immediately?
- If ACME gets one request per hour, how many pods are running most of the time?
- What would
minScale: 1change about the answer to the previous question — and about the bill?