Kubernetes Gateway API로 Ingress를 대체하기 — HTTPRoute·GRPCRoute·ReferenceGrant로 트래픽 라우팅을 선언적으로 제어하는 실전 가이드
nginx.ingress.kubernetes.io/rewrite-target: /$1 — 이 어노테이션, 익숙하시죠?
처음 Kubernetes를 배울 때 저도 이 한 줄을 이해하는 데 꽤 오래 걸렸습니다. Ingress는 기능이 아니라 어노테이션을 외우는 싸움이었습니다. 더 큰 문제는 그게 NGINX Ingress에서만 통한다는 점이었죠. Contour로 갈아타면 어노테이션이 완전히 달라졌고, Traefik은 또 다른 방언을 썼습니다. "쿠버네티스 표준"이라는 이름 아래 사실상 구현체마다 다른 DSL을 쓰고 있었던 셈입니다.
2026년 3월, NGINX Ingress Controller가 공식 deprecated됐습니다. 상당수 클러스터에 배포돼 있던 컨트롤러가 보안 결함과 설계 한계를 이유로 마이그레이션을 권고받은 것입니다. 이 사건은 단순한 deprecated 공지가 아니라, Kubernetes 커뮤니티가 Ingress를 더 이상 확장하지 않겠다는 명확한 신호였습니다. 그리고 그 대안으로 자리잡은 것이 바로 Gateway API입니다.
이 글에서는 HTTPRoute·GRPCRoute·ReferenceGrant 세 리소스를 실제 YAML과 함께 살펴보고, 카나리 배포와 크로스 네임스페이스 서비스 공유 같은 실무 시나리오에 어떻게 적용하는지 다룹니다. 구현체 선택 기준, 마이그레이션 전략, 그리고 단점도 빠짐없이 짚어봅니다.
Standard vs Experimental: 두 채널 먼저 이해하기
Gateway API의 모든 리소스는 두 채널 중 하나에 속합니다. 어느 채널에 있는지에 따라 프로덕션 도입 가능 여부가 달라지므로 먼저 짚고 가겠습니다.
| 채널 | 의미 | 대표 리소스 |
|---|---|---|
| Standard | API 안정성 보장, 장기 지원, 프로덕션 사용 권장 | HTTPRoute, GRPCRoute, Gateway, GatewayClass, ReferenceGrant |
| Experimental | API 변경 가능, 프로덕션 주의 | TCPRoute, TLSRoute, UDPRoute, BackendLBPolicy |
HTTP·HTTPS·gRPC 트래픽에 필요한 리소스는 모두 Standard 채널에 있습니다. TCP, TLS, UDP 라우팅은 아직 Experimental이라 프로덕션에서 쓸 때는 구현체 지원 현황과 API 안정성을 별도로 확인해야 합니다.
핵심 개념
GatewayClass · Gateway · Route: 역할 중심 3계층 모델
Gateway API의 핵심 설계 철학은 역할 중심 분리입니다. Ingress가 단일 리소스에 L7 라우팅 규칙, TLS 설정, 구현체별 확장까지 모두 욱여넣었던 것과 달리, Gateway API는 세 계층으로 책임을 나눕니다.
- GatewayClass: "어떤 컨트롤러를 쓸 것인가"를 정의합니다. 인프라팀이 Envoy Gateway를 선택하거나, 클라우드 팀이 AWS Load Balancer Controller를 선택하는 의사결정이 여기에 담깁니다.
- Gateway: "어디서 어떻게 트래픽을 받을 것인가"를 정의합니다. 포트, 프로토콜, TLS 설정이 여기 있습니다. 클러스터 운영팀이 관리합니다.
- Route 리소스: "들어온 트래픽을 어느 서비스로 보낼 것인가"를 정의합니다. 각 애플리케이션팀이 자신의 네임스페이스에서 독립적으로 관리합니다.
이 구조는 멀티테넌트 환경에서 RBAC과 자연스럽게 맞아떨어집니다. 앱팀은 Gateway를 건드릴 권한이 없어도 HTTPRoute는 자유롭게 수정할 수 있고, 운영팀은 앱팀의 Route 설정에 개입하지 않아도 됩니다.
# 인프라팀이 관리: 어떤 컨트롤러를 쓸지 결정
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-gateway
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controllerGateway의 allowedRoutes 설정은 어떤 네임스페이스의 Route가 이 Gateway에 붙을 수 있는지를 제어합니다.
# 클러스터 운영팀이 관리: 로드밸런서와 리스너 설정
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra
spec:
gatewayClassName: envoy-gateway
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: prod-tls-cert
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"위 예시에서 Selector 모드를 사용하면, gateway-access: "true" 레이블이 붙은 네임스페이스의 Route만 이 Gateway에 연결됩니다. 앱팀 네임스페이스에 해당 레이블을 추가해야 동작합니다.
kubectl label namespace team-a gateway-access=true주의 —
allowedRoutes기본값은Same입니다. 이 필드를 설정하지 않으면 Gateway와 같은 네임스페이스의 Route만 붙을 수 있습니다. 멀티테넌트 구조에서 Route가 아무리 잘 선언돼 있어도 연결되지 않는다면 이 설정을 먼저 확인해보세요.
HTTPRoute: 어노테이션 없는 HTTP 라우팅
HTTPRoute는 Gateway API의 핵심 리소스입니다. parentRefs로 어느 Gateway에 붙을지, hostnames로 Host 헤더 매칭, rules로 실제 라우팅 규칙을 선언합니다. 아래 예시는 경로 기반 라우팅과 URL 재작성을 함께 보여줍니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-route
namespace: team-a
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2/users
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /users
backendRefs:
- name: user-service
port: 8080Ingress에서 nginx.ingress.kubernetes.io/rewrite-target으로 해결했던 것을 이제 URLRewrite 필터로 명시적으로 선언합니다. 어노테이션 이름을 외울 필요 없이, 구조화된 YAML 필드로 의도가 명확하게 드러납니다.
GRPCRoute: gRPC 서비스를 위한 전용 라우팅
gRPC 라우팅은 Ingress 시대에 꽤 골치 아팠습니다. HTTP/2 업그레이드 협상 문제, /패키지명.서비스명/메서드명 형태의 경로를 수동으로 정규식 매칭해야 하는 번거로움이 있었죠. GRPCRoute는 이를 gRPC 네이티브 방식으로 해결합니다. v1.1에서 Standard 채널로 승격됐으니 프로덕션에서 바로 사용할 수 있습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: payment-grpc-route
namespace: team-payment
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "grpc.example.com"
rules:
- matches:
- method:
type: Exact
service: payment.PaymentService
method: ProcessPayment
backendRefs:
# ProcessPayment는 우선 처리 전용 서버로 분기
- name: payment-priority-service
port: 50051
- matches:
- method:
type: Exact
service: payment.PaymentService
# method 생략 시 PaymentService의 모든 메서드에 매칭
backendRefs:
# 나머지 PaymentService 메서드는 일반 서버로
- name: payment-service
port: 50051service와 method 필드로 gRPC 의도를 직접 표현할 수 있습니다. HTTP/2를 기본 전제로 하기 때문에 업그레이드 협상 설정을 별도로 신경 쓸 필요가 없습니다.
두 번째 규칙처럼 method를 생략하면 해당 서비스의 모든 메서드에 매칭됩니다. 위 예시에서 ProcessPayment 요청은 더 구체적인 첫 번째 규칙의 payment-priority-service로 분기되고, 나머지 PaymentService 메서드는 두 번째 규칙의 payment-service로 처리됩니다.
ReferenceGrant: 크로스 네임스페이스 안전 참조
팀A의 HTTPRoute가 팀B의 Service를 직접 참조하고 싶을 때 어떻게 될까요? Gateway API는 기본적으로 이를 차단합니다. 단방향 참조만으로는 권한이 없으며, 양쪽의 명시적 동의가 필요합니다. 이것이 ReferenceGrant의 역할입니다.
ReferenceGrant는 Standard 채널에 속하지만 apiVersion: gateway.networking.k8s.io/v1beta1을 사용합니다. 채널과 apiVersion은 별개 개념입니다. Standard 채널은 API의 안정성 보장을 의미하며, v1beta1이라는 버전 표기는 그와 독립적으로 유지될 수 있습니다.
# shared-services 네임스페이스에 배치
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-team-a
namespace: shared-services
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-a
to:
- group: ""
kind: Service
name: auth-service"내 네임스페이스 리소스를 다른 팀이 참조하도록 허용한다"는 명시적 선언이 있어야만 동작하는 구조입니다. 이 ReferenceGrant가 없으면 다이어그램의 실패 경로처럼 컨트롤러가 조용히 트래픽 전달을 차단합니다.
주의 — 트래픽이 전달되지 않을 때 디버깅 방법:
kubectl get httproute <name> -o yaml로.status.parents[].conditions필드를 확인해보세요. ReferenceGrant 부재나 allowedRoutes 불일치 같은 거부 이유가 상태 필드에 기록됩니다. 컨트롤러가 별도 에러 로그 없이 조용히 거부하는 경우가 많아 이 필드가 디버깅의 첫 번째 단서가 됩니다.
실전 적용
가중치 기반 카나리 배포
신버전 배포 시 가장 많이 쓰는 패턴입니다. HTTPRoute의 backendRefs 안에 weight 필드를 사용하면 트래픽 비율을 선언적으로 제어할 수 있습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-canary
namespace: team-a
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: my-app-v1
port: 8080
weight: 90
- name: my-app-v2
port: 8080
weight: 10Argo Rollouts와 연동하면 이 weight를 자동으로 조정할 수 있습니다. gatewayAPI 플러그인이 25% → 50% → 75% → 100% 스텝으로 자동 조정하고, 오류 감지 시 즉시 rollback합니다. GitOps 워크플로와도 자연스럽게 맞아떨어집니다.
헤더 기반 카나리 테스트
실 사용자 트래픽 없이 QA 팀만 신버전을 테스트하고 싶을 때 유용한 패턴입니다. traffic: test 헤더가 있는 요청만 v2로 라우팅하고, 나머지는 v1으로 유지합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-header-canary
namespace: team-a
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "app.example.com"
rules:
- matches:
- headers:
- name: traffic
value: test
backendRefs:
- name: my-app-v2
port: 8080
- backendRefs:
- name: my-app-v1
port: 8080Gateway API 스펙은 더 구체적인 매칭이 자동으로 우선순위를 갖도록 정의하고 있습니다. 헤더 조건이 추가된 규칙이 경로만 있는 규칙보다 더 구체적이므로, traffic: test 헤더가 있는 요청은 스펙의 우선순위 규칙에 의해 자동으로 v2로 라우팅됩니다. 규칙 순서에 의존하는 것이 아니라 매칭의 구체성이 이 동작을 보장합니다.
gRPC 멀티서비스 라우팅
동일한 Gateway 뒤에서 여러 gRPC 서비스를 팀별로 분리해 라우팅하는 시나리오입니다.
# team-order 네임스페이스 — 주문팀 소유
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: order-grpc-route
namespace: team-order
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "grpc.example.com"
rules:
- matches:
- method:
type: Exact
service: order.OrderService
# method 생략 시 OrderService의 모든 메서드에 매칭
backendRefs:
- name: order-service
port: 50051결제팀(team-payment)과 주문팀(team-order)이 각각 자신의 네임스페이스에서 GRPCRoute를 관리하고, 동일한 Gateway를 공유합니다. 팀별 독립성과 공유 인프라의 장점을 동시에 누릴 수 있는 구조입니다.
크로스 네임스페이스 공유 서비스
공통 인증 서비스가 shared-services 네임스페이스에 있고, 여러 팀이 이를 백엔드로 활용하는 시나리오입니다.
# team-a 네임스페이스의 HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: team-a-route
namespace: team-a
spec:
parentRefs:
- name: prod-gateway
namespace: infra
rules:
- matches:
- path:
type: PathPrefix
value: /auth
backendRefs:
- name: auth-service
namespace: shared-services
port: 8080
group: ""
kind: Service# shared-services 네임스페이스의 ReferenceGrant
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-teams
namespace: shared-services
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-a
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-b
to:
- group: ""
kind: Service
name: auth-servicefrom 배열에 여러 네임스페이스를 명시할 수 있습니다. 새로운 팀이 auth-service를 사용하려면 shared-services 팀에게 ReferenceGrant 추가를 요청하는 워크플로가 자연스럽게 형성됩니다.
마이그레이션 전략: Ingress에서 Gateway API로
Ingress2Gateway로 현황 파악하기
kubectl-ingress2gateway는 기존 Ingress를 Gateway API 리소스로 자동 변환해주는 공식 도구입니다. 아래 명령으로 현재 클러스터의 Ingress를 HTTPRoute로 변환한 결과를 미리 볼 수 있습니다.
kubectl ingress2gateway print단, 자동 변환이 가능한 항목은 제한적입니다. 변환되지 않는 항목이 실제 마이그레이션 범위의 핵심이 됩니다.
| 자동 변환 가능 | 수동 작업 필요 |
|---|---|
| 호스트 기반 라우팅 | nginx.ingress.kubernetes.io/limit-rps (Rate Limiting) |
| 경로 기반 라우팅 | nginx.ingress.kubernetes.io/auth-url (외부 인증 연동) |
| TLS 종료 | nginx.ingress.kubernetes.io/configuration-snippet (Lua/NGINX 커스텀 설정) |
| 기본 백엔드 설정 | 정규식 기반 경로 재작성 어노테이션 |
NGINX 전용 어노테이션을 많이 쓰고 있다면 변환 불가 항목이 상당수 나올 수 있습니다. 이 항목들은 구현체의 Policy API(예: Envoy Gateway의 BackendTrafficPolicy)나 ExtensionRef 메커니즘으로 대체해야 합니다.
점진적 전환 접근법
기존 Ingress를 당장 교체하지 않더라도 두 API는 같은 클러스터에서 공존할 수 있습니다. 신규 서비스는 HTTPRoute나 GRPCRoute로 시작하고, 기존 서비스는 Ingress를 유지하면서 팀 단위로 순차 전환하는 것이 현실적입니다. 조직 규모와 어노테이션 의존도에 따라 상당한 시간이 걸리므로, 전환 범위를 먼저 확인한 뒤 일정을 잡는 것이 좋습니다.
장단점 분석
구현체 비교
| 구현체 | 데이터플레인 | Rate Limiting | 변경 전파 속도 | 특이사항 |
|---|---|---|---|---|
| Envoy Gateway | Envoy Proxy | 네이티브 지원 | 빠름 | Policy API로 Rate Limiting 제공 |
| Istio Ambient | Envoy + ztunnel | 정책 기반 | 밀리초 수준 | 사이드카 홉 제거, L7은 waypoint 경유 |
| Cilium | eBPF | 미지원 | 빠름 | L4 최고 처리량, 고부하 route churn 환경에서 트래픽 드롭 보고 |
| Kong Gateway | nginx | 플러그인 | 밀리초 수준 | 엔터프라이즈 플러그인 생태계 |
| NGINX Gateway Fabric | NGINX | 제한적 | 수 초 지연 | 성숙한 L7 처리 |
| Traefik | 자체 | 미들웨어 | 수 초 지연 | 소규모 환경에 적합 |
장점
| 항목 | 내용 |
|---|---|
| 역할 분리와 RBAC 정합성 | GatewayClass·Gateway·Route 3계층이 인프라·운영·앱팀의 권한 경계와 자연스럽게 정렬됨 |
| 선언적 설정 | 어노테이션 대신 구조화된 CRD 필드로 GitOps 워크플로와 통합이 쉬움 |
| 멀티 프로토콜 지원 | HTTP·HTTPS·gRPC(Standard) / TCP·TLS·UDP(Experimental)를 단일 API로 처리 |
| 표준 트래픽 기능 | 가중치 분할, 미러링, 헤더 재작성이 스펙 레벨에서 정의돼 구현체 이식성 보장 |
| 구현체 이식성 | GatewayClass만 교체하면 Route 리소스를 수정하지 않고 컨트롤러 전환 가능 |
단점 및 고려사항
| 항목 | 내용 |
|---|---|
| 구현체 간 행동 차이 | Rate Limiting, 업데이트 전파 속도에서 구현체별 편차가 큼 |
| Cilium 고부하 주의 | 높은 route churn 환경에서 트래픽 드롭이 보고됨 — 부하 규모별 검증 권장 |
| 마이그레이션 복잡도 | 단순 변환 외에 네임스페이스 설계, ReferenceGrant 정책, 어노테이션 대체 방안 검토 필요 |
| 초기 학습 곡선 | 3계층 모델, Standard/Experimental 채널 구분, 구현체별 확장 정책 추가 학습 필요 |
| ReferenceGrant 관리 오버헤드 | 크로스 네임스페이스 참조마다 별도 리소스 필요해 대규모 환경에서 관리 대상 증가 |
구현체 전용 확장 기능 주의
BackendTrafficPolicy(Rate Limiting)는 Envoy Gateway 전용 CRD입니다. 다른 구현체로 전환할 때 이 부분은 별도 대응이 필요합니다. "GatewayClass만 바꾸면 모든 게 해결된다"는 기대는 표준 Route 리소스에 한정됩니다. 구현체 전용 Policy API는 이식되지 않습니다.
마치며
Gateway API는 Kubernetes 트래픽 라우팅의 현재 표준입니다. NGINX Ingress Controller deprecated와 Ingress2Gateway v1.0 출시가 연달아 일어난 2026년 초는 분명한 전환점이었고, GRPCRoute는 이미 v1.1에서 Standard 채널로 안착해 있습니다. 어노테이션 기반 Ingress의 시대는 저물고 있으며, 역할 분리·선언적 설정·구현체 이식성이 Gateway API의 핵심 가치입니다.
지금 시작해보기 좋은 3단계를 소개합니다.
-
Ingress2Gateway 도구로 현황 파악:
kubectl-ingress2gateway print로 기존 Ingress를 변환해보면, 자동 변환되는 항목과 수동 작업이 필요한 항목이 나뉩니다. 변환되지 않는 항목이 마이그레이션 범위를 알려줍니다. -
신규 서비스 하나에 HTTPRoute를 적용해보기: 기존 Ingress를 당장 교체하지 않더라도 새로 배포하는 서비스에 HTTPRoute를 적용해보는 것은 지금 당장 가능합니다. 두 API는 공존하므로 점진적 전환이 가능합니다.
-
구현체를 목적에 맞게 선택: Rate Limiting이 필요하다면 Envoy Gateway, 서비스 메시 통합이 중요하다면 Istio Ambient, 클라우드 네이티브 통합을 원한다면 GKE Gateway Controller나 AWS Load Balancer Controller를 검토해보세요. GatewayClass만 바꾸면 Route 리소스는 그대로 유지되므로 나중에 교체도 가능합니다.
참고 자료
- Gateway API 공식 문서 — Getting Started
- HTTPRoute API 레퍼런스
- GRPCRoute API 레퍼런스
- GEP-709: Cross Namespace References — ReferenceGrant 설계 문서
- Gateway API v1.5 릴리스 블로그
- Gateway API v1.4 릴리스 블로그
- Announcing Ingress2Gateway 1.0
- From Ingress to Gateway API — Microsoft Azure 아키텍처 블로그
- Kubernetes Gateway API in 2026: Envoy·Istio·Cilium·Kong 비교
- Kubernetes Ingress vs Gateway API: What to Use in 2026
- HTTP Traffic Splitting — Gateway API 공식 가이드
- gRPC Routing — Gateway API 공식 가이드
- Kubernetes Gateway API — Amazon Web Services 사례 분석
- Gateway API Provider Support In 2026: 구현체 평가
- gateway-api GitHub 저장소