Kubernetes Ingress has a structural flaw known for a long time: anything beyond the most basic HTTP routing (path rewriting, weighted traffic splitting, custom headers) goes through annotations, a non-standardized mechanism specific to each controller. Gateway API, an official Kubernetes project distinct from the core API, replaces that model with typed resources portable across controllers.
The problem Ingress couldn’t solve
An nginx.ingress.kubernetes.io/rewrite-target annotation only works with the NGINX Ingress controller; Traefik, HAProxy, or any other controller requires its own annotation syntax for the same intent. Switching Ingress controllers (see the NGINX vs Traefik article) means rewriting every advanced annotation, since nothing in the original Ingress specification standardizes this behavior beyond the most basic routing.
# An NGINX annotation, meaningless to
# any other Ingress controller
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
Three separate resources, one role each
Gateway API breaks down what a single Ingress used to mix together into three distinct resources, each corresponding to a responsibility and often a different team. GatewayClass (managed by the platform team) defines a controller implementation type. Gateway (managed by the platform or network team) defines a concrete entry point: addresses, ports, TLS certificates. HTTPRoute (managed by the application team) defines routing rules specific to a service, never touching the shared entry point’s configuration.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
spec:
gatewayClassName: nginx
listeners:
- protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: checkout-route
spec:
parentRefs:
- name: shared-gateway
rules:
- matches:
- path: {value: /checkout}
backendRefs:
- name: checkout-service
port: 8080
This separation solves a real organizational problem Ingress mixed together by construction: an application team can declare its own routing rules (HTTPRoute) without ever needing access to or touching the shared entry point’s configuration (Gateway), a permission boundary RBAC can enforce natively since these are two distinct resource types.
Weighted routing, standardized, no longer an annotation
What Ingress could only express through controller-specific annotations (splitting 90% of traffic to one version, 10% to another) becomes a standard API field, portable across every controller implementing the specification.
# Standardized weighted split, not an annotation
# specific to a particular controller
rules:
- backendRefs:
- name: checkout-v1
port: 8080
weight: 90
- name: checkout-v2
port: 8080
weight: 10
What doesn’t change right away
Gateway API doesn’t replace Ingress overnight: both coexist, and most controllers (NGINX, Traefik, Istio) now support both models in parallel. Migration stays a choice, not an immediate requirement, and an existing cluster running well on classic Ingress has no urgent reason to migrate until the need for cross-controller portability or team-based responsibility separation becomes concretely felt.
Takeaway
Ingress encodes anything advanced into annotations that don’t port across controllers, a structural flaw Gateway API fixes by splitting roles into three typed resources (GatewayClass, Gateway, HTTPRoute), each corresponding to a distinct team and responsibility. Weighted routing and other advanced behaviors become standard API fields rather than controller-specific annotations. Migration stays gradual, not a forced replacement, one of the network architecture choices that comes up the moment a Kubernetes migration needs to distribute traffic management across several teams.