πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Networking intermediate ⏱ 30 minutes K8s 1.28+

Migrate Ingress to Gateway API ingress2gateway

Migrate Ingress to Gateway API using ingress2gateway. Convert HTTPRoute and TLSRoute with zero-downtime parallel migration.

By Luca Berton β€’ β€’ πŸ“– 8 min read

πŸ’‘ Quick Answer: Install ingress2gateway (go install github.com/kubernetes-sigs/ingress2gateway@latest), run ingress2gateway print --all-resources to generate Gateway API equivalents of your Ingress resources, review the output, then apply alongside existing Ingress for zero-downtime parallel migration.

The Problem

Kubernetes Ingress API is frozen β€” no new features since v1. Gateway API is the successor with support for HTTP header routing, traffic splitting, request mirroring, and multi-tenancy. You have dozens of Ingress resources and need to migrate without downtime and without manually rewriting every YAML.

The Solution

Step 1: Install ingress2gateway

# Install the CLI tool
go install github.com/kubernetes-sigs/ingress2gateway@latest

# Or download the binary
curl -Lo ingress2gateway https://github.com/kubernetes-sigs/ingress2gateway/releases/latest/download/ingress2gateway-linux-amd64
chmod +x ingress2gateway
sudo mv ingress2gateway /usr/local/bin/

Step 2: Install Gateway API CRDs

# Install the standard Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml

# Verify
kubectl get crd | grep gateway
# gatewayclasses.gateway.networking.k8s.io
# gateways.gateway.networking.k8s.io
# httproutes.gateway.networking.k8s.io
# referencegrants.gateway.networking.k8s.io

Step 3: Review Your Current Ingress Resources

# List all Ingress resources
kubectl get ingress -A
# NAMESPACE   NAME            CLASS   HOSTS              ADDRESS        PORTS
# production  myapp-ingress   nginx   myapp.example.com  10.0.0.50      80, 443
# production  api-ingress     nginx   api.example.com    10.0.0.50      80, 443
# staging     staging-ingress nginx   staging.example.com 10.0.0.51     80

# Export for reference
kubectl get ingress -A -o yaml > /tmp/all-ingress-backup.yaml

Step 4: Generate Gateway API Resources

# Convert ALL Ingress resources in the cluster
ingress2gateway print --all-resources

# Convert specific namespace
ingress2gateway print --namespace production

# Convert specific Ingress controller type
ingress2gateway print --providers=ingress-nginx --all-resources

# Save to file for review
ingress2gateway print --all-resources > gateway-resources.yaml

Example: Before and After

Before β€” Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/rate-limit-connections: "10"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-frontend
                port:
                  number: 80
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: myapp-backend
                port:
                  number: 8080
          - path: /static
            pathType: Prefix
            backend:
              service:
                name: myapp-cdn
                port:
                  number: 80

After β€” Gateway API (generated by ingress2gateway):

# Gateway β€” shared infrastructure (one per cluster or team)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: production
spec:
  gatewayClassName: nginx  # or istio, envoy-gateway, etc.
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: "*.example.com"
      allowedRoutes:
        namespaces:
          from: Same
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: myapp-tls
      allowedRoutes:
        namespaces:
          from: Same
---
# HTTPRoute β€” per-application routing (replaces Ingress rules)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: myapp-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: production
  hostnames:
    - myapp.example.com
  rules:
    # Static content β€” most specific first
    - matches:
        - path:
            type: PathPrefix
            value: /static
      backendRefs:
        - name: myapp-cdn
          port: 80
    # API backend
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: myapp-backend
          port: 8080
    # Frontend catch-all
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: myapp-frontend
          port: 80

Step 5: Handle Provider-Specific Annotations

ingress2gateway converts standard Ingress fields automatically. Provider-specific annotations need manual handling:

# See which annotations weren't converted
ingress2gateway print --all-resources 2>&1 | grep -i "warning\|skipped\|unsupported"

Common annotation mappings:

Ingress AnnotationGateway API Equivalent
nginx.ingress.kubernetes.io/rewrite-targetHTTPRoute URLRewrite filter
nginx.ingress.kubernetes.io/ssl-redirectHTTPRoute RequestRedirect filter
nginx.ingress.kubernetes.io/proxy-body-sizeImplementation-specific Policy
nginx.ingress.kubernetes.io/rate-limitImplementation-specific Policy
cert-manager.io/cluster-issuerGateway TLS certificateRefs + cert-manager Gateway integration

URL Rewrite (manual):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-rewrite
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /api/v1
      backendRefs:
        - name: api-service
          port: 8080

HTTP to HTTPS Redirect:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: https-redirect
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      sectionName: http           # Attach to HTTP listener only
  hostnames:
    - myapp.example.com
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301

Step 6: Zero-Downtime Parallel Migration

Run both Ingress and Gateway API simultaneously during migration:

# Phase 1: Deploy Gateway resources alongside existing Ingress
kubectl apply -f gateway-resources.yaml

# Verify Gateway is programmed
kubectl get gateway production-gateway -n production
# NAME                  CLASS   ADDRESS      PROGRAMMED
# production-gateway    nginx   10.0.0.52    True

# Verify HTTPRoutes are accepted
kubectl get httproute -n production
# NAME          HOSTNAMES                 PARENTREFS              AGE
# myapp-route   ["myapp.example.com"]     ["production-gateway"]  30s

# Phase 2: Test via the Gateway address
curl -H "Host: myapp.example.com" https://10.0.0.52/
curl -H "Host: myapp.example.com" https://10.0.0.52/api/health

# Phase 3: Switch DNS to Gateway address (or update LoadBalancer)
# myapp.example.com β†’ 10.0.0.52 (Gateway) instead of 10.0.0.50 (old Ingress)

