Skip to content
Documentation Background

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 own Route objects. 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
DimensionIngress APIGateway API
API hierarchyIngressClass -> Ingress -> ServiceGatewayClass -> Gateway -> Route -> Service
Resource structureMonolithic: listener config + routing in one objectDecoupled: Gateway defines listeners; Route objects define routing
Protocol scopeL7 HTTP/HTTPS onlyL4-L7: HTTPRoute, TLSRoute, GRPCRoute, TCPRoute, UDPRoute
Target bindingDirect Service referenceGateway via parentRefs; Service via backendRefs
Cross-namespaceSingle namespace onlyNative: one Gateway can serve routes from many namespaces
Advanced featuresAnnotations only (vendor-specific, non-portable)Standardized filters: header modify, URL rewrite, redirect, mirror
Gateway API vs Ingress API
  • Release channels: Standard (stable, e.g. HTTPRoute at v1) vs Experimental (incubating, e.g. TLSRoute, TCPRoute, UDPRoute, GRPCRoute at v1alpha2).
  • 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 ExtensionRef filter.
  • Explicit group/kind: All parentRefs and backendRefs specify group and kind - this allows the GAMMA initiative to use a Service as a parentRef for east/west mesh routing.

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:
AspectHybrid Data PlaneFully Managed Data Plane
ArchitectureCouples 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 PodsYes (managed via Kubernetes Deployments inside the cluster).No (entirely managed outside the cluster).
Example ImplementationsCilium, Envoy Gateway, Istio, NGINX Gateway Fabric, Traefik.Proprietary cloud platform add-ons (GKE, EKS, AKS).
Trade-offsProvides cloud mobility and consistent behavior across multi-cloud/local environments.Simpler maintenance, but introduces potential cloud platform lock-in.

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 backends
  • ReferenceGrant - managed by ops; grants permission for cross-namespace route-to-service references
Gateway API Role-Oriented Architecture
Terminal window
# Check if already installed
kubectl 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 CRDs
kubectl 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/experimental

Kubernetes ships no built-in Gateway API controller. Popular options:

ProviderTypeNotes
Envoy GatewayEnvoy-backedCNCF project, reference implementation
IstioService mesh + gatewayStandalone mode available (no mesh required)
ContourEnvoy-backedOpen source
CiliumeBPF-basedHigh-performance, no sidecars
KongAPI gatewayFull API management
NGINX Kubernetes GatewayNGINX-backedOfficial NGINX implementation
GKECloud-managedgcloud container clusters update --gateway-api=standard

Envoy Gateway (Helm):

Terminal window
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.4.2 \
-n envoy-gateway-system --create-namespace
# Wait for controller to be ready
kubectl wait --timeout=5m -n envoy-gateway-system \
deployment/envoy-gateway --for=condition=Available

NGINX Gateway Fabric (Helm):

Terminal window
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
--version 2.3.0 --namespace nginx-gateway --create-namespace

Cloud Provider for KIND (Local Testing): Runs as a process outside the cluster, authenticating with the local KIND API server.

Terminal window
# macOS
brew install cloud-provider-kind
# Launch (requires root)
sudo cloud-provider-kind --gateway-channel="standard"

Istio (minimal - gateway only, no mesh):

Terminal window
curl -sL https://istio.io/downloadIstioctl | sh -
istioctl install -y --set profile=minimal
kubectl get pods -n istio-system

GKE (managed):

Terminal window
gcloud container clusters update <cluster-name> --gateway-api=standard --region=<region>

A minimal walkthrough: CRDs → controller → GatewayClass → Gateway → HTTPRoute → test.

Terminal window
# 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 controller
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.4.2 \
-n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
# 3. GatewayClass - binds to Envoy controller
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
# 4. Gateway - listens on port 80
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: hello-gateway
spec:
gatewayClassName: envoy
listeners:
- name: http
protocol: HTTP
port: 80
---
# 5. HTTPRoute - routes to backend service
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hello-route
spec:
parentRefs:
- name: hello-gateway
hostnames:
- "hello.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web
port: 3000
Terminal window
# 6. Verify
kubectl get gatewayclasses # ACCEPTED = True
kubectl get gateways # PROGRAMMED = True
kubectl 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:8080

