gw-10 — Gateway API, CRDs, and the Operator Pattern
The JD asks for "Kubernetes internals (… CRDs, and Operator patterns)" and names "Kubernetes Gateway API, Istio Gateway." This lab closes the loop: the Gateway API is the standard, role-oriented, extensible Kubernetes way to express L7 routing (the successor to Ingress), and the operator pattern is how you implement a gateway's control plane as a Kubernetes controller — watch custom resources, reconcile them into data-plane config (the xDS of gw-08, or vendor config), and report status back.
This is where gw-08 (control plane) and gw-09 (K8s internals) combine.
An operator is a control plane that speaks Kubernetes. You will build a
controller-runtime operator that watches Gateway/HTTPRoute
resources and reconciles them into proxy configuration — the exact shape
of Envoy Gateway, Contour, and Istio.
1. What is it?
A Custom Resource Definition (CRD) extends the Kubernetes API with
your own object types. Once registered, a CRD's resources are
first-class: kubectl get, RBAC, watches, status — all work. A
controller/operator is a process that watches those resources
and drives the real world to match them, via the reconcile loop:
┌─────────── reconcile loop (level-triggered) ───────────┐
watch ▶│ observe desired state (the CRD spec) │
│ observe actual state (the data plane / external system)│
│ compute the diff │
│ take actions to converge actual → desired │
│ write status back to the CRD │
└──────────────────────▲─────────────────────────────────┘
│ re-trigger on any change or resync
The key property is level-triggered, declarative reconciliation: the controller doesn't react to events ("a route was added"); it repeatedly drives toward the desired state ("these routes should exist"), so it's self-healing and idempotent — it converges no matter how many events it missed or how it was restarted.
The Gateway API is a set of standard CRDs for L7 routing, split by role:
| Resource | Owner role | Meaning |
|---|---|---|
| GatewayClass | infra provider | "this kind of gateway is implemented by controller X" (like a StorageClass) |
| Gateway | cluster operator | "listen on these ports/protocols/hostnames with these certs" |
| HTTPRoute | app developer | "match these requests → send to these Services" (the gw-03 route table) |
| TCPRoute/GRPCRoute/TLSRoute | app developer | L4/gRPC/TLS variants |
| ReferenceGrant | namespace owner | cross-namespace permission to route to a Service |
It replaces Ingress (which was too limited and annotation-hell) with a typed, role-separated, extensible model — and it's portable across implementations (Envoy Gateway, Istio, Contour, NGINX, cloud LBs).
2. Why does it matter?
-
It's the named technology and the future of K8s ingress. "Gateway API, Istio Gateway" is in the JD. Ingress is legacy; the Gateway API is where the industry (and a migration-minded team) is heading — and migrations are the team's bread and butter (gw-12).
-
The operator pattern is how gateway control planes are built on K8s. Envoy Gateway, Contour, and Istio are operators: watch Gateway API CRDs → reconcile into Envoy xDS. Building one teaches you exactly how the gateway you'd operate is wired, and the JD's "Operator patterns" requirement is literally this.
-
Reconciliation is the safest way to manage fleet config. Because it's level-triggered and idempotent, an operator self-heals: drift, partial failures, and restarts all converge. This is the same desired-state discipline as gw-08's reconcile loop, formalized by Kubernetes.
-
Status reporting closes the operability loop. A good operator writes back
status(Accepted/Programmed/conditions) so users and dashboards know whether theirHTTPRouteactually took effect — the CRD analog of xDS ACK/NACK (gw-08) and a key observability surface (gw-11).
3. How does it work?
The reconcile loop (controller-runtime)
Reconcile(ctx, req): # req = namespaced name of a changed object
obj = get(req) # desired state from etcd
if not found: return # deleted; finalizers handle cleanup
actual = observe_external_state(obj) # what the data plane currently has
if actual != desired(obj):
program_data_plane(desired(obj)) # converge (e.g. push xDS — gw-08)
set_status(obj, Programmed=true) # report back
return (requeue if needed)
It runs whenever a watched object changes and on a periodic resync, so a missed event never leaves you wrong forever. It must be idempotent (reconciling the same state twice = no-op) and fast (work is queued; slow reconciles back up the queue).
How a request flows from CRD to data plane
app dev: kubectl apply HTTPRoute (prefix /v1/play -> Service playback)
│ watch
operator: reconcile -> compute routes/clusters/endpoints
│ (endpoints from EndpointSlices — gw-09)
▼
data plane config: Envoy xDS snapshot (gw-08) OR reload a proxy
▼
Envoy/proxy serves /v1/play -> playback pods
│
operator: write HTTPRoute.status.conditions = [Accepted, Programmed]
The operator is the bridge from the declarative K8s API (gw-09) to the imperative data-plane config (gw-08). That's the whole job.
CRD machinery you must know
- OpenAPI schema + validation — the CRD declares its spec's shape; the API server validates on write (plus webhooks for richer rules).
- Versioning + conversion webhooks —
v1alpha2→v1etc., with a conversion webhook so old and new clients coexist (vital for migrations). - Finalizers — block deletion until the controller cleans up external state (e.g. deprogram the data plane before the Gateway object disappears), preventing orphaned config.
- Owner references — child objects (a Deployment the operator creates for a Gateway) get garbage-collected with the parent.
statussubresource — separates user-writtenspecfrom controller-writtenstatus; conditions communicate Accepted / Programmed / errors.- Leader election — only one controller replica reconciles at a time (HA without double-writes).
Why level-triggered beats edge-triggered
Edge-triggered ("do X when event Y fires") loses correctness if you miss an event (controller was down, queue dropped it). Level-triggered ("make the world match the spec, repeatedly") is self-correcting: it re-derives the right state from scratch each time. This is the single most important operator concept and a frequent interview probe.
4. Core terminology
| Term | Definition |
|---|---|
| CRD | Custom Resource Definition: extends the K8s API with a new typed object. |
| CR | A custom resource (an instance of a CRD). |
| Controller / Operator | A process that watches resources and reconciles real state to match. |
| Reconcile loop | The level-triggered converge-to-desired-state function. |
| Level- vs edge-triggered | React to state (self-healing) vs react to events (lossy). |
| Idempotent | Reconciling the same state repeatedly causes no extra change. |
| Gateway API | Standard CRDs for L7 routing: GatewayClass/Gateway/HTTPRoute/… |
| GatewayClass / Gateway / HTTPRoute | Provider / operator / app-dev layers of the routing model. |
| Finalizer | A marker that blocks deletion until cleanup runs. |
| Owner reference | Links child objects for cascading GC. |
| status / conditions | Controller-written state reporting outcome (Accepted/Programmed). |
| controller-runtime / kubebuilder | The Go libraries/scaffolding for building operators. |
| Leader election | Ensures a single active controller replica. |
5. Mental models
-
An operator is a thermostat, not a light switch. A switch is edge-triggered (you flip it, once). A thermostat is level-triggered: it continuously compares actual temperature to the set point and acts to close the gap, forever. Miss a moment and it just corrects on the next read. That's why operators self-heal.
-
The Gateway API is org-chart-as-API. Ingress mashed everyone's concerns into one annotated object. Gateway API splits them by role: the infra team owns GatewayClass, the platform team owns Gateway (ports/certs), app teams own HTTPRoutes — each with its own RBAC. The schema encodes the org boundaries, which is why large orgs adopt it.
-
CRD + controller = "teach Kubernetes a new noun and a new verb." The CRD adds the noun (
Gateway); the controller adds the behavior (what it means for a Gateway to exist). Kubernetes becomes a general-purpose reconciliation engine for your domain. -
Status is the receipt. A user applies an HTTPRoute and walks away;
status.conditionsis how they learn it was Accepted and Programmed (or why not). Without it, "I applied it but is it live?" is unanswerable — the CRD version of an unmonitored xDS NACK (gw-08).
6. Common misconceptions
-
"Operators react to events." They reconcile to desired state. Events just trigger a reconcile; the reconcile re-derives everything from current state, so missing an event is survivable. Building an edge-triggered "on add do X, on delete do Y" controller is the classic beginner mistake that breaks on restart.
-
"Gateway API is just Ingress v2." It's a role-separated, extensible, portable, typed model with first-class L4/L7/TLS/gRPC routes, cross-namespace
ReferenceGrant, and status conditions. Ingress couldn't express most of this without vendor annotations. -
"CRDs make Kubernetes a database for my config." They're desired state, not just storage — the value is the controller continuously enforcing them. A CRD with no controller is inert YAML.
-
"Reconcile should be fast because it's called rarely." It's called on every watched change and on resync; under churn it's called a lot. It must be idempotent and quick; slow reconciles back up the work queue and delay convergence across all objects.
-
"One controller replica is a SPOF." Run multiple with leader election: one reconciles, the others stand by, failover is automatic. You get HA without two controllers fighting over the same resources.
7. Interview talking points
-
"Explain the operator/reconcile pattern." Level-triggered, declarative, idempotent reconciliation: observe desired (CR spec) + actual (external) state, converge, write status; re-run on change and resync. Contrast level- vs edge-triggered and explain why level-triggered self-heals. This is the answer that proves you understand Kubernetes, not just use it.
-
"What is the Gateway API and why does it exist?" Standard, role-oriented, extensible L7 routing CRDs (GatewayClass/Gateway/ HTTPRoute) replacing Ingress; portable across implementations (Envoy Gateway, Istio, Contour); typed L4/L7/gRPC/TLS routes with status conditions. The role separation maps to org structure and RBAC.
-
"How would you build a gateway control plane on Kubernetes?" An operator: watch Gateway API CRDs + EndpointSlices (gw-09) → reconcile into Envoy xDS snapshots (gw-08) → write status back. It's gw-08's reconcile loop, but the source of truth is the K8s API and the framework is controller-runtime (work queue, caching informers, leader election).
-
"Why finalizers and owner references?" Finalizers block deletion until you deprogram the data plane (no orphaned config / no traffic to a deleted backend); owner references cascade-delete the child objects an operator creates. Both prevent the "deleted the CR but the side effects linger" class of bugs.
-
"How do you safely evolve a CRD?" Versioned API (
v1alpha2→v1) with a conversion webhook so old/new clients coexist; additive, optional fields; a deprecation window. This is a migration (gw-12) at the API level — exactly the "leading large-scale migrations" the JD prizes. -
"How do users know their route is live?"
status.conditions(Accepted, Programmed) written by the controller — the CRD analog of xDS ACK/NACK. Surface it in dashboards/alerts (gw-11). An operator that doesn't report status is unobservable.
8. Connections to other labs
- gw-08 (Envoy/xDS) — the operator reconciles CRDs into the xDS snapshots gw-08 serves; together they are a complete K8s-native gateway control plane.
- gw-09 (K8s networking) — built on the same API machinery; EndpointSlices feed the operator's endpoint computation; finalizers interact with pod/Service lifecycle.
- gw-03 (API gateway) — an
HTTPRouteis the declarative form of gw-03's route table; the operator programs the data plane that runs the filter chain. - gw-07 (security) —
Gatewaylisteners reference TLS secrets; the operator wires certs (and can drive SDS). - gw-12 (migration) — migrating Ingress → Gateway API, and rolling out CRD changes safely, are textbook large-scale migrations; CRD versioning/conversion is migration tooling.
- db-16…20 (consensus) — Kubernetes is etcd-backed (Raft, db-17); the reconcile loop's "converge to a replicated desired state" is the consensus mindset at the application layer.