Skip to content

Split View: Kubernetes Gateway API 실전 가이드: Ingress 대체부터 HTTPRoute·GRPCRoute·Envoy Gateway 프로덕션 배포까지

✨ Learn with Quiz
|

Kubernetes Gateway API 실전 가이드: Ingress 대체부터 HTTPRoute·GRPCRoute·Envoy Gateway 프로덕션 배포까지

Kubernetes Gateway API 실전 가이드

들어가며

Kubernetes Ingress는 오랫동안 외부 트래픽을 클러스터 내부로 라우팅하는 표준 방법이었습니다. 하지만 구현체마다 다른 어노테이션 의존성, 단일 리소스에 집중된 설정, gRPC나 TCP 같은 비HTTP 프로토콜 미지원 등 한계가 명확했습니다. Kubernetes 커뮤니티는 이러한 문제를 해결하기 위해 Gateway API를 설계했고, 현재 v1.2에서 HTTPRoute와 GRPCRoute가 모두 GA(Generally Available) 상태로 프로덕션 환경에서 안정적으로 사용할 수 있습니다.

특히 Ingress NGINX의 공식 리타이어먼트가 발표되면서(2026년 3월 이후 베스트 에포트 유지만 제공), Gateway API로의 마이그레이션은 선택이 아닌 필수가 되고 있습니다. 이 글에서는 Envoy Gateway를 구현체로 사용하여 HTTPRoute, GRPCRoute, TLS 종료, 트래픽 분할, 헤더 기반 라우팅, 레이트 리미팅까지 프로덕션에 필요한 모든 설정을 실전 코드와 함께 다룹니다.

Gateway API vs Ingress 비교

아키텍처 차이점

Gateway API는 역할 기반의 리소스 모델로 설계되어, 인프라 운영자와 애플리케이션 개발자의 관심사를 명확히 분리합니다.

비교 항목IngressGateway API
리소스 모델단일 Ingress 리소스GatewayClass / Gateway / Route 분리
역할 분리불가인프라팀 / 플랫폼팀 / 개발팀 분리
프로토콜 지원HTTP/HTTPS만HTTP, gRPC, TCP, UDP, TLS
설정 방식구현체별 어노테이션표준화된 스펙
크로스 네임스페이스미지원ReferenceGrant로 지원
트래픽 분할어노테이션 의존네이티브 weight 기반
헤더 매칭구현체별 상이표준 스펙 포함
GA 상태안정적이나 리타이어 예정v1.2 GA (HTTPRoute, GRPCRoute)

리소스 계층 구조

Gateway API의 3계층 리소스 모델을 살펴보겠습니다.

# 1계층: GatewayClass - 인프라 제공자가 관리 (클러스터 스코프)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
# 2계층: Gateway - 플랫폼팀이 관리 (네임스페이스 스코프)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra-gateway
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls-cert
    - name: http
      protocol: HTTP
      port: 80
---
# 3계층: HTTPRoute - 개발팀이 관리 (각 애플리케이션 네임스페이스)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: app-team-a
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https
  hostnames:
    - 'api.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1/users
      backendRefs:
        - name: user-service
          port: 8080

Envoy Gateway 설치 및 구성

Helm을 통한 설치

# Envoy Gateway Helm 차트 설치
helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.0 \
  -n envoy-gateway-system \
  --create-namespace \
  --set config.envoyGateway.logging.level.default=info

# 설치 확인
kubectl get pods -n envoy-gateway-system
kubectl get gatewayclass

# CRD 확인
kubectl get crd | grep gateway.networking.k8s.io

GatewayClass 및 Gateway 생성

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-production
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
  parametersRef:
    group: gateway.envoyproxy.io
    kind: EnvoyProxy
    name: production-config
    namespace: envoy-gateway-system
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: production-config
  namespace: envoy-gateway-system
spec:
  provider:
    type: Kubernetes
    kubernetes:
      envoyDeployment:
        replicas: 3
        pod:
          annotations:
            prometheus.io/scrape: 'true'
            prometheus.io/port: '19001'
        container:
          resources:
            requests:
              cpu: '500m'
              memory: '512Mi'
            limits:
              cpu: '2000m'
              memory: '2Gi'
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra-gateway
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: envoy-production
  listeners:
    - name: https-wildcard
      protocol: HTTPS
      port: 443
      hostname: '*.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-tls
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: 'true'
    - name: http-redirect
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same

