LabHub
배우기 러닝패스 코스

Envoyの内部構造

Envoy設定を読む順序

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

리스너로 받아서, 필터체인을 통과시키고, 라우트가 고른 클러스터엔드포인트로 보낸다. Envoy 설정은 전부 이 다섯 단어의 조합이다.

概念マップ: 리스너・필터체인・라우트・클러스터

왜 이게 필요했나

Envoy 설정 JSON 은 처음 보면 압도적이다. 그런데 구조는 단순하다. 들어오는 쪽과 나가는 쪽이 짝을 이룬다.

개념 대응하는 것 하는 일
Listener nginx 의 server { listen } 어느 주소·포트에서 받나
Filter chain nginx 의 location + 모듈 받은 것을 어떻게 처리하나
Route nginx 의 location 매칭 어느 백엔드로 보낼지 고른다
Cluster nginx 의 upstream 백엔드 묶음(정책 포함)
Endpoint upstream 의 server 한 줄 실제 주소:포트

어떻게 동작하나

요청 하나가 지나가는 길은 이렇다.

클라이언트
  → Listener (0.0.0.0:15001)
    → Filter chain (TLS 종료 → HTTP 커넥션 매니저)
      → Route (Host: shop.example.com, path /api/* )
        → Cluster (outbound|8080||shop.default.svc.cluster.local)
          → Endpoint (10.244.1.7:8080)   ← 로드밸런싱으로 하나 고름

핵심은 클러스터와 엔드포인트가 나뉘어 있다는 것이다. 클러스터는 정책(로드밸런싱 알고리즘, 커넥션 풀 크기, 이상치 감지, 서킷 브레이커)을 담고, 엔드포인트는 그 순간의 실제 주소 목록이다. 파드가 뜨고 죽어도 클러스터 정의는 그대로고 엔드포인트 목록만 바뀐다.

xDS — 설정이 흘러드는 통로

Envoy 는 설정 파일을 다시 읽고 재시작하지 않는다. 컨트롤 플레인이 gRPC 스트림으로 밀어 넣는다. 종류마다 이름이 있다.

약자 무엇을 보내나
LDS Listener
RDS Route
CDS Cluster
EDS Endpoint
SDS 인증서(Secret)

Envoy 를 지나는 요청 하나의 순서. 리스너에서 받아 필터체인으로 처리하고 라우트가 백엔드를 고르면 클러스터의 정책을 거쳐 엔드포인트 하나에 닿는다. 컨트롤 플레인은 이 다섯 자리에 각각 LDS, RDS, CDS, EDS 로 설정을 밀어 넣는다

Istio 의 istiod 가 하는 일이 정확히 이것이다. 여러분이 쓴 VirtualService 는 RDS 로, DestinationRule 은 CDS 로, 서비스의 파드 목록은 EDS 로 번역돼 각 사이드카에 밀린다.

그래서 "Istio 설정을 고쳤는데 반영이 안 된다" 를 조사하는 방법도 정해진다 — 사이드카가 실제로 받은 설정을 본다.

istioctl proxy-config route <pod>       # RDS 로 받은 것
istioctl proxy-config cluster <pod>     # CDS
istioctl proxy-config endpoint <pod>    # EDS

VirtualService 를 다시 읽는 것은 소용이 없다. 우리가 쓴 것과 프록시가 받은 것이 다를 수 있고, 그 차이가 바로 원인이다.

설정을 못 읽겠을 때 실제로 여는 창

엔보이의 설정 덤프는 수만 줄이 나온다. 통째로 읽으려 하면 아무것도 못 얻으므로, 질문을 정하고 그 조각만 꺼낸다.

curl -s localhost:15000/config_dump | jq '.configs[].dynamic_listeners[]?.name'
curl -s localhost:15000/clusters | grep -E "myapi|health"
curl -s localhost:15000/stats | grep -E "upstream_rq_(5xx|pending|timeout)"
curl -s localhost:15000/server_info | jq '.state'

요청이 어디로 갔는지는 통계가 먼저 답한다. upstream_rq_5xx 가 늘었는지, upstream_cx_connect_fail 이 있는지, pending 이 쌓였는지에 따라 볼 곳이 갈린다. 설정을 읽는 것은 그다음이다.

503 은 원인이 여러 가지이고, 응답 플래그가 그것을 구분해 준다. 접근 로그에 %RESPONSE_FLAGS% 를 넣어 두면 한 글자 코드로 나온다.

플래그
UH 그 클러스터에 건강한 대상이 하나도 없다
UF 상류에 연결하지 못했다
UO 회로 차단기가 열려 막았다
NR 맞는 경로(route)가 없다
URX 재시도 상한에 걸렸다

이 한 글자가 "설정이 틀렸다" 와 "대상이 죽었다" 를 가른다. 접근 로그에 응답 플래그가 없으면 그 클러스터에서는 503 하나를 놓고 계속 추측하게 된다.

회로 차단기의 기본값은 대체로 너무 낮다. max_pending_requests 가 1024 인데 동시 요청이 그보다 많으면, 상류는 멀쩡한데 엔보이가 막는다. UO 플래그가 보이면 이 자리다.

설정이 바뀌지 않는 것처럼 보이면 xDS 를 본다. /stats*.update_rejected 가 늘고 있다면 컨트롤 플레인이 보낸 설정을 엔보이가 거절한 것이다. 이때 엔보이는 마지막으로 성공한 설정을 그대로 유지하므로, 겉보기에는 아무 일도 없다. 이 지표에 경보를 걸어 두는 것이 실효가 크다.

흔한 착각

"사이드카가 요청을 느리게 만든다" — Envoy 자체의 지연은 보통 밀리초 이하다. 체감 지연의 대부분은 커넥션 풀 설정, mTLS 핸드셰이크, 그리고 재시도 설정에서 온다. 특히 재시도는 장애 시 부하를 몇 배로 키운다.

클러스터 이름이 랜덤해 보이는 것. outbound|8080||shop.default.svc.cluster.local 은 규칙이다 — 방향|포트|서브셋|호스트. 서브셋이 비어 있으면 DestinationRule 의 subset 을 안 쓴다는 뜻이다. 이 규칙을 알면 설정 덤프에서 원하는 줄을 바로 찾는다.

실무에서 진짜 중요한 것

Envoy 통계 이름도 규칙이 있고, 그게 장애 조사의 지도다.

cluster.<클러스터이름>.upstream_rq_5xx          백엔드가 낸 5xx
cluster.<클러스터이름>.upstream_rq_pending_overflow   커넥션 풀이 넘쳤다
cluster.<클러스터이름>.outlier_detection.ejections_active  쫓겨난 엔드포인트 수
listener.<주소>.downstream_cx_total             들어온 연결 수

upstream_rq_pending_overflow 가 늘어나면 애플리케이션이 느린 게 아니라 커넥션 풀이 작은 것이다. 이 둘은 대처가 정반대다.