필사 모드: Envoy Proxy Deep Dive — xDS, Filter Chain, Service Mesh, Hot Restart 내부 완전 정복 (2025)
한국어TL;DR
- **Envoy**는 Lyft가 2016년 오픈소스로 공개한 C++ L7 프록시. 현재 CNCF graduated 프로젝트, 서비스 메시의 사실상 표준 데이터 플레인.
- **3대 추상화**: **Listener** (수신), **Filter Chain** (처리), **Cluster** (업스트림 풀). 이 셋의 조합으로 거의 모든 프록시 시나리오 표현 가능.
- **xDS**: Envoy의 동적 설정 프로토콜. LDS(Listener), RDS(Route), CDS(Cluster), EDS(Endpoint), SDS(Secret). 모두 gRPC streaming으로 control plane에서 받아온다.
- **per-worker thread-local**: 각 워커 스레드가 설정 복사본을 가진다. 락 없음, CPU 선형 확장. 대신 메모리 중복.
- **Hot Restart**: 무중단 설정 업데이트. 새 프로세스가 옛 프로세스의 리스닝 소켓을 SCM_RIGHTS로 넘겨받고 점진적으로 교체.
- **HTTP Filter Chain**: 요청/응답에 순차 적용되는 필터들. 인증, 라우팅, rate limit, 관측성, 변환 등을 조합.
- **Cluster Manager**: Upstream 엔드포인트 풀 관리. Circuit breaker, outlier detection, health check, connection pool.
- **관측성 3종 세트**: Stats (Prometheus), Tracing (OpenTelemetry), Access logs.
- **서비스 메시**: Envoy + 컨트롤 플레인(Istio, Consul Connect, Gloo, Kuma). 각 Pod에 사이드카로 배포 → 트래픽 가시성, 보안, 정책.
- **WASM 필터**: 커스텀 로직을 Rust/Go/AssemblyScript로 짜서 런타임에 삽입. Envoy 재컴파일 없음.
1. Envoy는 왜 생겼는가
1.1 Lyft의 아픈 경험
2015년 Lyft는 **수십 개의 마이크로서비스**를 운영 중이었다. 각 서비스는 다양한 언어로 쓰였고(Python, Go, Java 등), 각자 HTTP 클라이언트 라이브러리를 사용했다.
문제:
- **일관되지 않은 재시도 정책**. Python은 X초 타임아웃, Go는 Y초. 엉망.
- **관측성 부재**. 각 서비스가 자기만의 로그 포맷.
- **TLS 구현 불일치**. 누구는 mTLS, 누구는 일반 TLS.
- **circuit breaker 구현 X**. 한 서비스 장애가 전체 연쇄 실패.
- **배포 마다 HTTP 스택 버그 재등장**.
전통적 접근: **모든 언어용 공통 라이브러리**를 만든다. 하지만 언어마다 재구현해야 하고, 버전 불일치 문제, 배포 동기화 어려움.
1.2 "Out-of-Process" 아키텍처
Matt Klein(Lyft)의 제안: "네트워크 스택을 **별도 프로세스**로 분리하자."
Application Code (Python/Go/Java/...)
↓
localhost:9080
↓
Envoy Sidecar
↓
mTLS, 재시도, 로드밸런싱, 관측성, circuit breaker
↓
Remote Service
장점:
- **언어 무관**: 모든 서비스가 같은 프록시를 공유.
- **배포 독립**: Envoy 업데이트가 애플리케이션 배포와 분리.
- **통일된 관측성**: 모든 트래픽이 Envoy를 거치므로 일관된 메트릭.
- **동적 설정**: 라우팅/정책 변경이 런타임에 가능.
이것이 **사이드카 패턴**의 원조. Istio, Linkerd, Consul Connect 모두 같은 아이디어.
1.3 오픈소스 공개 (2016)
Lyft는 Envoy를 2016년 9월 공개했다. 반응은 폭발적이었다:
- **Google/IBM**: Istio를 Envoy 기반으로 시작 (2017).
- **HashiCorp**: Consul Connect가 Envoy 채택.
- **AWS**: App Mesh의 데이터 플레인으로 Envoy.
- **CNCF**: 2018년 incubating, 2020년 graduated.
Matt Klein은 현재도 Lyft의 프린시펄 엔지니어이자 Envoy의 메인테이너.
1.4 2025년 현재
Envoy는:
- 전 세계 서비스 메시 배포의 **80%+** 데이터 플레인.
- Google 내부에서 **Cloud Gateway**로 사용 (수백만 QPS).
- Airbnb, Netflix, Pinterest 등 대형 테크 기업 전면 사용.
- 200+ 커밋/월 활발한 개발.
2. 아키텍처 개요
2.1 3대 개념
Envoy의 데이터 흐름을 이해하는 3개의 핵심 추상화:
Listener → Filter Chain → Cluster
(수신) (처리) (업스트림 풀)
example:
0.0.0.0:8080 → http_connection_manager → cluster "backend-v1"
├─ Router
├─ JWT Auth
├─ Rate Limit
└─ Fault Injection
2.2 Listener
Envoy가 **연결을 수신**하는 지점. TCP 소켓과 동등:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
...
한 Envoy가 여러 listener를 가질 수 있다. 각각 다른 포트, 다른 프로토콜.
2.3 Filter Chain
Listener 위에서 일어나는 **처리 로직의 순차 파이프라인**.
두 종류:
**Network Filter**: 바이트 스트림 처리. TCP proxy, HTTP CM, Thrift proxy, Redis proxy, Postgres proxy 등.
**HTTP Filter** (Network Filter인 HTTP CM 내부): HTTP 요청/응답 처리. Router, JWT, rate limit, 변환 등.
이 레이어링은 Envoy가 HTTP만이 아니라 **범용 L4/L7 프록시**가 될 수 있게 한다.
2.4 Cluster
**Upstream 서버의 논리적 그룹**. 실제로는:
- 엔드포인트 목록 (IP:port).
- 로드밸런싱 정책.
- Connection pool.
- Health check.
- Circuit breaker.
clusters:
- name: backend-v1
type: STRICT_DNS
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: backend-v1
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend.example.com
port_value: 8080
2.5 전체 그림
┌─────────────┐
│ Listener │
└──────┬──────┘
│
┌──────▼─────────────────┐
│ Network Filter Chain │
│ ┌─────────────────┐ │
│ │ TLS 검증/해독 │ │
│ ├─────────────────┤ │
│ │ HTTP Conn Mgr │──────────────┐
│ └─────────────────┘ │ │
└────────────────────────┘ │
▼
┌──────────────────────┐
│ HTTP Filter Chain │
│ ┌──────────────────┐ │
│ │ JWT Auth │ │
│ ├──────────────────┤ │
│ │ Rate Limit │ │
│ ├──────────────────┤ │
│ │ Router │ │
│ └──────────────────┘ │
└──────┬───────────────┘
│
▼
┌──────────────────┐
│ Cluster: backend │
│ - LB │
│ - Conn Pool │
│ - Circuit Break │
└──────┬───────────┘
│
▼
Upstream
3. xDS 프로토콜
Envoy의 가장 큰 혁신 중 하나. "설정 파일 재로드 + SIGHUP"에서 벗어나 **동적 설정**을 가능하게 한 프로토콜.
3.1 xDS란
**xDS** = **X** Discovery **S**ervice. X는 여러 가지:
- **LDS**: Listener Discovery Service. 리스너 목록.
- **RDS**: Route Discovery Service. HTTP 라우팅 테이블.
- **CDS**: Cluster Discovery Service. 클러스터 목록.
- **EDS**: Endpoint Discovery Service. 클러스터 멤버 목록.
- **SDS**: Secret Discovery Service. TLS 인증서/키.
- **ADS**: Aggregated Discovery Service. 위의 모든 것을 하나의 스트림으로.
Envoy는 이들을 **gRPC streaming**으로 control plane에서 받는다.
3.2 작동 원리
┌─────────────┐ ┌──────────────┐
│ Envoy │ │ Control Plane│
│ │ │ (Istio) │
│ │ DiscoveryRequest │ │
│ │ ──────────────────► │ │
│ │ │ │
│ │ DiscoveryResponse │ │
│ │ ◄────────────────── │ │
│ │ │ │
│ (apply config) │ │
│ │ ACK │ │
│ │ ──────────────────► │ │
└─────────────┘ └──────────────┘
Envoy는:
1. Control plane에 **DiscoveryRequest** 전송: "나 envoy-v1.30, 내 노드 ID는 X, 현재 version은 Y".
2. Control plane이 **DiscoveryResponse**로 답함: "여기 LDS 설정". 버전 포함.
3. Envoy가 설정 적용.
4. **ACK** 또는 **NACK** 송신.
gRPC streaming이라 양방향 지속 연결. Control plane이 변경을 push할 수 있다.
3.3 Resource Versioning
Envoy는 현재 적용된 버전을 기억한다. Control plane은 변경이 있을 때만 새 응답 전송:
Envoy: "내 LDS version은 v42"
Control plane: "변경 있음, 새 설정 v43 보냄"
Envoy: "v43 적용 완료, ACK"
만약 새 설정이 잘못되었으면 NACK, 이전 버전 유지.
3.4 ADS (Aggregated Discovery Service)
여러 xDS를 **단일 스트림**으로. 순서 보장이 필요할 때 중요:
- CDS를 먼저 받아야 → EDS가 그 cluster의 endpoint를 가리킬 수 있다.
- LDS → RDS → CDS → EDS 순서가 있다.
ADS는 이 순서를 제어 플레인이 명시할 수 있게 해준다. Istio는 기본 ADS.
3.5 Delta xDS
초기 xDS는 **State of the World**: 변경 시 **전체 리소스 목록** 전송. 1만 개 cluster가 있으면 한 개 변경에 1만 개 전송.
**Delta xDS**: 변경된 것만 보냄 (추가/수정/삭제). 큰 설정을 가진 거대 클러스터에 필수.
3.6 Control Plane 구현
대표적인 control plane들:
- **Istio Pilot** (istiod): K8s 리소스 → xDS.
- **Consul Connect**: Consul 카탈로그 → xDS.
- **Gloo**: 자체 API → xDS.
- **Contour**: Ingress/HTTPProxy → xDS.
- **go-control-plane**: Envoy 공식 라이브러리. 커스텀 구현에 사용.
커스텀 control plane을 만들고 싶다면 `go-control-plane`이 출발점.
4. Per-Worker Thread-Local 아키텍처
Envoy의 스레딩 모델은 설계 결정의 핵심이다.
4.1 문제: 프록시의 락 경합
네트워크 프록시는 수만 연결을 처리한다. 전통적 방식:
많은 워커 스레드 → 공유 상태 (설정, 통계, 라우팅 테이블)
↓
락 경합, 캐시 라인 핑퐁
확장성이 코어 수에 linear하지 않음.
4.2 Envoy의 해결
각 워커 스레드가 **자기 복사본**을 가진다.
Main Thread Worker 0 Worker 1 Worker 2
(설정 관리) ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Listener│ │ Listener│ │ Listener│
│ Copy │ │ Copy │ │ Copy │
├─────────┤ ├─────────┤ ├─────────┤
│ Cluster │ │ Cluster │ │ Cluster │
│ Copy │ │ Copy │ │ Copy │
├─────────┤ ├─────────┤ ├─────────┤
│ Stats │ │ Stats │ │ Stats │
│ Copy │ │ Copy │ │ Copy │
└─────────┘ └─────────┘ └─────────┘
- **Connection**은 accept한 worker에 **고정**. 다른 worker로 이동 안 함.
- **Config 업데이트**: Main thread가 각 worker에게 "update your copy" 메시지 전달.
- **Stats 합산**: 읽기 시점에 각 worker의 값 합산 (atomic하지 않아도 됨).
4.3 장점
1. **락 없음**: 워커 스레드가 자기 copy만 건드린다.
2. **선형 확장**: 코어 추가 = 성능 증가.
3. **캐시 친화적**: 같은 데이터가 한 코어에서만 읽힘.
4.4 단점
1. **메모리 오버헤드**: 설정이 워커 수만큼 복사. 1만 cluster × 32 워커 = 32만 copy.
2. **업데이트 지연**: 모든 워커가 copy를 업데이트하려면 시간 필요.
3. **통계 합산 비용**: 읽기 시 순회.
4.5 TLS (Thread-Local Storage)
C++에서 `thread_local` 또는 Envoy의 `ThreadLocal::Slot` 추상화 사용. 각 워커가 자기 슬롯에 객체 저장, main thread가 "update" 메시지 보내면 해당 객체 교체.
// Main thread
tls_slot.runOnAllThreads([new_config](Object& obj) {
obj.update(new_config);
});
락 없음. 각 worker가 "내 차례"에 업데이트.
5. Hot Restart
설정 파일 변경 시 무중단 재시작.
5.1 왜 필요한가
Envoy 설정이 변경되면 재시작이 필요할 수 있다(예: 새 컴파일된 이진 배포). 전통적 방식:
1. 옛 프로세스 kill
2. 새 프로세스 start
3. 다운타임 = (start time)
수십 ms라도 고성능 서비스에서는 치명적.
5.2 Envoy의 Hot Restart
**두 프로세스 공존 + 소켓 공유**:
Time 0: Envoy v1 단독 실행, 모든 트래픽 처리
Time 1: Envoy v2 시작 (--restart-epoch 1)
→ v2가 v1에게 SCM_RIGHTS로 listener socket 요청
→ v1이 fd 전달
Time 2: v2 트래픽 처리 시작 (v1과 병행)
Time 3: v1이 drain timer 시작 (예: 15분)
→ v1의 기존 연결만 처리, 새 연결은 v2로
Time 4: v1의 기존 연결 종료 시 graceful shutdown
Time 5: v1 종료
5.3 SCM_RIGHTS
UNIX 도메인 소켓의 특수 기능. 프로세스 간 **파일 디스크립터 전달** 가능:
// 송신 측 (Envoy v1)
struct msghdr msg = {0};
struct cmsghdr *cmsg;
int fds[1] = { listener_fd };
char buf[CMSG_SPACE(sizeof(fds))];
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fds));
memcpy(CMSG_DATA(cmsg), fds, sizeof(fds));
sendmsg(unix_socket, &msg, 0);
// 수신 측 (Envoy v2)
// recvmsg로 받은 fds가 v2 프로세스에서도 유효한 fd
v2가 같은 listening socket에 accept하면 **새 연결이 자동으로 v2로 간다** (OS가 여러 프로세스 중 하나를 선택).
5.4 Shared Memory
두 프로세스가 통계를 공유하려면? **shared memory**:
/dev/shm/envoy_shared_memory_0 (v1)
/dev/shm/envoy_shared_memory_1 (v2)
Envoy는 hot restart 시 stats 같은 일부 상태를 공유 메모리에 저장. v2가 v1의 stats를 이어받는다.
5.5 Drain Manager
v1이 종료될 때:
- **Drain sequence**: 새 연결은 v2로, 기존 연결은 graceful close.
- **Connection counting**: 열린 연결이 0 될 때까지 대기.
- **Max drain time**: 무한 대기 방지 (기본 1시간).
Hot restart는 Envoy의 **시그니처 기능** 중 하나. 많은 서비스 메시의 "무중단 설정 업데이트"가 실제로는 여기에 의존.
6. HTTP Connection Manager & Filter Chain
6.1 HCM 역할
**HTTP Connection Manager** (HCM)은 Network Filter 중 하나지만 가장 중요하다:
1. **HTTP 디코딩**: 원시 바이트 → HTTP 메시지.
2. **HTTP/1.1, HTTP/2, HTTP/3 지원**.
3. **HTTP Filter Chain 실행**.
4. **Router를 통해 cluster로 라우팅**.
5. **응답 인코딩**.
6.2 HTTP Filter 종류
Envoy에는 40+ 개의 HTTP filter가 내장:
**인증/인가**:
- `envoy.filters.http.jwt_authn`: JWT 검증.
- `envoy.filters.http.oauth2`: OAuth 2.0 흐름.
- `envoy.filters.http.ext_authz`: 외부 인가 서비스 호출.
- `envoy.filters.http.rbac`: RBAC 정책.
**트래픽 제어**:
- `envoy.filters.http.router`: 최종 라우팅 (반드시 마지막에 와야).
- `envoy.filters.http.ratelimit`: Rate limit (외부 서비스).
- `envoy.filters.http.local_ratelimit`: 로컬 rate limit.
- `envoy.filters.http.fault`: Fault injection (지연, HTTP 에러).
**변환**:
- `envoy.filters.http.header_to_metadata`: 헤더 → 동적 메타데이터.
- `envoy.filters.http.lua`: Lua 스크립트로 커스텀.
- `envoy.filters.http.wasm`: WASM 모듈.
**관측성**:
- `envoy.filters.http.tap`: 요청/응답 캡처.
- `envoy.filters.http.tracer`: 분산 추적.
**프로토콜 변환**:
- `envoy.filters.http.grpc_json_transcoder`: gRPC ↔ REST.
- `envoy.filters.http.grpc_web`: gRPC-Web.
6.3 필터 실행 순서
http_filters:
- name: envoy.filters.http.jwt_authn # 1. 먼저 토큰 검증
- name: envoy.filters.http.rbac # 2. 권한 체크
- name: envoy.filters.http.ratelimit # 3. 레이트 리밋
- name: envoy.filters.http.fault # 4. 장애 주입 (테스트 시)
- name: envoy.filters.http.router # 5. 최종 라우팅 (반드시 마지막)
각 필터는:
- **Decode path** (요청 처리): `decodeHeaders` → `decodeData` → `decodeTrailers`.
- **Encode path** (응답 처리): `encodeHeaders` → `encodeData` → `encodeTrailers`.
필터가 `StopIteration`을 반환하면 뒤 필터 실행 안 됨. 예: JWT 검증 실패 시 401 응답.
6.4 Router
`envoy.filters.http.router`는 특별하다:
- **반드시 마지막** 필터.
- Route 테이블을 조회해 **cluster 선택**.
- 선택된 cluster에 요청 전송.
- 재시도, timeout, 외부 연결 관리.
6.5 Routing Rules
routes:
- match:
prefix: "/api/v1/"
route:
cluster: api-v1
timeout: 10s
retry_policy:
retry_on: 5xx
num_retries: 3
- match:
prefix: "/api/v2/"
route:
weighted_clusters:
clusters:
- name: api-v2-stable
weight: 90
- name: api-v2-canary
weight: 10
- match:
prefix: "/"
route:
cluster: frontend
매칭 기준:
- **Path**: prefix, exact, regex.
- **Headers**: 특정 헤더 값.
- **Query params**.
- **Connection props**: TLS SNI, source IP.
6.6 Direct Response
어떤 경로에 즉시 응답 (upstream 호출 없이):
- match:
path: "/health"
direct_response:
status: 200
body:
inline_string: "OK"
헬스 체크, 유지보수 모드에 유용.
7. Cluster Manager
Upstream 연결 관리의 심장.
7.1 Cluster Types
**STATIC**: 엔드포인트를 설정에 하드코딩.
load_assignment:
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.0.0.1
port_value: 8080
**STRICT_DNS**: DNS로 엔드포인트 해석. 짧은 캐시(설정 가능).
type: STRICT_DNS
load_assignment:
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend.example.com # DNS 이름
**LOGICAL_DNS**: DNS 결과 중 첫 IP만 사용. 덜 정확하지만 가볍다.
**EDS** (Endpoint Discovery Service): xDS로 엔드포인트 동적 업데이트. Istio 기본.
type: EDS
eds_cluster_config:
eds_config:
ads: {}
**ORIGINAL_DST**: iptables redirect 등으로 프록시에 도착한 패킷의 원래 목적지로 포워딩. 투명 프록시.
7.2 Load Balancing
Envoy는 여러 LB 알고리즘:
- **ROUND_ROBIN**: 기본. 순차 순환.
- **LEAST_REQUEST**: 활성 요청이 가장 적은 엔드포인트.
- **RING_HASH**: Consistent hashing. 세션 고정.
- **RANDOM**: 무작위.
- **MAGLEV**: Google Maglev 해싱. Ring hash보다 균등하고 빠름.
- **LOAD_BALANCING_POLICY_CONFIG**: 사용자 정의.
7.3 Subset Load Balancing
Cluster 안에서 **부분집합** 선택. 예: 특정 버전, 특정 지역.
lb_subset_config:
fallback_policy: ANY_ENDPOINT
subset_selectors:
- keys: [version, region]
호출 시:
route:
cluster: backend
metadata_match:
filter_metadata:
envoy.lb:
version: v2
region: us-east
이로써 같은 cluster 내에서 "v2 + us-east"만 선택 가능. Canary 배포, A/B 테스트.
7.4 Connection Pool
각 cluster는 **connection pool**을 유지한다:
- HTTP/1.1: 여러 연결, 각 연결이 한 번에 하나의 요청.
- HTTP/2: 적은 연결(보통 1개), 많은 스트림 멀티플렉싱.
- HTTP/3: 유사.
- Raw TCP: 1:1 연결.
Pool 파라미터:
- `max_connections`: 최대 연결 수.
- `max_pending_requests`: 대기 큐.
- `max_requests`: 연결당 최대 요청 (HTTP/2).
Pool이 포화되면 요청이 대기 또는 거부.
7.5 Circuit Breaker
Upstream 과부하 시 **트래픽 차단**:
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 1024
max_pending_requests: 1024
max_requests: 1024
max_retries: 3
임계치 초과 시 새 요청은 503 즉시 반환. Upstream이 회복할 시간을 준다.
7.6 Outlier Detection
**자동으로 나쁜 엔드포인트 제외**:
outlier_detection:
consecutive_5xx: 5
interval: 10s
base_ejection_time: 30s
max_ejection_percent: 50
의미:
- 5xx가 5번 연속 → 30초간 제외.
- 10초마다 검사.
- 최대 50% 엔드포인트까지 제외 가능 (안전 한도).
이것이 **passive health check** — 실제 트래픽으로 판단. **Active health check**도 별도 가능:
health_checks:
- timeout: 1s
interval: 10s
unhealthy_threshold: 3
healthy_threshold: 3
http_health_check:
path: /health
주기적으로 `/health` GET → 3번 실패 시 제외.
8. 관측성
Envoy의 약속: **"모든 트래픽이 Envoy를 거치므로 관측성을 통일한다"**.
8.1 Statistics
Envoy는 수천 개의 stat을 제공한다.
**카운터 (Counter)**: 증가만.
cluster.backend.upstream_rq_total: 12345
cluster.backend.upstream_rq_2xx: 11000
cluster.backend.upstream_rq_5xx: 45
**게이지 (Gauge)**: 현재 값.
cluster.backend.upstream_cx_active: 128
**히스토그램 (Histogram)**: 분포.
cluster.backend.upstream_rq_time:
p50: 15ms, p95: 80ms, p99: 200ms
Prometheus 형식으로 export:
http://envoy_admin:9901/stats/prometheus
8.2 Access Logs
모든 요청을 로그로:
access_log:
- name: envoy.access_loggers.file
typed_config:
path: /var/log/access.log
log_format:
text_format_source:
inline_string: "[%START_TIME%] %REQ(:METHOD)% %REQ(:PATH)% %RESPONSE_CODE% %DURATION%\n"
또는 JSON:
log_format:
json_format:
start_time: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(:PATH)%"
status: "%RESPONSE_CODE%"
duration: "%DURATION%"
upstream_host: "%UPSTREAM_HOST%"
8.3 Distributed Tracing
Envoy가 trace 헤더 전파 + 자신의 span 생성.
tracing:
provider:
name: envoy.tracers.opentelemetry
typed_config:
grpc_service:
envoy_grpc:
cluster_name: otel-collector
지원 backend:
- Jaeger
- Zipkin
- Datadog
- OpenTelemetry (OTLP)
- SkyWalking
- LightStep
**자동으로 span 생성**: Envoy가 요청을 받으면 trace ID 생성(없으면) 및 span 시작. Upstream으로 전달할 때 헤더에 삽입.
8.4 Tap Filter
요청/응답을 **실시간 캡처**:
- name: envoy.filters.http.tap
typed_config:
common_config:
admin_config:
config_id: my_tap
Admin API로 trigger:
curl -X POST http://envoy:9901/tap?config_id=my_tap \
-d '{"config_id":"my_tap","tap_config":{"match_config":{"http_request_headers_match":{"headers":[{"name":":path","exact_match":"/api/v1/users"}]}},"output_config":{"sinks":[{"streaming_admin":{}}]}}}'
`/api/v1/users` 요청이 올 때마다 전체 메시지가 HTTP 스트림으로 전송. 디버깅에 강력.
8.5 Admin Interface
Envoy는 기본으로 **admin interface**를 제공 (보통 port 9901):
- `/stats`: 통계 덤프.
- `/clusters`: cluster 상태.
- `/config_dump`: 현재 설정 전체.
- `/listeners`: 리스너 목록.
- `/certs`: 인증서 정보.
- `/healthcheck/fail`: 수동 fail(drain용).
- `/hot_restart_version`: hot restart 정보.
- `/runtime`: 런타임 플래그.
보안: admin은 **localhost에만** 바인딩 권장. 인증 없음(성능 이슈).
9. mTLS와 보안
9.1 서비스 메시의 mTLS
전통적으로 mTLS는 복잡했다. 인증서 발급/갱신/배포가 어려웠다. 서비스 메시는 이를 **자동화**:
CA (예: Istio의 Citadel)
↓ 각 workload에게 단기 인증서 발급
↓ SPIFFE SVID 형식
Envoy (sidecar)
↓ 인증서 사용
연결 상대방과 mTLS
9.2 SDS (Secret Discovery Service)
Envoy가 인증서를 **동적으로** 받는 메커니즘.
tls_context:
common_tls_context:
tls_certificate_sds_secret_configs:
- name: server_cert
sds_config:
api_config_source:
api_type: GRPC
grpc_services:
- envoy_grpc:
cluster_name: sds_server
인증서 만료 전에 control plane이 새 인증서 push → Envoy가 즉시 교체. 다운타임 없음.
9.3 SPIFFE / SPIRE
**SPIFFE**: Secure Production Identity Framework for Everyone. 표준 ID 형식.
spiffe://cluster.local/ns/default/sa/myservice
**SPIRE**: SPIFFE 구현체. workload의 신원을 인증하고 SVID 발급.
Istio는 SPIFFE 호환 ID를 사용 → 다양한 환경에서 상호운용.
9.4 Authorization
**RBAC 필터**:
- name: envoy.filters.http.rbac
typed_config:
rules:
action: ALLOW
policies:
"api-access":
permissions:
- url_path:
path:
prefix: "/api/"
principals:
- authenticated:
principal_name:
exact: "spiffe://cluster.local/ns/default/sa/frontend"
"`frontend` 서비스 계정이 `/api/` 경로에 접근 허용". 서비스 메시 정책의 기본.
10. WASM Filter
Envoy의 확장성은 필터 기반이지만, 새 필터를 추가하려면 C++로 짜고 Envoy를 재컴파일해야 했다. **WASM**이 이를 바꿨다.
10.1 WASM Filter Basic
Rust로 작성 예:
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
#[no_mangle]
pub fn _start() {
proxy_wasm::set_log_level(LogLevel::Info);
proxy_wasm::set_http_context(|_, _| -> Box<dyn HttpContext> {
Box::new(MyHttpFilter)
});
}
struct MyHttpFilter;
impl HttpContext for MyHttpFilter {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
if let Some(token) = self.get_http_request_header("Authorization") {
self.set_http_request_header("X-User", Some(&decode_token(&token)));
}
Action::Continue
}
}
컴파일:
cargo build --target wasm32-unknown-unknown --release
Envoy 설정:
- name: envoy.filters.http.wasm
typed_config:
config:
vm_config:
code:
local:
filename: "/etc/envoy/my_filter.wasm"
10.2 장점
- **언어 독립**: Rust, Go, AssemblyScript, C++.
- **Hot Reload**: 재시작 없이 필터 교체.
- **샌드박스**: 필터 크래시가 Envoy 크래시 되지 않음.
- **배포 분리**: 필터를 별도 저장소에서.
10.3 단점
- **성능**: 네이티브 C++ 필터보다 약간 느림 (수 μs 차이).
- **디버깅 어려움**: WASM 스택 트레이스 제한.
- **기능 제약**: 일부 Envoy API는 WASM에서 호출 불가.
주요 사용처: Solo.io의 Gloo, Istio의 WasmPlugin, Kong의 Gateway.
11. 서비스 메시 실제 배포
11.1 Istio 아키텍처
┌──────────────────────────────────┐
│ istiod (Control Plane) │
│ - Pilot (xDS) │
│ - Citadel (CA) │
│ - Galley (config validation) │
└──────────────────────────────────┘
↑ xDS gRPC
│
┌─────────────┴────────────────────┐
│ Data Plane │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ Pod A │ │ Pod B │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │Envoy │ │ │ │Envoy │ │ │
│ │ ├────────┤ │ │ ├────────┤ │ │
│ │ │App │ │ │ │App │ │ │
│ │ └────────┘ │ │ └────────┘ │ │
│ └────────────┘ └────────────┘ │
└──────────────────────────────────┘
- `istiod`가 K8s 리소스(Service, VirtualService, DestinationRule)를 Envoy xDS로 변환.
- 각 Pod에 사이드카 Envoy 주입.
- 모든 Pod 간 트래픽이 Envoy를 거침.
11.2 Sidecar Injection
Istio는 K8s admission webhook으로 Pod 생성 시 Envoy 컨테이너 추가:
spec:
containers:
- name: my-app
image: my-app:v1
- name: istio-proxy # 자동 추가
image: istio/proxyv2:1.20
traffic redirect iptables 규칙으로 설정
또한 **init container**로 iptables 규칙 설정:
모든 pod의 inbound/outbound 트래픽 → localhost:15006 (Envoy)
11.3 전통적 vs Ambient Mesh
**Sidecar Mesh** (전통): 각 Pod에 Envoy. 오버헤드 있지만 격리 완벽.
**Ambient Mesh** (Istio 2023+): 사이드카 없음. 노드당 ztunnel + 필요 시 waypoint proxy.
Pod A (no sidecar)
↓
ztunnel on Node A (L4 + mTLS)
↓
ztunnel on Node B
↓
Pod B
L7 기능이 필요하면 **waypoint proxy**를 명시적으로 배포. 경량화 + 필요할 때만 full proxy.
11.4 Consul Connect
HashiCorp의 대안. Consul의 서비스 카탈로그를 Envoy xDS로 제공.
Consul Server
↓ xDS
Consul Agent (per node)
↓ xDS
Envoy Sidecar
Istio보다 가볍지만 기능 적음.
11.5 AWS App Mesh
AWS 매니지드 서비스 메시. control plane은 AWS, data plane은 Envoy (ECS/EKS에 배포).
App Mesh Service (AWS) → xDS → Envoy (EC2/ECS/EKS/EKS Fargate)
AWS와의 통합이 좋지만 멀티 클라우드는 제한.
12. 실무 튜닝
12.1 워커 수
concurrency: 4 # worker 스레드 수 (기본 CPU 코어 수)
- 너무 적으면 CPU 병목.
- 너무 많으면 메모리 + 스케줄링 오버헤드.
- 일반적으로 `CPU 코어 수`가 좋은 시작점.
12.2 Connection Pool 튜닝
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 2048
max_requests: 16384 # HTTP/2 스트림
max_pending_requests: 2048
업스트림 부하에 맞춰 조정. `upstream_cx_overflow` stat 확인.
12.3 Buffer Size
per_connection_buffer_limit_bytes: 32768 # 32 KB
큰 요청/응답에는 늘려야 함. 너무 크면 OOM 위험.
12.4 Timeout
route:
timeout: 15s
idle_timeout: 300s
- `timeout`: 요청 전체 시간.
- `idle_timeout`: 연결이 idle 상태로 얼마나 유지.
12.5 Retry Policy
retry_policy:
retry_on: "5xx,reset,connect-failure"
num_retries: 3
per_try_timeout: 2s
retry_back_off:
base_interval: 0.1s
max_interval: 1s
주의: 재시도가 upstream 부하를 키울 수 있다. **retry budget**:
retry_budget:
budget_percent: 20
min_retry_concurrency: 3
"현재 활성 요청의 20% 이내에서만 retry 허용."
12.6 흔한 성능 문제
**CPU 포화**: WASM 필터, Lua, 과도한 필터 체인.
→ 측정 후 최적화.
**메모리 증가**: 큰 stats, 설정 복사본, connection pool.
→ stats matcher로 불필요한 stat 비활성화.
**레이턴시 spike**: GC 없지만 allocator(tcmalloc) 스파이크, 락, DNS 해석.
→ `per_connection_buffer_limit_bytes` 조정, STRICT_DNS 대신 EDS.
13. 대안 프록시
13.1 NGINX
- **장점**: 매우 안정적, 오래된 생태계, 풍부한 모듈.
- **단점**: 동적 설정 제한, C/Lua 모듈만, 서비스 메시 지원 제한.
- **용도**: 정적 웹사이트, 리버스 프록시, Ingress.
13.2 HAProxy
- **장점**: 매우 빠름, 뛰어난 L4/L7 로드 밸런싱.
- **단점**: xDS 미지원(변환 필요), observability 덜 풍부.
- **용도**: L4 로드 밸런서, TLS terminator.
13.3 Traefik
- **장점**: 쉬운 설정, K8s 통합.
- **단점**: 성능 Envoy 대비 낮음, 커스터마이즈 제한.
- **용도**: 간단한 Ingress.
13.4 Pingora (Cloudflare)
2022년 Cloudflare가 발표. Rust로 작성. NGINX를 대체 중.
- **장점**: Rust의 안전성, 높은 동시성, Cloudflare 규모 검증.
- **단점**: 아직 오픈소스 생태계 형성 중.
- **용도**: Cloudflare 내부 + 2024년부터 오픈소스.
13.5 Caddy
- **장점**: 자동 HTTPS (Let's Encrypt 자동), Go로 작성.
- **단점**: xDS 미지원, 성능 Envoy 대비 낮음.
- **용도**: 작은 서비스, 자동 TLS가 필요한 경우.
14. 학습 리소스
**공식 문서**:
- https://www.envoyproxy.io/docs — 권위 있는 reference.
- https://www.envoyproxy.io/learn — 학습 자료.
**블로그**:
- Matt Klein의 블로그 (Envoy 창시자).
- Istio 블로그.
- Solo.io 블로그 (Gloo 개발).
**책**:
- "Envoy Proxy: Comprehensive Guide" (비공식이지만 좋음).
- "Istio Up and Running" — Lee Calcote.
- "Cloud Native Patterns" — Cornelia Davis.
**영상**:
- EnvoyCon 발표들 (YouTube).
- KubeCon의 Envoy/Istio 세션.
**코드**:
- GitHub envoyproxy/envoy — C++ 코드.
- envoyproxy/go-control-plane — custom control plane 개발.
15. 요약 — 한 장 정리
┌─────────────────────────────────────────────────────┐
│ Envoy Cheat Sheet │
├─────────────────────────────────────────────────────┤
│ 3대 추상화: │
│ Listener (수신) → Filter Chain → Cluster │
│ │
│ xDS: │
│ LDS (Listener) → RDS (Route) → │
│ CDS (Cluster) → EDS (Endpoint) │
│ SDS (Secret), ADS (Aggregated) │
│ gRPC streaming, version-based │
│ │
│ Filter Chain: │
│ Network Filter (TCP, HTTP CM, Thrift, Redis) │
│ ↓ │
│ HTTP Filter (JWT, RBAC, Rate Limit, Router) │
│ │
│ Thread Model: │
│ Main thread + N worker threads │
│ Per-worker thread-local config │
│ 락 없음, 선형 확장 │
│ Connection fixed to accepting worker │
│ │
│ Cluster: │
│ Types: STATIC, STRICT_DNS, EDS, ORIGINAL_DST │
│ LB: RR, LR, RING_HASH, MAGLEV │
│ Subset LB (version + region 등) │
│ Circuit breaker, Outlier detection │
│ Active + Passive health check │
│ │
│ Hot Restart: │
│ v1 + v2 병행 실행 │
│ SCM_RIGHTS로 listener socket 전달 │
│ Shared memory로 stats 공유 │
│ Drain manager로 graceful shutdown │
│ │
│ 관측성: │
│ Stats (Prometheus, Statsd) │
│ Access log (text or JSON) │
│ Distributed tracing (OTel, Jaeger, Zipkin) │
│ Tap filter (실시간 캡처) │
│ Admin API (port 9901) │
│ │
│ 서비스 메시: │
│ Istio: istiod 컨트롤 + Envoy sidecar │
│ Consul Connect: Consul + Envoy │
│ App Mesh: AWS + Envoy │
│ Ambient: sidecarless, ztunnel + waypoint │
│ │
│ WASM: │
│ Rust/Go/AS로 필터 작성 │
│ Hot reload, sandbox │
│ │
│ 튜닝: │
│ concurrency (worker 수) │
│ Connection pool │
│ Buffer size │
│ Timeout / retry / circuit breaker │
└─────────────────────────────────────────────────────┘
16. 퀴즈
**A.** **락 없이 코어 수에 선형 확장**. 전통적 프록시는 여러 워커가 공유 상태(설정, 통계, 라우팅 테이블)를 락으로 보호해야 해서 코어가 많아질수록 캐시 라인 경합이 커진다. Envoy는 각 워커가 설정의 **전체 복사본**을 가지고, 한 연결은 accept한 워커에 고정된다. 설정 업데이트는 main thread가 각 워커에게 "update your copy" 메시지로 전달(TLS = Thread-Local Storage). 대가는 메모리 오버헤드(워커 수만큼 복사) 하지만 "CPU가 프록시의 병목, 메모리는 싸다"는 트레이드오프가 타당하다.
**A.** **동적, 점진적, 원자적 설정 업데이트**. 설정 파일 방식은 (1) SIGHUP로 재로드 필요, (2) 파일 배포 인프라 필요, (3) 수백 개 프록시에 동시 업데이트 어려움, (4) 부분 업데이트 불가. xDS는 gRPC streaming으로 control plane이 push → Envoy가 즉시 적용 + ACK/NACK. 버전 기반이라 잘못된 설정은 rollback. 대규모 서비스 메시에서 "수천 개 Envoy의 설정을 5초 안에 업데이트" 같은 요구를 만족한다. 이것이 Envoy가 NGINX 같은 기존 프록시와 결정적으로 다른 점.
**A.** **파일 디스크립터를 프로세스 간 전달**. UNIX 도메인 소켓의 특수 기능으로, 한 프로세스의 fd를 다른 프로세스에게 넘길 수 있다. Envoy는 이를 써서 새 프로세스(v2)가 옛 프로세스(v1)의 listening socket을 그대로 이어받게 한다. 커널 관점에서는 "두 프로세스가 같은 소켓을 공유" → accept()를 둘 다 호출 가능 → 새 연결은 v2로, 옛 연결은 v1이 drain 후 종료. 이 메커니즘 없이는 v2가 listen()을 새로 해야 하고 잠깐이나마 포트 충돌이 생긴다. 무중단 배포의 핵심 OS 기능.
**A.** **Circuit breaker**는 "업스트림에 얼마나 많이 요청을 쏠지"의 제한 — max_connections, max_requests 등 초과 시 새 요청 즉시 거부. 업스트림을 보호하고 이쪽 Envoy의 자원도 보호. **Outlier detection**은 "어느 엔드포인트가 나쁜지" 탐지 — 연속 5xx 기준으로 특정 엔드포인트를 load balancing 풀에서 일시 제외. Circuit breaker는 cluster 전체 수준, Outlier는 개별 엔드포인트 수준. 둘을 조합해서 "cluster 과부하 시 차단 + 특정 broken 노드 자동 제거"를 동시에 달성. 큰 분산 시스템에서 필수.
**A.** **Kubernetes admission webhook + iptables redirect**. (1) Pod 생성 요청이 오면 Istio의 **mutating admission webhook**이 YAML을 가로채 Envoy 컨테이너(`istio-proxy`)를 자동 추가. (2) **Init container**가 iptables 규칙을 설정해 Pod의 모든 inbound/outbound 트래픽을 Envoy로 redirect (port 15006 inbound, 15001 outbound). (3) 애플리케이션은 이 사실을 모르고 평소대로 동작하지만 모든 바이트가 Envoy를 거친다. 결과: 애플리케이션 코드 변경 없이 mTLS, 관측성, 정책 enforcement. 단점은 매 Pod에 Envoy 메모리(100-200MB)와 작은 CPU 오버헤드.
**A.** **사이드카의 리소스 오버헤드와 운영 복잡도**. 사이드카 메시는 Pod당 Envoy → 1000 Pod면 Envoy 1000개 (각 150MB). Envoy 업그레이드 시 전체 Pod 재시작 필요(애플리케이션에 영향). Ambient는 L4(mTLS, 라우팅)를 노드당 하나의 `ztunnel`(경량, Rust)로, L7(필터, 라우팅)은 **필요할 때만** waypoint proxy로 분리. 장점: (1) 리소스 대폭 감소(노드당 하나), (2) 앱 재시작 없이 메시 업그레이드, (3) L7 기능이 필요 없는 서비스는 더 가벼움. 단점: 격리 약함, 아직 성숙도 낮음. 2024+ 프로덕션 채택 시작.
**A.** **VM 오버헤드와 호스트 경계**. WASM은 샌드박스 VM에서 돌아가며 (1) JIT 컴파일이 LLVM보다 덜 공격적이라 네이티브 코드가 약간 느리고, (2) Envoy의 C++ API를 호출하려면 **WASM ↔ 호스트 경계**를 넘어야 해서 함수 호출당 수십 ns 오버헤드, (3) 문자열/버퍼를 WASM 선형 메모리 ↔ Envoy 메모리 사이에 복사해야 함. 측정 결과 보통 네이티브 대비 1.5-2배 느리지만, 대부분 마이크로초 단위라 실제 레이턴시 차이는 무시 가능. 대신 얻는 것: 언어 독립, hot reload, 크래시 격리. 트레이드오프가 거의 항상 WASM 쪽에 유리하다는 것이 2025년의 컨센서스.
이 글이 도움이 됐다면 다음 포스트도 확인해 보세요:
- "Load Balancer Internals L4/L7 Deep Dive" — Envoy 이전의 로드 밸런서 기술.
- "Service Discovery Deep Dive" — Consul, etcd, Eureka와 xDS의 연관성.
- "HTTP/3 & QUIC Deep Dive" — Envoy가 지원하는 최신 프로토콜.
- "WebAssembly Deep Dive" — WASM 필터 구현 배경.
현재 단락 (1/743)
- **Envoy**는 Lyft가 2016년 오픈소스로 공개한 C++ L7 프록시. 현재 CNCF graduated 프로젝트, 서비스 메시의 사실상 표준 데이터 플레인.