The Ceiling of Traditional Ingress
I’ve lost count of the hours I’ve spent wrestling with Nginx Ingress annotations. If you’ve ever tried implementing a canary release or blue-green deployment using standard Ingress, you’ve likely stared down a 50-line wall of nginx.ingress.kubernetes.io/... metadata. These annotations are brittle, hard to debug, and—worst of all—they lock you into a specific vendor. If you move from Nginx to HAProxy, you have to rewrite your entire routing logic from scratch.
The issue isn’t just syntax; it’s structural. Ingress was built for a simpler era of the web. It assumes a basic model: one host, one path, one backend. Modern requirements like header-based routing, traffic mirroring, or weighted balancing don’t fit that mold. We’ve spent years forcing Ingress to work using vendor hacks, but this created a fragmented ecosystem where configurations aren’t portable between different controllers.
The Organizational Bottleneck
In practice, the biggest friction point is often human, not technical. Traditional Ingress forces cluster admins and application developers to edit the same YAML file. When a developer updates a path for their microservice, they might accidentally break a global TLS configuration managed by the security team. It’s a recipe for production incidents.
The Kubernetes Gateway API solves this by splitting the configuration into distinct, role-oriented resources:
- GatewayClass: Set by the Platform Team to define the load balancer type (e.g., AWS, GKE, or Envoy).
- Gateway: Managed by Cluster Operators to handle entry points, IP addresses, and TLS certificates.
- HTTPRoute: Owned by App Developers to define how traffic reaches their specific services.
This separation of concerns turns networking into a self-service model. Developers can ship code and routing changes without ever touching the infrastructure-level Gateway settings.
Quick Start: Deploying Your First Gateway (5 Min)
To try this out, you need the Gateway API Custom Resource Definitions (CRDs) and a compatible controller. While options like Cilium or Istio are popular, Envoy Gateway is my go-to for its lightweight, standards-compliant design.
1. Install the CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
2. Install Envoy Gateway
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.1.0 -n envoy-gateway-system --create-namespace
3. Define a Gateway
This resource acts as your entry point. It tells the controller to listen for HTTP traffic on port 80.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra-ns
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
4. Map a Service with HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: app-ns
spec:
parentRefs:
- name: prod-gateway
namespace: infra-ns
rules:
- matches:
- path:
type: PathPrefix
value: /v1/api
backendRefs:
- name: user-service
port: 8080
The Power of the “Handshake”
Notice the parentRefs in the HTTPRoute above. This is the secret sauce. The Route must explicitly point to a Gateway, and the Gateway must allow Routes from that specific namespace. This two-way handshake prevents a developer in the ‘test’ namespace from accidentally hijacking traffic meant for the ‘production’ namespace. It’s security by design.
Advanced Traffic Control: No Mesh Required
Native traffic splitting is where the Gateway API leaves Ingress in the dust. You no longer need a heavy Service Mesh just to run a canary test.
Weighted Routing for Canary Releases
Suppose you’re launching v2 of your checkout service. You can send exactly 10% of traffic to the new version with a simple weight change:
spec:
rules:
- backendRefs:
- name: checkout-v1
port: 80
weight: 90
- name: checkout-v2
port: 80
weight: 10
Precision Routing via Headers
I frequently use header-based routing to test experimental features with internal staff. You can route users with a specific cookie or header directly to a beta pod without affecting the general public:
rules:
- matches:
- headers:
- name: x-user-group
value: internal-beta
backendRefs:
- name: feature-lab-service
port: 80
- backendRefs:
- name: stable-service
port: 80
Three Lessons from Production
Transitioning isn’t just about changing YAML; it’s about changing how you manage the cluster. Here is what I’ve learned after migrating several production environments.
1. Kill “Load Balancer Sprawl”
A single AWS NLB costs roughly $18/month before data processing. In a 50-service cluster where every service uses its own Ingress, you could be burning nearly $1,000 monthly on idle infrastructure. Gateway API allows you to share one Gateway (and one IP) across dozens of namespaces, slashing your cloud bill significantly.
2. Verify Feature Support
While the core API is GA (General Availability), advanced filters like URL rewrites or custom headers might still be in the “Experimental” channel for your specific provider. Always run kubectl get gatewayclass to see what your controller actually supports before committing to a design.
3. Trust the Status Block
Debugging Ingress used to involve digging through cryptic controller logs. Gateway API surfaces errors directly in the resource status. If a route fails, run:
kubectl describe httproute api-route
The Conditions section will tell you immediately if the backend service is missing or if the Gateway refused the attachment. It’s a massive quality-of-life improvement for on-call engineers.
Final Thoughts
The Gateway API is the new standard for Kubernetes networking. It replaces vendor-specific annotations with clean, portable logic that fits how modern teams actually work. While Ingress still works for small, simple projects, any environment scaling beyond a few services should start the move to Gateway API today. It’s more expressive, cheaper to run, and far easier to troubleshoot when things go wrong.

