Kubernetes Gateway API Migration in 2026: From Ingress to Gateway API
Kubernetes Gateway API Migration in 2026: From Ingress to Gateway API
If you're still using Kubernetes Ingress to manage cluster traffic in 2026, you're missing the future of cloud-native traffic management. Gateway API officially became a Kubernetes GA API in late 2025, with major vendors (Istio, Contour, Traefik, Kong) fully supporting it. Ingress's design flaws—single-role, poor extensibility, inability to express complex routing—are no longer acceptable in today's exploding microservice scale.
Gateway API introduces role-separated APIs like Gateway, GatewayClass, and HTTPRoute, supporting multi-team collaboration, multi-cluster deployment, traffic splitting, and mirroring. This article starts with architecture comparison, provides complete migration steps, and covers traffic management, multi-tenancy, and multi-cluster scenarios.
Why Gateway API Replaces Ingress
| Dimension | Ingress | Gateway API |
|---|---|---|
| API Maturity | GA (but stopped evolving) | GA (2025.11) |
| Role Separation | None (all managed by cluster admin) | Three roles (infra/cluster/app) |
| Routing Expressiveness | Limited (path+host) | Powerful (header/query/weight/mirror) |
| Multi-protocol Support | HTTP/HTTPS only | HTTP/TLS/TCP/UDP/gRPC |
| Traffic Splitting | Requires annotation hack | Native support (weight field) |
| Multi-tenancy | Not supported | Native support (Namespace isolation) |
| Extensibility | Relies on annotations | Native extension (PolicyAttachment) |
| Cross-cluster | Not supported | Multi-cluster Gateway support |
Core difference: Ingress mixes all configuration together, while Gateway API achieves role separation—infrastructure teams manage GatewayClass, ops teams manage Gateway, dev teams manage Routes.
1. Architecture Comparison
1.1 Ingress Architecture
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /v1/users(/|$)(.*)
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
Problem: All configuration crammed into one Ingress object, annotations everywhere, vendor-specific annotations are incompatible.
1.2 Gateway API Architecture
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: istio
spec:
controllerName: istio.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: api-gateway
namespace: infra
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: user-route
namespace: app-team
spec:
parentRefs:
- name: api-gateway
namespace: infra
rules:
- matches:
- path:
type: PathPrefix
value: /v1/users
backendRefs:
- name: user-service
port: 8080
weight: 90
- name: user-service-canary
port: 8080
weight: 10
Advantage: Clear roles—GatewayClass defined by platform team, Gateway configured by ops team, HTTPRoute managed by dev team.
2. Step-by-Step Migration Guide
2.1 Install Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
2.2 Install Controller (Istio Example)
istioctl install --set profile=minimal
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: istio
spec:
controllerName: istio.io/gateway-controller
EOF
2.3 Migrate Ingress to Gateway+HTTPRoute
Migration script approach:
for ingress in $(kubectl get ingress -A -o name); do
namespace=$(echo $ingress | cut -d'/' -f1 | cut -d'.' -f1)
name=$(echo $ingress | cut -d'/' -f2)
kubectl get $ingress -o yaml | python3 ingress2gateway.py > gateway-route.yaml
kubectl apply -f gateway-route.yaml
echo "Migrated $ingress, verify before deleting"
done
2.4 Automated Migration Tool
import yaml
import sys
def ingress_to_gateway_route(ingress_data):
metadata = ingress_data['metadata']
spec = ingress_data['spec']
gateway = {
'apiVersion': 'gateway.networking.k8s.io/v1',
'kind': 'Gateway',
'metadata': {
'name': f"{metadata['name']}-gateway",
'namespace': metadata.get('namespace', 'default'),
},
'spec': {
'gatewayClassName': 'istio',
'listeners': [],
},
}
routes = []
for rule in spec.get('rules', []):
host = rule.get('host', '*')
listener_name = f"http-{host.replace('.', '-')}" if host != '*' else 'http'
gateway['spec']['listeners'].append({
'name': listener_name,
'port': 80,
'protocol': 'HTTP',
'hostname': host if host != '*' else None,
})
for path in rule.get('http', {}).get('paths', []):
route = {
'apiVersion': 'gateway.networking.k8s.io/v1',
'kind': 'HTTPRoute',
'metadata': {
'name': f"{metadata['name']}-route",
'namespace': metadata.get('namespace', 'default'),
},
'spec': {
'parentRefs': [{'name': f"{metadata['name']}-gateway"}],
'rules': [{
'matches': [{
'path': {
'type': 'PathPrefix',
'value': path.get('path', '/'),
}
}],
'backendRefs': [{
'name': path['backend']['service']['name'],
'port': path['backend']['service']['port']['number'],
}],
}],
},
}
routes.append(route)
return gateway, routes
if __name__ == '__main__':
ingress = yaml.safe_load(sys.stdin)
gw, routes = ingress_to_gateway_route(ingress)
for doc in [gw] + routes:
print('---')
print(yaml.dump(doc, default_flow_style=False))
3. Traffic Management
3.1 Traffic Splitting (Canary Release)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
spec:
parentRefs:
- name: api-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2-stable
port: 8080
weight: 95
- name: api-v2-canary
port: 8080
weight: 5
3.2 Traffic Mirroring
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRouteFilter
metadata:
name: mirror-filter
spec:
type: RequestMirror
requestMirror:
backendRef:
name: api-mirror
port: 8080
3.3 Header-based Routing
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-route
spec:
parentRefs:
- name: api-gateway
rules:
- matches:
- headers:
- type: Exact
name: X-Feature-Flag
value: new-ui
backendRefs:
- name: frontend-v2
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: frontend-v1
port: 80
3.4 gRPC Routing
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: grpc-route
spec:
parentRefs:
- name: api-gateway
rules:
- matches:
- method:
service: "com.example.UserService"
method: "GetUser"
backendRefs:
- name: user-grpc-service
port: 50051
4. Multi-cluster and Multi-tenant Scenarios
4.1 Multi-cluster Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-cross-namespace
namespace: app-team
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: infra
to:
- group: ""
kind: Service
4.2 Multi-tenant Isolation
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tenant-a-gateway
namespace: tenant-a
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
Tenant Isolation Strategy Comparison:
| Strategy | Isolation Level | Resource Overhead | Management Complexity | Use Case |
|---|---|---|---|---|
| Shared Gateway | Route-level | Low | Low | Small scale, trusted tenants |
| Namespace Isolation | Namespace-level | Medium | Medium | Medium scale, general isolation |
| Dedicated Gateway | Gateway-level | High | High | Large scale, strong isolation |
5 Common Pitfalls
| # | Pitfall | Consequence | Solution |
|---|---|---|---|
| 1 | Delete Ingress before creating Gateway | Traffic interruption | Run in parallel, verify before deleting |
| 2 | HTTPRoute references Gateway in another Namespace | Permission denied | Create ReferenceGrant |
| 3 | Gateway's allowedRoutes too restrictive | Route cannot bind | Check namespace selector |
| 4 | Ignoring GatewayClass parameters | Controller behavior unexpected | Read GatewayClassSpec.parametersRef carefully |
| 5 | Traffic splitting weight sum ≠ 100 | Undefined behavior (implementation-dependent) | Ensure weight sum is 100 |
10 Error Troubleshooting Items
| # | Error Symptom | Possible Cause | Troubleshooting Method |
|---|---|---|---|
| 1 | HTTPRoute Parents condition Accepted: False |
Gateway doesn't allow this Namespace | Check Gateway's allowedRoutes |
| 2 | Traffic not reaching backend | Route matching rules incorrect | Use kubectl describe httproute to check ResolvedRefs |
| 3 | GatewayNotReady |
Controller not installed or running | Check GatewayClass and controller Pod |
| 4 | TLS certificate not working | Certificate Secret missing or wrong format | Check Secret type is kubernetes.io/tls |
| 5 | Canary traffic ratio wrong | Weight calculation error | Confirm weight sum is 100 and ratio is correct |
| 6 | Cross-Namespace reference denied | Missing ReferenceGrant | Create ReferenceGrant allowing cross-Namespace reference |
| 7 | GRPCRoute not working | Gateway listener not configured for H2C | Add protocol: HTTP listener with H2C enabled |
| 8 | Traffic mirroring not working | Controller doesn't support RequestMirror | Confirm controller implementation supports this extension |
| 9 | Gateway IP not assigned | Controller not configured for LB | Check controller's Service and LoadBalancer |
| 10 | Performance degradation after migration | Controller config not optimized | Compare Ingress and Gateway proxy configuration |
Tool Recommendations
During Gateway API migration, these tools help with configuration and encoding tasks:
- JSON Formatter — Format Gateway API Status and condition information for debugging route status
- Base64 Encoder — Base64 encode TLS certificates and keys for Kubernetes Secrets
- Hash Calculator — Generate version fingerprints for Gateway configuration for change detection and rollback decisions
Summary: Gateway API isn't an "upgrade" to Ingress—it's a "redesign" of traffic management. Three-role separation lets platform, ops, and dev teams each manage their own scope; native traffic splitting makes canary releases work without annotation hacks; multi-protocol support lets gRPC and TCP services enjoy unified traffic management. In 2026, Ingress has entered maintenance mode—Gateway API is the only direction forward. The core migration principle: run in parallel first, then verify, then switch—don't do it all at once. Let Ingress and Gateway API coexist for a while, confirm traffic is healthy, then remove Ingress.
Try these browser-local tools — no sign-up required →