Envoy Gateway 실전 HTTP 라우팅: Host, Path, Header, 그리고 트래픽 분할
앞의 두 글이 "이게 대체 뭐냐"라는 기초를 잡아줬다면, 이번 글은 여러분이 일상적으로 가장 많이 수정하게 될 대상, 즉 HTTPRoute로 들어갑니다.
HTTPRoute를 들어오는 트래픽을 위한 라우팅 스크립트라고 생각해 보세요. 다만 Nginx 규칙을 쓰거나 컨트롤러 annotation을 외우는 대신, Kubernetes 네이티브 리소스를 쓴다는 점이 다릅니다.
먼저 분명히 해둘 점이 하나 있습니다. 이 글은 HTTPRoute만 다룹니다. HTTPRoute는 HTTP/HTTPS 의미 체계를 처리하기 때문입니다. gRPC라면 GRPCRoute, raw TCP라면 TCPRoute, TLS passthrough라면 TLSRoute 쪽이 맞습니다. 모든 걸 HTTPRoute에 우겨 넣지 마세요. 그러면 YAML 전체에서 "일단 되는 대로 맞춰 쓰는" 기운이 나기 시작합니다.
가장 흔한 라우팅: Host와 Path
가장 전형적인 예시는 이렇습니다:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-route
spec:
parentRefs:
- name: eg
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080의미는 다음과 같습니다:
Host: app.example.com일 때만 이 route에 매칭됨- path는
/api로 시작해야 함 - 트래픽은
api-service:8080으로 전달됨
hostnames를 제거하면 이 route는 "경로만 보고, 도메인은 아무거나"가 됩니다.
matches는 무엇을 매칭할 수 있나
matches는 HTTPRoute에서 가장 중심이 되는 개념 중 하나입니다.
"어떤 조건일 때 이 규칙을 발동할 것인가"라고 생각하면 됩니다.
자주 쓰는 매칭 조건:
pathheadersqueryParamsmethod
예시:
matches:
- method: GET
path:
type: PathPrefix
value: /api
headers:
- name: version
value: v2의미:
GET요청만- path가
/api로 시작해야 함 - 요청에
version: v2header가 포함되어야 함
이 패턴은 API 버전 시험 운영이나 특정 사용자만 대상으로 한 단계적 rollout에 이상적입니다.
하나의 Route, 여러 개의 규칙
하나의 HTTPRoute 안에서 서로 다른 path를 서로 다른 서비스로 보낼 수 있습니다:
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
- matches:
- path:
type: PathPrefix
value: /admin
backendRefs:
- name: admin-service
port: 8080하나의 도메인 아래에서 하위 path를 나눌 때 아주 좋습니다. 예를 들면:
/api는 API 서비스로/admin은 admin 백엔드로
라우팅 로직이 바로 눈에 들어옵니다. 오래된 설정 파일처럼 반쯤 읽다가 "내가 지금 뭘 보고 있지"라는 실존적 고민에 빠지지 않아도 됩니다.
Header, Method, Query를 조건으로 쓰기
HTTPRoute는 path 매칭에만 제한되지 않습니다. 요청 header, method, query param으로도 매칭할 수 있습니다.
예를 들어 version: v2가 붙은 요청만 새 서비스로 보내고 싶다면:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: versioned-route
spec:
parentRefs:
- name: eg
rules:
- matches:
- path:
type: PathPrefix
value: /api
headers:
- name: version
value: v2
backendRefs:
- name: api-v2
port: 8080
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-v1
port: 8080이 패턴은 다음에 잘 맞습니다:
- Canary 배포
- 특정 고객에게만 새 버전을 먼저 체험하게 하기
- 새 기능 수동 검증
새 도메인을 통째로 전환하는 것보다, header 기반 라우팅은 더 세밀하고 실험하기 쉽습니다.
filters: 전달 전에 요청을 가공하기
HTTPRoute는 단순히 "매칭하고 전달"만 하는 것이 아닙니다. 전달 전후로 변환도 적용할 수 있습니다. 그 역할을 하는 것이 filters입니다.
가장 흔한 사용 사례는 header 추가입니다:
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: x-env
value: prod
backendRefs:
- name: api-service
port: 8080이건 다음에 유용합니다:
- 백엔드로 보내기 전에 tracing metadata 주입
- ingress 계층에서 고정 header 추가
- 요청/응답 변환
다만 filters가 강력하다고 해서 ingress 계층을 거대한 middleware 덩어리로 만들라는 뜻은 아닙니다. route에 로직을 너무 많이 쌓아 올리면 디버깅이 따로 상담이 필요할 정도로 피곤해집니다.
Weighted Traffic Splitting: 가장 흔한 Canary 패턴
기존 버전에 90%, 새 버전에 10%를 보내고 싶다면 weight를 사용합니다:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-route
spec:
parentRefs:
- name: eg
hostnames:
- "app.example.com"
rules:
- backendRefs:
- name: api-v1
port: 8080
weight: 90
- name: api-v2
port: 8080
weight: 10이것이 기본적인 canary / traffic splitting입니다.
트래픽 실험을 하려고 굳이 전체 service mesh가 필요한 것은 아닙니다. 많은 API 서비스에서는 이것만으로도 충분하고, 설정도 훨씬 깔끔합니다.
💡
weight는 상대값이며 반드시 100이 될 필요는 없습니다.9와1은90과10과 같은 비율을 의미합니다.
timeouts: 요청이 영원히 매달리지 않게 하자
HTTPRoute는 rule 수준의 timeout 설정을 지원합니다. 이건 꽤 중요합니다. ingress 계층에 제한이 없으면, 나쁜 요청이 마치 안 나가는 손님처럼 계속 자리를 차지하게 됩니다.
rules:
- matches:
- path:
type: PathPrefix
value: /reports
timeouts:
request: 10s
backendRequest: 2s
backendRefs:
- name: report-service
port: 8080두 필드의 의미:
request: 클라이언트 요청 전체에 대해 허용하는 최대 대기 시간backendRequest: 백엔드로 보내는 단일 요청에 대한 최대 대기 시간
backendRequest가 request보다 크면 안 된다는 점도 기억하세요. 전체 여행이 10분인데 버스 한 구간을 20분 기다릴 수 있다는 말과 같아서, 우주가 혼란스러워집니다.
자주 놓치는 두 가지 뉘앙스
1. parentRefs는 특정 Listener를 지정할 수 있다
Gateway에 listener가 여러 개 있다면, 그중 하나에만 바인딩할 수 있습니다:
parentRefs:
- name: eg
sectionName: http이 뜻은 이 route가 http listener에만 attach된다는 것입니다.
멀티 listener 환경에서는 모든 곳에 붙게 두는 것보다 훨씬 예측 가능합니다.
2. matches가 없으면 전부 매칭된다
rule에 matches가 없으면 기본적으로 /에 매칭되는, 사실상 catch-all 규칙이 됩니다.
편리하긴 하지만, 여러분이 더 구체적이라고 생각한 규칙들을 조용히 집어삼킬 수 있습니다. 귀찮다고 생략했다가 왜 정밀한 규칙이 안 먹는지 한 시간 동안 헤매지 마세요.
라우팅이 실제로 동작하는지 검증하기
route를 구성한 뒤에는 curl로 검증하세요:
curl -H "Host: app.example.com" http://$GATEWAY_HOST/api/users
curl -H "Host: app.example.com" -H "version: v2" http://$GATEWAY_HOST/api/usersHTTPRoute 상태를 확인하려면:
kubectl get httproute app-route -o yaml특히 다음을 주의 깊게 보세요:
status.parentsAcceptedcondition
가끔 YAML은 멀쩡해 보여도 Gateway가 거부한 경우가 있습니다. 지원서를 냈는데 아무 연락이 없었다고 합격했다고 믿는 것과 비슷하죠.
한 줄 요약
HTTPRoute의 핵심은 "먼저 매칭하고, 그다음 전달한다"입니다. host, path, header, weight만 잘 다뤄도 일상적인 HTTP ingress 요구사항의 80%는 처리할 수 있습니다.
다음 단계
다음 글에서는 모든 운영 환경에서 반드시 마주치게 되는 주제를 다룹니다. HTTPS, 인증서, TLS termination, 그리고 보안입니다: 👉 TLS와 보안