Ingress의 뒤를 잇다 — Gateway API가 역할 분리로 푸는 문제

2024년 ingress-nginx에서 치명적 CVE(CVE-2025-1974)가 발견됐다 — 컨트롤 플레인 컴포넌트가 임의 코드 실행까지 허용할 수 있는 구멍이었다. 동시에 ingress-nginx의 유지보수 부담이 오랜 이슈로 떠올랐다. 커뮤니티는 한 방향으로 수렴했다: 단순히 패치를 넘어, L7 진입의 다음 세대 APIGateway API로의 전환. 한 팀은 이를 기회 삼아, 수백 줄의 nginx annotation을 표준 API로 재작성했다 — 더 이상 nginx 전용이 아닌, 어느 구현체든 해석하는 매니페스트로.

이 글이 푸는 것은: Gateway API가 Ingress의 어떤 근본 한계를 어떻게 해결하는가, 그리고 왜 "Ingress 다음"인가다. nginx ingress EOL 컨텍스트가 이 글의 중심이다.

Ingress 한계의 핵심 — 역할 분리가 없다

08장에서 Ingress의 표현력 한계(annotation 지옥)를 봤다. 그런데 진짜 근본 한계는 역할 분리(role separation)가 없다는 것이다.

실제 L7 인프라엔 두 역할이 있다:

  1. 인프라 담당(플랫폼 팀): "이 클러스터에 외부 진입점(도메인, TLS, 외부 IP)을 세팅한다."
  2. 앱 담당(개발 팀): "내 앱으로 오는 트래픽은 이 경로 규칙을 따른다."

Ingress API엔 이 분리가 없다 — 하나의 Ingress 객체에 도메인/TLS(인프라)와 경로 라우팅(앱)이 뒤섞인다. 그래서 개발팀이 Ingress를 고치려 할 때 인프라 속성(TLS, 도메인)까지 만질 수 있게 되거나, 반대로 인프라팀이 매번 개발팀의 경로 규칙을 대신 고쳐줘야 한다. 책임 경계가 객체 단위로 안 나뉜 것이다.

Gateway API의 답 — 객체를 쪼개서 역할을 분리한다

Gateway API는 이 분리를 객체를 쪼개서 해결한다. 인프라가 소유할 객체와 앱이 소유할 객체를 아예 분리해, 각 팀이 자기 객체만 다루게 한다. (Gateway API 공식 문서)

flowchart TD
    GC["GatewayClass<br/>(인프라: '이 종류의 게이트웨이')"] --> G["Gateway<br/>(인프라: 도메인/TLS/리스너)"]
    G -. 청취 .-> R["HTTPRoute<br/>(앱: 경로/헤더 규칙)"]
    R --> SVC["Service: backend"]
    R2["HTTPRoute (다른 앱)"] -. 같은 Gateway .-> G
  • GatewayClass: "어떤 구현체(contour, envoy-gateway, nginx-gateway-fabric 등)가 이 종류의 Gateway를 구현하나". IngressClass와 비슷.
  • Gateway: 실제 진입점 — 리스너(포트, 호스트, TLS). 인프라 팀이 소유.
  • HTTPRoute(및 TCPRoute/GRPCRoute/UDPRoute): 라우팅 규칙 — 경로, 헤더, 가중치. 앱 팀이 소유. 하나의 Gateway에 여러 Route가 붙을 수 있다.
# Kubernetes 1.36 — Gateway (인프라 팀 소유)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: edge}
spec:
  gatewayClassName: envoy-gateway
  listeners:
  - name: https
    port: 443
    protocol: HTTPS
    hostname: app.example.com
    tls: {mode: Terminate, certificateRefs: [{name: app-tls}]}
# HTTPRoute (앱 팀 소유) — 표준 API로 헤더/가중치 표현
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: app-routes}
spec:
  parentRefs: [{name: edge}]
  hostnames: [app.example.com]
  rules:
  - matches: [{path: {type: PathPrefix, value: /api}}]
    backendRefs: [{name: backend, port: 80}]
  - matches: [{path: {type: PathPrefix, value: /}}]
    backendRefs: [{name: frontend, port: 80}]
  - matches: [{headers: [{name: X-Canary, value: "true"}]}]   # 헤더 기반 — 표준!
    backendRefs: [{name: canary, port: 80}]

핵심: 헤더 매칭이 표준 API에 있다. 08장의 annotation이 아니다. 이 매니페스트는 envoy-gateway든 nginx-gateway-fabric이든 표준 API로 해석 — 이식성이 살아난다.

RBAC으로 역할 분리를 강제한다 — 객체가 나뉘었으니 권한도 나뉜다

객체가 나뉘었으니 RBAC으로 권한도 나뉜다:

  • 인프라 팀: Gateway/GatewayClass 쓰기 권한.
  • 앱 팀: HTTPRoute만 쓰기 권한. Gateway는 참조만(parentRefs).
