Ingress to Gateway API Migration
Ingress to Gateway API Migration: A Practical Strategy
TL;DR
- Ingress and Gateway API can coexist in the same cluster; partition ownership by hostname/path to avoid conflicts.
- ingress2gateway tool auto-converts Ingress → HTTPRoute (basic rules, hosts, paths, TLS), but NOT vendor-specific annotations.
- Migration strategy: incremental (route-by-route), not big-bang — limits blast radius if conversion breaks.
- Test thoroughly: PathPrefix behavior edge cases (trailing slashes) may differ between controllers; validate converted routes.
- Keep both controllers running until full Ingress→HTTPRoute conversion; then decommission Ingress Controller only after
kubectl get ingress -Areturns empty.
Core Concepts
Safe Coexistence Model
Problem: both controllers listening on same hostname = ambiguous routing.
Solution: partition ownership at load-balancer/DNS level:
- Phase 1: Ingress owns
api-v1.example.com, Gateway ownsapi-v2.example.com - Phase 2: update DNS to point
api.example.com→ Gateway - Phase 3: decommission Ingress Controller
ingress2gateway Tool Output
Converts automatically:
- Host / path rules → HTTPRoute rules
- TLS blocks → HTTPRoute TLS config
- Service references
Does NOT convert:
- Vendor annotations (NGINX: rewrite-target, rate-limit; GCE: load-balancer-policy)
- Canary / traffic splitting logic
- Custom auth / WAF policies
Manual work required: re-apply vendor features using HTTPRoute filters (e.g., RequestHeaderModifier, URLRewrite).
Migration Workflow
-
Audit annotations:
kubectl get ingress -A -o json | jq -r '.items[].metadata.annotations | keys[]' | sort -u -
Plan equivalents: map each annotation to Gateway API filter or controller extension
-
Set up Gateway: create GatewayClass + Gateway (platform team)
-
Incremental conversion:
- Pick lowest-risk Ingress (fewest vendor features)
- Run ingress2gateway on it
- Manually re-apply vendor logic as filters
- Test (curl with Host header)
- Deploy HTTPRoute alongside Ingress
- Monitor both, then remove Ingress
-
Repeat: next Ingress → HTTPRoute
Configuration & Command Reference
Inventory all annotations:
kubectl get ingress -A -o json | jq '.items[] | {name, annotations: .metadata.annotations}'
Convert single Ingress:
ingress2gateway print --input-file=payments-ingress.yaml
Test converted HTTPRoute:
kubectl apply -f converted-httproute.yaml
kubectl describe httproute <name> # Check parentRef attachment
curl -H "Host: api.example.com" http://<gateway-ip>/api/health
Verify no Ingress remains before decommissioning controller:
kubectl get ingress -A # Must return empty
Common Pitfalls
-
Pitfall: Converted Ingress → HTTPRoute, routes don’t attach (parentRef shows not attached). Why: Gateway not listening on route’s hostname, or HTTPRoute namespace not allowed by Gateway’s allowedRoutes. Fix:
kubectl describe httprouteshows attachment errors; verify Gatewaylisteners[].hostnamesincludes route’s hostname. -
Pitfall: PathPrefix matching changed (e.g.,
/apinow matches/apiextrawhen it shouldn’t, or vice versa). Why: controller-specific Prefix behavior differs (some honor slash-delimited boundaries, others don’t). Fix: test each converted route in staging; use exact path matching if Prefix behavior differs from original controller. -
Pitfall: Vendor features (NGINX rewrite, canary weight) lost after conversion; routes work but behavior differs. Why: ingress2gateway doesn’t convert annotations; manual re-implementation required. Fix: map annotation to HTTPRoute filter; if filter doesn’t exist, use controller-specific extension or CRD.
Interview Questions
-
Why is incremental migration safer than big-bang, and what specific risk does it reduce? — Tests blast radius awareness.
-
An HTTPRoute was converted from Ingress but doesn’t route traffic. What should you check first? — Tests troubleshooting approach.
-
Your Ingress uses NGINX annotations not supported by your Gateway controller. How do you handle this? — Tests understanding of feature parity gaps.
This handles the mechanical translation of paths, hosts, and TLS blocks reliably, but **vendor-specific annotations are not automatically converted** — those require manual translation to the equivalent HTTPRoute `filters` or controller-specific extension resources.
---
## 3. Incremental, Route-by-Route Cutover
Phase 1: Deploy Gateway + GatewayClass alongside existing Ingress Controller Phase 2: Migrate ONE low-risk route to HTTPRoute, validate in production Phase 3: Migrate remaining routes incrementally, in order of increasing criticality Phase 4: Decommission the old Ingress Controller once zero Ingress objects remain
During the overlap period, both an Ingress Controller and a Gateway API controller can run simultaneously, each serving different hostnames/paths — DNS or a load balancer in front decides which system actually receives traffic for a given route during the transition.
```yaml
# A converted HTTPRoute equivalent to a simple path-based Ingress rule
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: payments-migrated
namespace: payments
spec:
parentRefs:
- name: shared-gateway
namespace: infra
hostnames: ["payments.example.com"]
rules:
- matches:
- path: {type: PathPrefix, value: /api}
backendRefs:
- name: payments-api
port: 8080
4. Hands-on Command Cheat Sheet
Run the conversion tool against live cluster Ingress objects directly:
ingress2gateway print --namespace=payments > payments-gateway-api.yaml
Compare the behavior of both objects side by side during the overlap window:
kubectl describe ingress payments-ingress -n payments
kubectl describe httproute payments-migrated -n payments
Verify traffic is actually reaching the new HTTPRoute path (test directly, bypassing DNS if still pointed at the old Ingress):
curl -H "Host: payments.example.com" http://<new-gateway-ip>/api/health
Confirm zero remaining Ingress objects before decommissioning the old controller:
kubectl get ingress -A
5. Architectural Interview Q&A
Q: Why shouldn’t a team attempt a single big-bang cutover from Ingress to Gateway API across an entire production cluster in one change window? A: Vendor-specific Ingress annotations (rewrite rules, custom auth, rate limiting, canary weights) don’t have a fully automatic translation to Gateway API — each one requires manual verification that the equivalent HTTPRoute filter or extension actually produces identical behavior, and subtle behavioral differences (default path-matching semantics, header case sensitivity) are easy to miss until real production traffic exercises them. An incremental, route-by-route migration limits the blast radius of any single translation mistake to one route rather than every route in the cluster simultaneously.
Q: During the overlap period with both an Ingress Controller and a Gateway API controller running, how do you avoid routing conflicts for the same hostname? A: Explicitly partition ownership at the DNS/load-balancer layer — each hostname (or path prefix, if the front-end load balancer supports path-based routing to different backend controller IPs) is directed to exactly one controller at a time, never both simultaneously for the same traffic. Only after a specific hostname’s HTTPRoute is validated and confirmed correct should the DNS record (or LB rule) actually be flipped to send that traffic to the new Gateway API controller instead of the old Ingress Controller.
6. Common Pitfalls
[!WARNING] Assuming Path-Matching Semantics Are Identical Ingress’s
pathType: Prefixand Gateway API’sPathPrefixmatch type look similar but have historically had subtle edge-case differences in trailing-slash handling and exact-boundary matching depending on the specific controller implementation on each side. Never assume a converted route behaves byte-for-byte identically without explicit testing against real request patterns (including edge cases like trailing slashes, empty paths, and query strings) — this is exactly the kind of subtle regression thatingress2gateway’s mechanical conversion cannot catch for you.