LabHub

Blog

Kubernetes Gateway API Production Guide: HTTPRoute, GRPCRoute, and Ingress Migration Strategy

English日本語

Kubernetes Gateway API

Why Gateway API Replaces Ingress

The Kubernetes Ingress resource served the community well for years, but its limitations became a bottleneck for modern workloads. Vendor-specific annotations, no native gRPC support, no role separation, and a single-resource-for-everything design all drove the creation of the Gateway API.

Gateway API (GA since v1.0, with GRPCRoute GA since v1.1.0) introduces a role-oriented, protocol-aware, and portable networking model. As of v1.2, the old v1alpha2 versions of GRPCRoute and ReferenceGrant have been removed from both Standard and Experimental channels, signaling maturity and production-readiness.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
Cluster Administrator│                                                                 │
│  ┌──────────────┐                                               │
│  │ GatewayClassDefines which controller handles Gateways│  └──────┬───────┘                                               │
│         │                                                       │
│         ▼                                                       │
│  ┌──────────────┐     Platform / Infra Team│  │   GatewayConfigures listeners, TLS, allowed routes    │
│  └──┬───┬───┬───┘                                               │
│     │   │   │                                                   │
│     ▼   ▼   ▼      Application Developer│  ┌────┐┌─────────┐┌──────────┐                                  │
│  │HTTP││GRPCRoute││TLSRoute  │  Define per-service routing      │
│  │Route│└─────────┘└──────────┘                                 │
│  └─┬──┘                                                        │
│    │                                                            │
│    ▼                                                            │
│  ┌──────────────────────────┐                                   │
│  │  Backend Services / Pods │                                   │
│  └──────────────────────────┘                                   │
└─────────────────────────────────────────────────────────────────┘

Role Separation Model

RoleResourcesResponsibility
Infrastructure ProviderGatewayClassDeploy and manage the gateway controller
Platform OperatorGatewayConfigure listeners, TLS termination, cross-namespace policies
Application DeveloperHTTPRoute, GRPCRouteDefine routing rules per service

This separation means developers never need to touch TLS certificates, and platform operators never need to understand application-level routing logic.

Comparison: Gateway API vs Ingress vs Service Mesh

FeatureIngressGateway APIService Mesh (Istio)
HTTP RoutingBasic path/hostAdvanced (headers, query params, method)Full L7 routing
gRPC NativeNo (annotation hacks)Yes (GRPCRoute GA)Yes
TLS TerminationPer-IngressPer-Listener on GatewayPer-sidecar/waypoint
Traffic SplittingAnnotation-basedNative (weight field)Native (VirtualService or HTTPRoute)
Role SeparationNoneGatewayClass / Gateway / RouteComplex RBAC
Cross-NamespaceLimitedReferenceGrantServiceEntry
TCP/UDP SupportNoTCPRoute / UDPRouteYes
PortabilityAnnotation-dependentConformance tests ensure portabilityVendor lock-in risk
Canary/Blue-GreenRequires annotationsBuilt-in weight-based routingBuilt-in
East-West TrafficNoYes (GAMMA initiative)Primary use case

Installing Gateway API CRDs and a Controller

Step 1: Install the Gateway API CRDs

# Install the Standard Channel CRDs (includes HTTPRoute, GRPCRoute GA resources)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml

# Verify CRDs are installed
kubectl get crds | grep gateway
# Expected output:
# gatewayclasses.gateway.networking.k8s.io
# gateways.gateway.networking.k8s.io
# grpcroutes.gateway.networking.k8s.io
# httproutes.gateway.networking.k8s.io
# referencegrants.gateway.networking.k8s.io

# (Optional) Install Experimental Channel for TCPRoute, TLSRoute, UDPRoute
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/experimental-install.yaml

Step 2: Deploy a Gateway Controller

Choose an implementation that fits your needs. Popular options:

# Option A: Envoy Gateway
helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.0 \
  -n envoy-gateway-system --create-namespace