GatewayClass is cluster-scoped and identifies which controller manages gateways of that class - analogous to IngressClass:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: istio
spec:
controllerName: istio.io/gateway-controller # unique controller identifier
description: The default Istio GatewayClass
# parametersRef: links to a CRD with implementation-specific config
Terminal window
kubectl get gatewayclasses

A Gateway declares one or more network entry points (listeners) where the proxy accepts connections:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
spec:
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
Gateway API Architecture

Key listener fields:

  • protocol: HTTP, HTTPS, TLS, TCP, or UDP.
  • 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:

  1. Gateway Claiming: The controller detects the Gateway matching its GatewayClass.
  2. Proxy Deployment & Service: The controller deploys in-cluster L7 data-plane proxy Pods (in the same namespace as the Gateway) and creates a LoadBalancer Service.
  3. NodePort Allocation: The Kubernetes Service controller assigns a unique NodePort for each listener port.
  4. kube-proxy Routing: kube-proxy programs local node iptables/IPVS rules to intercept NodePort traffic and forward it to the proxy Pods.
  5. Cloud Load Balancer Provisioning: The Cloud Controller Manager (CCM) provisions an external L4 cloud load balancer pointing to the node ports.
  6. Public IP Attachment: The public Virtual IP (VIP) is assigned to the LoadBalancer Service and the Gateway’s status.addresses field is populated.
Terminal window
kubectl get gtw # list gateways (gtw shorthand)
kubectl get gtw main-gateway -o yaml # inspect status + assigned IP
Terminal window
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 + conditions

Condition 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: The metadata.generation this condition reflects. If it doesn’t match metadata.generation, the status is stale.

Top-level Gateway conditions:

ConditionMeaning
AcceptedController validated and accepted the spec
ProgrammedProxy infrastructure (Service + Pod) is provisioned and configured
ReadyReserved for future iterations
ScheduledDeprecated

Listener conditions (status.listeners[].conditions) - each listener is reported separately:

ConditionStatusProgrammatic ReasonWhen it fires
AcceptedTrue / False / UnknownAccepted / PortUnavailable / UnsupportedProtocol / UnsupportedAddress / PendingPort in use, unsupported protocol, or address unbindable
ConflictedTrue / FalseHostnameConflict / ProtocolConflict / NoConflictsTwo listeners share a port with conflicting hostnames or protocols - neither is configured
ProgrammedTrue / False / UnknownProgrammed / Invalid / PendingListener proxy config is generated and ready
ReadyTrue / False / UnknownReady / Invalid / PendingListener is fully active and serving traffic
ResolvedRefsTrue / FalseResolvedRefs / InvalidCertificateRef / InvalidRouteKinds / RefNotPermittedTLS Secret missing or invalid, unsupported route kinds, cross-namespace cert reference denied

Listener metadata fields:

  • attachedRoutes: Count of Route objects 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.

A single Route can bind to multiple Gateways - status is reported per parent in status.parents[], not as a single top-level condition:

Terminal window
kubectl get httproute web-route -o yaml # inspect per-parent conditions

Each entry in status.parents contains:

  • parentRef: Which Gateway (group/kind/name/namespace) this status applies to.
  • controllerName: The controller (from GatewayClass) that wrote this entry.

Route conditions per parent:

ConditionStatusProgrammatic ReasonWhen it fires
AcceptedTrue / False / UnknownAccepted / NotAllowedByListeners / NoMatchingListenerHostname / NoMatchingParent / UnsupportedValue / PendingRoute’s namespace not allowed (allowedRoutes), no listener hostname matches, invalid parent reference
ResolvedRefsTrue / FalseResolvedRefs / RefNotPermitted / InvalidKind / BackendNotFoundCross-namespace backendRef without ReferenceGrant, unknown group/kind, Service does not exist

HTTPRoute is the primary Gateway API route resource (gateway.networking.k8s.io/v1, Standard channel - the only fully stable route kind).

HTTPRoute Example
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
spec:
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: 80

