Knative
Mental model (what you “get” when you install it)
Knative is two largely independent subsystems that happen to work well together:
Knative Serving makes HTTP-addressable workloads behave “serverless”: revisions, traffic splitting, and autoscaling (including scale-to-zero). The native unit of deployment is a container behind an HTTP port.
Knative Eventing makes events routable inside the cluster: sources produce CloudEvents, Brokers buffer/ingest, Triggers filter/route to sinks (often Knative Services). Event delivery is fundamentally HTTP POST, with retry/backoff semantics.
![important] Knative is not “functions runtime” by default; it’s “Kubernetes-native control planes + conventions” for serverless-style ops. If you want “functions from source”, that’s Knative Functions (an extra developer toolchain that produces Knative Services).
Installing Knative (Operator vs YAML) and where Helm fits
You do not need an operator. There are two common install paths:
YAML-based install is explicit and diffable: you apply the CRDs and core components for Serving/Eventing, and you own upgrades step-by-step.
# Example: install Serving v1.20.0 via upstream release YAMLs
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.20.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.20.0/serving-core.yamlOperator-based install adds a reconciler that owns component lifecycle. You install the operator, then create KnativeServing / KnativeEventing custom resources to declare the desired version/config.
# Example: install Knative Operator (v1.19 docs show this pattern; adjust version as needed)
kubectl apply -f https://github.com/knative/operator/releases/download/knative-v1.19.5/operator.yamlWith the operator, you can pin versions:
apiVersion: operator.knative.dev/v1beta1
kind: KnativeServing
metadata:
name: knative-serving
namespace: knative-serving
spec:
version: "1.20"![warning]
If you manage Knative via the Operator, treat Knative-owned ConfigMaps as derived state. Manual edits are typically overwritten; configuration should flow through the Operator CR (spec.config, spec.workloads, etc.) rather than imperative kubectl edit configmap ….
Helm: upstream Knative does support Helm for some installs (notably the Operator chart in some doc sets), but in practice most production setups stick to either “Operator + CRs” or “raw YAMLs” to avoid Helm-vs-reconciler ownership ambiguity.
Serving CRDs: what matters operationally
Serving has several CRDs, but one is the primary interface:
- Service (
serving.knative.dev/v1, kindService) is the top-level object you apply. - Configuration is the “floating HEAD” of the rollout; updating it creates a new Revision.
- Revision is immutable and points at a specific image (tag resolves to a digest at creation time).
- Route programs ingress and handles traffic splitting between revisions.
The “shape” you should internalize is: Service.spec.template → stamps a new Revision; Service.spec.traffic → decides where requests go.
Key fields you tend to tune at staff-level (because they affect failure modes):
containerConcurrency: request parallelism per pod (drives queueing vs pod fan-out).timeoutSeconds,responseStartTimeoutSeconds,idleTimeoutSeconds: separate “slow start” vs “slow stream” vs “slow total”.- autoscaling annotations (min/max scale, metrics mode) determine cold-start frequency vs cost.
Deploying a “function” to Knative (container-first)
If you already have a container image (your “function” is just an HTTP handler), deploy as a Knative Service:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "0" # allow scale-to-zero
autoscaling.knative.dev/maxScale: "20"
spec:
containerConcurrency: 50
timeoutSeconds: 30
containers:
- image: ghcr.io/knative/helloworld-go:latest
ports:
- containerPort: 8080
env:
- name: TARGET
value: "World"Apply it:
kubectl apply -f hello.yaml
kubectl get ksvc helloTraffic splitting (canary) is expressed on the Service:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: checkout
spec:
template:
spec:
containers:
- image: ghcr.io/acme/checkout:2.0.0
traffic:
- latestRevision: true
percent: 10
- revisionName: checkout-00012
percent: 90The subtle point: you are not “routing to deployments”; you’re routing to immutable revisions (or “latest ready revision”).
Deploying from source with Knative Functions (the “FaaS UX” layer)
Knative Functions gives you a source-centric workflow that builds an OCI image and deploys it as a Knative Service.
# Create a function project from a template
func create -l go hello
cd hello
# Deploy (build + push image + create/update ksvc)
func deploy --registry ghcr.io/<your-org>This is operationally important: func is a developer toolchain; the runtime artifact is still a serving.knative.dev/v1 Service you can manage with kubectl/GitOps.
Eventing: Broker + Trigger (and what filtering really means)
Eventing’s core CRDs are:
Broker(event pool + ingress URL)Trigger(subscription + attribute filter + delivery target)
Create a broker:
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
namespace: defaultRoute all events to a Knative Service:
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: to-processor
namespace: default
spec:
broker: default
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: processorFilters are on CloudEvents attributes (not event payload). This impacts schema evolution: you can safely add fields in data, but routing decisions need stable attributes like type, source, or extensions.
Send a CloudEvent to the broker from inside the cluster:
kubectl run curl --image=docker.io/curlimages/curl --rm=true --restart=Never -ti -- \
-X POST -v \
-H "content-type: application/json" \
-H "ce-specversion: 1.0" \
-H "ce-source: my/curl/command" \
-H "ce-type: my.demo.event" \
-H "ce-id: 0815" \
-d '{"value":"Hello Knative"}' \
http://broker-ingress.knative-eventing.svc.cluster.local/default/defaultEvent delivery knobs you’ll actually use
Broker-level delivery lets you define retry/backoff and a dead-letter sink (DLQ). This is where “at-least-once + duplicates” becomes explicit.
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
namespace: default
spec:
delivery:
retry: 5
backoffPolicy: exponential
backoffDelay: "PT1S"
deadLetterSink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: dlq-handler![warning]
Scale-to-zero + retries can create “thundering herd” behavior: dispatcher retries while your sink is cold-starting, causing duplicates and bursty concurrency once it comes up. The fix is usually a combination of idempotency, sensible responseStartTimeoutSeconds, and delivery backoff.
Kafka in Knative Eventing (what “using Kafka” really means)
Knative Eventing can use different Broker implementations via the immutable broker-class annotation. Kafka-backed brokers let Kafka be the durability layer while still delivering to consumers over HTTP.
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
name: default
namespace: default
annotations:
eventing.knative.dev/broker.class: Kafka
# Optional: bring-your-own topic
# kafka.eventing.knative.dev/external.topic: my-topic
spec:
config:
apiVersion: v1
kind: ConfigMap
name: kafka-broker-config
namespace: knative-eventing