# Option B: NGINX Gateway Fabric
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --version 2.4.2 \
  -n nginx-gateway --create-namespace

# Option C: Istio (with Gateway API support)
istioctl install --set profile=minimal

HTTPRoute: Core Concepts and Patterns

HTTPRoute is the workhorse of Gateway API. It handles HTTP and terminated HTTPS connections with rich matching capabilities.

Basic HTTPRoute Configuration

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production
  namespace: infra
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
            namespace: cert-manager
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: 'true'
    - name: http
      protocol: HTTP
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
  name: allow-cert-ref
  namespace: cert-manager
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: Gateway
      namespace: infra
  to:
    - group: ''
      kind: Secret
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-routes
  namespace: app-team
spec:
  parentRefs:
    - name: production
      namespace: infra
  hostnames:
    - 'api.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v2/users
          headers:
            - name: X-API-Version
              value: '2'
      backendRefs:
        - name: users-v2
          port: 8080
          weight: 90
        - name: users-v3
          port: 8080
          weight: 10
    - matches:
        - path:
            type: PathPrefix
            value: /v2/users
      backendRefs:
        - name: users-v2
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /healthz
      backendRefs:
        - name: health-service
          port: 8080

Advanced: HTTP-to-HTTPS Redirect and Request Modification

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-to-https
  namespace: app-team
spec:
  parentRefs:
    - name: production
      namespace: infra
      sectionName: http
  hostnames:
    - 'api.example.com'
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-with-modifications
  namespace: app-team
spec:
  parentRefs:
    - name: production
      namespace: infra
      sectionName: https
  hostnames:
    - 'api.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: X-Request-ID
                value: 'generated-by-gateway'
            remove:
              - X-Internal-Debug
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1
      backendRefs:
        - name: api-backend
          port: 8080

GRPCRoute: Native gRPC Traffic Management

GRPCRoute provides first-class support for gRPC traffic. Unlike wrapping gRPC in HTTPRoute with path matching, GRPCRoute understands gRPC service and method names directly.

GRPCRoute Configuration

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-user-service
  namespace: grpc-apps
spec:
  parentRefs:
    - name: production
      namespace: infra
  hostnames:
    - 'grpc.example.com'
  rules:
    # Route by gRPC service name
    - matches:
        - method:
            service: com.example.UserService
            method: GetUser
      backendRefs:
        - name: user-service
          port: 50051
    # Route by service name with header matching
    - matches:
        - method:
            service: com.example.UserService
          headers:
            - name: x-canary
              value: 'true'
      backendRefs:
        - name: user-service-canary
          port: 50051
    # Default route for all other gRPC methods
    - matches:
        - method:
            service: com.example.UserService
      backendRefs:
        - name: user-service-stable
          port: 50051
          weight: 95
        - name: user-service-canary
          port: 50051
          weight: 5

When to Use GRPCRoute vs HTTPRoute for gRPC

ScenarioRecommendedReason
gRPC with service/method matchingGRPCRouteNative support, cleaner syntax
gRPC with only path matchingHTTPRoute worksPath-based matching is simpler
Mixed HTTP + gRPC on same hostSeparate HTTPRoute + GRPCRouteEach route type handles its protocol
gRPC-Web (browser clients)HTTPRoutegRPC-Web uses HTTP/1.1 framing

Step-by-Step Ingress Migration

Migrating from Ingress to Gateway API should be done incrementally. Never perform a hard cutover in production.

Migration Architecture

                    ┌──────────────────────┐
DNS / LB                    │  api.example.com                    └──────────┬───────────┘
                    ┌──────────▼───────────┐
Traffic Split                      (DNS weight or LB)                    └──┬───────────────┬───┘
                       │               │
              ┌────────▼─────┐  ┌──────▼────────┐
Ingress    │  │   Gateway                (existing)   (new)              │              │  │                │
              │  nginx-ctrl  │  │  envoy-gw      │
              └──────┬───────┘  └──────┬─────────┘
                     │                 │
                     └────────┬────────┘
                     ┌────────▼────────┐