HTTPRoute 심층 활용

경로 기반 라우팅

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-routes
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https-wildcard
  hostnames:
    - 'app.example.com'
  rules:
    # Exact 매칭 - 가장 높은 우선순위
    - matches:
        - path:
            type: Exact
            value: /healthz
      backendRefs:
        - name: health-check-service
          port: 8080
    # PathPrefix 매칭 - API 버전별 라우팅
    - matches:
        - path:
            type: PathPrefix
            value: /api/v2
      backendRefs:
        - name: api-v2-service
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /api/v1
      backendRefs:
        - name: api-v1-service
          port: 8080
    # RegularExpression 매칭
    - matches:
        - path:
            type: RegularExpression
            value: '/users/[0-9]+/profile'
      backendRefs:
        - name: user-profile-service
          port: 8080

헤더 기반 라우팅

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: header-based-routing
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'api.example.com'
  rules:
    # 특정 헤더 값에 따라 다른 백엔드로 라우팅
    - matches:
        - headers:
            - name: x-api-version
              value: 'beta'
            - name: x-user-tier
              value: 'premium'
      backendRefs:
        - name: api-beta-premium
          port: 8080
    # A/B 테스트를 위한 헤더 기반 라우팅
    - matches:
        - headers:
            - name: x-experiment-group
              value: 'treatment'
      backendRefs:
        - name: api-experiment
          port: 8080
    # 기본 라우팅 (매칭 조건 없음)
    - backendRefs:
        - name: api-stable
          port: 8080

트래픽 분할 (카나리 배포)

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        # 안정 버전: 90% 트래픽
        - name: app-stable
          port: 8080
          weight: 90
        # 카나리 버전: 10% 트래픽
        - name: app-canary
          port: 8080
          weight: 10

HTTP 리다이렉트와 URL 리라이트

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-and-rewrite
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'app.example.com'
  rules:
    # HTTP -> HTTPS 리다이렉트
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: url-rewrite
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'api.example.com'
  rules:
    # /old-api/* -> /new-api/* 경로 리라이트
    - matches:
        - path:
            type: PathPrefix
            value: /old-api
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /new-api
      backendRefs:
        - name: api-service
          port: 8080

GRPCRoute 프로덕션 구성

기본 GRPCRoute 설정

GRPCRoute는 Gateway API v1.1에서 GA로 승격되었으며, v1.2에서는 기존 v1alpha2 버전이 완전히 제거되었습니다.

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: order-service-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https-wildcard
  hostnames:
    - 'grpc.example.com'
  rules:
    # 서비스 기반 매칭
    - matches:
        - method:
            service: 'order.v1.OrderService'
      backendRefs:
        - name: order-service-grpc
          port: 50051
    # 메서드 기반 매칭
    - matches:
        - method:
            service: 'order.v1.OrderService'
            method: 'CreateOrder'
      backendRefs:
        - name: order-write-service
          port: 50051
    # 헤더 기반 매칭
    - matches:
        - headers:
            - name: x-region
              value: 'asia'
          method:
            service: 'order.v1.OrderService'
      backendRefs:
        - name: order-service-asia
          port: 50051

GRPCRoute 트래픽 분할

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-canary
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'grpc.example.com'
  rules:
    - matches:
        - method:
            service: 'payment.v1.PaymentService'
      backendRefs:
        - name: payment-service-stable
          port: 50051
          weight: 95
        - name: payment-service-canary
          port: 50051
          weight: 5

TLS 종료와 인증서 관리

cert-manager 연동

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          gatewayHTTPRoute:
            parentRefs:
              - name: production-gateway
                namespace: infra-gateway
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-tls
  namespace: infra-gateway
spec:
  secretName: wildcard-example-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - '*.example.com'
    - 'example.com'

mTLS 설정 (Envoy Gateway BackendTLSPolicy)

apiVersion: gateway.networking.k8s.io/v1alpha3
kind: BackendTLSPolicy
metadata:
  name: backend-mtls
  namespace: production