Default field expansion (controller fills in omitted values):

  • parentRefs[].group -> gateway.networking.k8s.io, kind -> Gateway
  • backendRefs[].group -> "" (core API), kind -> Service, weight -> 1
  • rules[].matches -> catch-all PathPrefix: /
Terminal window
kubectl apply -f httproute.yaml
kubectl get httproutes
kubectl get httproute web-route -o yaml # inspect full expanded spec + status

Test (same pattern as Ingress):

Terminal window
curl --resolve app.example.com:80:<GATEWAY_IP> http://app.example.com
HTTPRoute Traffic Splitting

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:

  1. Backend Applications: Deploy two distinct versions, each fronted by its own ClusterIP Service.
  2. Ingress Gateway: Provide the shared listener (e.g., port 80).
  3. 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 traffic

Traffic 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.

HTTPRoute 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: 80

Header routing:

matches:
- headers:
- type: Exact # Exact | RegularExpression
name: Release
value: canary

Path routing:

matches:
- path:
type: PathPrefix # PathPrefix | Exact | RegularExpression
value: /api/v2

Query parameter routing:

matches:
- queryParams:
- type: Exact
name: release
value: canary # matches ?release=canary

Compound 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.*
HTTPRoute Filters

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-Debug

Response header modification:

filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Strict-Transport-Security
value: max-age=31536000; includeSubDomains

URL rewrite:

# Replace full path: /foo or /foo/bar -> /new/path
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplaceFullPath
replaceFullPath: /new/path
# Replace prefix only: /foo/bar -> /new/path/bar
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new/path

HTTP to HTTPS redirect (no backendRefs needed - gateway terminates the request):

filters:
- type: RequestRedirect
requestRedirect:
scheme: https
port: 443
statusCode: 301

Traffic mirroring (shadow testing):

# Primary response goes to client; mirror response is discarded
filters:
- type: RequestMirror
requestMirror:
backendRef:
name: web-canary
port: 80
backendRefs:
- name: web-stable
port: 80

Vendor extension:

filters:
- type: ExtensionRef
extensionRef:
group: networking.example.com
kind: SomeCustomFilter
name: my-filter # must be in same namespace as HTTPRoute

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.3

Attach 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.

Terminal window
curl --resolve app.example.com:443:<GATEWAY_IP> https://app.example.com -k

Encrypted TLS stream passes through the gateway unmodified; pods terminate TLS themselves (e.g., via sidecar proxy):

# Gateway: protocol TLS + mode Passthrough, no certificateRefs
spec:
listeners:
- name: tls
port: 443
protocol: TLS # not HTTPS - gateway is L4 only
tls:
mode: Passthrough

Use TLSRoute (experimental v1alpha2) - routing is based on SNI only, no L7 inspection possible:

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: web-tls
spec:
parentRefs:
- name: main-gateway
hostnames:
- app.example.com # matched via SNI extension in ClientHello
rules:
- backendRefs:
- name: web-stable
port: 443 # pod's TLS port

SNI 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.

HTTPRoute TLS
FeatureHTTPRoute (TLS Terminate)TLSRoute (TLS Passthrough)
Decryption atGatewayPod
Path/header routingFull supportNot possible (payload encrypted)
Traffic splittingSupportedSupported (by connection, not request)
Cert managementCentralized in GatewayPer-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/v1alpha3
kind: BackendTLSPolicy
metadata:
name: backend-tls
spec:
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 ModeResourceWho holds certBackend traffic
TerminateGateway spec.listeners.tlsGateway (Secret)Plain HTTP
PassthroughTLSRoutePodEncrypted (unchanged)
Re-encryptBackendTLSPolicyBothNew 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/v1alpha2
kind: BackendTrafficPolicy
metadata:
name: web-sticky-session
spec:
targetRefs:
- group: ""
kind: Service
name: web # the backend Service to apply sticky sessions to
sessionPersistence:
sessionName: session-id
absoluteTimeout: 24h
type: Cookie

Expose any TCP-based service (databases, message queues, custom daemons):

# Gateway listener for TCP
spec:
listeners:
- name: tcp-db
port: 5432
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: TCPRoute
metadata:
name: db-route
spec:
parentRefs:
- name: main-gateway
rules:
- backendRefs:
- name: postgres
port: 5432
  • No hostname matching, no path/header routing (L4 is payload-agnostic).
  • Weighted splitting: Multiple backendRefs with weight fields splits TCP connections proportionally.
  • Test: nc <GATEWAY_IP> 5432
