Kubernetes Gateway API v1.2로 Ingress에서 벗어나기: HTTPRoute·GRPCRoute 선언 패턴과 마이그레이션 함정
NGINX Ingress Controller가 2026년 3월부로 보안 업데이트를 종료했고, 이제 EOL 이후 반년이 지난 시점에 이 글을 쓰고 있습니다. 솔직히 처음 공지를 봤을 때는 "어차피 언젠가는 바꿔야 했던 거잖아"라는 생각이 먼저 들었습니다. Ingress를 수년째 쓰면서 컨트롤러별 어노테이션을 외우고, gRPC 트래픽을 어떻게든 Ingress로 우회하려 씨름하던 기억이 있기 때문입니다. 아직 Ingress에 남아 계신 분들에게는 이미 보안 패치가 멈춘 상태라는 점이 더 큰 압박일 겁니다.
Kubernetes Gateway API v1.2는 2024년 11월에 릴리즈되면서 중요한 변곡점을 찍었습니다. GRPCRoute와 ReferenceGrant가 v1 Standard 채널로 졸업하며 프로덕션 사용에 충분한 안정성을 확보한 것입니다. 이 글에서는 Gateway API의 핵심 개념과 역할 분리 철학을 먼저 짚고, HTTPRoute·GRPCRoute를 실제로 어떻게 선언하는지, 그리고 EOL 이후 마이그레이션 과정에서 어떤 함정이 있는지를 다룹니다.
Ingress가 가진 근본적인 한계
Ingress는 단순하고 빠르게 쓸 수 있다는 장점이 있었지만, 복잡도가 올라갈수록 문제가 드러났습니다.
어노테이션 지옥과 이식 불가능한 설정
NGINX, Traefik, HAProxy — 컨트롤러마다 어노테이션이 달랐습니다. nginx.ingress.kubernetes.io/proxy-read-timeout이나 traefik.ingress.kubernetes.io/request-modifier 같은 설정은 그 컨트롤러 없이는 아무 의미도 없는 매직 스트링이었습니다. gRPC 트래픽은 더 심각해서, Ingress는 gRPC를 네이티브로 지원하지 않으니 nginx.ingress.kubernetes.io/grpc-backend: "true" 같은 어노테이션을 추가하는 방식으로 우회했습니다.
역할 경계의 부재
멀티테넌트 클러스터에서 Ingress는 '누가 어디까지 건드릴 수 있는지'를 명확히 가르기 어려웠습니다. 플랫폼 팀이 관리해야 할 TLS 인증서 설정과, 개발팀이 관리해야 할 라우팅 규칙이 하나의 YAML에 뒤섞였습니다.
Gateway API는 이 두 가지 문제를 설계 수준에서 해결합니다.
Gateway API의 세 계층과 역할 분리
Gateway API의 핵심은 GatewayClass → Gateway → Route로 이어지는 3계층 구조입니다. 각 계층은 명확하게 다른 사람의 영역입니다.
- GatewayClass: 어떤 구현체(Envoy Gateway, Cilium, NGINX Gateway Fabric 등)를 사용할지 클러스터 단위로 선언합니다. 클러스터 관리자만 건드리는 영역입니다.
- Gateway: 어떤 포트에서 어떤 프로토콜을 받을지, TLS를 어떻게 처리할지를 정의합니다. 플랫폼 팀이 프로비저닝합니다.
- HTTPRoute / GRPCRoute: 실제 라우팅 규칙을 선언합니다. 개발팀이 자신의 서비스 네임스페이스 안에서 독립적으로 관리합니다.
이 분리 덕분에 개발팀은 인프라 설정을 건드리지 않고도 자신의 라우팅을 바꿀 수 있고, 플랫폼 팀은 Gateway 레벨에서 정책을 강제할 수 있습니다.
v1.2에서 달라진 것들
2026년 10월 기준으로, v1.2(2024년 11월 릴리즈)의 주요 변경 내용을 정리하면 다음과 같습니다.
| 항목 | 변경 내용 |
|---|---|
GRPCRoute |
v1alpha2 → v1 Standard 졸업, Standard 채널 CRD에서 v1alpha2 서빙 제거 |
ReferenceGrant |
v1beta1 → v1 Standard 졸업, cross-namespace 참조의 공식 신뢰 모델 확정 |
HTTPRoute.timeouts |
Standard 채널에 timeouts 스탠자 추가, 경로별 타임아웃 선언 가능 |
ParentReference |
SectionName·Port 필드 개선으로 리스너 attach 지정이 명확해짐 |
ReferenceGrant의 v1 졸업은 뒤에서 cross-namespace 라우팅 예시와 함께 다시 다룹니다. 이 리소스가 Standard로 올라왔다는 건, 서로 다른 네임스페이스 사이에서 '참조를 허용한다'는 신뢰 선언이 공식 API로 승격됐다는 뜻입니다. 멀티테넌트 환경에서 아주 중요한 변화입니다.
HTTPRoute: 타임아웃과 카나리까지 YAML 한 장으로
기본 라우팅 선언
Gateway부터 만들어봅니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
port: 80
protocol: HTTP
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: prod-tls-secret이제 개발팀이 자신의 네임스페이스에서 HTTPRoute를 선언합니다. 이 예시는 my-app 네임스페이스의 Route가 infra 네임스페이스의 Gateway에 attach하는 cross-namespace 구성인데, 이 경우에는 ReferenceGrant가 반드시 필요합니다. 바로 다음에서 함께 보겠습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: my-app
spec:
parentRefs:
- name: prod-gateway
namespace: infra
sectionName: https
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v1
timeouts:
request: 30s # 환경·SLA에 맞춰 조정하세요. 개념적 예시 값입니다.
backendRefs:
- name: api-service
port: 8080timeouts.request가 눈에 띕니다. 이전에는 이걸 컨트롤러 어노테이션으로 설정했는데, 이제는 라우팅 규칙 안에 선언적으로 쓸 수 있습니다. 값은 서비스 특성에 따라 다르니, 예시의 30s는 복붙용이 아니라 '여기에 숫자를 명시한다'는 자리 표시로 봐주세요.
네임스페이스를 넘어 참조하려면 ReferenceGrant
위 HTTPRoute가 infra 네임스페이스의 Gateway를 가리키므로, 참조를 받는 쪽 네임스페이스(infra)에서 명시적으로 허용해야 합니다. 이게 빠지면 컨트롤러가 attach를 거부합니다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-my-app-routes
namespace: infra
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: my-app
to:
- group: gateway.networking.k8s.io
kind: Gatewayv1.2에서 ReferenceGrant가 v1 Standard로 졸업한 덕분에, 이 신뢰 선언이 공식 API로 확정됐습니다. 멀티테넌트 환경에서 '내 네임스페이스를 참조해도 좋다'를 명시적으로 선언한다는 발상 자체가 Gateway API의 보안 모델에 깊이 들어와 있는 부분입니다. 설치된 CRD의 서빙 버전에 따라 v1beta1 또는 v1을 사용하면 되고, 양쪽 모두 Standard 채널에서 지원됩니다.
카나리 배포: weight 필드 하나로
HTTPRoute의 backendRefs에 weight를 지정하면 트래픽 분기가 됩니다. Ingress에서는 컨트롤러마다 다른 어노테이션이 필요했던 기능입니다.
rules:
- matches:
- path:
type: PathPrefix
value: /checkout
backendRefs:
- name: checkout-v1
port: 80
weight: 90
- name: checkout-v2
port: 80
weight: 10이 선언 하나로 checkout 트래픽의 10%가 v2로 흘러갑니다. 컨트롤러를 바꿔도 이 YAML은 그대로 동작합니다. 다만 '컨트롤러가 해당 기능을 구현했다'는 전제가 필요한데, 이 부분은 트레이드오프 섹션에서 다시 짚습니다.
GRPCRoute: 서비스·메서드 단위로 백엔드를 가르기
gRPC 라우팅이 왜 달랐나
HTTP/1.1 기반의 Ingress는 gRPC(HTTP/2) 트래픽을 제대로 다루기 어려웠습니다. 서비스 이름과 메서드 이름은 URL 경로(/package.ServiceName/MethodName)로 인코딩되는데, 이걸 컨텍스트 없이 문자열 매칭으로만 처리했습니다. GRPCRoute는 이 구조를 이해하고 service와 method 필드로 정밀하게 매칭합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: grpc-route
namespace: my-app
spec:
parentRefs:
- name: prod-gateway
namespace: infra
hostnames:
- "grpc.example.com"
rules:
- matches:
- method:
type: Exact
service: com.example.UserService
backendRefs:
- name: user-service
port: 50051
- matches:
- method:
type: Exact
service: com.example.OrderService
method: PlaceOrder
backendRefs:
- name: order-service
port: 50052type 필드는 생략하면 기본적으로 Exact로 동작하지만, 명시해두는 편이 읽는 사람에게 친절합니다. 스펙상 RegularExpression도 선택지로 열려 있는데, 이는 구현체마다 지원 여부가 달라지는 영역이라 Conformance Report 확인이 필수입니다. service만 지정하면 해당 서비스의 모든 메서드가 매칭되고, method까지 지정하면 특정 메서드만 잡아냅니다.
요청 처리 경로
Gateway API를 처음 접할 때 헷갈리기 쉬운 지점이 있습니다. GRPCRoute는 '설정 리소스'이고, 매 요청마다 조회되는 런타임 컴포넌트가 아니라는 점입니다. Gateway 컨트롤러가 GRPCRoute를 읽어 Envoy 같은 data plane의 라우팅 설정을 미리 구성해두고, 실제 요청은 그 사전 설정된 data plane이 처리합니다. 요청 흐름만 보면 아래 그림이 더 정확합니다.
트레이드오프: 좋은 것만 있진 않습니다
장점
| 항목 | 내용 |
|---|---|
| 역할 분리 | 멀티테넌트 클러스터에서 인프라·개발 권한 경계가 명확해짐 |
| 표준화 | 컨트롤러별 어노테이션 탈피, 동일 YAML로 다른 구현체에서 동작 |
| gRPC 네이티브 지원 | 서비스·메서드 레벨 매칭, 어노테이션 우회 불필요 |
| 선언적 트래픽 정책 | 타임아웃, 가중치 분기, 헤더 조작 등을 YAML로 표현 |
단점 및 주의 지점
| 항목 | 내용 |
|---|---|
| 학습 곡선 | 3계층 구조 이해 필요, Ingress 단일 리소스보다 복잡 |
| 구현체별 지원 편차 | API 스펙이 GA여도 컨트롤러가 구현했는지는 별도 확인 필요 |
| GRPCRoute 타임아웃 미지원 | v1.2 기준 HTTPRoute에는 timeouts가 있으나 GRPCRoute는 아직 미지원 |
| TCPRoute/UDPRoute는 여전히 Experimental | L4 라우팅은 GA가 아니므로 프로덕션 주의 |
실무에서 자주 마주치는 함정들
함정 1: GRPCRoute v1alpha2 매니페스트를 그대로 적용하는 경우
v1.2에서는 Standard 채널 CRD에서 v1alpha2 GRPCRoute의 서빙이 제거되었습니다. 즉, apiVersion: gateway.networking.k8s.io/v1alpha2로 새 리소스를 apply하면 API 서버가 reject합니다. 반면 기존에 etcd에 저장된 리소스는 CRD의 변환 웹훅이 설정돼 있다면 자동 변환되어 계속 조회될 수 있으므로, '업그레이드 즉시 모든 게 깨진다'와는 다릅니다. GitOps 저장소에 남아 있는 매니페스트와 CI에서 생성되는 YAML을 v1으로 갱신하는 것이 핵심입니다.
# 변경 전 (apply 시 v1.2 환경에서 거부됨)
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GRPCRoute
# 변경 후
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute함정 2: 컨트롤러 Conformance Report를 확인하지 않는 경우
Gateway API 스펙이 GA라고 해서 모든 컨트롤러가 모든 기능을 구현한 건 아닙니다. timeouts나 weight 같은 기능이 특정 컨트롤러에서 무시될 수 있습니다. 공식 구현체 Conformance Report를 먼저 확인하는 습관을 들이면 좋습니다.
함정 3: Ingress와 Gateway가 혼재하는 과도기
마이그레이션 중에 동일한 호스트에 대해 Ingress와 HTTPRoute가 동시에 존재하면 라우팅이 충돌할 수 있습니다. 단계별로 한 서비스씩 전환하고, 전환 후에는 반드시 기존 Ingress를 제거하는 것을 권장합니다.
함정 4: cross-namespace 참조에서 ReferenceGrant를 빼먹는 경우
앞서 다룬 ReferenceGrant가 없는 상태로 다른 네임스페이스의 Gateway나 Service를 참조하면, Route 리소스에는 Accepted=False 상태 조건이 붙으면서 조용히 attach가 실패합니다. 로그보다 kubectl describe httproute의 Status.Parents[].Conditions를 먼저 보는 습관을 들이면 디버깅이 훨씬 빨라집니다.
마이그레이션 흐름과 ingress2gateway의 한계
ingress2gateway는 Ingress YAML을 HTTPRoute YAML로 변환해주는 공식 CLI 도구입니다. 하지만 '어노테이션이 사라졌다는 것'은 바꿔 말하면 '어노테이션에 담겨 있던 로직을 변환기가 알 수 없다'는 뜻이기도 합니다. 구체적으로 다음과 같은 케이스는 자동 변환이 어렵거나 수동 재작성이 필요합니다.
- 컨트롤러 전용 어노테이션:
nginx.ingress.kubernetes.io/proxy-buffer-size,nginx.ingress.kubernetes.io/limit-rps같이 컨트롤러 고유 기능에 묶인 어노테이션은 Gateway API 표준에 1:1 매핑이 없는 경우가 많습니다. 구현체별 정책 CRD(예: Envoy Gateway의BackendTrafficPolicy)로 다시 선언해야 합니다. - URL rewrite / redirect 규칙:
nginx.ingress.kubernetes.io/rewrite-target류는 HTTPRoute의URLRewrite필터로 바꿀 수 있지만, 캡처 그룹을 쓰는 복잡한 정규식 rewrite는 그대로 옮겨지지 않는 경우가 있습니다. - 인증 관련 설정:
nginx.ingress.kubernetes.io/auth-url,auth-signin같은 외부 인증 연계는 변환기 범위 밖입니다. 구현체 전용 ExtAuth 정책이나 서비스 메시 수준의 인증 플로우로 재설계가 필요합니다. - TLS passthrough와 L4 라우팅: TLSRoute·TCPRoute 영역은 아직 Experimental이라, Standard 채널만 사용하는 환경이라면 변환 대상에서 제외해야 합니다.
변환기는 시작점을 만들어주는 도구이지, 최종 산출물을 만들어주는 도구가 아니라고 생각하는 편이 안전합니다.
구현체 선택 기준
어떤 컨트롤러를 쓸지는 환경에 따라 달라집니다.
| 구현체 | 추천 상황 |
|---|---|
| Envoy Gateway | CNCF 생태계 중심, 적극적인 Gateway API 지원이 필요할 때 |
| NGINX Gateway Fabric | 기존 NGINX Ingress 사용 중, EOL 대응 마이그레이션 경로 필요 |
| Cilium | eBPF 기반 네트워킹 이미 사용 중, Ingress→Gateway 공식 가이드 있음 |
| Istio | 서비스 메시와 north-south·east-west 트래픽 통합 관리 필요 |
| AWS LBC | EKS 환경에서 ALB/NLB를 Gateway 리소스로 선언 |
특히 NGINX Ingress를 쓰고 있다면 NGINX Gateway Fabric으로의 전환을 검토해볼 만합니다. F5 팀이 공식 후계 구현체로 제시하고 있습니다.
지금 이 흐름이 중요한 이유
NGINX Ingress EOL은 이미 2026년 3월에 지나간 사건이지만, 그 영향은 지금도 진행 중입니다. 아직 Ingress에 머물러 있는 클러스터는 보안 패치 없이 운영되고 있다는 뜻이고, 이 상태가 길어질수록 리스크는 누적됩니다. 그리고 Gateway API 자체는 단일 벤더의 대안이 아니라, 여러 CNI·서비스 메시·클라우드 LB 벤더가 공통으로 수렴하고 있는 표준이라는 점이 중요합니다. gateway.networking.k8s.io API 그룹의 코어 이관 논의도 커뮤니티에서 꾸준히 다뤄지고 있습니다(진행 상태는 공식 문서에서 확인하는 것이 가장 정확합니다).
전체 마이그레이션이 당장 부담스럽다면, 새로 만드는 서비스부터 HTTPRoute로 시작해보는 접근을 추천합니다. GatewayClass와 Gateway는 플랫폼 팀이 한 번 셋업해두면, 이후 개발팀은 Route 리소스만 자신의 네임스페이스에서 관리하면 됩니다. 운영상 이게 가장 큰 변화입니다. 인프라 변경 요청을 티켓으로 올리고 플랫폼 팀의 승인을 기다리던 사이클이, 개발팀의 PR 하나로 끝나는 사이클로 바뀝니다. 그만큼 플랫폼 팀은 Gateway 레벨에서 공통 정책(TLS, rate limit, 공통 인증)을 강제하는 데 집중할 수 있습니다. 역할 경계가 리소스 경계와 일치하게 되는 것, 이것이 Gateway API가 Ingress 대신 자리 잡는 가장 실용적인 이유라고 생각합니다.