spec:
  targetRefs:
    - group: ''
      kind: Service
      name: secure-backend
  validation:
    caCertificateRefs:
      - name: backend-ca-cert
        group: ''
        kind: ConfigMap
    hostname: secure-backend.production.svc.cluster.local

Envoy Gateway 레이트 리미팅

글로벌 레이트 리미팅

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: global-rate-limit
  namespace: infra-gateway
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: production-gateway
  rateLimit:
    type: Global
    global:
      rules:
        - clientSelectors:
            - headers:
                - name: x-api-key
                  type: Distinct
          limit:
            requests: 100
            unit: Minute
        - limit:
            requests: 1000
            unit: Minute

경로별 레이트 리미팅

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: api-rate-limit
  namespace: production
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: api-route
  rateLimit:
    type: Global
    global:
      rules:
        - clientSelectors:
            - headers:
                - name: x-user-tier
                  value: 'free'
          limit:
            requests: 10
            unit: Minute
        - clientSelectors:
            - headers:
                - name: x-user-tier
                  value: 'premium'
          limit:
            requests: 1000
            unit: Minute

ReferenceGrant를 통한 크로스 네임스페이스 라우팅

# app-team-a 네임스페이스의 HTTPRoute가
# shared-services 네임스페이스의 Service를 참조할 수 있도록 허용
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-cross-ns-routing
  namespace: shared-services
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-team-a
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-team-b
  to:
    - group: ''
      kind: Service

Ingress에서 Gateway API로 마이그레이션

ingress2gateway 도구 활용

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

# 기존 Ingress 리소스를 Gateway API로 변환
ingress2gateway print \
  --input-file existing-ingress.yaml \
  --providers ingress-nginx \
  --all-resources

# 클러스터에서 직접 변환
ingress2gateway print \
  --providers ingress-nginx \
  --all-resources \
  --namespace production

마이그레이션 전략: 병렬 운영

# 기존 Ingress (유지)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: legacy-app-ingress
  namespace: production
  annotations:
    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: /
            pathType: Prefix
            backend:
              service:
                name: app-service
                port:
                  number: 8080
---
# 새로운 Gateway API HTTPRoute (병렬 배포)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: app-service
          port: 8080

단계별 마이그레이션 절차

# 1단계: Gateway API CRD 설치 확인
kubectl get crd gateways.gateway.networking.k8s.io

# 2단계: Envoy Gateway 설치
helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.0 \
  -n envoy-gateway-system --create-namespace

# 3단계: GatewayClass/Gateway 생성
kubectl apply -f gateway-class.yaml
kubectl apply -f gateway.yaml

# 4단계: 기존 Ingress를 HTTPRoute로 변환
ingress2gateway print --providers ingress-nginx --all-resources > routes.yaml

# 5단계: 변환된 HTTPRoute 배포 (병렬 운영)
kubectl apply -f routes.yaml

# 6단계: DNS를 새 Gateway LoadBalancer로 전환
GATEWAY_IP=$(kubectl get gateway production-gateway -n infra-gateway \
  -o jsonpath='{.status.addresses[0].value}')
echo "Update DNS A record to: $GATEWAY_IP"

# 7단계: 트래픽 모니터링 후 기존 Ingress 제거
kubectl delete ingress legacy-app-ingress -n production

모니터링 구성

Prometheus 메트릭 수집

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: envoy-gateway-metrics
  namespace: envoy-gateway-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: envoy
  endpoints:
    - port: metrics
      interval: 15s
      path: /stats/prometheus
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: gateway-api-alerts
  namespace: monitoring
spec:
  groups:
    - name: gateway-api
      rules:
        - alert: GatewayHighErrorRate
          expr: |
            sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m])) by (envoy_http_conn_manager_prefix)
            /
            sum(rate(envoy_http_downstream_rq_total[5m])) by (envoy_http_conn_manager_prefix)
            > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: 'Gateway error rate exceeds 5%'
        - alert: GatewayHighLatency
          expr: |
            histogram_quantile(0.99,
              sum(rate(envoy_http_downstream_rq_time_bucket[5m])) by (le, envoy_http_conn_manager_prefix)
            ) > 1000
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: 'Gateway p99 latency exceeds 1s'

Grafana 대시보드 쿼리