# Phase 4: Monitor for errors, then remove old Ingress
kubectl delete ingress myapp-ingress -n production
graph LR
    A[DNS: myapp.example.com] --> B{Migration Phase}
    B -->|Phase 1-2| C[Old Ingress<br>10.0.0.50]
    B -->|Phase 2| D[New Gateway<br>10.0.0.52]
    B -->|Phase 3+| D
    
    C --> E[myapp-frontend]
    C --> F[myapp-backend]
    D --> E
    D --> F
    
    style C fill:#fef3c7
    style D fill:#d1fae5

Step 7: Advanced Gateway API Features (Post-Migration)

Once migrated, unlock features Ingress never had:

Traffic Splitting (Canary):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: myapp-canary
spec:
  parentRefs:
    - name: production-gateway
  hostnames:
    - myapp.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: myapp-v1
          port: 80
          weight: 90              # 90% to stable
        - name: myapp-v2
          port: 80
          weight: 10              # 10% to canary

Header-Based Routing:

rules:
  - matches:
      - headers:
          - name: X-Feature-Flag
            value: new-ui
    backendRefs:
      - name: myapp-v2
        port: 80
  - matches:
      - path:
            type: PathPrefix
            value: /
    backendRefs:
      - name: myapp-v1
        port: 80

Request Mirroring:

rules:
  - matches:
      - path:
          type: PathPrefix
          value: /api
    filters:
      - type: RequestMirror
        requestMirror:
          backendRef:
            name: api-shadow       # Mirror traffic here for testing
            port: 8080
    backendRefs:
      - name: api-production
        port: 8080

Cross-Namespace Routing (Multi-Tenancy):

# Gateway in infra namespace allows routes from app namespaces
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: gateway-infra
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: "true"    # Only labeled namespaces
---
# ReferenceGrant allows cross-namespace backend references
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-ref
  namespace: production
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: gateway-infra
  to:
    - group: ""
      kind: Service

Batch Migration Script

#!/bin/bash
# migrate-ingress.sh β€” Convert and apply all Ingress resources

set -e

echo "=== Ingress to Gateway API Migration ==="

# Backup
echo "1. Backing up all Ingress resources..."
kubectl get ingress -A -o yaml > ingress-backup-$(date +%Y%m%d).yaml

# Generate
echo "2. Generating Gateway API resources..."
ingress2gateway print --all-resources > gateway-generated.yaml

# Count
INGRESS_COUNT=$(kubectl get ingress -A --no-headers | wc -l)
ROUTE_COUNT=$(grep -c "kind: HTTPRoute" gateway-generated.yaml || echo 0)
echo "   Found: $INGRESS_COUNT Ingress β†’ $ROUTE_COUNT HTTPRoutes"

# Review
echo ""
echo "3. Review gateway-generated.yaml before applying!"
echo "   Check for:"
echo "   - Unconverted annotations (grep for 'WARNING' in stderr)"
echo "   - Correct GatewayClass name"
echo "   - TLS certificate references"
echo ""
read -p "Apply gateway resources? (y/N) " confirm
[ "$confirm" = "y" ] || exit 0

# Apply
echo "4. Applying Gateway resources..."
kubectl apply -f gateway-generated.yaml

# Verify
echo "5. Verifying..."
kubectl get gateway -A
kubectl get httproute -A

echo ""
echo "βœ… Gateway resources applied. Old Ingress resources still active."
echo "   Next: test via Gateway address, switch DNS, then remove old Ingress."

Common Issues

GatewayClass Not Found

You need a Gateway API controller installed (not just the CRDs):

# For NGINX
kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/main/deploy/default/deploy.yaml

# For Istio
istioctl install --set profile=minimal

# For Envoy Gateway
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.2.0 -n envoy-gateway-system --create-namespace

# Check available GatewayClasses
kubectl get gatewayclass

OpenShift: Use OpenShift Gateway API

# OpenShift 4.15+ has built-in Gateway API support
# Enable via the ingress operator
oc patch ingresscontroller default -n openshift-ingress-operator \
  --type merge -p '{"spec":{"routeAdmission":{"enableGatewayAPI":true}}}'

# GatewayClass: openshift-default

cert-manager Integration

cert-manager supports Gateway API natively since v1.15:

# Gateway with cert-manager annotation
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: myapp.example.com
      tls:
        mode: Terminate
        certificateRefs:
          - name: myapp-tls     # cert-manager creates this automatically

HTTPRoute Not Accepted

kubectl describe httproute myapp-route -n production
# Look at conditions:
#   Accepted: False β€” "no matching parent listener"
# Common fix: hostname in HTTPRoute must match Gateway listener hostname

Best Practices

  • Backup all Ingress resources before starting migration
  • Run parallel β€” Gateway + Ingress simultaneously until verified
  • Test with curl against the Gateway address before switching DNS
  • Migrate one namespace at a time β€” not everything at once
  • Handle annotations manually β€” ingress2gateway converts standard fields, not provider-specific annotations
  • Use ReferenceGrant for cross-namespace setups β€” Gateway API is explicit about permissions
  • Pin Gateway API CRD version β€” don’t use latest in production

Key Takeaways

  • ingress2gateway print --all-resources auto-converts Ingress β†’ Gateway + HTTPRoute
  • Gateway API separates infrastructure (Gateway) from application routing (HTTPRoute)
  • Run both Ingress and Gateway in parallel for zero-downtime migration
  • Post-migration: unlock traffic splitting, header routing, request mirroring β€” features Ingress never had
  • Provider-specific annotations need manual conversion to Gateway API filters or policies
  • Gateway API is the future β€” Ingress API is frozen with no new features planned
#gateway-api #ingress #migration #ingress2gateway #httproute #networking
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens