Envoy 이해하기: Listener, Route, Cluster로 따라가는 요청 흐름

@yunhobb· July 22, 2026 · 9 min read

Istio나 Kubernetes Gateway API 문서를 읽다 보면 Envoy라는 이름이 반복해서 나옵니다. 이런 도구의 사용법을 먼저 접하면, 정작 그 아래에서 트래픽을 실제로 옮기는 Envoy가 무엇을 하는지는 건너뛰기 쉽습니다. 이 글은 Envoy가 왜 만들어졌는지부터 시작해, 요청 하나가 Envoy를 통과하는 경로를 Listener·Route·Cluster 세 구성요소로 따라가며 정리합니다.


1. Envoy는 왜 필요한가: 애플리케이션 밖으로 꺼낸 네트워크 처리

모놀리스(Monolith)를 여러 서비스로 나누면, 지금까지 프로세스 안의 메서드 호출이던 것이 네트워크 호출로 바뀝니다. 네트워크 호출은 메서드 호출과 달리 실패합니다. 상대 서버가 응답하지 않거나, 응답이 늦거나, 중간에 연결이 끊기기도 합니다.

그래서 서비스 간 호출에는 여러 처리가 따라붙습니다. 타임아웃(Timeout), 재시도(Retry), 서킷 브레이커(Circuit Breaker, 계속 실패하는 대상으로 가는 호출을 잠시 차단하는 장치), TLS 암호화, 호출 지표 수집 같은 것들입니다.

이 처리를 각 서비스의 코드 안에서 라이브러리로 해결할 수 있습니다. JVM 진영의 Resilience4j가 그런 예입니다. 그런데 라이브러리 방식에는 반복되는 문제가 있습니다.

  • 언어마다 같은 기능을 다시 구현해야 합니다. Java용 라이브러리는 Python이나 Go 서비스에서 쓸 수 없습니다.
  • 서비스마다 라이브러리 버전이 달라집니다. 같은 재시도 정책이라도 서비스별로 동작이 어긋날 수 있습니다.
  • 비즈니스 로직과 네트워크 처리 코드가 섞입니다. 재시도 정책 하나를 바꾸려 해도 서비스를 다시 빌드하고 배포해야 합니다.

Envoy는 이 처리를 애플리케이션 프로세스 밖으로 꺼내는 접근입니다. 애플리케이션은 목적지 서비스가 아니라 옆에 있는 Envoy로 요청을 보내고 재시도·타임아웃·암호화·지표 수집은 Envoy가 대신 수행합니다. 애플리케이션 코드에서 네트워크 처리가 빠지므로, 어떤 언어로 만든 서비스든 같은 정책을 적용할 수 있습니다.

참고: Envoy 공식 문서는 프로젝트의 지향을 "네트워크는 애플리케이션에 투명해야 한다(The network should be transparent to applications)"라고 설명합니다. 문제가 생겼을 때 원인이 네트워크인지 애플리케이션인지 쉽게 가려낼 수 있어야 한다는 취지입니다.


2. Envoy란 무엇인가: 서비스 간 통신을 위해 설계된 L7 프록시