# 주요 Envoy 메트릭 확인
# 초당 요청 수
sum(rate(envoy_http_downstream_rq_total[5m])) by (envoy_http_conn_manager_prefix)

# 에러율 (5xx)
sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m]))

# 평균 응답 시간
sum(rate(envoy_http_downstream_rq_time_sum[5m])) / sum(rate(envoy_http_downstream_rq_time_count[5m]))

# 활성 연결 수
envoy_http_downstream_cx_active

운영 시 주의사항

1. 리소스 제한 설정

프로덕션 환경에서는 반드시 Envoy Proxy의 리소스 제한을 설정해야 합니다. 기본값으로 운영하면 트래픽 급증 시 OOM(Out of Memory)이 발생할 수 있습니다.

2. Gateway 리스너 제한

하나의 Gateway에 너무 많은 리스너를 추가하면 Envoy 설정이 비대해져 리로드 시간이 길어집니다. 도메인별 또는 팀별로 Gateway를 분리하는 것을 권장합니다.

3. ReferenceGrant 최소 권한

크로스 네임스페이스 참조는 필요한 경우에만 허용하고, 대상 네임스페이스와 리소스를 최대한 구체적으로 지정해야 합니다.

4. HTTPRoute 우선순위

여러 HTTPRoute가 동일한 경로를 매칭할 때 우선순위 규칙을 반드시 이해해야 합니다:

  1. 가장 긴 호스트네임이 우선
  2. 가장 긴 경로가 우선
  3. Exact 매칭이 PathPrefix보다 우선
  4. 동일 조건이면 생성 시간이 빠른 리소스 우선

5. v1alpha2 제거 대응

Gateway API v1.2에서는 GRPCRoute와 ReferenceGrant의 v1alpha2 버전이 제거되었습니다. 기존에 v1alpha2를 사용하고 있다면 반드시 v1으로 마이그레이션해야 합니다.

장애 사례 및 복구 절차

사례 1: TLS 인증서 만료

# 증상: 503 에러, TLS handshake 실패
# 진단
kubectl get certificate -n infra-gateway
kubectl describe certificate wildcard-example-tls -n infra-gateway

# 복구: cert-manager 강제 갱신
kubectl delete certificaterequest -n infra-gateway --all
kubectl annotate certificate wildcard-example-tls \
  -n infra-gateway \
  cert-manager.io/renew-before="720h" --overwrite

# 긴급 시: 수동 인증서 교체
kubectl create secret tls wildcard-example-tls \
  --cert=fullchain.pem --key=privkey.pem \
  -n infra-gateway --dry-run=client -o yaml | kubectl apply -f -

사례 2: Gateway 컨트롤러 장애

# 증상: 새 HTTPRoute가 적용되지 않음
# 진단
kubectl get pods -n envoy-gateway-system
kubectl logs -n envoy-gateway-system deploy/envoy-gateway -f

# Envoy Proxy 상태 확인
kubectl get pods -l app.kubernetes.io/name=envoy -A

# 복구: 컨트롤러 재시작
kubectl rollout restart deployment/envoy-gateway -n envoy-gateway-system

# Gateway 상태 확인
kubectl get gateway production-gateway -n infra-gateway -o yaml

사례 3: 잘못된 HTTPRoute로 인한 트래픽 블랙홀

# 증상: 특정 경로로의 요청이 모두 404
# 진단: HTTPRoute 상태 확인
kubectl get httproute -A
kubectl describe httproute app-route -n production

# 상태에서 Accepted/ResolvedRefs 조건 확인
# 원인: backendRef가 존재하지 않는 서비스를 가리킴

# 복구: 올바른 서비스명으로 수정 후 재적용
kubectl apply -f corrected-httproute.yaml

# Envoy 설정 동기화 확인
kubectl exec -n envoy-gateway-system deploy/envoy-gateway -- \
  curl -s localhost:19000/config_dump | python3 -m json.tool | head -100

사례 4: 레이트 리미팅 오작동

# 증상: 정상 사용자도 429 Too Many Requests 발생
# 진단
kubectl get backendtrafficpolicy -A
kubectl describe backendtrafficpolicy global-rate-limit -n infra-gateway

