Skip to content
Published on

Envoy Proxy Deep Dive — xDS, Filter Chain, Service Mesh, Hot Restart 내부 완전 정복 (2025)

Authors

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개의 핵심 추상화:

ListenerFilter ChainCluster
(수신)       (처리)            (업스트림 풀)

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 Service. 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 │   │ ClusterCopy   │   │  Copy   │   │  Copy                         ├─────────┤   ├─────────┤   ├─────────┤
Stats  │   │  Stats  │   │  StatsCopy   │   │  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 (요청 처리): decodeHeadersdecodeDatadecodeTrailers.
  • Encode path (응답 처리): encodeHeadersencodeDataencodeTrailers.

필터가 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. 학습 리소스

공식 문서:

블로그:

  • 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 ChainCluster│                                                       │
│ 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_DSTLB: RR, LR, RING_HASH, MAGLEVSubset 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 + EnvoyApp Mesh: AWS + EnvoyAmbient: sidecarless, ztunnel + waypoint           │
│                                                       │
WASM:Rust/Go/AS로 필터 작성                              │
Hot reload, sandbox                                │
│                                                       │
│ 튜닝:concurrency (worker 수)Connection pool                                     │
Buffer size                                         │
Timeout / retry / circuit breaker                  │
└─────────────────────────────────────────────────────┘

16. 퀴즈

Q1. Envoy의 per-worker thread-local 아키텍처가 주는 이득은?

A. 락 없이 코어 수에 선형 확장. 전통적 프록시는 여러 워커가 공유 상태(설정, 통계, 라우팅 테이블)를 락으로 보호해야 해서 코어가 많아질수록 캐시 라인 경합이 커진다. Envoy는 각 워커가 설정의 전체 복사본을 가지고, 한 연결은 accept한 워커에 고정된다. 설정 업데이트는 main thread가 각 워커에게 "update your copy" 메시지로 전달(TLS = Thread-Local Storage). 대가는 메모리 오버헤드(워커 수만큼 복사) 하지만 "CPU가 프록시의 병목, 메모리는 싸다"는 트레이드오프가 타당하다.

Q2. xDS가 기존 설정 파일 방식 대비 어떤 문제를 해결하는가?

A. 동적, 점진적, 원자적 설정 업데이트. 설정 파일 방식은 (1) SIGHUP로 재로드 필요, (2) 파일 배포 인프라 필요, (3) 수백 개 프록시에 동시 업데이트 어려움, (4) 부분 업데이트 불가. xDS는 gRPC streaming으로 control plane이 push → Envoy가 즉시 적용 + ACK/NACK. 버전 기반이라 잘못된 설정은 rollback. 대규모 서비스 메시에서 "수천 개 Envoy의 설정을 5초 안에 업데이트" 같은 요구를 만족한다. 이것이 Envoy가 NGINX 같은 기존 프록시와 결정적으로 다른 점.

Q3. Hot Restart에서 SCM_RIGHTS의 역할은?

A. 파일 디스크립터를 프로세스 간 전달. UNIX 도메인 소켓의 특수 기능으로, 한 프로세스의 fd를 다른 프로세스에게 넘길 수 있다. Envoy는 이를 써서 새 프로세스(v2)가 옛 프로세스(v1)의 listening socket을 그대로 이어받게 한다. 커널 관점에서는 "두 프로세스가 같은 소켓을 공유" → accept()를 둘 다 호출 가능 → 새 연결은 v2로, 옛 연결은 v1이 drain 후 종료. 이 메커니즘 없이는 v2가 listen()을 새로 해야 하고 잠깐이나마 포트 충돌이 생긴다. 무중단 배포의 핵심 OS 기능.

Q4. Circuit breaker와 Outlier detection의 차이는?

A. Circuit breaker는 "업스트림에 얼마나 많이 요청을 쏠지"의 제한 — max_connections, max_requests 등 초과 시 새 요청 즉시 거부. 업스트림을 보호하고 이쪽 Envoy의 자원도 보호. Outlier detection은 "어느 엔드포인트가 나쁜지" 탐지 — 연속 5xx 기준으로 특정 엔드포인트를 load balancing 풀에서 일시 제외. Circuit breaker는 cluster 전체 수준, Outlier는 개별 엔드포인트 수준. 둘을 조합해서 "cluster 과부하 시 차단 + 특정 broken 노드 자동 제거"를 동시에 달성. 큰 분산 시스템에서 필수.

Q5. Istio의 sidecar 주입은 어떻게 작동하는가?

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 오버헤드.

Q6. Ambient Mesh가 Sidecar Mesh 대비 해결하려는 문제는?

A. 사이드카의 리소스 오버헤드와 운영 복잡도. 사이드카 메시는 Pod당 Envoy → 1000 Pod면 Envoy 1000개 (각 150MB). Envoy 업그레이드 시 전체 Pod 재시작 필요(애플리케이션에 영향). Ambient는 L4(mTLS, 라우팅)를 노드당 하나의 ztunnel(경량, Rust)로, L7(필터, 라우팅)은 필요할 때만 waypoint proxy로 분리. 장점: (1) 리소스 대폭 감소(노드당 하나), (2) 앱 재시작 없이 메시 업그레이드, (3) L7 기능이 필요 없는 서비스는 더 가벼움. 단점: 격리 약함, 아직 성숙도 낮음. 2024+ 프로덕션 채택 시작.

Q7. WASM 필터가 C++ 네이티브 필터보다 느린 이유는?

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 필터 구현 배경.