Envoy는 Lyft에서 개발해 2016년 오픈소스로 공개한 프록시(Proxy)입니다. 공식 문서는 Envoy를 "대규모 서비스 지향 아키텍처를 위해 설계된 L7 프록시이자 통신 버스(L7 proxy and communication bus)"라고 소개합니다. 2017년 CNCF(Cloud Native Computing Foundation)에 합류했고 2018년 11월 Kubernetes와 Prometheus에 이어 세 번째로 CNCF를 졸업했습니다.

  • 독립 프로세스로 동작합니다. 애플리케이션과 같은 프로세스에 라이브러리로 들어가는 게 아니라, 별도 프로세스로 옆에서 실행됩니다. 그래서 애플리케이션의 언어와 무관하게 쓸 수 있습니다.
  • C++로 작성된 이벤트 기반 프록시입니다. 논블로킹 이벤트 루프로 동작하며 적은 스레드로 많은 연결을 처리하도록 설계됐습니다.
  • L4와 L7을 모두 다룹니다. TCP 수준의 전달뿐 아니라 HTTP/1.1, HTTP/2, HTTP/3, gRPC를 해석해서 요청 단위로 라우팅합니다.
  • 설정을 API로 받을 수 있습니다. 파일뿐 아니라 원격 API에서 설정을 실시간으로 받아 재시작 없이 반영합니다. 이 동적 설정 체계(xDS)는 뒤에서 다시 다룹니다.

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) ──→ 업스트림 서버로 요청 전달
  1. Listener가 지정된 주소·포트에서 다운스트림의 TCP 연결을 받습니다.
  2. Listener에 붙은 네트워크 필터가 연결을 처리합니다. HTTP 트래픽이라면 대표 필터인 HTTP Connection Manager가 바이트 스트림을 HTTP 요청으로 해석하고, 그 안의 HTTP 필터 체인을 차례로 통과시킵니다. 인증, 속도 제한 같은 기능이 이 체인에 필터로 끼어들고 체인의 마지막은 항상 router 필터입니다.
  3. router 필터가 라우트 설정과 요청을 대조합니다. Host 헤더로 가상 호스트(Virtual Host)를 고르고 경로·헤더 조건을 위에서부터 순서대로 대조해 처음 매칭된 라우트를 사용합니다. 매칭된 라우트에는 요청을 보낼 Cluster가 적혀 있습니다.
  4. Cluster의 로드밸런싱 정책이 Endpoint 하나를 고르고 그 주소로 업스트림 연결을 맺어 요청을 전달합니다. 응답은 같은 경로를 반대로 되돌아갑니다.

