Knative: Serverless on Your Own Cluster
Cloud Run solved a real problem: you give Google a container image and a concurrency target, and the platform handles everything else — routing, scale-to-zero, traffic splitting, cold starts, revisions. The contract is clean and the operational burden is near-zero. The catch is that you are running on Google’s infrastructure, speaking Google’s API surface, at Google’s pricing. If you are already running Kubernetes and want that same serverless developer experience without the managed-cloud lock-in, the answer the ecosystem converged on is Knative.
Knative graduated from CNCF incubation to full graduated status in September 2025, which is the community’s signal that the project is production-ready, well-governed, and here to stay. It ships two independent components — Serving and Eventing — that can be installed separately. Serving is the Cloud Run clone: it takes your container, adds request-based autoscaling with scale-to-zero, and gives you revision-based traffic routing. Eventing is a pub-sub layer built on the CloudEvents specification that wires arbitrary event sources to arbitrary sinks over a brokered or direct channel. Together they cover the two shapes that “serverless” usually means in practice: do this when HTTP arrives and do this when something happened.
This post goes deep on both components, explains the internal data path that makes cold-start buffering work, and then turns the honest eye on the comparison nobody writes plainly: when does Knative beat KEDA, when does KEDA beat Knative, and when should you just use Cloud Run and stop trying to self-host a serverless platform entirely.
Knative Serving: the object model
Knative Serving adds four CRDs to your cluster. You interact with three of them directly; the fourth is managed for you.
| Object | What it is |
|---|---|
Service (ksvc) |
The top-level object. Owns a configuration and a route. kubectl apply of a Service is the normal path. |
| Configuration | Describes the desired pod template. Each change creates a new Revision. |
| Revision | An immutable snapshot of a Configuration at a point in time. Revisions are never mutated; you roll forward. |
| Route | Maps traffic percentages to one or more Revisions. Blue-green and canary live here. |
The relationship in practice: you kn service create or apply a Service manifest, which creates a Configuration, which Knative uses to create a Revision, which the Route sends 100% of traffic to. When you update the Service (new image, new env var, new concurrency target), a fresh Revision is born and the Route is updated automatically. The old Revision keeps running until its traffic weight drops to zero.
|
|
Deploy it with kubectl apply -f service.yaml or the kn CLI:
kn service create hello \
--image gcr.io/knative-samples/helloworld-go \
--concurrency-target 10 \
--scale-init 0 \
--scale-max 50
The Knative Pod Autoscaler: how scale-to-zero actually works
The Horizontal Pod Autoscaler that ships with Kubernetes scales on CPU and memory by polling the metrics API. That is a lagging signal: CPU spikes after the work arrives. Knative’s KPA (Knative Pod Autoscaler) measures a different thing — concurrent in-flight requests (or optionally requests-per-second) — and it does so by putting a component called the Activator in the request path. This is the architectural decision that makes scale-to-zero possible.
┌──────────────────────────────┐
│ Knative Serving │
│ │
incoming request │ ┌───────────┐ scale=0 │
──────────────────▶ │ │ Activator │◀──────────── │──▶ (no pods yet)
│ │ (buffer) │ │
│ └─────┬─────┘ │
│ │ concurrency metrics │
│ ▼ │
│ ┌───────────────┐ │
│ │ Autoscaler │──scale up─▶│──▶ new pods
│ └───────────────┘ │
│ │ │
│ (pods warm, Activator │
│ drains buffered requests │
│ directly to pods) │
└──────────────────────────────┘
The sequence for a cold-start request:
- A request arrives. There are zero pods. The ingress gateway routes the request to the Activator instead.
- The Activator holds (buffers) the request and immediately reports the new concurrency to the Autoscaler.
- The Autoscaler sees demand > 0 on a zero-pod revision and issues a scale-up to Kubernetes.
- As pods become Ready, the Activator drains its buffered requests to the real pods and removes itself from the data path — traffic flows pod-to-pod for the rest of the burst.
- When all requests drain and the idle grace period (default: 30 seconds, tunable) expires, the Autoscaler scales back to zero.
The Autoscaler runs in two modes simultaneously — stable (a 60-second rolling window, smooth) and panic (a 6-second window, aggressive). When a burst drives the panic-window metric well above target, the Autoscaler switches to panic mode and scales hard and fast. When the burst passes and the stable window settles, it hands control back to stable mode. This dual-window design is why Knative handles flash traffic better than a simple time-averaged HPA would.
You can swap the KPA for the standard HPA per-service if your workload is better described by CPU than by concurrency, but you lose scale-to-zero (HPA’s floor is 1). Annotations do the switching:
|
|
Traffic splitting: revisions, blue-green, canary
Because every change creates a new immutable Revision, traffic splitting is a first-class Knative primitive rather than a Deployment trick. The kn CLI makes it ergonomic:
# Canary: send 5% of traffic to a new revision, hold 95% on the current
kn service update hello \
--image gcr.io/knative-samples/helloworld-go:v2 \
--traffic hello-00001=95,@latest=5
# After validation, promote fully
kn service update hello --traffic @latest=100
# Tag a revision for stable DNS access
kn service update hello --tag hello-00001=stable --tag @latest=canary
Named tags (stable, canary) get their own URL prefixes — https://stable-hello.default.example.com — so you can route smoke tests to a specific revision without touching the main ingress weight. The underlying Route object is a plain Kubernetes resource you can inspect or patch directly:
|
|
This is blue-green deployment with zero-downtime rollback: kn service update hello --traffic stable=100,canary=0 and the canary revision idles (or scales to zero) without being deleted. If you need it again later, you re-weight it.
Knative Eventing: brokers, triggers, and CloudEvents
Knative Eventing is a different beast from Serving — it is not about HTTP scaling, it is about building event-driven systems with a standardized event format. The key design decision is the CloudEvents specification (also a CNCF project): every event in the system is a structured HTTP POST with a standard set of headers (ce-type, ce-source, ce-specversion, ce-id) and an arbitrary body. Producers and consumers agree on the spec, not on a proprietary wire format, so you can swap brokers or sinks without rewriting your application.
┌──────────────┐ CloudEvent ┌─────────────────┐
│ Source │────────────────▶│ Broker │
│ (ApiServer, │ │ (in-memory or │
│ Kafka, etc) │ │ Kafka-backed) │
└──────────────┘ └────────┬────────┘
│ matches Trigger filter?
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Trigger A│ │Trigger B│ │Trigger C│
│type= │ │type= │ │(catch- │
│order.v1 │ │audit.v1 │ │ all) │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
▼ ▼ ▼
sink A sink B sink C
(ksvc, SVC, (ksvc, SVC, (ksvc, SVC,
Channel, URI) Channel, URI) Channel, URI)
Sources emit events. Knative ships several built-in Sources (ApiServerSource watches Kubernetes events, PingSource fires on a cron schedule, SinkBinding injects a K_SINK env var into any pod) and the ecosystem provides more (Kafka source, GitHub source, NATS source). A Source declaration points at a Sink:
|
|
Brokers receive events from multiple Sources and hold them for routing. The default in-memory broker is fine for development; for production you back it with Kafka or RabbitMQ, getting durability and replay:
|
|
Triggers subscribe to a Broker with an optional filter and deliver matching events to a Sink:
|
|
An unfiltered Trigger receives every event on the broker. Filters are attribute-exact-match today (not regex or CEL), which is simple and fast but occasionally requires fan-out if you need richer routing — though the RequestReply resource added to the Eventing roadmap in 2025 bridges synchronous request-response patterns on top of the async bus.
Knative vs KEDA vs plain HPA
This is where honest trade-off writing matters, because these three tools occupy overlapping territory and picking the wrong one costs you six months of operational friction.
| Knative KPA | KEDA | HPA (vanilla) | |
|---|---|---|---|
| Scale-to-zero | Yes (native) | Yes (via ScaledObject minReplicaCount: 0) |
No (floor = 1) |
| Scaling signal | Request concurrency or RPS | Any external metric (queue depth, Kafka lag, DB rows, custom) | CPU, memory, custom metrics API |
| Workload shape | HTTP/gRPC request-response | Event-driven, queue-draining, batch | Long-running CPU/memory-bound |
| Traffic routing | Built-in (revisions, weights, named tags) | None | None |
| Eventing layer | Yes (Serving + Eventing together) | No (pairs with your own broker) | No |
| Operational weight | High (Serving CRDs, networking layer, Eventing CRDs if used) | Low (one controller, one CRD family) | Near-zero (built in) |
| Cold-start buffering | Yes (Activator holds requests) | No (scale event fires; requests may 502 during scale-up) | No |
The clearest guidance: KEDA wins when your workload is queue-draining or batch — Kafka consumers, SQS workers, database polling loops. KEDA’s signal is the backlog depth, which is a direct and honest measure of how much work needs doing. It can scale based on dozens of external sources and it adds almost nothing to your cluster’s operational surface. Knative KPA, by contrast, is a better choice when the workload is request-response HTTP/gRPC and you want the activator to buffer cold-start requests rather than letting them 502. If you also want traffic splitting and revisions, Knative is the only game in town.
Note that KEDA and Knative are not mutually exclusive. Red Hat and the KEDA maintainers have published integration patterns where KEDA drives scaling of Knative Serving revisions based on external event pressure — you get KEDA’s broad signal library with Knative’s routing and activator mechanics. It is more to operate, but it is a legitimate architecture for services that are both HTTP-facing and queue-draining.
When NOT to adopt Knative
Knative’s biggest honest problem is its operational weight relative to what most teams actually need. The controller set, the networking layer requirements (Istio, Contour, or Kourier), the CRD surface, the Eventing broker infrastructure — it adds up. Consider skipping it when:
- Your team is small and your workload is simple. A service with predictable, non-zero traffic does not benefit from scale-to-zero. KEDA with a minimum replica count of 1 and a Kafka-depth scaler costs you one controller and a few lines of YAML.
- You are already paying for a managed serverless product. If AWS Lambda or Cloud Run already handles your function workload, self-hosting Knative to avoid a managed API is trading one vendor dependency for a large operational one. The serverless Lambda patterns post covers what managed serverless can already do; Knative makes sense when you genuinely need it on-prem or in a private cloud.
- You need GPU workloads or long-running batch. Knative’s request-driven model is hostile to workloads that are not short-to-medium HTTP handlers. A ten-minute ML inference job will fight every timeout default in the stack.
- Your team does not have Kubernetes experts. Knative debugging — cold-start failures, Activator queue saturation, networking layer misconfiguration — requires comfort with
kubectl, CRD inspection, and Kubernetes networking. The surface is large. - You need richer event filtering than exact-match attributes. Trigger filters are attribute-equality only as of 1.x. If you need CEL expressions or content-based routing, you will need additional tooling on top.
What Knative is excellent for: platform teams building internal developer platforms where developers kn service deploy an image and never touch Kubernetes directly; workloads with bursty traffic profiles that genuinely benefit from scale-to-zero; and environments where the eventing abstraction — standard CloudEvents, broker-backed durability, pluggable sources — simplifies the integration story across many services.
The verdict
Knative is the most complete self-hosted serverless platform available for Kubernetes, and its September 2025 CNCF graduation means it is no longer an experiment. The Knative Pod Autoscaler’s dual-window panic/stable design, the Activator’s cold-start buffering, revision-native traffic splitting, and a CloudEvents-based eventing layer that integrates with Kafka, RabbitMQ, and a growing catalog of sources are genuinely well-built and well-maintained. If you want Cloud Run’s developer experience on your own cluster, Knative is the honest answer.
But the honest answer is also that Knative carries real weight. For teams whose actual problem is “scale my Kafka consumers based on lag,” KEDA does that in a fraction of the CRD surface. For teams whose actual problem is “I want functions without managing servers,” a managed platform like Cloud Run or Lambda is almost certainly cheaper to operate than a self-hosted Knative installation. The value proposition is sharpest for platform teams that operate at a scale where internal developer experience is a first-class product, where workloads are genuinely HTTP-request-driven with variable traffic, and where multi-cloud or on-premises constraints make managed serverless unavailable. That is a real and significant population of teams. It is not everyone.
Sources
- Knative project page — CNCF
- Cloud Native Computing Foundation Announces Knative’s Graduation (October 2025)
- Knative Has Finally Graduated From the CNCF — The New Stack
- About autoscaling — Knative docs
- Knative Pod Autoscaler (KPA) specific configuration — Knative docs
- Configuring scale to zero — Knative docs
- Knative Eventing overview — Knative docs
- How to Implement Knative Eventing Broker and Trigger Patterns (2026)
- KEDA vs. Knative vs. Kubernetes HPA: Choosing the Right Auto-Scaling Strategy
- Application Scalability, Part 3: Knative and KEDA — Tomasz Urbaszek
- Knative serving scaling system design (GitHub)
Comments