K8s Gateway API Replacing Ingress? Complete Migration Guide for Service Mesh Traffic Management in 2026
K8s Gateway API Replacing Ingress? Complete Migration Guide for Service Mesh Traffic Management in 2026
Still writing piles of annotations on Ingress for traffic management? Scouring docs every time you configure canary deployments for the nginx.ingress.kubernetes.io/canary syntax? Tearing your hair out over cross-cluster routing with Ingress? In 2026, Kubernetes Gateway API is GA and stable — it's time to leave the Ingress chaos behind.
Background
Limitations of Ingress
Since Kubernetes 1.1, Ingress has been the standard for cluster ingress traffic management. But its design has fundamental flaws:
| Dimension | Ingress | Gateway API |
|---|---|---|
| Role Model | No role separation | Three roles (infra ops / cluster ops / app dev) |
| Extensibility | Annotations, controller-incompatible | Native extension, standard CRDs |
| Protocol Support | Mainly HTTP/HTTPS | HTTP, gRPC, TCP, UDP, TLS |
| Traffic Routing | Single-layer path matching | Multi-layer (Gateway→Route→Backend) |
| Multi-Cluster | Not supported | Native (MultiClusterService) |
| Status | v1 stable, no evolution | GA, continuously iterating |
Gateway API Core Resources
Gateway API defines three core roles and corresponding resources:
- GatewayClass: Infrastructure admin defines gateway type (analogous to StorageClass)
- Gateway: Cluster operator defines gateway instance (analogous to PVC)
- HTTPRoute/TCPRoute/GRPCRoute: App developer defines routing rules (analogous to app deployment)
Problem Analysis
Why Ingress Falls Short in Service Mesh Scenarios
Root cause: Ingress couples infrastructure configuration and application routing rules in the same resource. In service mesh scenarios:
- Permission conflicts: Ops configure TLS certs, devs change routing rules — all on the same Ingress object
- Annotation hell: Canary, traffic mirroring, retry policies rely on annotations with different syntax per controller
- Cross-cluster blind spot: Ingress cannot discover services in other clusters; multi-cluster traffic needs external solutions
- Protocol limitations: gRPC, TCP long connections and other non-HTTP protocols lack full support
Step-by-Step: Migrating from Ingress to Gateway API
Step 1: Install Gateway API CRDs and Istio
# Install Gateway API standard CRDs (v1.2.0+)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
# Install Istio 1.24+ (built-in Gateway API support)
istioctl install --set profile=ambient --set values.pilot.env.ENABLE_GATEWAY_API=true
# Verify GatewayClass is registered
kubectl get gatewayclass
# NAME CONTROLLER ACCEPTED AGE
# istio istio.io/gateway-controller True 1m
Step 2: Define Gateway Instance
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: infra
annotations:
istio.io/traffic-policy: |
connectionPool:
http:
h2UpgradePolicy: UPGRADE
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Selector
selectorLabels:
gateway-access: "true"
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: wildcard-cert
namespace: cert-manager
allowedRoutes:
namespaces:
from: All
- name: grpc
port: 15443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: grpc-cert
Step 3: Migrate Routing Rules
Original Ingress config → Gateway API HTTPRoute with native weight-based canary:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: app
spec:
parentRefs:
- name: main-gateway
namespace: infra
sectionName: https
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: api-v1
port: 8080
weight: 80
- name: api-v2
port: 8080
weight: 20
matches:
- path:
type: PathPrefix
value: /api
Step 4: Header-Based Canary Routing
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-canary-header
namespace: app
spec:
parentRefs:
- name: main-gateway
namespace: infra
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
headers:
- type: Exact
name: X-Canary
value: "true"
backendRefs:
- name: api-v2
port: 8080
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-v1
port: 8080
Step 5: Multi-Cluster Gateway Configuration
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ServiceImport
metadata:
name: api-v1-multi
namespace: app
spec:
type: ClusterSetIP
ports:
- port: 8080
resolution: DNS
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: MultiClusterService
metadata:
name: api-v1-global
namespace: app
spec:
serviceImport:
name: api-v1-multi
namespace: app
clusterBackends:
- cluster: us-west-1
weight: 60
- cluster: us-east-1
weight: 40
Complete Code: Istio + Gateway API Production Configuration
# gateway-class.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: istio-production
spec:
controllerName: istio.io/gateway-controller
parametersRef:
group: ""
kind: ConfigMap
name: istio-gw-params
namespace: infra
---
# gateway-params.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: istio-gw-params
namespace: infra
data:
concurrency: "4"
proxy-config: |
connectionPool:
http:
maxRequestsPerConnection: 1000
h2UpgradePolicy: DEFAULT
tcp:
maxConnections: 10000
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
---
# gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra
spec:
gatewayClassName: istio-production
listeners:
- name: http-public
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: https-public
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: prod-wildcard-cert
allowedRoutes:
namespaces:
from: All
addresses:
- type: IPAddress
value: "10.0.1.100"
---
# http-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
namespace: app
spec:
parentRefs:
- name: prod-gateway
namespace: infra
sectionName: https-public
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v2
headers:
- type: Exact
name: X-Canary
value: "true"
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Route-Tag
value: canary-v2
backendRefs:
- name: api-v2
port: 8080
weight: 100
- matches:
- path:
type: PathPrefix
value: /api/v2
backendRefs:
- name: api-v2
port: 8080
weight: 20
- name: api-v1
port: 8080
weight: 80
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-v1
port: 8080
- matches:
- path:
type: PathPrefix
value: /health
backendRefs:
- name: api-v1
port: 8080
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: X-Health-Check
value: "true"
---
# grpc-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: grpc-route
namespace: app
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "grpc.example.com"
rules:
- matches:
- method:
service: "com.example.api.UserService"
method: "GetUser"
backendRefs:
- name: user-service
port: 9090
- matches:
- method:
service: "com.example.api.OrderService"
backendRefs:
- name: order-service
port: 9090
Pitfall Guide
Pitfall 1: GatewayClass Not Registered
Symptom: Gateway shows Accepted: False, routes don't take effect.
Fix: Verify Istio controller has Gateway API enabled, check ENABLE_GATEWAY_API=true, ensure GatewayClass controllerName matches Istio's registration.
Pitfall 2: Cross-Namespace Routes Rejected
Symptom: HTTPRoute's parentRefs references a Gateway in another namespace, but routing doesn't work.
Fix: Gateway's allowedRoutes must explicitly allow cross-namespace routing. Set from: All or use Selector with matching labels.
Pitfall 3: TLS Certificate Cross-Namespace Reference Fails
Symptom: Gateway's HTTPS listener references a certificate in another namespace.
Fix: Gateway API requires certificates in the same namespace. Use cert-manager to replicate certificates, or use Istio's istio.io/credential-name annotation.
Pitfall 4: Canary Weights Don't Sum to 100
Symptom: Configuring weight 80 and 30 (total 110) causes unexpected traffic distribution.
Fix: Gateway API weights are relative and don't need to sum to 100. However, keeping the total at 100 is recommended for clarity. Istio normalizes automatically.
Pitfall 5: Multi-Cluster ServiceImport DNS Resolution Fails
Symptom: MultiClusterService created but cross-cluster traffic can't route.
Fix: Ensure Istio cross-cluster control plane is connected (east-west gateway deployed), check ServiceImport resolution is DNS, and verify inter-cluster network reachability.
Error Troubleshooting
| # | Error Message | Cause | Solution |
|---|---|---|---|
| 1 | GatewayClass not found |
CRDs not installed or controller not registered | Install Gateway API CRDs, confirm Istio Gateway API enabled |
| 2 | no GatewayClass controller matching |
controllerName mismatch | Check GatewayClass.spec.controllerName matches Istio registration |
| 3 | route is not accepted by any Gateway |
parentRefs reference error | Check Gateway name, namespace, sectionName |
| 4 | certificate not found |
TLS cert missing or cross-namespace | Ensure cert in same namespace as Gateway |
| 5 | port conflict on Gateway listener |
Multiple listeners on same port | Only one listener per port, merge protocol config |
| 6 | backendRef service not found |
Target Service doesn't exist | Check Service name and namespace |
| 7 | HTTPRoute condition Accepted=False |
Route rejected by Gateway | Check allowedRoutes policy and hostname matching |
| 8 | Istio proxy not ready |
Sidecar/ztunnel not injected | Confirm istio-injection=enabled label or ambient mode |
| 9 | MultiClusterService endpoint empty |
Cross-cluster discovery not working | Check east-west gateway, control plane connectivity |
| 10 | GRPCRoute method match failed |
gRPC method format error | Use package.Service/Method format |
Advanced Optimization
1. Traffic Mirroring (Shadow Traffic)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mirror-route
namespace: app
annotations:
istio.io/traffic-mirror: |
mirror:
hostname: mirror-service
port: 8080
mirrorPercentage:
value: 10
spec:
parentRefs:
- name: prod-gateway
namespace: infra
rules:
- backendRefs:
- name: api-v1
port: 8080
2. Connection Pool and Circuit Breaker
Configure global connection pool policies via GatewayClass parametersRef ConfigMap to prevent single backend overload.
3. Error-Rate Based Auto Traffic Shifting
Combine Prometheus metrics with Argo Rollouts for automatic traffic shifting based on error rates.
4. Gateway API Extension Policies
Use policyRef (v1.3+) to decouple retry, timeout, and circuit breaker policies from routes.
Comparison
| Dimension | Ingress + nginx-ingress | Ingress + Istio VS | Gateway API + Istio | Gateway API + Envoy GW |
|---|---|---|---|---|
| Config Complexity | Low (single resource) | Medium (Ingress+VS) | Medium (Gateway+Route) | Medium (Gateway+Route) |
| Role Separation | None | Partial | Complete | Complete |
| Canary Deployment | Annotations | VirtualService | Native weight | Native weight |
| gRPC Support | Limited annotations | Native | Native GRPCRoute | Native GRPCRoute |
| Multi-Cluster | Not supported | Supported (needs config) | Native | Needs extension |
| Traffic Mirroring | Not supported | Supported | Annotation extension | Extension policy |
| Community Activity | Maintenance mode | Active | Very active | Active |
| Learning Curve | Low | High | Medium | Medium |
| Production Readiness | Mature | Mature | 2026 GA mature | Rapidly maturing |
Summary
Summary: Gateway API has become the de facto standard for Kubernetes traffic management in 2026. Compared to Ingress, it provides role separation, protocol extensibility, and multi-cluster support. The migration path is clear: install CRDs, create Gateway, then migrate Routes. Istio provides the most mature Gateway API support. New projects should use Gateway API directly; existing projects can migrate service by service — Ingress and Gateway API can coexist.
Recommended Online Tools
- YAML/JSON Formatter: /en/json/format
- Base64 Encode/Decode (certificate handling): /en/encode/base64
- curl to Code (API testing): /en/dev/curl-to-code
Try these browser-local tools — no sign-up required →