참고: 실제로는 필터가 세 층으로 나뉩니다. 연결 수립 단계에서 동작하는 리스너 필터(예: 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 이름 하나로 적었지만, 실제 운영에서는 이 목록이 서비스 디스커버리를 통해 동적으로 채워집니다.

Listener는 "어디로 들어오는가", Route는 "어떤 요청을 어디로 보내는가", Cluster는 "보낼 곳의 실체가 무엇인가"를 정의합니다.


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 인증서 배포

이때 설정을 만들어 내려 주는 별도 서버를 컨트롤 플레인(Control Plane)이라 부르고, 설정을 받아 실제 트래픽을 처리하는 Envoy를 데이터 플레인(Data Plane)이라 부릅니다. 오토스케일링으로 서버 IP가 계속 바뀌는 환경이라면, 컨트롤 플레인이 바뀐 목록을 EDS로 밀어 넣고 Envoy는 끊김 없이 새 목록으로 라우팅합니다.

복원력 기능

업스트림 호출의 실패에 대응하는 기능이 기본으로 들어 있습니다.

  • 재시도와 타임아웃. 라우트 단위로 재시도 횟수·조건과 타임아웃을 지정합니다.
  • 서킷 브레이킹. 클러스터 단위 임계값 방식입니다. 최대 동시 연결 수, 대기 요청 수, 동시 요청 수를 제한하고 초과분은 즉시 실패시킵니다.
  • 이상치 감지(Outlier Detection). 연속 5xx 같은 기준으로 특정 Endpoint를 로드밸런싱 대상에서 일정 시간 제외합니다. 실제 트래픽의 결과로 판단하는 수동적 헬스체크입니다.
  • 능동적 헬스체크. 별도의 헬스체크 요청을 주기적으로 보내 Endpoint 상태를 확인합니다.
  • 로드밸런싱 정책. 라운드 로빈 외에 최소 요청(Least Request), 링 해시(Ring Hash) 등을 클러스터별로 고를 수 있습니다.

관측성

Envoy를 거치는 모든 요청에 대해 다운스트림·업스트림·클러스터 단위의 상세 통계가 기본으로 쌓입니다. 접근 로그 형식을 세밀하게 지정할 수 있고 분산 추적(Distributed Tracing) 시스템과 연동해 추적 헤더를 전파하고 스팬(Span)을 생성합니다. 네트워크 처리가 Envoy로 모이면서, 서비스 간 호출의 성공률·지연 같은 지표를 애플리케이션 코드 수정 없이 얻게 되는 것입니다.

이 밖에 실행 중인 프로세스의 연결을 유지한 채 새 버전 바이너리로 교체하는 핫 리스타트(Hot Restart)도 지원합니다.


6. Nginx와 Envoy의 차이: 태생이 다른 두 프록시

리버스 프록시라는 점은 같아서 자주 비교되지만, 두 프로젝트는 만들어진 목적이 다릅니다. 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가 중앙에서 외부 트래픽을 받고, 메시에서는 다수의 Envoy가 분산 배치되어 내부 트래픽을 받는다는 배치의 차이, 그리고 설정을 내려 주는 주체의 차이가 있을 뿐입니다.


8. 장점과 주의사항

  • 언어와 무관하게 정책이 일관됩니다. 재시도·타임아웃·mTLS를 서비스 언어와 관계없이 한 방식으로 적용합니다.
  • 재시작 없이 설정이 바뀝니다. xDS로 라우팅·Endpoint 목록·인증서까지 무중단으로 교체합니다.
  • 관측성이 기본 내장입니다. 프록시를 거치는 것만으로 서비스 간 호출의 지표·로그·추적이 확보됩니다.
  • 생태계가 넓습니다. Istio, Envoy Gateway, Contour 등 주요 도구가 Envoy를 데이터 플레인으로 채택하고 있어, Envoy를 이해하면 이 도구들의 동작을 따라가는 데도 도움이 됩니다.

주의할 점도 있습니다.

  • 홉(Hop)이 늘어납니다. 요청이 프록시를 거치는 만큼 지연이 추가됩니다. 대개 크지 않지만 워크로드에 따라 다르므로, 지연에 민감한 서비스라면 도입 전에 측정이 필요합니다.
  • 설정 모델의 학습 비용이 큽니다. 필터 체인과 xDS 체계가 방대해서, 정적 파일로 직접 다루기보다 Istio나 Envoy Gateway 같은 컨트롤 플레인을 통해 쓰는 경우가 많습니다. 다만 그 경우에도 문제 분석은 결국 Envoy의 설정과 지표를 읽는 일로 내려옵니다.
  • 사이드카 방식은 자원 비용이 있습니다. 인스턴스마다 프록시 프로세스가 하나씩 추가되므로, 인스턴스 수에 비례해 CPU·메모리가 더 듭니다.
  • 장애 원인을 가릴 지점이 하나 늘어납니다. 5xx 응답이 애플리케이션에서 난 것인지 프록시 단계에서 난 것인지 구분해야 합니다. Envoy 접근 로그의 response flags(업스트림 연결 실패, 타임아웃 등 실패 지점을 표시하는 코드)가 이 구분을 돕지만, 읽는 법을 익혀 둬야 합니다.

9. 마무리

Envoy는 서비스 간 통신에 필요한 네트워크 처리를 애플리케이션 밖의 독립 프로세스로 꺼낸 L7 프록시입니다. 요청은 Listener(연결을 받는 지점) → Route(보낼 곳을 정하는 규칙) → Cluster(보낼 곳의 논리적 묶음) → Endpoint(실제 주소) 순서로 통과하고, 이 모델은 Envoy가 엣지에 놓이든 사이드카로 놓이든 동일합니다.

이 세 구성요소를 알아 두면 상위 도구를 다룰 때도 기준점이 생깁니다. Istio의 VirtualService나 Gateway 같은 리소스도 컨트롤 플레인이 xDS 설정으로 번역해 Envoy에 내려 주는 것이라, 메시나 게이트웨이에서 라우팅 문제가 생겼을 때 "지금 어느 Listener의 어떤 Route가 어느 Cluster를 가리키고 있는가"라는 질문으로 내려가 확인할 수 있습니다. 더 깊이 볼 때는 Envoy 공식 문서의 아키텍처 개요와 정적 설정 예제부터 시작하는 것을 권합니다.

@yunhobb
녹차 주도 개발