flowchart TD
    INFRA["인프라 팀<br/>(Gateway, GatewayClass 쓰기)"] --> G["Gateway: 도메인/TLS/리스너"]
    APP1["앱팀 A<br/>(HTTPRoute만 쓰기)"] --> R1["HTTPRoute: /api 규칙"]
    APP2["앱팀 B<br/>(HTTPRoute만 쓰기)"] --> R2["HTTPRoute: /billing 규칙"]
    R1 -->|parentRefs| G
    R2 -->|parentRefs| G

개발팀은 HTTPRoute로 자기 경로 규칙을 고치되, 인프라 속성(Gateway의 TLS/도메인)엔 손 못 댄다. 이것이 Ingress가 못 한 역할 분리의 강제*다 — Ingress 객체에 도메인/TLS(인프라)와 경로(앱)가 뒤섞여, 한 객체를 고치려면 양쪽 권한이 다 필요했다. Gateway API가 객체를 쪼갬으로써 RBAC 분리가 *구조적으로 강제된다.

HTTPRoute가 Gateway에 붙는 조건 — parentRefs와 attachment

HTTPRoute가 parentRefs로 Gateway를 가리키지만, 무조건 붙는 게 아니다. Gateway 쪽이 "이 Route를 받을지"를 제어:

  • Gateway의 리스너가 해당 호스트네임을 허용하는가?
  • Route의 namespace가 Gateway의 허용 범위(allowedRoutes)에 있는가?

attachment 모델이 인프라 팀에게 "어느 Route를 내 Gateway에 붙일지" 통제권을 준다. 무분별한 Route 부착을 막는 구조적 장치다.

Ingress 한계, Gateway API의 답 — 요약

Ingress 한계 Gateway API 해결
역할 분리 없음 GatewayClass/Gateway(인프라) ↔ Route(앱) 객체 분리 + RBAC
구현체 종속(annotation) 표준 API(헤더/가중치/L7)
HTTP 중심 HTTP/TCP/UDP/gRPC Route 타입
서비스 메시 미지원 GAMMA initiative(메시 통합)

nginx ingress EOL 컨텍스트 — 왜 지금 Gateway API로

CVE-2025-1974와 ingress-nginx의 유지보수 부담이 겹치며, 커뮤니티는 신규 L7 설계를 Gateway API 기준으로 잡는다. 구체적 EOL 일자/버전은 ingress-nginx 공식 발표로 매번 확인해야 한다(추측 금지). 핵심 방향:

  • nginx-gateway-fabric: NGINX사가 Gateway API 구현체로 제공. ingress-nginx(커뮤니티)와 다름.
  • envoy-gateway, Contour, Cilium Gateway 등: Gateway API 구현체 다수. 이식성이 살아난다.

중요: Gateway API가 Ingress를 즉시 대체하는 게 아니다. Ingress는 여전히 동작하고 널리 쓰인다. 다만 *신규 설계와 *이식성 중시 환경에서 Gateway API가 기준이 되는 흐름. 점진적 전환이 현실적 패턴.

TLS의 세 모드 — Terminate, Passthrough, Reencrypt

Gateway API가 TLS를 다루는 세 방식:

모드 동작 용도
Terminate Gateway가 TLS 종단, 백엔드엔 HTTP 일반적. 인증서 관리 한 곳
Passthrough TLS 그대로 백엔드까지 (SNI만 라우팅) 백엔드가 자체 인증서로 종단 (mTLS 등)
Reencrypt Gateway가 종단 후 백엔드와 다시 TLS 백엔드까지 암호화 유지하되 Gateway도 검사

Terminate가 가장 흔하다(인증서 관리 집중). 단 백엔드까지 평문이 흐르므로, 메시 내부가 아닌 환경에선 Reencrypt로 백엔드까지 암호화. Passthrough는 Gateway가 L7 검사를 못 하는 대신 백엔드 자체 인증서가 필요한 경우(자체 mTLS 서비스). 08장의 Ingress Terminate/Passthrough와 같은 축이지만, Reencrypt가 추가된다.

GAMMA — 외부 진입과 메시가 같은 API로

지금까지 외부 진입(Ingress/Gateway API)과 내부 메시(Istio VirtualService)는 서로 다른 API로 따로 관리해야 했다. *GAMMA**(Gateway API for Mesh Management Administration) initiative는 이 분리를 없앤다 — Gateway API 규칙이 *메시 데이터플레인(Istio/Cilium)에서도 같은 의미로 동작하게 한다. (GAMMA initiative)

구체적 예:

  • 외부: 사용자 → Gateway → Service A (Gateway API HTTPRoute).
  • 메시 내부: Service A → Service B 호출도 같은 HTTPRoute 규칙으로 제어(GAMMA).

이것이 "외부 진입"과 "메시 내부"가 하나의 API 모델로 통합된다는 의미다. Ingress(외부)와 Istio VirtualService(내부)가 다른 API였던 과거와 대비. GAMMA가 성숙하면 하나의 HTTPRoute로 외부/내부 트래픽을 모두 표현할 수 있게 된다. 이것이 10/11장(서비스 메시)과의 연결점이다.

