Gateway API
The Gateway API (gateway.networking.k8s.io) is the next-generation Kubernetes ingress infrastructure, developed by the Network SIG as the official successor to the legacy Ingress API.
Where Ingress is monolithic, HTTP-only, and annotation-driven, Gateway API is role-oriented, multi-protocol (L4–L7), and portable – advanced features like traffic splitting, header rewriting, and canary routing are first-class spec fields, not vendor-specific annotations.
- Gateway API (
gateway.networking.k8s.io) is the next-generation replacement for Ingress, developed by the Kubernetes Network SIG. - Why replace Ingress? The Ingress API is monolithic (listener config + routing rules in one object), strictly L7 HTTP/HTTPS, and forces heavy reliance on vendor annotations for any advanced feature. Furthermore, Kubernetes SIG Network and the Security Response Committee formally announced that all development and bug fixes for Ingress NGINX will permanently cease in March 2026. The Gateway API solves all these issues.
- Role-oriented: Infrastructure teams own
GatewayClass/Gateway; app teams ownRouteobjects. No cluster-admin privileges needed to deploy routes. - Requires CRD installation - unlike Ingress, Gateway API resources are not bundled in the Kubernetes core API. Install them first.
Gateway API vs. Ingress API
Section titled “Gateway API vs. Ingress API”
| Dimension | Ingress API | Gateway API |
|---|---|---|
| API hierarchy | IngressClass -> Ingress -> Service | GatewayClass -> Gateway -> Route -> Service |
| Resource structure | Monolithic: listener config + routing in one object | Decoupled: Gateway defines listeners; Route objects define routing |
| Protocol scope | L7 HTTP/HTTPS only | L4-L7: HTTPRoute, TLSRoute, GRPCRoute, TCPRoute, UDPRoute |
| Target binding | Direct Service reference | Gateway via parentRefs; Service via backendRefs |
| Cross-namespace | Single namespace only | Native: one Gateway can serve routes from many namespaces |
| Advanced features | Annotations only (vendor-specific, non-portable) | Standardized filters: header modify, URL rewrite, redirect, mirror |
API Governance
Section titled “API Governance”- Release channels:
Standard(stable, e.g.HTTPRouteatv1) vsExperimental(incubating, e.g.TLSRoute,TCPRoute,UDPRoute,GRPCRouteatv1alpha2). - Support levels:
- Core: Must be supported by every implementation - portable across providers.
- Extended: Optional but standardized if implemented - still portable.
- Implementation-Specific: Non-portable; exposed via
ExtensionReffilter.
- Explicit group/kind: All
parentRefsandbackendRefsspecifygroupandkind- this allows the GAMMA initiative to use aServiceas aparentReffor east/west mesh routing.
Control Plane vs. Data Plane
Section titled “Control Plane vs. Data Plane”Gateway API strictly separates the control plane (configuration and management logic) from the data plane (live traffic handling and routing execution).
- Control Plane: Formed by Gateway API controller Pods that observe API objects (
GatewayClass,Gateway,HTTPRoute) and dynamically provision, configure, and reconcile data plane infrastructure. - Data Plane: Handles live client request processing across L4 (TCP/UDP) and L7 (HTTP/HTTPS/gRPC). It is deployed in one of two major architectural models:
| Aspect | Hybrid Data Plane | Fully Managed Data Plane |
|---|---|---|
| Architecture | Couples an external L4 cloud load balancer (raw TCP forwarding) with in-cluster L7 proxy Pods (e.g., Envoy, NGINX). | Tightly integrated into the cloud provider’s networking infrastructure. The cloud platform provisions a fully managed L7 load balancer. |
| User-Visible Proxy Pods | Yes (managed via Kubernetes Deployments inside the cluster). | No (entirely managed outside the cluster). |
| Example Implementations | Cilium, Envoy Gateway, Istio, NGINX Gateway Fabric, Traefik. | Proprietary cloud platform add-ons (GKE, EKS, AKS). |
| Trade-offs | Provides cloud mobility and consistent behavior across multi-cloud/local environments. | Simpler maintenance, but introduces potential cloud platform lock-in. |
Role-Based Architecture
Section titled “Role-Based Architecture”Unlike Ingress (one object, one owner), Gateway API splits responsibilities across three organizational roles:
flowchart TD
subgraph ProviderRole["🏢 Platform Provider (Cluster Scope)"]
GC["<b>GatewayClass</b><br/><i>Defines controller implementation</i>"]
end
subgraph AdminRole["🛠️ Cluster Administrator (Cluster / Infra Scope)"]
GW["<b>Gateway</b><br/><i>Provisions load balancer & listeners</i>"]
end
subgraph DevRole["💻 Application Developer (Namespace Scope)"]
Route["<b>HTTPRoute</b><br/><i>Path matching, rules & filters</i>"]
Svc["<b>Service</b><br/><i>Backend Pod endpoints</i>"]
Route -->|"backendRefs"| Svc
end
GC -->|"spec.gatewayClassName"| GW
GW -->|"spec.parentRefs"| Route
GatewayClass- managed by platform/cloud provider; specifies the controller (e.g. Envoy Gateway, NGINX Gateway Fabric)Gateway- managed by cluster admins; configures listeners (ports, protocols, TLS certs)HTTPRoute/GRPCRoute- managed by app developers; defines path matching, weights, filters, and backendsReferenceGrant- managed by ops; grants permission for cross-namespace route-to-service references
Installing the Gateway API
Section titled “Installing the Gateway API”Step 1: Install CRDs
Section titled “Step 1: Install CRDs”# Check if already installedkubectl get crd gateways.gateway.networking.k8s.io
# Standard channel (HTTPRoute + GRPCRoute + ReferenceGrant only - stable)kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
# Verify installed CRDskubectl get crds | grep gateway.networking.k8s.io
# OR: Experimental channel (also adds TLSRoute, TCPRoute, UDPRoute)kubectl apply -k github.com/kubernetes-sigs/gateway-api/config/crd/experimentalStep 2: Install a Controller
Section titled “Step 2: Install a Controller”Kubernetes ships no built-in Gateway API controller. Popular options:
| Provider | Type | Notes |
|---|---|---|
| Envoy Gateway | Envoy-backed | CNCF project, reference implementation |
| Istio | Service mesh + gateway | Standalone mode available (no mesh required) |
| Contour | Envoy-backed | Open source |
| Cilium | eBPF-based | High-performance, no sidecars |
| Kong | API gateway | Full API management |
| NGINX Kubernetes Gateway | NGINX-backed | Official NGINX implementation |
| GKE | Cloud-managed | gcloud container clusters update --gateway-api=standard |
Envoy Gateway (Helm):
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.4.2 \ -n envoy-gateway-system --create-namespace
# Wait for controller to be readykubectl wait --timeout=5m -n envoy-gateway-system \ deployment/envoy-gateway --for=condition=AvailableNGINX Gateway Fabric (Helm):
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \ --version 2.3.0 --namespace nginx-gateway --create-namespaceCloud Provider for KIND (Local Testing): Runs as a process outside the cluster, authenticating with the local KIND API server.
# macOSbrew install cloud-provider-kind# Launch (requires root)sudo cloud-provider-kind --gateway-channel="standard"Istio (minimal - gateway only, no mesh):
curl -sL https://istio.io/downloadIstioctl | sh -istioctl install -y --set profile=minimalkubectl get pods -n istio-systemGKE (managed):
gcloud container clusters update <cluster-name> --gateway-api=standard --region=<region>End-to-End Quick Start (Envoy Gateway)
Section titled “End-to-End Quick Start (Envoy Gateway)”A minimal walkthrough: CRDs → controller → GatewayClass → Gateway → HTTPRoute → test.
# 1. Install CRDs (standard channel)kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
# 2. Install Envoy Gateway controllerhelm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.4.2 \ -n envoy-gateway-system --create-namespacekubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available# 3. GatewayClass - binds to Envoy controllerapiVersion: gateway.networking.k8s.io/v1kind: GatewayClassmetadata: name: envoyspec: controllerName: gateway.envoyproxy.io/gatewayclass-controller---# 4. Gateway - listens on port 80apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: hello-gatewayspec: gatewayClassName: envoy listeners: - name: http protocol: HTTP port: 80---# 5. HTTPRoute - routes to backend serviceapiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: hello-routespec: parentRefs: - name: hello-gateway hostnames: - "hello.example.com" rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: web port: 3000# 6. Verifykubectl get gatewayclasses # ACCEPTED = Truekubectl get gateways # PROGRAMMED = Truekubectl get httproutes
# 7. Test (no cloud LB - use port-forward)export ENVOY_SVC=$(kubectl get svc -n envoy-gateway-system \ --selector=gateway.envoyproxy.io/owning-gateway-name=hello-gateway \ -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward -n envoy-gateway-system svc/${ENVOY_SVC} 8080:80 &curl -H "Host: hello.example.com" http://localhost:8080GatewayClass and Gateway
Section titled “GatewayClass and Gateway”GatewayClass
Section titled “GatewayClass”GatewayClass is cluster-scoped and identifies which controller manages gateways of that class - analogous to IngressClass:
apiVersion: gateway.networking.k8s.io/v1kind: GatewayClassmetadata: name: istiospec: controllerName: istio.io/gateway-controller # unique controller identifier description: The default Istio GatewayClass # parametersRef: links to a CRD with implementation-specific configkubectl get gatewayclassesGateway
Section titled “Gateway”A Gateway declares one or more network entry points (listeners) where the proxy accepts connections:
apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: main-gatewayspec: gatewayClassName: istio # links to GatewayClass listeners: - name: http port: 80 protocol: HTTP hostname: '*.example.com' # optional hostname filter on listener allowedRoutes: namespaces: from: All # All | Same (default) | Selector - name: https port: 443 protocol: HTTPS tls: mode: Terminate # Terminate | Passthrough certificateRefs: - kind: Secret name: tls-example-com
Key listener fields:
protocol:HTTP,HTTPS,TLS,TCP, orUDP.hostname: Restricts accepted connections to matching hostnames.allowedRoutes.namespaces.from:Same(default) |All|Selector- controls which namespaces can attach routes.
Automated Infrastructure Provisioning & Load Balancer Mechanics
Section titled “Automated Infrastructure Provisioning & Load Balancer Mechanics”When you create a Gateway, the controller automatically provisions infrastructure in a multi-stage workflow:
- Gateway Claiming: The controller detects the Gateway matching its
GatewayClass. - Proxy Deployment & Service: The controller deploys in-cluster L7 data-plane proxy Pods (in the same namespace as the Gateway) and creates a
LoadBalancerService. - NodePort Allocation: The Kubernetes Service controller assigns a unique
NodePortfor each listener port. kube-proxyRouting:kube-proxyprograms local node iptables/IPVS rules to intercept NodePort traffic and forward it to the proxy Pods.- Cloud Load Balancer Provisioning: The Cloud Controller Manager (CCM) provisions an external L4 cloud load balancer pointing to the node ports.
- Public IP Attachment: The public Virtual IP (VIP) is assigned to the
LoadBalancerService and the Gateway’sstatus.addressesfield is populated.
kubectl get gtw # list gateways (gtw shorthand)kubectl get gtw main-gateway -o yaml # inspect status + assigned IPGateway Status and Conditions
Section titled “Gateway Status and Conditions”kubectl get gtw main-gateway# NAME CLASS ADDRESS PROGRAMMED AGE# main-gateway istio 172.18.255.200 True 30s
kubectl get gtw main-gateway -o yaml # full status.addresses + conditionsCondition metadata fields (present on every condition entry):
type: The specific condition type (e.g.,Accepted).status:"True","False", or"Unknown".reason: Machine-readable reason for the last transition.message: Human-readable diagnostic detail.lastTransitionTime: When the status last changed.observedGeneration: Themetadata.generationthis condition reflects. If it doesn’t matchmetadata.generation, the status is stale.
Top-level Gateway conditions:
| Condition | Meaning |
|---|---|
Accepted | Controller validated and accepted the spec |
Programmed | Proxy infrastructure (Service + Pod) is provisioned and configured |
Ready | Reserved for future iterations |
Scheduled | Deprecated |
Listener conditions (status.listeners[].conditions) - each listener is reported separately:
| Condition | Status | Programmatic Reason | When it fires |
|---|---|---|---|
Accepted | True / False / Unknown | Accepted / PortUnavailable / UnsupportedProtocol / UnsupportedAddress / Pending | Port in use, unsupported protocol, or address unbindable |
Conflicted | True / False | HostnameConflict / ProtocolConflict / NoConflicts | Two listeners share a port with conflicting hostnames or protocols - neither is configured |
Programmed | True / False / Unknown | Programmed / Invalid / Pending | Listener proxy config is generated and ready |
Ready | True / False / Unknown | Ready / Invalid / Pending | Listener is fully active and serving traffic |
ResolvedRefs | True / False | ResolvedRefs / InvalidCertificateRef / InvalidRouteKinds / RefNotPermitted | TLS Secret missing or invalid, unsupported route kinds, cross-namespace cert reference denied |
Listener metadata fields:
attachedRoutes: Count ofRouteobjects bound to this listener.0= all requests return 404.supportedKinds: The route types (group + kind) this listener accepts (e.g.,HTTPRoute).
Triage strategy: check type -> status -> reason -> message.
Route Status (status.parents)
Section titled “Route Status (status.parents)”A single Route can bind to multiple Gateways - status is reported per parent in status.parents[], not as a single top-level condition:
kubectl get httproute web-route -o yaml # inspect per-parent conditionsEach entry in status.parents contains:
parentRef: Which Gateway (group/kind/name/namespace) this status applies to.controllerName: The controller (fromGatewayClass) that wrote this entry.
Route conditions per parent:
| Condition | Status | Programmatic Reason | When it fires |
|---|---|---|---|
Accepted | True / False / Unknown | Accepted / NotAllowedByListeners / NoMatchingListenerHostname / NoMatchingParent / UnsupportedValue / Pending | Route’s namespace not allowed (allowedRoutes), no listener hostname matches, invalid parent reference |
ResolvedRefs | True / False | ResolvedRefs / RefNotPermitted / InvalidKind / BackendNotFound | Cross-namespace backendRef without ReferenceGrant, unknown group/kind, Service does not exist |
HTTPRoute
Section titled “HTTPRoute”HTTPRoute is the primary Gateway API route resource (gateway.networking.k8s.io/v1, Standard channel - the only fully stable route kind).
Basic Routing
Section titled “Basic Routing”apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: web-routespec: parentRefs: - name: main-gateway # binds to the Gateway (same namespace) hostnames: - app.example.com # matched against HTTP Host header rules: - backendRefs: - name: web # target Service port: 80Default field expansion (controller fills in omitted values):
parentRefs[].group->gateway.networking.k8s.io,kind->GatewaybackendRefs[].group->""(core API),kind->Service,weight->1rules[].matches-> catch-allPathPrefix: /
kubectl apply -f httproute.yamlkubectl get httprouteskubectl get httproute web-route -o yaml # inspect full expanded spec + statusTest (same pattern as Ingress):
curl --resolve app.example.com:80:<GATEWAY_IP> http://app.example.comTraffic Splitting (Canary)
Section titled “Traffic Splitting (Canary)”
Weight-based splitting across multiple backends natively handles canary releases without requiring a full service mesh. Each backend needs a dedicated Service (shared selectors break weight control).
The deployment relies on decoupled layers:
- Backend Applications: Deploy two distinct versions, each fronted by its own
ClusterIPService. - Ingress Gateway: Provide the shared listener (e.g., port 80).
- HTTPRoute: Enforce the splitting ratios.
spec: parentRefs: - name: main-gateway hostnames: - app.example.com rules: - backendRefs: - name: web-stable # existing release port: 80 weight: 80 # 80% of traffic - name: web-canary # new release port: 80 weight: 20 # 20% of trafficTraffic share = weight / sum(all weights). Omitting weight splits evenly. The L7 proxy pod evaluates the weights and distributes requests to the underlying ClusterIP endpoints proportionally.
Request Matching
Section titled “Request Matching”
spec.rules.matches - AND within a match entry, OR across entries:
Method routing:
rules:- matches: - method: POST # only POST requests backendRefs: - name: web-canary port: 80- backendRefs: # catch-all for all other methods - name: web-stable port: 80Header routing:
matches:- headers: - type: Exact # Exact | RegularExpression name: Release value: canaryPath routing:
matches:- path: type: PathPrefix # PathPrefix | Exact | RegularExpression value: /api/v2Query parameter routing:
matches:- queryParams: - type: Exact name: release value: canary # matches ?release=canaryCompound match (all conditions must be met - AND logic):
matches:- path: type: PathPrefix value: /api headers: - type: Exact name: Release value: canary - type: RegularExpression name: Cookie value: .*beta.*Filters (Traffic Transformation)
Section titled “Filters (Traffic Transformation)”
Declared in spec.rules.filters. Applied before forwarding to backendRefs:
Request header modification:
filters:- type: RequestHeaderModifier requestHeaderModifier: add: - name: X-Added-By-Gateway value: "true" set: - name: X-Environment value: production remove: - X-Internal-DebugResponse header modification:
filters:- type: ResponseHeaderModifier responseHeaderModifier: set: - name: Strict-Transport-Security value: max-age=31536000; includeSubDomainsURL rewrite:
# Replace full path: /foo or /foo/bar -> /new/pathfilters:- type: URLRewrite urlRewrite: path: type: ReplaceFullPath replaceFullPath: /new/path
# Replace prefix only: /foo/bar -> /new/path/barfilters:- type: URLRewrite urlRewrite: path: type: ReplacePrefixMatch replacePrefixMatch: /new/pathHTTP to HTTPS redirect (no backendRefs needed - gateway terminates the request):
filters:- type: RequestRedirect requestRedirect: scheme: https port: 443 statusCode: 301Traffic mirroring (shadow testing):
# Primary response goes to client; mirror response is discardedfilters:- type: RequestMirror requestMirror: backendRef: name: web-canary port: 80backendRefs:- name: web-stable port: 80Vendor extension:
filters:- type: ExtensionRef extensionRef: group: networking.example.com kind: SomeCustomFilter name: my-filter # must be in same namespace as HTTPRouteTLS in Gateway API
Section titled “TLS in Gateway API”TLS Termination (Gateway-level)
Section titled “TLS Termination (Gateway-level)”The gateway holds the cert, decrypts traffic, forwards plain HTTP to pods:
# Gateway with TLS listener (Secret must be in same namespace as Gateway)spec: listeners: - name: https port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - kind: Secret name: tls-example-com options: # provider-specific: cipher suites, min TLS version, etc. example.com/min-version: TLSv1.3Attach an HTTPRoute to the HTTPS listener the same way as HTTP - the gateway handles decryption transparently. A single HTTPRoute can serve both HTTP (port 80) and HTTPS (port 443) listeners.
curl --resolve app.example.com:443:<GATEWAY_IP> https://app.example.com -kTLS Passthrough (Pod-level)
Section titled “TLS Passthrough (Pod-level)”Encrypted TLS stream passes through the gateway unmodified; pods terminate TLS themselves (e.g., via sidecar proxy):
# Gateway: protocol TLS + mode Passthrough, no certificateRefsspec: listeners: - name: tls port: 443 protocol: TLS # not HTTPS - gateway is L4 only tls: mode: PassthroughUse TLSRoute (experimental v1alpha2) - routing is based on SNI only, no L7 inspection possible:
apiVersion: gateway.networking.k8s.io/v1alpha2kind: TLSRoutemetadata: name: web-tlsspec: parentRefs: - name: main-gateway hostnames: - app.example.com # matched via SNI extension in ClientHello rules: - backendRefs: - name: web-stable port: 443 # pod's TLS portSNI routing mechanic: The TLS ClientHello packet contains an unencrypted SNI extension. The gateway reads it without decrypting anything, matches the TLSRoute hostname, and forwards the raw encrypted TCP stream to the pod.
| Feature | HTTPRoute (TLS Terminate) | TLSRoute (TLS Passthrough) |
|---|---|---|
| Decryption at | Gateway | Pod |
| Path/header routing | Full support | Not possible (payload encrypted) |
| Traffic splitting | Supported | Supported (by connection, not request) |
| Cert management | Centralized in Gateway | Per-pod |
Backend TLS (Re-Encrypt) - BackendTLSPolicy
Section titled “Backend TLS (Re-Encrypt) - BackendTLSPolicy”The third TLS mode: the gateway terminates client TLS and re-establishes a new TLS connection to the backend. Required when backend pods enforce HTTPS or for end-to-end encryption compliance.
Client (HTTPS) -> Gateway (terminates) -> (new TLS) -> Backend Pod (HTTPS)BackendTLSPolicy is a Standard-channel resource (GA in v1.4.0) that configures the gateway-to-backend TLS connection:
apiVersion: gateway.networking.k8s.io/v1alpha3kind: BackendTLSPolicymetadata: name: backend-tlsspec: targetRefs: - group: "" kind: Service name: secure-backend # the backend Service validation: caCertificateRefs: - kind: ConfigMap name: backend-ca-cert # CA that signed the backend's cert hostname: secure-backend.example.com # expected SNI / cert hostname| TLS Mode | Resource | Who holds cert | Backend traffic |
|---|---|---|---|
| Terminate | Gateway spec.listeners.tls | Gateway (Secret) | Plain HTTP |
| Passthrough | TLSRoute | Pod | Encrypted (unchanged) |
| Re-encrypt | BackendTLSPolicy | Both | New TLS connection |
Session Persistence (BackendTrafficPolicy)
Section titled “Session Persistence (BackendTrafficPolicy)”Unlike Ingress which relied on non-standard annotations (e.g. nginx.ingress.kubernetes.io/affinity), Gateway API standardizes sticky sessions using the BackendTrafficPolicy resource.
This policy attaches directly to the backend Service, ensuring that any route forwarding to that Service respects the persistence rules.
apiVersion: gateway.networking.k8s.io/v1alpha2kind: BackendTrafficPolicymetadata: name: web-sticky-sessionspec: targetRefs: - group: "" kind: Service name: web # the backend Service to apply sticky sessions to sessionPersistence: sessionName: session-id absoluteTimeout: 24h type: CookieL4 Route Types (Standard)
Section titled “L4 Route Types (Standard)”TCPRoute
Section titled “TCPRoute”Expose any TCP-based service (databases, message queues, custom daemons):
# Gateway listener for TCPspec: listeners: - name: tcp-db port: 5432 protocol: TCP
---apiVersion: gateway.networking.k8s.io/v1kind: TCPRoutemetadata: name: db-routespec: parentRefs: - name: main-gateway rules: - backendRefs: - name: postgres port: 5432- No hostname matching, no path/header routing (L4 is payload-agnostic).
- Weighted splitting: Multiple
backendRefswithweightfields splits TCP connections proportionally. - Test:
nc <GATEWAY_IP> 5432
UDPRoute
Section titled “UDPRoute”Expose UDP-based workloads (DNS, media streaming, game servers):
spec: listeners: - name: dns port: 53 protocol: UDP
---apiVersion: gateway.networking.k8s.io/v1kind: UDPRoutemetadata: name: dns-routespec: parentRefs: - name: main-gateway rules: - backendRefs: - name: dns-server port: 53- Provider support varies - if
statussection is empty after apply, no active controller reconciled it. - Test:
nc --udp <GATEWAY_IP> 53
GRPCRoute
Section titled “GRPCRoute”
Native gRPC routing with service/method-level matching:
# Gateway: HTTP listener (gRPC runs over HTTP/2)spec: listeners: - name: grpc port: 9000 protocol: HTTP hostname: 'api.example.com'
---apiVersion: gateway.networking.k8s.io/v1kind: GRPCRoutemetadata: name: echo-routespec: parentRefs: - name: main-gateway hostnames: - api.example.com rules: - matches: - method: service: myapp.Echo # fully qualified gRPC service name method: Ping # specific RPC method type: Exact # Exact | RegularExpression backendRefs: - name: echo-service port: 9000 # Header-based matching (gRPC metadata headers) - matches: - headers: - type: Exact name: x-tenant-id value: premium backendRefs: - name: echo-service-premium port: 9000- Supports
RequestHeaderModifier,ResponseHeaderModifier,RequestMirror,ExtensionReffilters. - Test:
grpcurl -proto schema.proto --plaintext api.example.com:9000 myapp.Echo.Ping
Cross-Namespace Routing
Section titled “Cross-Namespace Routing”
Sharing a Gateway Across Namespaces
Section titled “Sharing a Gateway Across Namespaces”By default, a Gateway only accepts routes from its own namespace. Configure allowedRoutes.namespaces on individual listeners:
spec: listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: All # any namespace can attach routes
- name: tcp port: 200 protocol: TCP allowedRoutes: namespaces: from: Selector # only namespaces matching the label selector: matchLabels: team: backend # label the namespace to grant accessRoutes in another namespace reference the Gateway’s namespace in parentRefs:
# HTTPRoute in namespace: app-teamspec: parentRefs: - name: main-gateway namespace: gateway-ns # explicit cross-namespace reference hostnames: - app.example.comGrant namespace access via label:
kubectl label ns app-team team=backendIf the namespace doesn’t match, the route status shows Accepted: False with reason NotAllowedByListeners.
End-to-End Multi-Tenant Scenario:
- Infrastructure Admins (
infra-admins) deploy a shared Gateway inns-infrathat restricts route attachments to namespaces carrying thegtw-prod: "true"label viaallowedRoutes.namespaces.from: Selector. - Cluster Operators (
cluster-ops) create tenant namespaces (e.g.,ns-shieldandns-hydra) and apply thegtw-prod: "true"label to them. - Application Developers (
app-devs) insidens-shielddeploy theirHTTPRoutereferencing the Gateway inns-infra. Because their namespace bears the authorized label, the Gateway accepts the attachment, securely separating infrastructure control from application routing.
Cross-Namespace Backend References (ReferenceGrant)
Section titled “Cross-Namespace Backend References (ReferenceGrant)”A route can target a Service in a different namespace, but requires explicit permission from the target namespace’s admin:
# Applied in namespace: service-ns (the namespace that OWNS the Service)apiVersion: gateway.networking.k8s.io/v1beta1kind: ReferenceGrantmetadata: name: allow-httproutes-from-app-team namespace: service-ns # must live in the target namespacespec: from: - group: gateway.networking.k8s.io kind: HTTPRoute namespace: app-team # source namespace allowed to reference to: - group: "" kind: Service name: some-service # omit name to allow all Services in this nsRoute backendRef with explicit namespace:
rules:- backendRefs: - name: some-service namespace: service-ns # cross-namespace target port: 80Without a ReferenceGrant, the route status shows ResolvedRefs: False with reason RefNotPermitted.
ListenerSet - Delegated Listener Management (v1.5+)
Section titled “ListenerSet - Delegated Listener Management (v1.5+)”ListenerSet (Standard channel, GA in v1.5) decouples listener configuration from the core Gateway resource - app teams can add their own listeners to a shared Gateway without needing cluster-admin rights.
Without ListenerSet: an app team needing a new port/hostname must ask a cluster admin to edit the Gateway spec.
With ListenerSet: teams create ListenerSet objects in their own namespaces; the Gateway owner permits attachment.
# Cluster admin: Gateway opts into receiving ListenerSetsapiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: shared-gateway namespace: infraspec: gatewayClassName: envoy listeners: - name: default-https port: 443 protocol: HTTPS allowedRoutes: namespaces: from: All allowedListeners: # permit ListenerSet attachment from: All---# App team: add their own listener (in team namespace)apiVersion: gateway.networking.k8s.io/v1alpha1kind: ListenerSetmetadata: name: team-listeners namespace: app-teamspec: parentRef: name: shared-gateway namespace: infra listeners: - name: team-app port: 8443 protocol: HTTPS tls: mode: Terminate certificateRefs: - kind: Secret name: team-tlsAn HTTPRoute then attaches to the ListenerSet instead of the Gateway:
spec: parentRefs: - kind: ListenerSet # attach to ListenerSet, not Gateway name: team-listeners namespace: app-team sectionName: team-app- Role separation preserved: infra team owns Gateway, app teams own ListenerSets and Routes.
- No privilege escalation:
ReferenceGrantstill governs cross-namespace backend references.
Service Mesh Integration (GAMMA)
Section titled “Service Mesh Integration (GAMMA)”The Gateway API Mesh Management and Administration (GAMMA) initiative extends Gateway API routes for east/west (service-to-service) traffic, not just north/south (external-to-cluster).
The key insight: Replace Gateway in parentRefs with a Service. This signals the mesh data plane to intercept traffic targeting that Service and apply the route rules:
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: api-v2-route namespace: app-teamspec: parentRefs: - group: "" # core API group kind: Service # not Gateway - this is the key difference name: api-service port: 80 rules: - matches: - path: type: PathPrefix value: /v2 # route /v2/* to the new version backendRefs: - name: api-service-v2 port: 80How it works:
- A client pod sends a request to
api-service:80. - The mesh sidecar (or ambient node proxy) intercepts the outbound connection.
- It matches the
HTTPRoutebound toapi-serviceasparentRef. - It applies filters and routes to
api-service-v2.
All HTTPRoute features work in mesh context: traffic splitting, header modification, URL rewrite, mirroring - enabling canary deployments and header-based routing entirely within the cluster network.
Migrating from Ingress to Gateway API
Section titled “Migrating from Ingress to Gateway API”The Gateway API is the official successor to Ingress. Major controller projects (NGINX, Istio, Envoy Gateway) now offer full Gateway API implementations alongside Ingress support.
Equivalence Table
Section titled “Equivalence Table”| Legacy Ingress | Gateway API Equivalent | Managed by |
|---|---|---|
| Ingress controller | GatewayClass | Platform provider |
| Ingress resource (LB config + ports) | Gateway | Cluster administrator |
| Ingress resource (rules + paths) | HTTPRoute / GRPCRoute | Application developer |
| Vendor annotations (canary, rewrite) | Core HTTPRoute spec.rules.filters | Application developer |
| Namespace isolation (none) | ReferenceGrant | Cluster administrator |
Phased Migration Strategy
Section titled “Phased Migration Strategy”- Side-by-side: Deploy Gateway API CRDs and controller alongside the existing Ingress controller. Most modern controllers support both simultaneously - no downtime required.
- Incremental: Start with non-critical workloads. Define
HTTPRoutemanifests, validate traffic, then shift production services one at a time. - Decommission: Once all routing is verified on Gateway API, delete the legacy Ingress objects and eventually the Ingress controller.
Automated Migration (ingress2gateway)
Section titled “Automated Migration (ingress2gateway)”ingress2gateway is the official SIG-maintained CLI tool that converts existing Ingress manifests to Gateway API equivalents:
# Installgo install sigs.k8s.io/ingress2gateway@latest
# Convert manifests in current namespaceingress2gateway printHandles basic translations; complex annotation logic requires manual review. Not required for the CKA exam.