# Redis 기반 글로벌 레이트 리미터 상태 확인
kubectl logs -n envoy-gateway-system deploy/envoy-ratelimit

# 복구: 레이트 리미팅 정책 일시 제거
kubectl delete backendtrafficpolicy global-rate-limit -n infra-gateway

# 정상화 후 수정된 정책 재적용
kubectl apply -f corrected-rate-limit.yaml

마치며

Kubernetes Gateway API는 Ingress의 한계를 극복하고, 역할 기반의 체계적인 트래픽 관리를 가능하게 합니다. v1.2에서 HTTPRoute와 GRPCRoute가 모두 GA로 안정화되었고, Ingress NGINX의 리타이어먼트가 임박한 지금이 마이그레이션의 적기입니다.

Envoy Gateway를 구현체로 선택하면 레이트 리미팅, 인증, 글로벌 로드밸런싱 등 Envoy의 강력한 기능을 Gateway API의 표준 인터페이스로 활용할 수 있습니다. 병렬 운영 전략으로 안전하게 마이그레이션하고, 모니터링과 알림을 반드시 함께 구성하여 프로덕션 안정성을 확보하시기 바랍니다.

참고자료

Kubernetes Gateway API Production Guide: From Ingress Migration to HTTPRoute, GRPCRoute, and Envoy Gateway Deployment

Kubernetes Gateway API Production Guide

Introduction

Kubernetes Ingress has long been the standard method for routing external traffic into the cluster. However, its limitations have been clear: dependency on implementation-specific annotations, all configuration concentrated in a single resource, and lack of support for non-HTTP protocols like gRPC and TCP. The Kubernetes community designed the Gateway API to address these issues, and as of v1.2, both HTTPRoute and GRPCRoute are GA (Generally Available) and production-ready.

With the official retirement of Ingress NGINX announced (best-effort maintenance only after March 2026), migrating to Gateway API has become a necessity rather than an option. This guide covers everything needed for production deployment using Envoy Gateway as the implementation: HTTPRoute, GRPCRoute, TLS termination, traffic splitting, header-based routing, rate limiting, and monitoring, all with practical code examples.

Gateway API vs Ingress Comparison

Architectural Differences

Gateway API is designed with a role-oriented resource model that clearly separates concerns between infrastructure operators and application developers.

ComparisonIngressGateway API
Resource ModelSingle Ingress resourceGatewayClass / Gateway / Route separation
Role SeparationNot possibleInfra team / Platform team / Dev team
Protocol SupportHTTP/HTTPS onlyHTTP, gRPC, TCP, UDP, TLS
ConfigurationImplementation-specific annotationsStandardized spec
Cross-namespaceNot supportedSupported via ReferenceGrant
Traffic SplittingAnnotation-dependentNative weight-based
Header MatchingVaries by implementationIncluded in standard spec
GA StatusStable but retiringv1.2 GA (HTTPRoute, GRPCRoute)

Resource Hierarchy

Let us examine the 3-tier resource model of Gateway API.

# Tier 1: GatewayClass - Managed by infrastructure provider (cluster-scoped)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
# Tier 2: Gateway - Managed by platform team (namespace-scoped)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra-gateway
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls-cert
    - name: http
      protocol: HTTP
      port: 80
---
# Tier 3: HTTPRoute - Managed by dev teams (per application namespace)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: app-team-a
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https
  hostnames:
    - 'api.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1/users
      backendRefs:
        - name: user-service
          port: 8080

Envoy Gateway Installation and Configuration

Installation via Helm

# Install Envoy Gateway Helm chart
helm install eg oci://docker.io/envoyproxy/gateway-helm \
  --version v1.3.0 \
  -n envoy-gateway-system \
  --create-namespace \
  --set config.envoyGateway.logging.level.default=info

# Verify installation
kubectl get pods -n envoy-gateway-system
kubectl get gatewayclass

# Verify CRDs
kubectl get crd | grep gateway.networking.k8s.io

GatewayClass and Gateway Creation

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-production
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
  parametersRef:
    group: gateway.envoyproxy.io
    kind: EnvoyProxy
    name: production-config
    namespace: envoy-gateway-system
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: production-config
  namespace: envoy-gateway-system
