쿠버네티스에서 클러스터 외부 트래픽을 서비스로 흘려보내는 표준은 오랫동안 Ingress였습니다. 그런데 조금만 요구사항이 복잡해져도 Ingress의 한계에 부딪혀 컨트롤러별 애너테이션 지옥에 빠지곤 했습니다. 카나리 배포, 헤더 기반 라우팅, 트래픽 분할, mTLS 종료 — 이 모든 것이 표준 스펙에는 없어 벤더마다 다른 애너테이션 키로 우회해야 했습니다.
이 문제를 정면으로 해결하려 등장한 것이 Gateway API입니다. 2023년 v1.0(GA)에 도달해 이제는 프로덕션 전환을 진지하게 검토할 시점입니다. 이 글에서는 두 스펙의 설계 철학 차이, 리소스 모델, 실제 라우팅 예제, 그리고 무중단 전환 전략까지 정리합니다.
Ingress가 막히는 지점
Ingress 스펙 자체는 매우 단순합니다. 호스트와 경로(path)를 백엔드 서비스로 매핑하는 것이 전부입니다. 문제는 실무에서 필요한 기능 대부분이 이 스펙에 없다는 점이고, 그래서 각 컨트롤러가 annotations로 확장 기능을 제공합니다.
# Ingress: 벤더 종속적인 애너테이션 지옥
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: legacy-ingress
annotations:
# NGINX 전용 - 다른 컨트롤러로 옮기면 전부 무효
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20" # 카나리 20%
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-v1
port:
number: 80
이 방식의 근본 문제는 이식성 부재입니다. 애너테이션은 문자열이라 타입 검증이 없고, 컨트롤러를 교체하면 라우팅 로직을 다시 써야 합니다. 또한 하나의 Ingress에 인프라(호스트·TLS)와 개발자(경로 라우팅) 관심사가 뒤섞여 대규모 조직에서 RBAC 경계를 긋기 어렵습니다.
Gateway API의 역할 분리 모델
Gateway API의 핵심 설계는 역할 기반 리소스 분리입니다. 하나의 거대한 리소스 대신 담당 주체별로 세 개로 나누는데, 단순한 문법 개선이 아니라 조직 구조에 맞춘 근본적 재설계입니다.
- GatewayClass: 인프라 제공자가 정의. 어떤 컨트롤러 구현체(NGINX, Istio, 클라우드 LB)를 쓸지 결정하는 템플릿.
- Gateway: 클러스터 운영자가 정의. 리스너(포트·프로토콜·TLS)와 허용할 Route 네임스페이스를 선언.
- *Route (HTTPRoute, GRPCRoute 등): 개발자가 정의. 실제 라우팅 규칙(호스트·경로·헤더·백엔드).
이 분리 덕분에 인프라 팀은 TLS 인증서와 인그레스 IP를, 개발 팀은 자기 서비스의 라우팅만 관리합니다. 서로의 리소스를 침범하지 않으면서 RBAC로 깔끔하게 권한을 나눌 수 있습니다.
리소스별 실전 정의
먼저 클러스터 운영자가 Gateway를 선언합니다. 리스너와 TLS 종료, 그리고 어떤 네임스페이스의 Route를 받아들일지만 정의합니다.
# Gateway: 운영자 소유 - 리스너와 TLS, Route 허용 정책
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra
spec:
gatewayClassName: nginx # GatewayClass 참조
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate # 게이트웨이에서 TLS 종료
certificateRefs:
- kind: Secret
name: example-com-tls
allowedRoutes:
namespaces:
from: Selector # 특정 네임스페이스만 허용
selector:
matchLabels: { gateway-access: "true" }
kinds:
- { kind: HTTPRoute }
그다음 개발자가 자기 네임스페이스에서 HTTPRoute를 붙입니다. parentRefs로 위 Gateway를 참조하되, 라우팅 규칙은 완전히 개발자 소유입니다. 아래 예시는 infra 네임스페이스의 Gateway에 붙어 /v1/payments 경로를 payments-v1 서비스로 보냅니다.
# HTTPRoute: 개발자 소유 - 경로 라우팅 규칙
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: team-payments
labels: { gateway-access: "true" }
spec:
parentRefs:
- name: prod-gateway
namespace: infra # 다른 네임스페이스의 Gateway에 붙임
hostnames: [ api.example.com ]
rules:
- matches:
- path: { type: PathPrefix, value: /v1/payments }
backendRefs:
- { name: payments-v1, port: 80 }
헤더·가중치 기반 라우팅: 애너테이션 없이
Ingress에서 애너테이션으로만 가능했던 카나리 배포가 여기서는 스펙의 1급 필드입니다. 여러 백엔드에 weight를 주면 트래픽이 그 비율대로 분할되고, 타입이 정의돼 잘못된 값은 API 서버가 거부합니다.
# 가중치 기반 카나리: v1에 90%, v2에 10% (rules 만 발췌)
rules:
- matches:
- path: { type: PathPrefix, value: /v1/payments }
backendRefs:
- { name: payments-v1, port: 80, weight: 90 }
- { name: payments-v2, port: 80, weight: 10 } # 신규에 10% 만
헤더 기반 라우팅도 선언적입니다. 내부 테스터에게만 신규 버전을 노출하거나 A/B 테스트를 할 때, matches 아래 headers 조건으로 특정 헤더(예: x-canary: "true")를 가진 요청만 v2로 보내고 나머지는 v1으로 보낼 수 있습니다.
# 헤더 매칭: x-canary 헤더가 있으면 v2로, 그 외는 v1으로
rules:
- matches:
- headers:
- type: Exact
name: x-canary
value: "true"
backendRefs:
- { name: payments-v2, port: 80 }
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- { name: payments-v1, port: 80 }
여기에 더해 헤더 조작, 경로 재작성(URLRewrite), 리다이렉트, 미러링 같은 필터도 filters 필드로 표준화돼, NGINX든 Istio든 같은 문법으로 동작합니다. type: RequestMirror로 운영 트래픽을 신규 백엔드에 복제해 실제 부하로 사전 검증하는 일도 애너테이션 없이 가능합니다.
ReferenceGrant: 네임스페이스 경계 넘기
Gateway API 보안 모델에서 자주 놓치는 부분이 교차 네임스페이스 참조 제어입니다. 기본적으로 한 네임스페이스의 Route는 다른 네임스페이스의 Service나 Gateway를 자유롭게 참조할 수 없고, 이를 허용하려면 ReferenceGrant가 필요합니다.
# team-payments 의 Route 가 shared-svc 의 Service 참조를 허용
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-payments-to-shared
namespace: shared-svc # 참조 대상 쪽에 둠
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: team-payments # 요청하는 쪽
to:
- { group: "", kind: Service } # 허용할 대상 종류
이 방향성이 중요합니다. ReferenceGrant는 참조 대상 소유자가 두는 리소스로, 리소스를 제공하는 쪽이 “누가 나를 참조해도 되는지”를 통제합니다. 임의의 네임스페이스가 다른 팀의 Service를 몰래 노출시키는 것을 원천 차단하는 설계입니다.
Ingress에서 무중단 전환하기
기존 Ingress 운영 클러스터에서 넘어갈 때 가장 안전한 방법은 병행 운영 후 점진적 이관입니다. 두 스펙은 서로 다른 API 그룹이라 동시에 존재할 수 있고, 최신 컨트롤러 대부분이 둘을 함께 처리합니다. 전환 순서는 다음을 권장합니다.
- 1단계: CRD 설치 후 기존 Ingress와 동일한 호스트를 별도 Gateway/HTTPRoute로 복제. DNS는 아직 기존 Ingress LB를 가리킴.
- 2단계: 새 Gateway의 외부 IP로 소수 트래픽을 흘려 검증(내부 테스트 헤더 또는 별도 서브도메인 활용).
- 3단계: DNS를 가중 라우팅으로 새 Gateway LB에 5% → 50% → 100% 단계 이관.
- 4단계: 트래픽이 완전히 넘어가고 메트릭이 안정되면 기존 Ingress 리소스 제거.
변환을 수작업으로 하면 실수가 나기 쉬우므로 공식 도구 ingress2gateway를 활용하면 좋습니다. 기존 Ingress를 읽어 Gateway와 HTTPRoute 매니페스트로 변환해 줍니다.
# ingress2gateway 로 기존 Ingress 를 Gateway API 로 변환 (바로 적용 X)
ingress2gateway print
--namespace payments
--providers ingress-nginx > converted-gateway.yaml
kubectl diff -f converted-gateway.yaml # 변환 결과 검토
kubectl apply -f converted-gateway.yaml # 검증 통과 시 적용
주의할 점은 벤더 전용 애너테이션(rate limiting, 커스텀 인증 등)은 자동 변환되지 않는다는 것입니다. 이런 기능은 컨트롤러별 확장 리소스나 BackendTrafficPolicy 같은 GEP 확장으로 별도 이관해야 하니, 변환 후 기능 목록을 대조하세요.
상태 확인과 디버깅
Gateway API는 리소스마다 풍부한 status 필드를 제공합니다. Route가 Gateway에 정상 바인딩됐는지, 백엔드가 유효한지를 조건(condition)으로 확인할 수 있어 디버깅이 Ingress보다 명료합니다.
# HTTPRoute 가 Gateway 에 정상 바인딩됐는지 status 확인
kubectl get httproute api-route -n team-payments
-o jsonpath='{.status.parents[0].conditions}' | jq
# 기대: Accepted=True, ResolvedRefs=True
ResolvedRefs가 False라면 backendRef가 가리키는 Service가 없거나, 교차 네임스페이스 참조인데 ReferenceGrant가 없는 경우가 대부분입니다. Accepted가 False라면 hostname이 리스너와 겹치지 않거나 allowedRoutes 정책에 막힌 것입니다. 실패 원인이 status에 구조화되어 드러난다는 점이 애너테이션 방식과의 큰 차이입니다.
언제 무엇을 선택할까
새로 시작하는 프로젝트라면 Gateway API를 기본값으로 두는 것이 합리적입니다. 이식성, 역할 분리, 타입 안전성, 카나리·트래픽 분할이 스펙에 내장돼 유지보수 비용이 낮고, 신규 기능은 Gateway API에만 추가됩니다. 반대로 요구가 단순한 호스트/경로 라우팅뿐이고 안정적인 Ingress 자산이 크다면 무리해서 전환할 이유는 없습니다. 전환 자체가 운영 리스크이므로 mTLS·gRPC 라우팅·트래픽 분할 같은 실질적 필요가 생겼을 때 이관하는 것이 현실적입니다.
마무리
Gateway API는 단순한 Ingress의 후속이 아니라 라우팅을 조직의 역할 경계에 맞춰 재설계한 표준입니다. 인프라·운영·개발의 관심사를 리소스 수준에서 분리하고, 벤더 종속적 애너테이션을 타입 있는 스펙 필드로 대체했습니다. 카나리와 트래픽 분할을 검증 가능한 필드로 다룬다는 것만으로도 운영 안정성이 크게 올라갑니다.
전환은 서두를 필요가 없습니다. CRD 설치 → 병행 운영 → DNS 가중 전환 → 기존 Ingress 정리 단계를 밟으면 무중단으로 넘어갈 수 있습니다. 핵심은 “지금 라우팅 요구가 Ingress 스펙 안에 있는가, 밖에 있는가”를 먼저 묻는 것입니다. 애너테이션에 의존하기 시작했다면 이미 Gateway API가 필요한 시점입니다.
자주 묻는 질문
Q. 기존 Ingress 컨트롤러(NGINX Ingress)를 그대로 쓰면서 Gateway API도 병행할 수 있나요?
A. NGINX 진영은 두 프로젝트가 분리돼 있습니다. 기존 ingress-nginx는 Ingress 전용, Gateway API는 별도 구현체 NGINX Gateway Fabric이 담당하므로 두 컨트롤러를 동시 배포합니다. 반면 Istio나 Traefik처럼 하나의 컨트롤러가 둘을 동시에 처리하는 구현체도 있어, 스택에 따라 전략이 달라집니다.
Q. Gateway API에서 rate limiting이나 인증 같은 기능은 어떻게 처리하나요?
A. 코어 스펙에 아직 없는 기능이 많습니다. 이런 정책은 BackendTrafficPolicy, SecurityPolicy 같은 확장 CRD(구현체별 제공)나 GEP로 논의 중인 정책 부착(policy attachment)으로 처리합니다. 구현체마다 방식이 다르니 필요한 기능이 선택한 컨트롤러에서 지원되는지 먼저 확인하세요. 아직 Ingress 애너테이션만큼 표준화가 완성되지 않은 영역입니다.
Q. 하나의 Gateway에 여러 팀의 HTTPRoute를 붙이면 서로 충돌하지 않나요?
A. Gateway의 allowedRoutes로 어떤 네임스페이스·종류의 Route를 받을지 제한하고, 각 Route는 자기 hostname과 경로만 선언합니다. 같은 hostname·경로가 겹치면 스펙에 정의된 우선순위 규칙(더 구체적인 매칭 우선, 생성 시각 순)으로 결정론적으로 해소되고, 밀려난 Route는 status 조건에 드러납니다. 덕분에 여러 팀이 하나의 공유 Gateway를 안전하게 나눠 쓸 수 있습니다.