들어가며
쿠버네티스에서 외부 트래픽을 클러스터 안으로 들여보내는 일은 거의 모든 서비스 운영의 출발점입니다. 오랫동안 그 역할은 Ingress 리소스가 맡아 왔습니다. 그런데 현장에서 Ingress를 조금만 깊게 써 보면 금세 한계에 부딪힙니다. 경로 기반 라우팅과 호스트 기반 라우팅 정도까지는 표준이지만, 가중치 기반 트래픽 분할, 헤더 조작, 타임아웃, 리트라이, mTLS, gRPC 라우팅 같은 실전 기능은 전부 어노테이션으로 욱여넣어야 합니다.
문제는 이 어노테이션이 컨트롤러마다 제각각이라는 점입니다. ingress-nginx에서 쓰던 어노테이션은 Traefik에서 동작하지 않고, AWS ALB Ingress Controller는 또 다른 문법을 요구합니다. 결국 Ingress 매니페스트는 특정 컨트롤러에 강하게 종속되고, 컨트롤러를 바꾸는 순간 라우팅 규칙 전체를 다시 작성해야 하는 상황이 벌어집니다.
2026년 현재 이 흐름은 명확하게 정리되었습니다. Ingress API는 사실상 동결(frozen) 상태입니다. 쿠버네티스 프로젝트는 Ingress에 더 이상 새 기능을 추가하지 않기로 했고, 모든 신규 기능은 Gateway API로 들어갑니다. 게다가 가장 널리 쓰이던 ingress-nginx는 유지보수 모드에 가까운 상태로 접어들면서 보안 이슈 대응 부담이 커졌습니다. 새로 시작하는 팀이라면 더 이상 Ingress를 선택할 이유가 없어진 것입니다.
이 글에서는 Gateway API의 레퍼런스급 구현인 Envoy Gateway를 깊이 있게 다룹니다. Envoy Gateway가 어떤 위치에 있는지, 내부 아키텍처가 어떻게 생겼는지, 실제 YAML은 어떻게 작성하는지, 정책 확장 CRD로 무엇을 할 수 있는지, 그리고 Ingress에서 어떻게 마이그레이션하는지까지 실전 관점에서 정리하겠습니다.
Gateway API와 Envoy Gateway의 위치
먼저 용어를 정리하겠습니다. Gateway API는 쿠버네티스 SIG-Network가 표준화한 차세대 트래픽 라우팅 API 명세입니다. 명세 자체는 구현체가 아니라 CRD의 집합으로, 여러 벤더가 이 명세를 각자 구현합니다. Istio, Cilium, Kong, Traefik, NGINX Gateway Fabric 등이 모두 Gateway API 구현체입니다.
Envoy Gateway는 그중에서도 Envoy 프록시 커뮤니티가 직접 만든 공식 구현입니다. CNCF 산하 Envoy 프로젝트의 일부로 출발했고, 데이터 플레인으로 Envoy 프록시를 쓰면서 그 위에 Gateway API를 그대로 매핑합니다. 즉 Envoy의 강력한 기능을 Gateway API라는 표준 인터페이스로 노출하는 것이 핵심 목표입니다.
아래 표는 기존 Ingress와 Gateway API의 차이를 정리한 것입니다.
| 항목 | Ingress API | Gateway API |
| --- | --- | --- |
| 표준화 상태 | 동결, 신규 기능 없음 | 활발히 발전, 표준 후계자 |
| 역할 분리 | 단일 리소스에 모든 책임 집중 | 인프라/앱 역할 명확히 분리 |
| 라우팅 표현력 | 경로/호스트 위주, 나머지는 어노테이션 | 헤더, 가중치, 메서드 등 1급 표현 |
| 프로토콜 | 주로 HTTP/HTTPS | HTTP, gRPC, TCP, UDP, TLS |
| 이식성 | 컨트롤러별 어노테이션 종속 | 명세 기반 이식성 확보 |
| 정책 확장 | 표준 없음 | 정책 어태치먼트 모델 제공 |
Gateway API의 가장 큰 설계 철학은 역할 기반 분리입니다. 클러스터 운영자(인프라 제공자)는 GatewayClass를 정의하고, 네임스페이스 운영자는 Gateway를 띄우며, 애플리케이션 개발자는 HTTPRoute로 자기 서비스의 라우팅만 정의합니다. 각 역할이 자기 책임 범위 안에서만 리소스를 만지기 때문에 권한 분리와 멀티테넌시가 자연스럽게 따라옵니다.
아키텍처 들여다보기
Envoy Gateway는 크게 컨트롤 플레인과 데이터 플레인으로 나뉩니다. 컨트롤 플레인은 Gateway API 리소스를 감시하다가 그것을 Envoy 설정으로 변환해 데이터 플레인에 내려보냅니다. 데이터 플레인은 실제 트래픽을 처리하는 Envoy 프록시 파드들입니다.
[ 사용자 / 클라이언트 ]
|
v
+----------------------------------+
| Envoy Proxy (데이터 플레인) |
| - 리스너 / 라우트 / 클러스터 |
| - 필터 체인 / TLS 종단 |
+----------------------------------+
^
| xDS (gRPC)
|
+----------------------------------+
| Envoy Gateway 컨트롤러 |
| (컨트롤 플레인) |
| - Gateway API 리소스 watch |
| - IR(중간 표현)로 변환 |
| - Envoy xDS 설정 생성 |
+----------------------------------+
^
| watch
|
+----------------------------------+
| Kubernetes API Server |
| GatewayClass / Gateway |
| HTTPRoute / GRPCRoute / TLSRoute |
| 정책 CRD |
+----------------------------------+
컨트롤러 내부의 처리 흐름을 조금 더 풀어 보면 다음과 같은 단계로 진행됩니다.
1. 프로바이더 계층이 쿠버네티스 API에서 Gateway API 리소스와 관련 리소스(Service, Secret, EndpointSlice 등)를 감시합니다.
2. 변환 계층이 이 리소스들을 내부 중간 표현(Intermediate Representation, IR)으로 변환합니다. IR은 특정 데이터 플레인에 종속되지 않은 추상 모델입니다.
3. IR을 다시 Envoy의 xDS 리소스(Listener, Route, Cluster, Endpoint, Secret)로 번역합니다.
4. xDS 서버가 이 설정을 Envoy 프록시 파드에 gRPC 스트림으로 전달합니다.
이 구조 덕분에 Envoy Gateway는 Gateway API의 선언적 모델과 Envoy의 동적 설정 모델 사이를 끊김 없이 이어줍니다. 사용자는 Envoy 설정 문법을 직접 쓸 필요 없이 Gateway API 리소스만 작성하면 됩니다.
설치
Helm으로 설치하는 것이 가장 일반적입니다. 다음은 기본 설치 예시입니다.
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.3.0 \
-n envoy-gateway-system \
--create-namespace
컨트롤러가 준비될 때까지 대기
kubectl wait --timeout=5m \
-n envoy-gateway-system \
deployment/envoy-gateway \
--for=condition=Available
설치가 끝나면 GatewayClass가 자동으로 생성되거나, 직접 만들어야 할 수 있습니다. 설치 후 다음 명령으로 CRD가 잘 들어왔는지 확인합니다.
kubectl get crd | grep -E 'gateway|envoyproxy'
kubectl get gatewayclass
kubectl get pods -n envoy-gateway-system
핸즈온: GatewayClass와 Gateway
가장 먼저 GatewayClass를 정의합니다. GatewayClass는 어떤 컨트롤러가 이 클래스를 처리할지를 지정하는 클러스터 범위 리소스입니다.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
다음으로 실제 진입점이 되는 Gateway를 만듭니다. Gateway는 리스너를 정의하는데, 리스너는 포트, 프로토콜, 호스트네임, 그리고 어떤 라우트를 붙일 수 있는지를 지정합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
namespace: default
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: example-com-tls
allowedRoutes:
namespaces:
from: All
여기서 allowedRoutes의 namespaces 설정이 멀티테넌시의 핵심입니다. from을 Same으로 두면 같은 네임스페이스의 라우트만 붙을 수 있고, All로 두면 모든 네임스페이스가 붙을 수 있으며, Selector로 두면 레이블 셀렉터로 허용 범위를 좁힐 수 있습니다.
Gateway가 정상적으로 프로그래밍되었는지는 다음과 같이 status로 확인합니다.
kubectl get gateway eg -o yaml | yq '.status.conditions'
kubectl get gateway eg \
-o jsonpath='{.status.addresses[0].value}'
핸즈온: HTTPRoute
HTTPRoute는 애플리케이션 개발자가 가장 많이 다루는 리소스입니다. 어떤 Gateway에 붙을지, 어떤 호스트네임과 경로에 매칭할지, 어떤 백엔드로 보낼지를 선언합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: backend
namespace: default
spec:
parentRefs:
- name: eg
hostnames:
- "www.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: backend-svc
port: 8080
가중치 기반 트래픽 분할(카나리 배포)은 Ingress에서는 어노테이션으로만 가능했지만 Gateway API에서는 1급 기능입니다. 두 백엔드에 가중치를 주기만 하면 됩니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary
namespace: default
spec:
parentRefs:
- name: eg
hostnames:
- "www.example.com"
rules:
- backendRefs:
- name: backend-v1
port: 8080
weight: 90
- name: backend-v2
port: 8080
weight: 10
헤더 매칭과 요청 헤더 조작도 표준으로 표현됩니다. 다음 예시는 특정 헤더가 있을 때만 매칭하고, 응답으로 보내기 전 요청 헤더를 추가하는 필터를 적용합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-route
namespace: default
spec:
parentRefs:
- name: eg
rules:
- matches:
- headers:
- name: x-canary
value: "true"
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: x-routed-by
value: envoy-gateway
backendRefs:
- name: backend-v2
port: 8080
URL 리라이트와 리다이렉트 필터도 자주 쓰입니다. 다음은 경로 접두사를 갈아끼우는 리라이트 예시입니다.
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /v2
핸즈온: GRPCRoute
gRPC는 Ingress에서 다루기 까다로운 대표적인 프로토콜이었습니다. Gateway API는 GRPCRoute라는 전용 리소스를 제공해 서비스와 메서드 단위로 라우팅할 수 있게 합니다.
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: grpc-route
namespace: default
spec:
parentRefs:
- name: eg
hostnames:
- "grpc.example.com"
rules:
- matches:
- method:
service: com.example.UserService
method: GetUser
backendRefs:
- name: user-grpc-svc
port: 9000
HTTP/2 기반 gRPC 트래픽은 Envoy가 매우 잘 처리하는 영역입니다. Envoy Gateway는 GRPCRoute를 Envoy의 라우트 설정으로 변환하면서 gRPC 특유의 트레일러 처리와 상태 코드 매핑까지 자동으로 챙겨 줍니다.
핸즈온: TLSRoute
TLSRoute는 TLS를 종단하지 않고 SNI 기반으로 통과(passthrough)시킬 때 사용합니다. 백엔드까지 암호화를 그대로 유지하고 싶은 경우에 유용합니다.
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: tls-passthrough
namespace: default
spec:
parentRefs:
- name: eg
sectionName: tls-passthrough
hostnames:
- "secure.example.com"
rules:
- backendRefs:
- name: secure-backend
port: 8443
이때 Gateway 리스너는 TLS 모드를 Passthrough로 두어야 합니다.
listeners:
- name: tls-passthrough
protocol: TLS
port: 8443
hostname: "secure.example.com"
tls:
mode: Passthrough
allowedRoutes:
kinds:
- kind: TLSRoute
정책 확장: Envoy Gateway의 진짜 강점
Gateway API의 핵심 라우팅 리소스만으로는 타임아웃, 리트라이, 레이트 리밋, 인증 같은 운영 기능을 다 표현할 수 없습니다. Gateway API는 이를 위해 정책 어태치먼트(policy attachment) 모델을 정의했고, Envoy Gateway는 이 모델을 따르는 강력한 정책 CRD들을 제공합니다. 이 부분이 Envoy Gateway를 단순한 Ingress 대체재 이상으로 만드는 지점입니다.
| 정책 CRD | 부착 대상 | 주요 기능 |
| --- | --- | --- |
| ClientTrafficPolicy | Gateway | 클라이언트 측 TLS, HTTP/2, 연결 버퍼, 헤더 처리 |
| BackendTrafficPolicy | Gateway/Route | 리트라이, 타임아웃, 서킷 브레이커, 레이트 리밋, 로드밸런싱 |
| SecurityPolicy | Gateway/Route | JWT 인증, OIDC, CORS, 기본 인증, 외부 인가 |
| EnvoyProxy | GatewayClass/Gateway | 프록시 배포 형태, 리소스, 로깅, 텔레메트리 |
ClientTrafficPolicy
클라이언트와 게이트웨이 사이의 동작을 제어합니다. 예를 들어 클라이언트 IP를 정확히 보존하거나 TLS 파라미터를 조정할 수 있습니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
name: client-policy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg
clientIPDetection:
xForwardedFor:
numTrustedHops: 1
tls:
minVersion: "1.3"
BackendTrafficPolicy
게이트웨이와 백엔드 사이의 회복성 동작을 다룹니다. 리트라이, 타임아웃, 서킷 브레이커를 한 곳에서 선언할 수 있습니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: backend-policy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: backend
retry:
numRetries: 3
perRetry:
timeout: 2s
retryOn:
httpStatusCodes:
- 503
- 502
timeout:
http:
requestTimeout: 10s
circuitBreaker:
maxConnections: 1024
maxPendingRequests: 256
레이트 리밋도 같은 CRD에서 선언합니다. 다음은 클라이언트 IP별로 분당 100건으로 제한하는 예시입니다. 정확한 한도 표현에 숫자만 쓰므로 안전합니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: ratelimit-policy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: backend
rateLimit:
type: Global
global:
rules:
- clientSelectors:
- sourceCIDR:
value: 0.0.0.0/0
type: Distinct
limit:
requests: 100
unit: Minute
SecurityPolicy
인증과 인가를 게이트웨이 계층에서 처리합니다. JWT 검증을 예로 들면 다음과 같습니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: jwt-policy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: backend
jwt:
providers:
- name: example-issuer
remoteJWKS:
uri: https://issuer.example.com/.well-known/jwks.json
issuer: https://issuer.example.com
CORS와 외부 인가(external authorization)도 SecurityPolicy로 선언할 수 있어, 애플리케이션 코드에서 공통 인증 로직을 걷어내는 데 큰 도움이 됩니다.
EnvoyProxy
데이터 플레인 자체를 조정합니다. 프록시 파드의 레플리카 수, 리소스 요청/제한, 서비스 타입, 접근 로그 포맷, 트레이싱 등을 여기서 설정합니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: custom-proxy
namespace: envoy-gateway-system
spec:
provider:
type: Kubernetes
kubernetes:
envoyDeployment:
replicas: 3
container:
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
memory: 512Mi
telemetry:
accessLog:
settings:
- format:
type: JSON
이 EnvoyProxy 리소스를 GatewayClass의 parametersRef로 연결하면 해당 클래스로 띄워지는 모든 게이트웨이의 데이터 플레인 형태를 한 번에 제어할 수 있습니다.
Envoy 프록시와 xDS의 관계
Envoy Gateway를 제대로 운영하려면 데이터 플레인인 Envoy 프록시가 어떻게 동작하는지 알아 둘 필요가 있습니다. Envoy의 설정 모델은 크게 네 가지 핵심 개념으로 이루어집니다.
- 리스너(Listener): 특정 포트에서 연결을 수신하는 진입점입니다. Gateway의 리스너가 여기에 대응됩니다.
- 라우트(Route): 들어온 요청을 어떤 클러스터로 보낼지 결정하는 규칙입니다. HTTPRoute가 여기에 대응됩니다.
- 클러스터(Cluster): 동일한 업스트림 서비스의 엔드포인트 묶음입니다. 백엔드 Service가 여기에 대응됩니다.
- 엔드포인트(Endpoint): 실제 파드의 IP와 포트입니다. EndpointSlice에서 가져옵니다.
이 모든 설정은 xDS라는 프로토콜 묶음을 통해 동적으로 전달됩니다. xDS는 LDS(리스너), RDS(라우트), CDS(클러스터), EDS(엔드포인트), SDS(시크릿) 등의 디스커버리 서비스로 구성됩니다. Envoy Gateway 컨트롤러가 xDS 서버 역할을 하고, Envoy 프록시가 클라이언트로서 gRPC 스트림으로 설정을 받아 갑니다.
Gateway API 리소스 Envoy 개념 xDS 서비스
----------------- --------- ----------
Gateway listener -> Listener -> LDS
HTTPRoute -> Route -> RDS
Service -> Cluster -> CDS
EndpointSlice -> Endpoint -> EDS
TLS Secret -> Secret -> SDS
이 동적 설정 모델 덕분에 라우팅 규칙이 바뀌어도 Envoy 프록시를 재시작하지 않고 설정만 갈아끼울 수 있습니다. 무중단 설정 반영은 Envoy의 가장 큰 강점 중 하나이며, Ingress 컨트롤러가 설정 변경 때마다 리로드를 일으키던 시절과 비교하면 큰 진전입니다.
설정이 실제로 Envoy에 어떻게 반영되었는지 디버깅하고 싶을 때는 다음 명령으로 Envoy의 설정 덤프를 직접 볼 수 있습니다.
Envoy 프록시 파드 이름 찾기
kubectl get pods -n envoy-gateway-system \
-l gateway.envoyproxy.io/owning-gateway-name=eg
관리 포트로 설정 덤프 조회
kubectl exec -n envoy-gateway-system <proxy-pod> -- \
curl -s localhost:19000/config_dump | head -c 2000
Ingress에서 Gateway API로 마이그레이션
기존에 Ingress로 운영하던 클러스터를 Gateway API로 옮기는 작업은 단계적으로 접근하는 것이 안전합니다. 한 번에 전부 바꾸기보다 두 시스템을 병행 운영하면서 트래픽을 점진적으로 옮기는 전략을 권장합니다.
먼저 기존 Ingress 규칙이 Gateway API의 어떤 리소스에 대응되는지 매핑을 정리합니다.
| Ingress 개념 | Gateway API 대응 |
| --- | --- |
| Ingress 리소스 | Gateway 더하기 HTTPRoute |
| ingressClassName | gatewayClassName |
| host 규칙 | HTTPRoute의 hostnames |
| path 규칙 | HTTPRoute의 matches |
| backend service | HTTPRoute의 backendRefs |
| TLS secret | Gateway 리스너의 certificateRefs |
| 카나리 어노테이션 | backendRefs의 weight |
| 리라이트 어노테이션 | URLRewrite 필터 |
마이그레이션 순서는 다음과 같이 잡으면 무리가 없습니다.
1. Envoy Gateway를 별도 네임스페이스에 설치하고 새 Gateway를 띄웁니다. 이때 기존 Ingress 컨트롤러는 그대로 둡니다.
2. 새 Gateway에 별도의 LoadBalancer 주소를 할당받습니다. 기존 트래픽에는 영향이 없습니다.
3. 서비스별로 HTTPRoute를 작성해 새 Gateway로 라우팅을 복제합니다.
4. DNS 또는 상위 로드밸런서에서 가중치를 조정해 트래픽을 조금씩 새 Gateway로 옮깁니다.
5. 모든 트래픽이 안정적으로 넘어가면 기존 Ingress 리소스와 컨트롤러를 제거합니다.
쿠버네티스 프로젝트는 이런 전환을 돕기 위해 ingress2gateway라는 변환 도구도 제공합니다. 기존 Ingress 매니페스트를 입력하면 동등한 Gateway API 리소스 초안을 만들어 줍니다.
ingress2gateway print \
--input-file=legacy-ingress.yaml \
--providers=ingress-nginx > gateway-resources.yaml
다만 자동 변환은 어디까지나 출발점입니다. 컨트롤러별 어노테이션 중 일부는 정책 CRD로 손수 옮겨야 하므로, 변환 결과는 반드시 사람이 검토해야 합니다.
운영과 튜닝
운영 단계에서 가장 먼저 챙겨야 할 것은 관측성입니다. Envoy Gateway는 접근 로그, 메트릭, 분산 트레이싱을 모두 EnvoyProxy 리소스의 telemetry 섹션에서 설정합니다. 접근 로그를 JSON으로 구조화하면 로그 수집 파이프라인에서 다루기 쉽습니다.
메트릭은 Envoy가 노출하는 Prometheus 포맷을 그대로 활용합니다. 다음과 같이 스크레이프 대상을 잡습니다.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: observable-proxy
namespace: envoy-gateway-system
spec:
telemetry:
metrics:
prometheus:
disable: false
tracing:
provider:
type: OpenTelemetry
host: otel-collector.monitoring.svc
port: 4317
가용성 측면에서는 데이터 플레인 프록시를 최소 두 개 이상의 레플리카로 띄우고, 파드 안티어피니티로 노드에 분산시키는 것이 기본입니다. 또한 PodDisruptionBudget을 걸어 두면 노드 드레인 시에도 최소 한 개 이상의 프록시가 항상 살아 있도록 보장할 수 있습니다.
성능 튜닝은 트래픽 특성에 따라 달라지지만 몇 가지 일반 원칙이 있습니다.
- 연결 재사용: 백엔드로의 keep-alive와 HTTP/2 멀티플렉싱을 활용해 연결 수립 비용을 줄입니다.
- 적절한 타임아웃: 너무 긴 타임아웃은 장애 전파를 부르고, 너무 짧은 타임아웃은 정상 요청을 끊습니다. 백엔드의 실제 응답 분포를 보고 정합니다.
- 서킷 브레이커: 백엔드가 과부하 상태일 때 추가 요청을 빠르게 차단해 연쇄 장애를 막습니다.
- 리소스 적정화: Envoy는 효율적이지만, 높은 처리량에서는 CPU가 병목이 됩니다. 부하 테스트로 적정 레플리카 수를 잡습니다.
cert-manager와 연동하면 TLS 인증서 발급과 갱신을 자동화할 수 있습니다. Gateway 리소스에 cert-manager 어노테이션을 붙이면 인증서가 자동으로 시크릿으로 발급됩니다. 이는 운영 부담을 크게 줄여 주는 조합입니다.
흔한 함정과 트러블슈팅
실전에서 자주 마주치는 문제와 진단법을 정리합니다.
첫째, 라우트가 Gateway에 붙지 않는 경우입니다. 가장 흔한 원인은 allowedRoutes의 네임스페이스 설정 불일치이거나, parentRefs가 잘못된 Gateway나 리스너를 가리키는 경우입니다. HTTPRoute의 status 조건을 확인하면 Accepted와 ResolvedRefs 상태가 명확히 나옵니다.
kubectl get httproute backend -o yaml | yq '.status.parents'
여기서 Accepted가 False이면 부모 Gateway가 라우트를 거부한 것이고, ResolvedRefs가 False이면 백엔드 Service나 Secret을 찾지 못한 것입니다.
둘째, 다른 네임스페이스의 Service나 Secret을 참조할 때입니다. Gateway API는 네임스페이스 경계를 넘는 참조를 기본적으로 막아 두고, ReferenceGrant라는 별도 리소스로 명시적 허가를 받도록 합니다. 크로스 네임스페이스 참조가 동작하지 않으면 ReferenceGrant가 빠졌는지부터 확인합니다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-route-to-svc
namespace: backend-ns
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: route-ns
to:
- group: ""
kind: Service
셋째, 정책이 적용되지 않는 경우입니다. 정책 CRD의 targetRefs가 실제 존재하는 리소스를 정확히 가리키는지, 그리고 동일 대상에 여러 정책이 충돌하지 않는지 확인합니다. 정책 CRD에도 status 조건이 있어 적용 여부를 알려 줍니다.
넷째, TLS 인증서 문제입니다. certificateRefs가 가리키는 시크릿이 Gateway와 같은 네임스페이스에 있는지, 시크릿 타입이 kubernetes.io/tls인지 확인합니다. SDS를 통해 인증서가 전달되지 않으면 리스너가 Programmed 상태로 넘어가지 않습니다.
마지막으로, 설정이 반영된 것 같은데 동작이 이상할 때는 앞서 본 config_dump로 Envoy에 실제로 어떤 설정이 내려갔는지 직접 확인하는 것이 가장 빠른 길입니다. 컨트롤러 로그도 함께 봅니다.
kubectl logs -n envoy-gateway-system \
deployment/envoy-gateway --tail=100
마치며
Ingress가 동결되고 Gateway API가 표준 후계자로 자리 잡은 지금, 새로운 트래픽 진입 계층을 설계한다면 Gateway API는 더 이상 선택이 아니라 기본값에 가깝습니다. 그중에서도 Envoy Gateway는 Envoy 커뮤니티가 직접 만든 레퍼런스급 구현으로, Envoy의 검증된 데이터 플레인 위에 표준 인터페이스를 깔끔하게 얹어 줍니다.
핵심은 역할 분리와 정책 확장입니다. 라우팅은 Gateway API의 표준 리소스로 선언하고, 회복성과 보안 같은 운영 기능은 ClientTrafficPolicy, BackendTrafficPolicy, SecurityPolicy 같은 정책 CRD로 깔끔하게 분리합니다. 데이터 플레인의 형태는 EnvoyProxy로 통제합니다. 이 구조를 이해하고 나면, 어노테이션 더미에 의존하던 과거의 Ingress 운영이 얼마나 취약했는지 새삼 느끼게 됩니다.
새로 시작하는 프로젝트라면 처음부터 Gateway API로 설계하고, 기존 Ingress 환경이라면 ingress2gateway로 초안을 잡아 단계적으로 옮겨 가시기 바랍니다. 표준 위에 서 있다는 것은, 컨트롤러를 바꾸더라도 내 라우팅 규칙이 그대로 살아남는다는 뜻입니다.
참고 자료
- Envoy Gateway 공식 문서: https://gateway.envoyproxy.io/docs/
- Envoy Gateway GitHub 저장소: https://github.com/envoyproxy/gateway
- Gateway API 공식 문서: https://gateway-api.sigs.k8s.io/
- Gateway API 정책 어태치먼트 설계: https://gateway-api.sigs.k8s.io/reference/policy-attachment/
- Envoy 프록시 공식 문서: https://www.envoyproxy.io/docs
- Envoy xDS 프로토콜 문서: https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol
- 쿠버네티스 Ingress 개념 문서: https://kubernetes.io/docs/concepts/services-networking/ingress/
- ingress2gateway 변환 도구: https://github.com/kubernetes-sigs/ingress2gateway
- cert-manager 공식 문서: https://cert-manager.io/docs/
현재 단락 (1/431)
쿠버네티스에서 외부 트래픽을 클러스터 안으로 들여보내는 일은 거의 모든 서비스 운영의 출발점입니다. 오랫동안 그 역할은 Ingress 리소스가 맡아 왔습니다. 그런데 현장에서 In...