spec:
  provider:
    type: Kubernetes
    kubernetes:
      envoyDeployment:
        replicas: 3
        pod:
          annotations:
            prometheus.io/scrape: 'true'
            prometheus.io/port: '19001'
        container:
          resources:
            requests:
              cpu: '500m'
              memory: '512Mi'
            limits:
              cpu: '2000m'
              memory: '2Gi'
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: infra-gateway
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: envoy-production
  listeners:
    - name: https-wildcard
      protocol: HTTPS
      port: 443
      hostname: '*.example.com'
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-tls
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: 'true'
    - name: http-redirect
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same

HTTPRoute In Depth

Path-Based Routing

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-routes
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https-wildcard
  hostnames:
    - 'app.example.com'
  rules:
    # Exact match - highest priority
    - matches:
        - path:
            type: Exact
            value: /healthz
      backendRefs:
        - name: health-check-service
          port: 8080
    # PathPrefix match - API version routing
    - matches:
        - path:
            type: PathPrefix
            value: /api/v2
      backendRefs:
        - name: api-v2-service
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /api/v1
      backendRefs:
        - name: api-v1-service
          port: 8080
    # RegularExpression match
    - matches:
        - path:
            type: RegularExpression
            value: '/users/[0-9]+/profile'
      backendRefs:
        - name: user-profile-service
          port: 8080

Header-Based Routing

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: header-based-routing
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'api.example.com'
  rules:
    # Route to different backends based on header values
    - matches:
        - headers:
            - name: x-api-version
              value: 'beta'
            - name: x-user-tier
              value: 'premium'
      backendRefs:
        - name: api-beta-premium
          port: 8080
    # A/B testing via header-based routing
    - matches:
        - headers:
            - name: x-experiment-group
              value: 'treatment'
      backendRefs:
        - name: api-experiment
          port: 8080
    # Default routing (no match conditions)
    - backendRefs:
        - name: api-stable
          port: 8080

Traffic Splitting (Canary Deployment)

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

HTTP Redirects and URL Rewrites

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-and-rewrite
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'app.example.com'
  rules:
    # HTTP -> HTTPS redirect
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: url-rewrite
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'api.example.com'
  rules:
    # /old-api/* -> /new-api/* path rewrite
    - matches:
        - path:
            type: PathPrefix
            value: /old-api
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /new-api
      backendRefs:
        - name: api-service
          port: 8080

GRPCRoute Production Configuration

Basic GRPCRoute Setup

GRPCRoute was promoted to GA in Gateway API v1.1, and in v1.2 the legacy v1alpha2 version was completely removed.

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: order-service-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
      sectionName: https-wildcard
  hostnames:
    - 'grpc.example.com'
  rules:
    # Service-based matching
    - matches:
        - method:
            service: 'order.v1.OrderService'
      backendRefs:
        - name: order-service-grpc
          port: 50051
    # Method-based matching
    - matches:
        - method:
            service: 'order.v1.OrderService'
            method: 'CreateOrder'
      backendRefs:
        - name: order-write-service
          port: 50051
    # Header-based matching
    - matches:
        - headers:
            - name: x-region
              value: 'asia'
          method:
            service: 'order.v1.OrderService'
      backendRefs:
        - name: order-service-asia
          port: 50051

GRPCRoute Traffic Splitting

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-canary
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'grpc.example.com'
  rules:
    - matches:
        - method:
            service: 'payment.v1.PaymentService'
      backendRefs:
        - name: payment-service-stable
          port: 50051
          weight: 95
        - name: payment-service-canary
          port: 50051
          weight: 5

TLS Termination and Certificate Management

cert-manager Integration

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          gatewayHTTPRoute:
            parentRefs:
              - name: production-gateway
                namespace: infra-gateway
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-tls
  namespace: infra-gateway
spec:
  secretName: wildcard-example-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - '*.example.com'
    - 'example.com'

mTLS Configuration (Envoy Gateway BackendTLSPolicy)

apiVersion: gateway.networking.k8s.io/v1alpha3
kind: BackendTLSPolicy
metadata:
  name: backend-mtls
  namespace: production
spec:
  targetRefs:
    - group: ''
      kind: Service
      name: secure-backend
  validation:
    caCertificateRefs:
      - name: backend-ca-cert
        group: ''
        kind: ConfigMap
    hostname: secure-backend.production.svc.cluster.local