beyond http: L4 routing

Expose UDP-based workloads (DNS, media streaming, game servers):

spec:
listeners:
- name: dns
port: 53
protocol: UDP
---
apiVersion: gateway.networking.k8s.io/v1
kind: UDPRoute
metadata:
name: dns-route
spec:
parentRefs:
- name: main-gateway
rules:
- backendRefs:
- name: dns-server
port: 53
  • Provider support varies - if status section is empty after apply, no active controller reconciled it.
  • Test: nc --udp <GATEWAY_IP> 53
nextgen RPC: gRPC routes

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/v1
kind: GRPCRoute
metadata:
name: echo-route
spec:
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, ExtensionRef filters.
  • Test: grpcurl -proto schema.proto --plaintext api.example.com:9000 myapp.Echo.Ping

cross namespace

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 access

Routes in another namespace reference the Gateway’s namespace in parentRefs:

# HTTPRoute in namespace: app-team
spec:
parentRefs:
- name: main-gateway
namespace: gateway-ns # explicit cross-namespace reference
hostnames:
- app.example.com

Grant namespace access via label:

Terminal window
kubectl label ns app-team team=backend

If the namespace doesn’t match, the route status shows Accepted: False with reason NotAllowedByListeners.

End-to-End Multi-Tenant Scenario:

  1. Infrastructure Admins (infra-admins) deploy a shared Gateway in ns-infra that restricts route attachments to namespaces carrying the gtw-prod: "true" label via allowedRoutes.namespaces.from: Selector.
  2. Cluster Operators (cluster-ops) create tenant namespaces (e.g., ns-shield and ns-hydra) and apply the gtw-prod: "true" label to them.
  3. Application Developers (app-devs) inside ns-shield deploy their HTTPRoute referencing the Gateway in ns-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/v1beta1
kind: ReferenceGrant
metadata:
name: allow-httproutes-from-app-team
namespace: service-ns # must live in the target namespace
spec:
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 ns

Route backendRef with explicit namespace:

rules:
- backendRefs:
- name: some-service
namespace: service-ns # cross-namespace target
port: 80

Without 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 ListenerSets
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: infra
spec:
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/v1alpha1
kind: ListenerSet
metadata:
name: team-listeners
namespace: app-team
spec:
parentRef:
name: shared-gateway
namespace: infra
listeners:
- name: team-app
port: 8443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: team-tls

An 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: ReferenceGrant still governs cross-namespace backend references.

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/v1
kind: HTTPRoute
metadata:
name: api-v2-route
namespace: app-team
spec:
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: 80

How it works:

  1. A client pod sends a request to api-service:80.
  2. The mesh sidecar (or ambient node proxy) intercepts the outbound connection.
  3. It matches the HTTPRoute bound to api-service as parentRef.
  4. 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.


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.

Legacy IngressGateway API EquivalentManaged by
Ingress controllerGatewayClassPlatform provider
Ingress resource (LB config + ports)GatewayCluster administrator
Ingress resource (rules + paths)HTTPRoute / GRPCRouteApplication developer
Vendor annotations (canary, rewrite)Core HTTPRoute spec.rules.filtersApplication developer
Namespace isolation (none)ReferenceGrantCluster administrator
  1. Side-by-side: Deploy Gateway API CRDs and controller alongside the existing Ingress controller. Most modern controllers support both simultaneously - no downtime required.
  2. Incremental: Start with non-critical workloads. Define HTTPRoute manifests, validate traffic, then shift production services one at a time.
  3. Decommission: Once all routing is verified on Gateway API, delete the legacy Ingress objects and eventually the Ingress controller.

ingress2gateway is the official SIG-maintained CLI tool that converts existing Ingress manifests to Gateway API equivalents:

Terminal window
# Install
go install sigs.k8s.io/ingress2gateway@latest
# Convert manifests in current namespace
ingress2gateway print

Handles basic translations; complex annotation logic requires manual review. Not required for the CKA exam.