LabHub

블로그

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

TL;DR


1. Envoy는 왜 생겼는가

1.1 Lyft의 아픈 경험

2015년 Lyft는 수십 개의 마이크로서비스를 운영 중이었다. 각 서비스는 다양한 언어로 쓰였고(Python, Go, Java 등), 각자 HTTP 클라이언트 라이브러리를 사용했다.

문제:

전통적 접근: 모든 언어용 공통 라이브러리를 만든다. 하지만 언어마다 재구현해야 하고, 버전 불일치 문제, 배포 동기화 어려움.

1.2 "Out-of-Process" 아키텍처

Matt Klein(Lyft)의 제안: "네트워크 스택을 별도 프로세스로 분리하자."

Application Code (Python/Go/Java/...)
    localhost:9080
    Envoy Sidecar
    mTLS, 재시도, 로드밸런싱, 관측성, circuit breaker
    Remote Service

장점:

이것이 사이드카 패턴의 원조. Istio, Linkerd, Consul Connect 모두 같은 아이디어.

1.3 오픈소스 공개 (2016)

Lyft는 Envoy를 2016년 9월 공개했다. 반응은 폭발적이었다:

Matt Klein은 현재도 Lyft의 프린시펄 엔지니어이자 Envoy의 메인테이너.

1.4 2025년 현재

Envoy는:


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 서버의 논리적 그룹. 실제로는:

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는 여러 가지:

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를 단일 스트림으로. 순서 보장이 필요할 때 중요:

ADS는 이 순서를 제어 플레인이 명시할 수 있게 해준다. Istio는 기본 ADS.

3.5 Delta xDS

초기 xDS는 State of the World: 변경 시 전체 리소스 목록 전송. 1만 개 cluster가 있으면 한 개 변경에 1만 개 전송.

Delta xDS: 변경된 것만 보냄 (추가/수정/삭제). 큰 설정을 가진 거대 클러스터에 필수.

3.6 Control Plane 구현

대표적인 control plane들:

커스텀 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                         └─────────┘   └─────────┘   └─────────┘

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이 종료될 때:

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가 내장:

인증/인가:

트래픽 제어:

변환:

관측성:

프로토콜 변환:

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. 최종 라우팅 (반드시 마지막)

각 필터는:

필터가 StopIteration을 반환하면 뒤 필터 실행 안 됨. 예: JWT 검증 실패 시 401 응답.

6.4 Router

envoy.filters.http.router는 특별하다:

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

매칭 기준:

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 알고리즘:

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을 유지한다:

Pool 파라미터:

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

의미:

이것이 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:

자동으로 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):

보안: 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 장점

10.3 단점

주요 사용처: 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     │ │  │
│  │ └────────┘ │  │ └────────┘ │  │
│  └────────────┘  └────────────┘  │
└──────────────────────────────────┘

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 코어 수)

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

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

13.2 HAProxy

13.3 Traefik

13.4 Pingora (Cloudflare)

2022년 Cloudflare가 발표. Rust로 작성. NGINX를 대체 중.

13.5 Caddy


14. 학습 리소스

공식 문서:

블로그:

:

영상:

코드:


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년의 컨센서스.


이 글이 도움이 됐다면 다음 포스트도 확인해 보세요:

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다