Gateway API: The Ingress Successor
- Expressive Successor: Gateway API replaces the limited Ingress resource with a more powerful, standardized way to manage traffic without vendor-specific annotations. It is a graduated GA project within the Kubernetes ecosystem as of v1.1.
- Role-Based Configuration: It separates infrastructure concerns (GatewayClass and Gateway managed by platform admins) from application routing (HTTPRoute, GRPCRoute, TCPRoute managed by developers), enabling self-service with guardrails.
- Native Traffic Control: Advanced features like weighted traffic splitting, header-based routing, request mirroring, URL rewrites, and request redirects are first-class citizens in the API specification — no annotations required.
- Industry Standard: Supported by all major implementations including Istio, Cilium, Envoy Gateway, NGINX Gateway Fabric, Contour, and every major cloud provider. It is the recommended path for all new Kubernetes deployments.
- Extensible by Design: The API uses policy attachment and extension points so implementations can add vendor-specific features without polluting the core specification.
For years, the Ingress resource was the standard way to get external HTTP traffic into a Kubernetes cluster. However, Ingress suffered from fundamental design limitations: it only supported basic host and path-based routing, it lacked a standard way to express TLS configuration beyond simple termination, and it had no concept of TCP or UDP routing at all. Teams resorted to dozens of vendor-specific annotations to fill the gaps — annotations that differed between NGINX, Traefik, HAProxy, and cloud load balancers — making configuration non-portable and error-prone.
The Gateway API was designed from the ground up to solve these problems. It provides an expressive, extensible, and role-oriented set of resources for configuring traffic routing in Kubernetes.
1. Why Gateway API Replaces Ingress
The core problems with Ingress that Gateway API addresses:
| Problem with Ingress | How Gateway API Solves It |
|---|---|
| No standard for traffic splitting | HTTPRoute supports weighted backendRefs natively |
| Header-based routing requires annotations | HTTPRoute matches on headers, query params, and methods |
| No TCP/UDP/gRPC routing | Dedicated TCPRoute, UDPRoute, GRPCRoute resources |
| Single resource mixes infra and app concerns | Separate Gateway (infra) and *Route (app) resources |
| No cross-namespace routing | Routes can attach to Gateways in other namespaces with explicit permission |
| TLS config is limited | Gateway listeners support TLS passthrough, termination, and per-listener certificates |
| Vendor lock-in via annotations | Standardized fields replace most annotation use cases |
If you are starting a new project, Gateway API is the clear choice. Ingress is effectively in maintenance mode — it will continue to work, but new features are landing in Gateway API exclusively.
2. Role-Based Separation
One of the most impactful design decisions in Gateway API is how it splits responsibilities between different teams. This mirrors real organizational structures where platform teams manage infrastructure and application teams manage their own routing.
controller: acme.io/gateway
Defines the implementation (e.g. AWS, Nginx, Istio).
listeners:
- port: 80
Defines the entry point (IP, Port, TLS).
rules:
- matches: /api
Defines routing rules for specific apps.
- Infrastructure Provider manages
GatewayClass— defines the underlying implementation (e.g., "use Envoy proxies deployed as a DaemonSet" or "provision an AWS NLB"). - Platform Admin / Cluster Operator manages
Gateway— instantiates the load balancer, configures listeners with ports and TLS certificates, and controls which namespaces can attach routes. - Application Developer manages
HTTPRoute/GRPCRoute/TCPRoute— defines the actual routing rules (paths, headers, backends, traffic weights) for their specific service.
This separation means a developer can ship a new canary route without needing cluster-admin privileges or touching the load balancer configuration.
3. The Resource Model
GatewayClass
A GatewayClass is a cluster-scoped resource that defines a class of Gateways. It points to a controller that implements the class. Think of it as a StorageClass but for network load balancers.
# GatewayClass — typically created once by the platform team
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-external
spec:
# controllerName must match the controller's identifier
controllerName: gateway.envoyproxy.io/gatewayclass-controller
description: "External-facing Envoy Gateway for production traffic"
Most clusters have one or two GatewayClasses (e.g., external and internal). The implementation controller watches for Gateways referencing its class and provisions the underlying infrastructure.
Gateway
A Gateway is a namespaced resource that requests a load balancer from the GatewayClass. Each listener specifies a port, protocol, and optional TLS configuration.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: gateway-infra
spec:
gatewayClassName: envoy-external
listeners:
# HTTPS listener with TLS termination
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-tls-cert
namespace: gateway-infra
# Control which namespaces can attach routes
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
# Plain HTTP listener (typically redirects to HTTPS)
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
The allowedRoutes field is critical for security. It controls which namespaces are permitted to attach routes to this Gateway. Options include All, Same (only the Gateway's namespace), or Selector (namespaces matching a label selector).
HTTPRoute
The HTTPRoute is where developers spend most of their time. It defines matching rules and backend targets.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: store-api
namespace: store # developer's namespace
spec:
# Attach to a Gateway in a different namespace
parentRefs:
- name: production-gateway
namespace: gateway-infra
sectionName: https # attach to the specific listener
hostnames:
- "store.example.com"
rules:
# Rule 1: Traffic splitting for canary deployment
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: store-api-stable
port: 8080
weight: 90 # 90% to stable
- name: store-api-canary
port: 8080
weight: 10 # 10% to canary
# Rule 2: Header-based routing for beta features
- matches:
- path:
type: PathPrefix
value: /api
headers:
- name: X-Beta-User
value: "true"
backendRefs:
- name: store-api-beta
port: 8080
# Rule 3: Default route
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: store-frontend
port: 3000
GRPCRoute
For gRPC services, GRPCRoute matches on gRPC service and method names rather than HTTP paths:
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: grpc-payments
namespace: payments
spec:
parentRefs:
- name: production-gateway
namespace: gateway-infra
rules:
- matches:
- method:
service: payments.v1.PaymentService
method: ProcessPayment
backendRefs:
- name: payment-service
port: 50051
TCPRoute
TCPRoute handles raw TCP traffic, useful for databases or other non-HTTP protocols:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
name: postgres-route
namespace: databases
spec:
parentRefs:
- name: internal-gateway
namespace: gateway-infra
sectionName: postgres
rules:
- backendRefs:
- name: postgres-primary
port: 5432
4. Advanced Traffic Management
Traffic Splitting
Weighted backend references enable fine-grained traffic distribution without any external tooling:
rules:
- backendRefs:
- name: app-v1
port: 8080
weight: 95 # 95% of requests
- name: app-v2
port: 8080
weight: 5 # 5% of requests — canary
Request Mirroring
Send a copy of live traffic to a test backend without affecting the response to the client. This is invaluable for validating new versions with real production traffic:
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: app-stable
port: 8080
filters:
- type: RequestMirror
requestMirror:
backendRef:
name: app-shadow
port: 8080
URL Rewrites and Redirects
rules:
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
# Rewrite the path before forwarding
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-api
backendRefs:
- name: api-service
port: 8080
Request Header Modification
Add, set, or remove headers before the request reaches the backend:
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Forwarded-By
value: gateway-api
set:
- name: X-Request-Source
value: external
remove:
- X-Internal-Debug
5. TLS Configuration
Gateway API provides far richer TLS configuration than Ingress:
- Terminate: The Gateway terminates TLS and forwards plaintext to backends (most common).
- Passthrough: The Gateway passes encrypted traffic directly to the backend, which handles TLS termination itself. Useful when the backend needs end-to-end encryption or mTLS.
listeners:
- name: tls-passthrough
protocol: TLS
port: 443
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
For TLS termination, certificates can be referenced from Secrets in the same or different namespaces (with ReferenceGrant permission).
6. Cross-Namespace Routing with ReferenceGrant
A key security feature: routes in one namespace can reference backends or secrets in another namespace only if the target namespace explicitly allows it via a ReferenceGrant:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-gateway-cert-access
namespace: cert-store # the namespace being referenced
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: gateway-infra # who is allowed to reference
to:
- group: ""
kind: Secret # what they can reference
This prevents a developer in namespace team-a from routing traffic to services in namespace team-b unless team-b explicitly grants permission.
7. Comparison: Gateway API vs. Ingress
| Feature | Ingress | Gateway API |
|---|---|---|
| HTTP host/path routing | Yes | Yes |
| Header/query matching | Annotation-dependent | Native |
| Traffic splitting | Annotation-dependent | Native (weighted backendRefs) |
| TCP/UDP routing | No | Yes (TCPRoute, UDPRoute) |
| gRPC routing | Annotation-dependent | Native (GRPCRoute) |
| Request mirroring | No | Native |
| Role-based separation | No | Yes (GatewayClass/Gateway/Route) |
| Cross-namespace routing | No | Yes (with ReferenceGrant) |
| TLS passthrough | Annotation-dependent | Native |
| Portability across implementations | Low (annotation divergence) | High (standardized spec) |
8. Implementation Status
Gateway API has reached GA (v1.0+) for the core HTTP resources. The current landscape:
- GA (Stable):
GatewayClass,Gateway,HTTPRoute - Standard channel:
GRPCRoute,ReferenceGrant - Experimental:
TCPRoute,UDPRoute,TLSRoute, policy attachment
Major implementations: Istio, Cilium, Envoy Gateway, NGINX Gateway Fabric, Contour, Traefik, HAProxy, Google Kubernetes Engine, Amazon EKS (via AWS Gateway API Controller), and Azure Application Gateway for Containers.
Common Pitfalls
- Forgetting
allowedRoutes: By default, a Gateway only allows routes from theSamenamespace. If routes in other namespaces are not attaching, check theallowedRoutes.namespacesfield on your Gateway listeners. - Missing
ReferenceGrant: Cross-namespace backend references silently fail without aReferenceGrantin the target namespace. The route will show aResolvedRefscondition ofFalse. - Conflicting listeners: Two listeners on the same port with overlapping hostnames will cause one to be rejected. Use distinct
sectionNamevalues and verify listener status conditions. - Assuming Ingress feature parity: Some legacy Ingress annotations (like rate limiting or WAF rules) may not have a standardized Gateway API equivalent yet. Check your implementation's documentation for policy attachment extensions.
- Not checking route status: Always inspect
kubectl get httproute -o yamland look at.status.parentsto verify the route was accepted by the Gateway.
Best Practices
- Use one Gateway per domain boundary (e.g., one external, one internal). Avoid creating a Gateway per application.
- Label namespaces for
allowedRoutesselectors rather than usingfrom: All, which grants every namespace access. - Use
sectionNameinparentRefsto bind routes to specific listeners, avoiding accidental exposure on the wrong port or protocol. - Store TLS certificates in a dedicated namespace and use
ReferenceGrantto share them, keeping certificate management centralized. - Monitor Gateway and Route status conditions — they provide detailed information about why a route is not working.
- Use
PathPrefixmatching carefully — a prefix of/appalso matches/application. Consider usingExactor more specific prefixes like/app/. - Test traffic splitting with request mirroring first before sending live user traffic to a canary.
What's Next?
- Service Mesh: Explore how the GAMMA Initiative is standardizing Gateway API for east-west traffic (mesh) routing, unifying Ingress and Mesh configuration.
- Progressive Delivery: Learn how to use traffic splitting for canary and blue-green deployments.
- Security for controlling east-west traffic between services.
- Policy as Code can enforce standards on Gateway and Route configurations.
- Multi-Tenancy patterns for sharing Gateways across teams with proper isolation.