Skip to content
Published on

Kubernetes Gateway API Hands-On Migration — From Ingress to Gateway

Authors
  • Name
    Twitter
Kubernetes Gateway API Migration

Introduction

The Kubernetes networking stack is evolving. The Ingress API, which has long been the standard, is now being replaced by the Gateway API. Gateway API reached GA (General Availability) in 2023, and since 2025, most Ingress Controllers have begun supporting Gateway API.

In this post, we walk through the practical process of migrating existing Ingress resources to Gateway API, step by step.

Ingress vs Gateway API: Why Migrate?

Limitations of Ingress

The Ingress API works well enough for simple HTTP routing, but it has the following limitations:

  • No role separation: Infrastructure administrators' and application developers' concerns are mixed in a single resource
  • Limited protocol support: Only HTTP/HTTPS; TCP/UDP/gRPC require non-standard annotations
  • Vendor lock-in: Different annotations per implementation (nginx, traefik, etc.)
  • No header-based routing: Weighted traffic splitting not possible

Advantages of Gateway API

┌─────────────────────────────────────┐
GatewayClass                │  ← Infrastructure provider (Platform team)
├─────────────────────────────────────┤
Gateway                   │  ← Cluster operator
├─────────────────────────────────────┤
HTTPRoute / TCPRoute / GRPCRoute  │  ← Application developer
└─────────────────────────────────────┘
  • Role-based design: Separation of concerns through the GatewayClass → Gateway → Route three-tier model
  • Rich routing: Matching based on headers, query parameters, and methods
  • Multi-protocol: Native support for HTTP, gRPC, TCP, UDP, and TLS
  • Portability: Minimized vendor lock-in through a standard API

Understanding Core Resources

GatewayClass

A top-level resource defined by the infrastructure provider. It specifies which controller manages the Gateway.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: nginx
spec:
  controllerName: gateway.nginx.org/nginx-gateway-controller

Gateway

Defines the actual listeners (ports, protocols). Managed by cluster operators.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main-gateway
  namespace: infra
spec:
  gatewayClassName: nginx
  listeners:
    - name: http
      protocol: HTTP
      port: 80
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-cert
            kind: Secret
      allowedRoutes:
        namespaces:
          from: All

HTTPRoute

Application developers define routing rules here.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app-route
  namespace: app-ns
spec:
  parentRefs:
    - name: main-gateway
      namespace: infra
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: frontend-service
          port: 3000

Hands-On Migration Steps

Step 1: Install Gateway API CRDs

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

# Include experimental features (TCPRoute, TLSRoute, etc.)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml

# Verify installation
kubectl get crd | grep gateway

Step 2: Analyze Existing Ingress Resources

Before migrating, take stock of your existing Ingress resources:

# List all Ingress resources
kubectl get ingress -A -o wide

# Inspect a specific Ingress in detail (including annotations)
kubectl get ingress my-ingress -o yaml

Example of an existing Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: 'true'
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 8080
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-svc
                port:
                  number: 3000

Step 3: Use the ingress2gateway Tool

Leverage the official conversion tool provided by the Kubernetes SIG:

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

# Convert Ingress resources in the current cluster to Gateway API
ingress2gateway print --all-namespaces

# Convert a specific namespace only
ingress2gateway print --namespace production

# Output to a file
ingress2gateway print --namespace production > gateway-resources.yaml

Step 4: Validate and Apply Converted Resources

# Validate with dry-run
kubectl apply -f gateway-resources.yaml --dry-run=server

# Apply
kubectl apply -f gateway-resources.yaml

# Check status
kubectl get gateway -A
kubectl get httproute -A
kubectl describe gateway main-gateway -n infra

Step 5: Traffic Splitting (Canary Deployment)

Weighted traffic splitting is a powerful feature unique to Gateway API:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-route
spec:
  parentRefs:
    - name: main-gateway
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: app-v1
          port: 8080
          weight: 90
        - name: app-v2
          port: 8080
          weight: 10