Envoy Gateway Rate Limiting

Global Rate Limiting

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: global-rate-limit
  namespace: infra-gateway
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: production-gateway
  rateLimit:
    type: Global
    global:
      rules:
        - clientSelectors:
            - headers:
                - name: x-api-key
                  type: Distinct
          limit:
            requests: 100
            unit: Minute
        - limit:
            requests: 1000
            unit: Minute

Per-Route Rate Limiting

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: api-rate-limit
  namespace: production
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: api-route
  rateLimit:
    type: Global
    global:
      rules:
        - clientSelectors:
            - headers:
                - name: x-user-tier
                  value: 'free'
          limit:
            requests: 10
            unit: Minute
        - clientSelectors:
            - headers:
                - name: x-user-tier
                  value: 'premium'
          limit:
            requests: 1000
            unit: Minute

Cross-Namespace Routing with ReferenceGrant

# Allow HTTPRoutes in app-team-a namespace to reference
# Services in shared-services namespace
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-cross-ns-routing
  namespace: shared-services
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-team-a
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: app-team-b
  to:
    - group: ''
      kind: Service

Migrating from Ingress to Gateway API

Using the ingress2gateway Tool

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

# Convert existing Ingress resources to Gateway API
ingress2gateway print \
  --input-file existing-ingress.yaml \
  --providers ingress-nginx \
  --all-resources

# Convert directly from cluster
ingress2gateway print \
  --providers ingress-nginx \
  --all-resources \
  --namespace production

Migration Strategy: Parallel Operation

# Existing Ingress (keep running)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: legacy-app-ingress
  namespace: production
  annotations:
    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: /
            pathType: Prefix
            backend:
              service:
                name: app-service
                port:
                  number: 8080
---
# New Gateway API HTTPRoute (parallel deployment)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: production
spec:
  parentRefs:
    - name: production-gateway
      namespace: infra-gateway
  hostnames:
    - 'app.example.com'
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: app-service
          port: 8080

Step-by-Step Migration Procedure

# Step 1: Verify Gateway API CRDs are installed
kubectl get crd gateways.gateway.networking.k8s.io

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

# Step 3: Create GatewayClass and Gateway
kubectl apply -f gateway-class.yaml
kubectl apply -f gateway.yaml

# Step 4: Convert existing Ingress to HTTPRoute
ingress2gateway print --providers ingress-nginx --all-resources > routes.yaml

# Step 5: Deploy converted HTTPRoutes (parallel operation)
kubectl apply -f routes.yaml

# Step 6: Switch DNS to new Gateway LoadBalancer
GATEWAY_IP=$(kubectl get gateway production-gateway -n infra-gateway \
  -o jsonpath='{.status.addresses[0].value}')
echo "Update DNS A record to: $GATEWAY_IP"

# Step 7: Monitor traffic then remove legacy Ingress
kubectl delete ingress legacy-app-ingress -n production

Monitoring Setup

Prometheus Metrics Collection

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: envoy-gateway-metrics
  namespace: envoy-gateway-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: envoy
  endpoints:
    - port: metrics
      interval: 15s
      path: /stats/prometheus
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: gateway-api-alerts
  namespace: monitoring
spec:
  groups:
    - name: gateway-api
      rules:
        - alert: GatewayHighErrorRate
          expr: |
            sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m])) by (envoy_http_conn_manager_prefix)
            /
            sum(rate(envoy_http_downstream_rq_total[5m])) by (envoy_http_conn_manager_prefix)
            > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: 'Gateway error rate exceeds 5%'
        - alert: GatewayHighLatency
          expr: |
            histogram_quantile(0.99,
              sum(rate(envoy_http_downstream_rq_time_bucket[5m])) by (le, envoy_http_conn_manager_prefix)
            ) > 1000
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: 'Gateway p99 latency exceeds 1s'

Grafana Dashboard Queries

# Key Envoy metrics
# Requests per second
sum(rate(envoy_http_downstream_rq_total[5m])) by (envoy_http_conn_manager_prefix)

# Error rate (5xx)
sum(rate(envoy_http_downstream_rq_xx{envoy_response_code_class="5"}[5m]))