Same BackendServices                     └─────────────────┘

Phase 1: Audit Existing Ingress Resources

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

# Export all Ingress resources for analysis
kubectl get ingress -A -o yaml > ingress-backup.yaml

# Check for annotation dependencies
kubectl get ingress -A -o json | \
  jq -r '.items[] | .metadata.name + " -> " + (.metadata.annotations | keys | join(", "))'

# Use ingress2gateway for automated conversion
# Install: go install github.com/kubernetes-sigs/ingress2gateway@latest
ingress2gateway print --all-namespaces

Phase 2: Create Gateway Resources Alongside Ingress

# Deploy Gateway alongside existing Ingress
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: migration-gateway
  namespace: infra
  annotations:
    purpose: 'ingress-migration-phase2'
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
      allowedRoutes:
        namespaces:
          from: All

Phase 3: Create HTTPRoutes Matching Ingress Rules

# Verify the Gateway is Programmed before proceeding
kubectl get gateway migration-gateway -n infra -o jsonpath='{.status.conditions}'

# Expected: type=Programmed, status=True
# WARNING: Do NOT delete Ingress resources until Gateway shows Programmed=True

Phase 4: Gradual Traffic Shift

# Validate each HTTPRoute individually
for route in $(kubectl get httproute -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'); do
  ns=$(echo $route | cut -d/ -f1)
  name=$(echo $route | cut -d/ -f2)
  echo "--- Checking $ns/$name ---"
  kubectl get httproute $name -n $ns -o jsonpath='{.status.parents[*].conditions}' | jq .
done

# Test with curl before switching DNS
GATEWAY_IP=$(kubectl get gateway migration-gateway -n infra \
  -o jsonpath='{.status.addresses[0].value}')
curl -H "Host: api.example.com" https://$GATEWAY_IP/healthz --resolve "api.example.com:443:$GATEWAY_IP"

Phase 5: Delete Ingress Resources One at a Time

# Delete one Ingress at a time, verify, then proceed
kubectl delete ingress old-api-ingress -n app-team

# Immediately verify the service is reachable
curl -f https://api.example.com/healthz || echo "ROLLBACK NEEDED"

# If rollback is needed:
kubectl apply -f ingress-backup.yaml

Traffic Splitting and Canary Deployments

Gateway API makes canary deployments a first-class feature with weight-based traffic splitting.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-deployment
  namespace: app-team
spec:
  parentRefs:
    - name: production
      namespace: infra
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        # Stable: receives 95% of traffic
        - name: app-stable
          port: 8080
          weight: 95
        # Canary: receives 5% of traffic
        - name: app-canary
          port: 8080
          weight: 5

To gradually shift traffic:

# Increase canary weight incrementally
# 5% -> 10% -> 25% -> 50% -> 100%
# At each step, check error rates and latency

# Monitor canary metrics (Prometheus example)
# rate(http_requests_total{app="app-canary",code=~"5.."}[5m])
# / rate(http_requests_total{app="app-canary"}[5m])

TLS Termination Patterns

Gateway-Level TLS with cert-manager

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: secure-gateway
  namespace: infra
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https-wildcard
      protocol: HTTPS
      port: 443
      hostname: '*.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-com
    - name: https-specific
      protocol: HTTPS
      port: 443
      hostname: 'api.specific.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: api-specific-cert
    - name: tls-passthrough
      protocol: TLS
      port: 8443
      hostname: 'mtls.example.com'
      tls:
        mode: Passthrough

Production Troubleshooting

Common Failure Cases and Recovery

Problem 1: Gateway stuck in "not Programmed" state

# Check Gateway status conditions
kubectl describe gateway production -n infra

# Common causes:
# 1. GatewayClass controller not running
kubectl get pods -n envoy-gateway-system

# 2. Invalid TLS certificate reference
kubectl get secret wildcard-tls -n infra

# 3. Listener conflict (duplicate port/hostname)
kubectl get gateway -A -o jsonpath='{range .items[*]}{.metadata.name}: {range .spec.listeners[*]}{.port}/{.hostname} {end}{"\n"}{end}'

Problem 2: HTTPRoute not attaching to Gateway

# Check route status
kubectl get httproute api-routes -n app-team -o yaml | yq '.status'

# Common causes:
# 1. Missing ReferenceGrant for cross-namespace
kubectl get referencegrant -A

# 2. Namespace not allowed by Gateway listener
kubectl get ns app-team --show-labels
# Ensure the label matches the Gateway's allowedRoutes selector

# 3. Hostname mismatch between Gateway listener and HTTPRoute
# The HTTPRoute hostname must be a subdomain of or match the listener hostname

Problem 3: 503 errors after migration

# Check if backend endpoints are healthy
kubectl get endpoints users-v2 -n app-team

# Check if the service port matches the HTTPRoute backendRef port
kubectl get svc users-v2 -n app-team -o jsonpath='{.spec.ports}'

# Check gateway controller logs
kubectl logs -n envoy-gateway-system -l app=envoy-gateway --tail=100

# Verify the Envoy proxy config was generated correctly
# (Envoy Gateway specific)
kubectl get envoyproxy -A

Problem 4: gRPC requests failing with UNAVAILABLE

# Verify the backend supports HTTP/2
kubectl exec -it deploy/user-service -n grpc-apps -- \
  grpcurl -plaintext localhost:50051 list

# Check if the Gateway listener protocol is correct
# For gRPC, you need HTTPS (with TLS termination) or
# the controller must support HTTP/2 cleartext (h2c)

# Check GRPCRoute status
kubectl get grpcroute -A -o yaml | yq '.items[].status'

Operational Warnings

  1. Never delete all Ingress resources at once. Migrate one service at a time and verify reachability after each deletion.

  2. Always check Gateway Programmed status before routing traffic. A Gateway that is Accepted but not Programmed has not provisioned its data plane.

  3. ReferenceGrant is required for cross-namespace references. Without it, a route in namespace A cannot reference a backend in namespace B or a secret in namespace C.

  4. Gateway API v1.2 removed v1alpha2 GRPCRoute. Before upgrading, ensure your controller supports v1 GRPCRoute. Run kubectl get crds grpcroutes.gateway.networking.k8s.io -o jsonpath='{.spec.versions[*].name}' to check.

  5. Weight-based splitting rounds to the nearest integer. If you set weights of 1 and 99, the actual split may vary slightly depending on the controller implementation.

  6. TLS Passthrough listeners cannot inspect HTTP headers. If you need header-based routing, use TLS Terminate mode instead.

Health Check Script for Production

#!/bin/bash
# gateway-health-check.sh
# Run after any Gateway API change in production

set -euo pipefail

echo "=== Gateway Status ==="
kubectl get gateways -A -o custom-columns=\
'NAMESPACE:.metadata.namespace,NAME:.metadata.name,CLASS:.spec.gatewayClassName,PROGRAMMED:.status.conditions[?(@.type=="Programmed")].status,ACCEPTED:.status.conditions[?(@.type=="Accepted")].status'

echo ""
echo "=== HTTPRoute Status ==="
kubectl get httproutes -A -o custom-columns=\
'NAMESPACE:.metadata.namespace,NAME:.metadata.name,HOSTNAMES:.spec.hostnames[*],ACCEPTED:.status.parents[*].conditions[?(@.type=="Accepted")].status'

echo ""
echo "=== GRPCRoute Status ==="
kubectl get grpcroutes -A -o custom-columns=\
'NAMESPACE:.metadata.namespace,NAME:.metadata.name,ACCEPTED:.status.parents[*].conditions[?(@.type=="Accepted")].status' 2>/dev/null || echo "No GRPCRoutes found"

echo ""
echo "=== ReferenceGrants ==="
kubectl get referencegrants -A

echo ""
echo "=== Remaining Ingress Resources (should be empty post-migration) ==="
kubectl get ingress -A

Implementation Comparison: Envoy Gateway vs NGINX Gateway Fabric vs Istio

FeatureEnvoy GatewayNGINX Gateway FabricIstio
Data PlaneEnvoy ProxyNGINXEnvoy (sidecar or ambient)
HTTPRouteFull supportFull supportFull support
GRPCRouteFull supportFull supportFull support
TLSRouteSupportedSupportedSupported
TCPRoute / UDPRouteSupportedPartialSupported
Custom ExtensionsEnvoyProxy, BackendTrafficPolicySnippetsFilterVirtualService, DestinationRule
East-West (mesh)Not primary focusNoPrimary strength
Rate LimitingBackendTrafficPolicyRateLimitPolicyEnvoyFilter or WASM
Best ForPure ingress/egress gatewayNGINX-familiar teamsFull service mesh + gateway

Real-World Implementation Pattern: Multi-Team Platform

# Platform team deploys shared Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: platform-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: platform
spec:
  gatewayClassName: platform-gateway
  listeners:
    - name: https-web
      protocol: HTTPS
      port: 443
      hostname: '*.web.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: web-wildcard
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              tier: web
    - name: https-api
      protocol: HTTPS
      port: 443
      hostname: '*.api.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: api-wildcard
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              tier: api
---
# Team A deploys their own routes (no TLS knowledge needed)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: team-a-frontend
  namespace: team-a
spec:
  parentRefs:
    - name: shared-gateway
      namespace: platform
      sectionName: https-web
  hostnames:
    - 'app.web.example.com'
  rules:
    - backendRefs:
        - name: frontend
          port: 3000
---
# Team B deploys gRPC routes independently
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: team-b-grpc
  namespace: team-b
spec:
  parentRefs:
    - name: shared-gateway
      namespace: platform
      sectionName: https-api
  hostnames:
    - 'payments.api.example.com'
  rules:
    - matches:
        - method:
            service: com.example.PaymentService
      backendRefs:
        - name: payment-grpc
          port: 50051

Summary

The Kubernetes Gateway API represents a fundamental shift in how we manage cluster networking. Key takeaways:

The ecosystem is mature: Envoy Gateway, NGINX Gateway Fabric, and Istio all provide robust Gateway API implementations. Choose based on your existing stack and whether you need east-west (mesh) capabilities.

References

Quiz

Q1: What is the main topic covered in "Kubernetes Gateway API Production Guide: HTTPRoute, GRPCRoute, and Ingress Migration Strategy"?

A comprehensive guide covering Kubernetes Gateway API core concepts, HTTPRoute and GRPCRoute configuration, step-by-step Ingress migration strategy, traffic splitting, TLS termination, and production troubleshooting.

Q2: Why Gateway API Replaces Ingress? The Kubernetes Ingress resource served the community well for years, but its limitations became a bottleneck for modern workloads.

Q3: What are the key steps for Installing Gateway API CRDs and a Controller? Step 1: Install the Gateway API CRDs Step 2: Deploy a Gateway Controller Choose an implementation that fits your needs. Popular options:

Q4: What are the key aspects of HTTPRoute: Core Concepts and Patterns? HTTPRoute is the workhorse of Gateway API. It handles HTTP and terminated HTTPS connections with rich matching capabilities. Basic HTTPRoute Configuration Advanced: HTTP-to-HTTPS Redirect and Request Modification

Q5: How does GRPCRoute: Native gRPC Traffic Management work? GRPCRoute provides first-class support for gRPC traffic. Unlike wrapping gRPC in HTTPRoute with path matching, GRPCRoute understands gRPC service and method names directly. GRPCRoute Configuration When to Use GRPCRoute vs HTTPRoute for gRPC

Comments

No comments yet.

Sign in to leave a comment