GAMMA는 아직 발전 중(initiative). 정확한 GA 상태와 지원 범위는 Gateway API GAMMA 실측. 단 방향은 명확 — Ingress와 메시의 API 통합.

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

전환이 점진적이어야 하는 이유:

  1. 공존: 한 클러스터에 Ingress와 Gateway API가 함께. 새 서비스는 Gateway API로, 기존은 Ingress로.
  2. 구현체 지원: 사용 중인 Ingress Controller가 Gateway API도 지원하는지(많은 최신 컨트롤러가 둘 다).
  3. annotation 변환: 기존 Ingress의 nginx annotation을 Gateway API 표준 필드로 재작성 — 이게 공들인 부분.
  4. 트래픽 전환: 한 도메인씩 Ingress → Gateway로 점진 이동. 전체 한 번에 바꾸면 위험.

직접 확인하기

# Kubernetes 1.36 — Gateway API CRD 설치 (표준 채널)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
# (버전은 실측 시점 최신 GA로, 문서에 명시)
kubectl get gatewayclass

실제 Gateway/HTTPRoute 동작은 구현체(envoy-gateway, Cilium 등) 설치 필요. k8s-verify 스킬 참조.

# 구현체 없이 CRD만 설치해 객체 생성은 가능(단, 실제 라우팅은 안 됨)
kubectl apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: demo}
spec:
  parentRefs: [{name: edge}]
  hostnames: [app.example.com]
  rules: [{matches: [{path: {type: PathPrefix, value: /}}], backendRefs: [{name: web, port: 80}]}]
EOF
kubectl get httproute

확인할 것: HTTPRoute 객체가 생기지만, Gateway edge와 구현체가 없으면 Accepted=False.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"Gateway API가 Ingress를 즉시 대체한다" 점진적 전환. Ingress는 여전히 널리 쓰임. 신규 설계 기준이 Gateway API
"Gateway API는 구현체다" API 규격. 구현체는 envoy-gateway/Contour/Cilium/nginx-gateway-fabric 등 다수
"HTTPRoute는 Ingress와 같다" 비슷하지만 더 강력(헤더/가중치 표준). 그리고 Gateway와 분리
"nginx-gateway-fabric = ingress-nginx" 다름. 전자는 NGINX사의 Gateway API 구현체, 후자는 커뮤니티 Ingress Controller
"Gateway API는 외부 진입만" GAMMA로 메시 내부도 확장
"Gateway API를 쓰면 RBAC이 자동 분리된다" 객체가 나뉘어 분리가 가능해진 것. RBAC 설정은 별도

요약 — 이 글의 결론

  • Gateway API의 핵심은 역할 분리 — GatewayClass/Gateway(인프라)와 HTTPRoute(앱)를 객체로 쪼개, RBAC으로 권한까지 분리. Ingress가 못 한 것.
  • 표현력이 표준 API에 — 헤더 매칭, 가중치, canary가 annotation이 아니라 API 필드. 이식성이 살아남(envoy-gateway/Cilium/nginx-gateway-fabric 공통 해석).
  • nginx ingress EOL 컨텍스트 — CVE-2025-1974와 유지보수 부담이 겹쳐 신규 L7 설계를 Gateway API 기준으로. 단 즉시 대체가 아니라 점진적 전환.
  • 멀티 프로토콜 — HTTP/TCP/UDP/gRPC Route 타입. Ingress의 HTTP 중심 한계 넘음.
  • TLS 세 모드(Terminate/Passthrough/Reencrypt)로 백엔드 암호화를 유지하면서 Gateway 검사까지.
  • GAMMA initiative가 서비스 메시로 확장 — 외부 진입과 메시 내부를 하나의 API 모델로.
  • Gateway API는 규격, 구현체는 다수. 이식성이 살아나는 것이 Ingress 대비 가장 큰 실익.

생각해 볼 문제

  1. 인프라 팀과 앱 팀이 같은 Ingress 객체를 고치는 구조의 구체적 위험을 시나리오로 그려라.
  2. HTTPRoute의 헤더 매칭으로 canary를 구현했다. Ingress의 annotation 방식과 비교해 이식성 측면에서 차이는?
  3. ingress-nginx에서 Gateway API로 전환할 때, 기존 annotation 기반 기능(속도 제한 등)은 어떻게 되나?
  4. Gateway API가 메시(GAMMA)까지 다룬다는 것의 의미를, 외부 진입과 내부 메시를 같은 API로 본다는 관점에서 설명하라.
  5. Gateway와 HTTPRoute를 분리했더니 개발팀이 자기 Route를 자유롭게 만든다. 남용을 막으려면? (RBAC + attachment 정책)
  6. Gateway API 구현체를 바꾸(envoy-gateway → Cilium)고 싶다. 매니페스트는 얼마나 바뀌어야 하나?

참고