Istio나 Kubernetes Gateway API 문서에는 Envoy라는 이름이 자주 나옵니다. 이런 도구는 사용법부터 익히게 되어서, 실제로 트래픽을 전달하는 Envoy의 동작은 모르고 넘어가기 쉽습니다. 이 글에서는 Envoy가 만들어진 이유를 먼저 보고, 요청 하나가 Listener·Route·Cluster를 거치는 과정을 따라갑니다.
1. Envoy는 왜 필요한가: 애플리케이션에서 분리한 네트워크 처리
모놀리스(Monolith, 하나의 프로세스로 배포되는 애플리케이션)를 여러 서비스로 나누면 메서드 호출이 네트워크 호출로 바뀝니다. 네트워크 호출은 실패할 수 있습니다. 상대 서버가 응답하지 않거나, 응답이 늦거나, 연결이 중간에 끊깁니다.
그래서 서비스 간 호출에는 추가 처리가 필요합니다. 타임아웃(Timeout), 재시도(Retry), TLS 암호화, 호출 지표 수집이 그 예입니다. 서킷 브레이커(Circuit Breaker)도 필요합니다. 서킷 브레이커는 계속 실패하는 대상으로 가는 호출을 일정 시간 차단합니다.
이 처리는 각 서비스 코드 안에서 라이브러리로 구현할 수 있습니다. JVM에서는 Resilience4j가 그런 라이브러리입니다. 이 방식에는 세 가지 문제가 있습니다.
- 언어마다 같은 기능을 다시 구현해야 합니다. Java 라이브러리는 Python이나 Go 서비스에서 쓸 수 없습니다.
- 서비스마다 라이브러리 버전이 달라집니다. 그러면 같은 재시도 정책도 서비스마다 다르게 동작할 수 있습니다.
- 비즈니스 로직과 네트워크 처리 코드가 섞입니다. 재시도 정책 하나를 바꿔도 서비스를 다시 빌드하고 배포해야 합니다.
Envoy는 이 처리를 애플리케이션과 분리된 프로세스에서 수행합니다. 애플리케이션은 목적지 서비스 대신 같은 호스트의 Envoy로 요청을 보냅니다. 재시도, 타임아웃, 암호화, 지표 수집은 Envoy가 맡습니다. 애플리케이션 코드에 네트워크 처리가 없으므로, 서비스 언어와 관계없이 같은 정책을 적용할 수 있습니다.
참고: Envoy 공식 문서는 프로젝트의 목표를 "네트워크는 애플리케이션에 투명해야 한다(The network should be transparent to applications)"로 설명합니다. 문제가 생겼을 때 원인이 네트워크인지 애플리케이션인지 쉽게 구분할 수 있어야 한다는 뜻입니다.
2. Envoy란 무엇인가: 서비스 간 통신용 L7 프록시
프록시(Proxy)는 클라이언트와 서버 사이에서 요청을 받아 대신 전달하는 서버입니다. Envoy는 Lyft가 개발해 2016년에 오픈소스로 공개한 프록시입니다. 공식 문서는 Envoy를 "대규모 서비스 지향 아키텍처를 위해 설계된 L7 프록시이자 통신 버스(L7 proxy and communication bus)"로 소개합니다. 2017년 CNCF(Cloud Native Computing Foundation)에 합류했고, 2018년 11월 Kubernetes와 Prometheus에 이어 세 번째로 CNCF를 졸업했습니다.
Envoy의 특징은 다음과 같습니다.
- 별도 프로세스로 실행됩니다. 애플리케이션에 라이브러리로 포함되지 않으므로 애플리케이션 언어와 관계없이 쓸 수 있습니다.
- C++로 작성됐고 논블로킹 이벤트 루프로 동작합니다. 적은 스레드로 많은 연결을 처리하도록 설계됐습니다.
- L4와 L7을 모두 처리합니다. L4는 TCP 연결 수준, L7은 HTTP 같은 애플리케이션 프로토콜 수준을 말합니다. Envoy는 HTTP/1.1, HTTP/2, HTTP/3, gRPC 요청을 해석해 요청 단위로 라우팅합니다.
- 설정을 원격 API로 받을 수 있습니다. 받은 설정은 재시작 없이 반영됩니다. 이 동적 설정 체계(xDS)는 5절에서 설명합니다.
3. 요청은 Envoy를 어떻게 통과하는가: Listener에서 Endpoint까지
Envoy 문서는 트래픽 방향을 다운스트림(Downstream)과 업스트림(Upstream)으로 구분합니다. 다운스트림은 Envoy에 연결해 요청을 보내는 클라이언트입니다. 업스트림은 Envoy가 요청을 전달하는 백엔드 서버입니다.
요청 하나는 다음 경로를 지납니다.
다운스트림 클라이언트
│ ① TCP 연결
▼
Listener (0.0.0.0:10000)
│ ② HTTP Connection Manager가 바이트 스트림을 HTTP 요청으로 해석
│ HTTP 필터 체인 통과 (마지막 필터는 router)
▼
Route (Host·경로·헤더 매칭)
│ ③ 매칭된 라우트가 가리키는 Cluster 선택
▼
Cluster (backend_service)
│ ④ 로드밸런싱 정책으로 Endpoint 하나 선택
▼
Endpoint (10.0.1.23:8080) ──→ 업스트림 서버로 요청 전달- Listener가 지정된 주소와 포트에서 다운스트림의 TCP 연결을 받습니다.
- Listener에 설정된 네트워크 필터가 연결을 처리합니다. HTTP 트래픽은 HTTP Connection Manager가 처리합니다. 이 필터는 바이트 스트림을 HTTP 요청으로 해석한 뒤 HTTP 필터 체인에 순서대로 넘깁니다. 인증이나 속도 제한은 이 체인에 필터로 추가합니다. 체인의 마지막에는 router 필터를 둡니다.
- router 필터가 요청을 라우트 설정과 비교합니다. 먼저 Host 헤더로 가상 호스트(Virtual Host)를 고릅니다. 가상 호스트는 도메인별로 묶은 라우트 목록입니다. 그 안에서 경로와 헤더 조건을 위에서부터 비교하고, 처음 일치한 라우트를 사용합니다. 라우트에는 요청을 보낼 Cluster가 지정돼 있습니다.
- Cluster의 로드밸런싱 정책이 Endpoint 하나를 고릅니다. Envoy는 그 주소로 업스트림 연결을 맺고 요청을 전달합니다. 응답은 같은 경로를 반대 방향으로 지나 클라이언트에 도착합니다.
참고: 필터는 실제로 세 종류입니다. 연결을 맺는 단계에서 동작하는 리스너 필터(예: TLS Inspector), 바이트 스트림을 처리하는 네트워크 필터, HTTP 요청 단위로 동작하는 HTTP 필터입니다. 이 글에서는 네트워크 필터(HTTP Connection Manager)와 HTTP 필터만 다룹니다.
4. Listener, Route, Cluster 이해하기: 설정 파일로 보는 세 구성요소
| 구성요소 | 역할 |
|---|---|
| Listener | 다운스트림 연결을 받는 지점. 주소·포트와 필터 체인을 가진다 |
| Route | 요청을 보낼 Cluster를 정하는 매칭 규칙의 모음 |
| Cluster | 같은 역할을 하는 업스트림 Endpoint의 묶음. 로드밸런싱 정책과 헬스체크 설정을 가진다 |
아래 설정은 Envoy 공식 문서의 정적 설정(v3 API) 예시를 줄인 것입니다. 10000번 포트로 들어온 모든 요청을 backend_service 클러스터로 보냅니다.
먼저 Listener와 Route입니다. Route 설정(route_config)은 최상위 항목이 아니고 HTTP Connection Manager 설정 안에 들어갑니다. 처음 볼 때 헷갈리기 쉬운 부분입니다.
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 } # ← 연결을 받는 지점
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
http_filters:
- name: envoy.filters.http.router # ← HTTP 필터 체인의 마지막
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config: # ← Route는 HCM 설정 안에 위치
name: local_route
virtual_hosts:
- name: backend
domains: ["*"] # ← 모든 Host 헤더 매칭
routes:
- match: { prefix: "/" } # ← 모든 경로 매칭
route: { cluster: backend_service } # ← 보낼 Cluster 지정다음은 라우트가 가리키는 Cluster입니다.
clusters:
- name: backend_service
type: STRICT_DNS # ← DNS 조회 결과의 모든 IP를 Endpoint로 사용, 주기적 재조회
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: backend_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: backend, port_value: 8080 }라우트의 cluster: backend_service 값이 클러스터의 name과 일치해야 연결됩니다. 이 예시는 Endpoint를 DNS 이름 하나로 지정했습니다. 운영 환경에서는 보통 서비스 디스커버리가 이 목록을 동적으로 채웁니다.
5. Envoy의 주요 기능: 동적 설정, 복원력, 관측성
동적 설정: xDS
4절의 예시는 파일에 적는 정적 설정입니다. Envoy는 동적 설정을 중심으로 설계됐습니다. Envoy는 xDS라는 gRPC/REST API 묶음으로 원격 서버에서 설정을 받고, 재시작 없이 반영합니다. API는 자원 종류별로 나뉘고 이름이 모두 Discovery Service로 끝나서 xDS라고 부릅니다.
- LDS(Listener Discovery Service): Listener 설정 배포
- RDS(Route Discovery Service): Route 설정 배포
- CDS(Cluster Discovery Service): Cluster 설정 배포
- EDS(Endpoint Discovery Service): Cluster 안의 Endpoint 목록 배포
- SDS(Secret Discovery Service): TLS 인증서 배포
설정을 만들어 Envoy에 보내는 서버를 컨트롤 플레인(Control Plane)이라고 합니다. 설정을 받아 트래픽을 처리하는 Envoy는 데이터 플레인(Data Plane)이라고 합니다. 오토스케일링으로 서버 IP가 계속 바뀌면, 컨트롤 플레인이 바뀐 목록을 EDS로 보냅니다. Envoy는 연결을 끊지 않고 새 목록으로 라우팅합니다.
복원력 기능
Envoy에는 업스트림 호출 실패에 대응하는 기능이 기본으로 들어 있습니다.
- 재시도와 타임아웃: 라우트 단위로 재시도 횟수, 재시도 조건, 타임아웃을 지정합니다.
- 서킷 브레이킹: 클러스터 단위로 최대 동시 연결 수, 대기 요청 수, 동시 요청 수를 제한합니다. 한도를 넘은 요청은 바로 실패 처리합니다.
- 이상치 감지(Outlier Detection): 연속 5xx 같은 기준에 해당하는 Endpoint를 일정 시간 로드밸런싱 대상에서 제외합니다. 실제 트래픽의 결과로 판단하므로 수동적 헬스체크라고 합니다.
- 능동적 헬스체크: 헬스체크 요청을 주기적으로 보내 Endpoint 상태를 확인합니다.
- 로드밸런싱 정책: 라운드 로빈 외에 최소 요청(Least Request), 링 해시(Ring Hash) 등을 클러스터별로 선택합니다.
관측성
Envoy는 거쳐 가는 모든 요청에 대해 다운스트림, 업스트림, 클러스터 단위의 통계를 기본으로 기록합니다. 접근 로그 형식을 세부적으로 지정할 수 있습니다. 분산 추적(Distributed Tracing) 시스템과 연동하면 추적 헤더를 전파하고 스팬(Span)을 생성합니다. 그래서 애플리케이션 코드를 고치지 않고도 서비스 간 호출의 성공률과 지연 시간을 볼 수 있습니다.
Envoy는 핫 리스타트(Hot Restart)도 지원합니다. 핫 리스타트는 기존 연결을 유지하면서 실행 중인 프로세스를 새 버전 바이너리로 교체하는 기능입니다.
6. Nginx와 Envoy의 차이: 출발점이 다른 두 프록시
Nginx와 Envoy는 둘 다 리버스 프록시(Reverse Proxy)로 쓰여서 자주 비교됩니다. 리버스 프록시는 서버 앞에서 클라이언트 요청을 받아 백엔드 서버로 전달하는 프록시입니다. 두 프로젝트는 만든 목적이 다릅니다. Nginx는 2004년에 웹 서버로 공개됐고, 정적 파일 서빙, 리버스 프록시, 캐싱에 널리 쓰입니다. Envoy는 2016년에 서비스 간 통신용 프록시로 공개됐습니다.
| 구분 | Nginx (오픈소스) | Envoy |
|---|---|---|
| 출발점 | 웹 서버 (2004) | 서비스 간 L7 프록시 (2016) |
| 설정 방식 | 설정 파일 + reload | 파일 또는 xDS API 스트리밍 |
| 동적 설정 API | 오픈소스에는 없음 (NGINX Plus 제공) | 기본 설계 (xDS) |
| 업스트림 프로토콜 | HTTP/1.x (gRPC는 별도 grpc_pass) |
HTTP/1.1·HTTP/2 등을 클러스터별 지정 |
| 기본 지표 | stub_status의 요약 수치 | 리스너·클러스터 단위 상세 통계 내장 |
가장 큰 차이는 설정을 반영하는 방식입니다. 오픈소스 Nginx는 설정 파일을 수정하고 reload 신호를 보내야 합니다. 그러면 새 설정으로 워커 프로세스를 새로 띄우고, 기존 워커는 처리 중인 연결을 마친 뒤 종료합니다. 연결은 끊기지 않습니다. 하지만 설정을 파일에서 읽으므로, 업스트림 목록이 자주 바뀌면 파일을 다시 만들고 reload하는 절차를 별도로 구성해야 합니다. Envoy는 설정을 API로 받으므로 컨트롤 플레인이 보낸 설정을 바로 반영합니다.
관측성에도 차이가 있습니다. 오픈소스 Nginx의 기본 지표는 stub_status 모듈이 제공하는 연결 수 같은 요약 수치입니다. 상세 지표를 보려면 NGINX Plus나 외부 모듈이 필요합니다. Envoy는 상세 통계를 기본으로 제공합니다.
용도에 따라 선택하면 됩니다. 정적 콘텐츠 서빙, 캐싱, 구성이 자주 바뀌지 않는 리버스 프록시에는 Nginx가 많이 쓰입니다. 서비스가 많고 구성이 자주 바뀌는 서비스 간 통신에는 Envoy가 맞습니다.
7. API Gateway와 Service Mesh에서의 Envoy: 같은 프록시, 다른 배치
Envoy는 주로 두 위치에 배치됩니다. 바이너리는 같고, 배치 위치에 따라 부르는 이름이 달라집니다.
첫 번째는 엣지(Edge)입니다. 엣지는 외부 트래픽이 시스템에 처음 들어오는 지점입니다. 여기에 Envoy를 두고 인증, 속도 제한, 라우팅을 맡기면 API Gateway가 됩니다. 이때는 Envoy를 직접 설정하기보다 Envoy 기반 제품을 쓰는 경우가 많습니다. Envoy Gateway(Envoy 공식 하위 프로젝트), Contour, Emissary-ingress가 Envoy를 데이터 플레인으로 쓰는 게이트웨이입니다.
두 번째는 서비스 메시(Service Mesh)입니다. 서비스 인스턴스(Kubernetes에서는 파드)마다 Envoy를 사이드카(Sidecar)로 하나씩 둡니다. 사이드카는 애플리케이션 옆에서 함께 실행되는 보조 컨테이너입니다. 서비스 간 트래픽은 모두 양쪽의 Envoy를 거칩니다.
[서비스 A 파드] [서비스 B 파드]
서비스 A ──→ Envoy(사이드카) ──mTLS──→ Envoy(사이드카) ──→ 서비스 B
└── 재시도·타임아웃·지표 수집이 이 구간에서 처리됨대표적인 서비스 메시는 Istio입니다. Istio의 데이터 플레인은 Envoy 사이드카입니다. 컨트롤 플레인인 istiod가 xDS로 모든 사이드카에 설정을 배포합니다. 그래서 서비스 코드를 고치지 않고 메시 전체에 다음 기능을 적용할 수 있습니다.
- 상호 TLS(mTLS): 양쪽이 서로의 인증서를 검증하는 TLS
- 재시도 정책
- 트래픽 비율 분할: 새 버전에 트래픽 일부만 보내는 카나리 배포에 사용
두 경우의 차이는 배치 방식과 설정을 보내는 주체입니다. 엣지에서는 소수의 Envoy가 외부 트래픽을 받습니다. 메시에서는 인스턴스마다 배치된 Envoy가 내부 트래픽을 처리합니다.
8. 장점과 주의사항
장점은 다음과 같습니다.
- 서비스 언어와 관계없이 재시도, 타임아웃, mTLS 정책을 같은 방식으로 적용합니다.
- 라우팅, Endpoint 목록, 인증서를 xDS로 재시작 없이 바꿉니다.
- 요청이 Envoy를 거치기만 하면 서비스 간 호출의 지표, 로그, 추적 정보를 얻습니다.
- Istio, Envoy Gateway, Contour 등 여러 도구가 Envoy를 데이터 플레인으로 씁니다. Envoy를 알면 이 도구들의 동작을 이해하기 쉽습니다.
주의할 점도 있습니다.
- 홉(Hop, 요청이 거치는 네트워크 단계)이 하나 늘어서 지연 시간이 추가됩니다. 추가되는 지연은 워크로드마다 다릅니다. 지연에 민감한 서비스라면 도입 전에 측정해야 합니다.
- 필터 체인과 xDS 체계가 방대해서 배우는 데 시간이 듭니다. 그래서 정적 파일을 직접 작성하기보다 Istio나 Envoy Gateway 같은 컨트롤 플레인을 통해 쓰는 경우가 많습니다. 이 경우에도 문제를 분석하려면 Envoy의 설정과 지표를 읽어야 합니다.
- 사이드카 방식은 인스턴스마다 프록시 프로세스가 하나씩 추가됩니다. 인스턴스 수에 비례해 CPU와 메모리를 더 씁니다.
- 5xx 응답이 애플리케이션에서 났는지 Envoy에서 났는지 구분해야 합니다. Envoy 접근 로그의 response flags를 보면 구분할 수 있습니다. response flags는 업스트림 연결 실패, 타임아웃처럼 실패 지점을 나타내는 코드입니다.
9. 마무리
Envoy는 서비스 간 통신의 네트워크 처리를 애플리케이션과 분리된 프로세스에서 수행하는 L7 프록시입니다. 요청은 Listener, Route, Cluster, Endpoint 순서로 지나갑니다. 이 순서는 Envoy를 엣지에 두든 사이드카로 두든 같습니다.
이 구조를 알면 상위 도구의 문제도 같은 기준으로 확인할 수 있습니다. Istio의 VirtualService나 Gateway 리소스도 컨트롤 플레인이 xDS 설정으로 변환해 Envoy에 보냅니다. 그래서 메시나 게이트웨이에서 라우팅 문제가 생기면 "어느 Listener의 어떤 Route가 어느 Cluster를 가리키는가"를 확인하면 됩니다. 더 알아보려면 Envoy 공식 문서의 아키텍처 개요와 정적 설정 예제부터 읽어 보세요.