# Average response time
sum(rate(envoy_http_downstream_rq_time_sum[5m])) / sum(rate(envoy_http_downstream_rq_time_count[5m]))

# Active connections
envoy_http_downstream_cx_active

Operational Notes

1. Resource Limits Configuration

In production environments, you must configure resource limits for Envoy Proxy. Running with defaults can lead to OOM (Out of Memory) during traffic spikes.

2. Gateway Listener Limits

Adding too many listeners to a single Gateway causes the Envoy configuration to become bloated, increasing reload times. It is recommended to separate Gateways by domain or team.

3. ReferenceGrant Least Privilege

Cross-namespace references should only be allowed when necessary, and target namespaces and resources should be specified as precisely as possible.

4. HTTPRoute Priority

When multiple HTTPRoutes match the same path, you must understand the priority rules:

  1. Longest hostname wins
  2. Longest path wins
  3. Exact match takes precedence over PathPrefix
  4. For identical conditions, the earliest created resource wins

5. v1alpha2 Removal Handling

Gateway API v1.2 removed the v1alpha2 versions of GRPCRoute and ReferenceGrant. If you are using v1alpha2, you must migrate to v1.

Failure Cases and Recovery Procedures

Case 1: TLS Certificate Expiration

# Symptom: 503 errors, TLS handshake failures
# Diagnosis
kubectl get certificate -n infra-gateway
kubectl describe certificate wildcard-example-tls -n infra-gateway

# Recovery: Force cert-manager renewal
kubectl delete certificaterequest -n infra-gateway --all
kubectl annotate certificate wildcard-example-tls \
  -n infra-gateway \
  cert-manager.io/renew-before="720h" --overwrite

# Emergency: Manual certificate replacement
kubectl create secret tls wildcard-example-tls \
  --cert=fullchain.pem --key=privkey.pem \
  -n infra-gateway --dry-run=client -o yaml | kubectl apply -f -

Case 2: Gateway Controller Failure

# Symptom: New HTTPRoutes not being applied
# Diagnosis
kubectl get pods -n envoy-gateway-system
kubectl logs -n envoy-gateway-system deploy/envoy-gateway -f

# Check Envoy Proxy status
kubectl get pods -l app.kubernetes.io/name=envoy -A

# Recovery: Restart controller
kubectl rollout restart deployment/envoy-gateway -n envoy-gateway-system

# Verify Gateway status
kubectl get gateway production-gateway -n infra-gateway -o yaml

Case 3: Traffic Black Hole from Invalid HTTPRoute

# Symptom: All requests to a specific path return 404
# Diagnosis: Check HTTPRoute status
kubectl get httproute -A
kubectl describe httproute app-route -n production

# Check Accepted/ResolvedRefs conditions in status
# Cause: backendRef points to a non-existent service

# Recovery: Fix service name and reapply
kubectl apply -f corrected-httproute.yaml

# Verify Envoy config sync
kubectl exec -n envoy-gateway-system deploy/envoy-gateway -- \
  curl -s localhost:19000/config_dump | python3 -m json.tool | head -100

Case 4: Rate Limiting Malfunction

# Symptom: Normal users receiving 429 Too Many Requests
# Diagnosis
kubectl get backendtrafficpolicy -A
kubectl describe backendtrafficpolicy global-rate-limit -n infra-gateway

# Check Redis-based global rate limiter status
kubectl logs -n envoy-gateway-system deploy/envoy-ratelimit

# Recovery: Temporarily remove rate limiting policy
kubectl delete backendtrafficpolicy global-rate-limit -n infra-gateway

# Reapply corrected policy after stabilization
kubectl apply -f corrected-rate-limit.yaml

Conclusion

Kubernetes Gateway API overcomes the limitations of Ingress and enables systematic role-based traffic management. With HTTPRoute and GRPCRoute both GA in v1.2, and the retirement of Ingress NGINX imminent, now is the ideal time to migrate.

Choosing Envoy Gateway as the implementation gives you access to powerful Envoy features like rate limiting, authentication, and global load balancing through the standardized Gateway API interface. Use a parallel operation strategy for safe migration, and always configure monitoring and alerting to ensure production stability.

References