Step 6: Header-Based Routing

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: header-route
spec:
  parentRefs:
    - name: main-gateway
  rules:
    - matches:
        - headers:
            - name: X-Version
              value: beta
      backendRefs:
        - name: app-beta
          port: 8080
    - backendRefs:
        - name: app-stable
          port: 8080

Gradual Transition Strategy

Ingress and Gateway API can coexist in the same cluster:

  1. Phase 1: Install Gateway API CRDs + create GatewayClass/Gateway
  2. Phase 2: Use HTTPRoute for new services
  3. Phase 3: Convert existing Ingress resources to HTTPRoute one by one
  4. Phase 4: Remove all Ingress resources and clean up the Ingress Controller
# Monitor migration progress
echo "Ingress: $(kubectl get ingress -A --no-headers | wc -l)"
echo "HTTPRoute: $(kubectl get httproute -A --no-headers | wc -l)"

Considerations and Troubleshooting

Annotation Mapping

Ingress annotations are replaced by Policy resources or Filters in Gateway API:

Ingress AnnotationGateway API Equivalent
rewrite-targetURLRewriteFilter
ssl-redirectHTTP-to-HTTPS redirect on Gateway listener
rate-limitBackendPolicy or implementation-specific Policy
corsHTTPRoute filter or Policy
# URL Rewrite example
rules:
  - matches:
      - path:
          type: PathPrefix
          value: /old-api
    filters:
      - type: URLRewrite
        urlRewrite:
          path:
            type: ReplacePrefixMatch
            replacePrefixMatch: /new-api
    backendRefs:
      - name: api-svc
        port: 8080

Common Mistakes

  1. Missing parentRefs namespace: If the Gateway and HTTPRoute are in different namespaces, the namespace must be specified
  2. Missing allowedRoutes configuration: Setting from: All is required for the Gateway to accept Routes from other namespaces
  3. Missing ReferenceGrant: A ReferenceGrant is required for cross-namespace backend references
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-route-from-app
  namespace: backend-ns
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-ns
  to:
    - group: ''
      kind: Service

Conclusion

Gateway API is not just a replacement for Ingress — it represents a paradigm shift in Kubernetes networking. With role-based design, rich routing capabilities, and multi-protocol support, it enables safer and more flexible traffic management. Since it can coexist with existing Ingress resources, we recommend starting a gradual transition now.

Quiz

Q1: What are the names and responsible parties of each tier in Gateway API's three-tier resource model?

GatewayClass (Infrastructure provider / Platform team), Gateway (Cluster operator), HTTPRoute/TCPRoute etc. (Application developer)

Q2: What mechanism did Ingress use for vendor-specific configurations? Annotations. Each implementation (nginx, traefik, etc.) used different non-standard annotations, which was a major cause of reduced portability.

Q3: What is the kubectl command to install Gateway API CRDs? kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml

Q4: What is the official tool that automatically converts existing Ingress to Gateway API resources?

ingress2gateway. Provided by the Kubernetes SIG, you can check the conversion results with the ingress2gateway print command.

Q5: Which field is used to implement weighted canary deployments in Gateway API?

The weight field in backendRefs. For example, setting weight: 90 for v1 and weight: 10 for v2 splits traffic in a 90:10 ratio.

Q6: What are two things to watch out for when HTTPRoute and Gateway are in different namespaces?

  1. The Gateway's namespace must be specified in parentRefs. 2) The Gateway's listeners must have allowedRoutes.namespaces.from set to "All".

Q7: What resource is needed when referencing a backend service across namespaces?

ReferenceGrant. Created in the target namespace, it specifies which resources from which namespaces are allowed to reference its Services.

Q8: How is the Ingress rewrite-target annotation replaced in Gateway API? Using URLRewriteFilter in HTTPRoute filters. The path is rewritten using type: URLRewrite and the urlRewrite.path configuration.