---
type: src
tags: [istio, xds, envoy, istioctl, proxy-status, envoyfilter, diagnosis]
created: 2026-06-07
---
# xDS 5계층과 istioctl 진단 — LDS/RDS/CDS/EDS/SDS (출처: Istio 1.30 공식 문서 + 홈랩 검증)
> [!abstract] 이 문서가 다루는 것
> **istiod는 컴파일러다.** K8s 상태 + Istio CRD를 입력으로 받아 Envoy 설정(Listener/Route/Cluster/Endpoint/Secret)으로 컴파일하고, xDS 채널로 각 프록시에 push한다. 그래서 모든 트래픽 장애는 "이 5계층 중 어디서 끊겼나"로 환원되고, 진단은 `Listener→Route→Cluster→Endpoint(+Secret)` 순으로 한 칸씩 내려가며 빈 칸을 찾는 일이 된다. 이 문서는 그 컴파일 산출물 5계층을 **운영·진단 관점**(무엇을 내려주나 / 확인 명령 / 영향 주는 리소스 / 고장 증상)으로 정리하고, ADS 적용 순서·장애 분석 순서·`proxy-status`·`x describe`·`EnvoyFilter`·CRD↔Envoy 번역표·매일 쓰는 명령 세트로 닫는다.
>
> **대상환경** Istio 1.30 / Envoy sidecar mode · **대상독자** xDS를 "YAML이 아니라 Envoy 설정"으로 사고하려는 DevOps/SRE · **범위** 진단·운영(개념 원론은 아래 링크) · **선행개념** Envoy listener/cluster, K8s Service/EndpointSlice
> 개념 원론은 [xDS API 계층](xds__note-xds-api-layers.html), sync 상태값은 [데이터플레인 sync 상태](xds__note-data-plane-sync-state.html). 본 문서는 그 둘의 **운영·진단 상세판**이다.
> ⚠️ **버전 skew 주의**: 이 환경은 istiod 1.30.0 ↔ 로컬 istioctl 1.27.0이다. `proxy-status`의 VERSION 컬럼이나 `proxy-config` 일부 필드가 어긋나 보일 수 있으나 이는 client 표시 한계이며 메시 동작 이상이 아니다. 정밀 진단은 버전을 맞춘 1.30 istioctl 사용 권장.
---
## 01. 배경 — 왜 xDS인가: Envoy는 "빈 상자"이고 istiod가 채운다
Envoy는 그 자체로는 아무 라우팅도 모르는 **고성능 L4/L7 프록시 엔진**입니다. "reviews로 가는 요청은 v1/v2로 나눠라" 같은 지식이 0이고, 그 지식은 전부 **설정(configuration)**으로 외부에서 주입돼야 합니다. 문제는 메시 환경이 정적이지 않다는 데 있습니다 — Pod는 뜨고 죽고, Service endpoint는 분 단위로 바뀌고, VirtualService 한 줄 고치면 수백 개 프록시가 동시에 새 route를 알아야 합니다. 설정 파일을 디스크에 깔고 프로세스를 재시작하는 방식으로는 이걸 못 따라갑니다.
그래서 Envoy는 설정을 **런타임에 원격으로 받아오는** 메커니즘을 가집니다. 이것이 xDS입니다. `xDS = "무언가 Discovery Service"`들의 묶음이고, `x` 자리에 Listener, Route, Cluster, Endpoint, Secret이 들어갑니다. Envoy는 이 동적 설정들을 **xDS management server**로부터 streaming gRPC로 받고, Istio에서는 그 management server가 바로 `istiod`입니다.
여기서 멘탈모델을 한 단계 끌어올려야 합니다. Istio를 "서비스 메시"라는 흐릿한 말로 두지 말고, **"K8s 상태 + Istio CRD를 Envoy 설정으로 컴파일해 모든 프록시에 배포하는 제어 시스템"**으로 보십시오. istiod = 컴파일러, K8s/CRD = 소스 코드, Envoy 설정 5계층 = 컴파일된 바이너리, xDS = 그 바이너리를 프록시 메모리에 적재하는 로더. 이 관점이 서면 진단이 단순해집니다 — 트래픽이 깨졌다는 건 **소스(CRD)가 잘못됐거나, 컴파일이 안 됐거나, 로드가 안 됐거나** 셋 중 하나이고, 그걸 가르는 게 아래 도구들입니다.
> [!key] 한 문장 멘탈모델
> xDS = istiod가 Kubernetes/Istio 상태를 Envoy 설정(Listener/Route/Cluster/Endpoint/Secret)으로 컴파일해서 각 프록시에 push하는 5개 API. 진단 = 이 5계층을 순서대로 따라가며 빈 칸을 찾는 일.
---
## 02. 아키텍처 — 5계층이 "요청 처리 경로" 그대로인 이유
핵심 통찰은 이것입니다. **5개 계층은 임의로 나눈 게 아니라, 요청 하나가 Envoy 내부에서 resolve되는 경로 그 자체**입니다. 요청이 들어오면 Envoy는 항상 같은 질문을 순서대로 던지고, 각 질문에 답하는 설정 조각이 곧 xDS 한 계층입니다.
```text
LDS "이 트래픽을 어디서 받나?" → Listener (소켓 + filter chain)
RDS "그래서 어느 cluster로 보내나?" → Route (host/path/header → cluster 이름)
CDS "그 cluster는 어떤 pool인가?" → Cluster (LB/timeout/circuit breaker/TLS)
EDS "그 pool의 실제 Pod IP는?" → Endpoint (IP:port 목록)
SDS "mTLS 인증서는?" (가로지름) → Secret (cert/key/CA)
```
```mermaid
flowchart TD
L["Listener (LDS)
어디서 받나"]
R["Route (RDS)
어느 cluster로"]
C["Cluster (CDS)
upstream pool"]
E["Endpoint (EDS)
실제 Pod IP"]
S["Secret (SDS)
mTLS cert/key/CA"]
L --> R
R --> C
C --> E
C -.mTLS.-> S
```
이 그림 한 장이 이 문서의 앵커입니다. 진단도, 적용 순서도, 응답 플래그도 전부 이 사슬에서 파생됩니다. 사슬의 어느 고리가 끊겼는지가 곧 장애의 위치입니다.
### 02.1 다섯 고리를 하나씩 — 무엇을 내려주고, 어떻게 보고, 왜 깨지나
**LDS — Listener Discovery Service.** Listener는 "Envoy가 **어디에서** 트래픽을 받을 것인가"입니다. sidecar에는 두 종류가 있습니다 — capture listener와 port별 listener.
```text
0.0.0.0:15001 outbound capture listener
0.0.0.0:15006 inbound capture listener
0.0.0.0:9080 outbound HTTP listener (port별, 대부분 0.0.0.0 wildcard)
10.96.0.10:53 service-IP별 listener (조건부 — 아래 주석)
```
> sidecar outbound listener는 **대부분 `0.0.0.0:PORT` wildcard로 capture**됩니다. `10.96.0.10:53`처럼 **service IP별 dedicated listener**는 protocol 충돌을 피해야 하는 경우(같은 포트에 여러 프로토콜)나 TCP/특정 케이스에서만 생성되며, 일반 HTTP outbound에서는 보통 만들어지지 않습니다.
15001/15006이 capture listener인 이유는 iptables가 **모든** outbound/inbound를 이 두 포트로 redirect하기 때문입니다(메커니즘은 → [Sidecar 트래픽 캡처](xds__src-sidecar-traffic-capture.html)). LDS가 반영되면 Envoy 안에 listener, filter chain, protocol inspector, HTTP connection manager, TCP proxy 같은 처리 구조가 생깁니다.
```bash
istioctl proxy-config listener -n
```
- **영향 주는 Istio 리소스**: `Gateway`(edge listener + server + TLS), `Sidecar`(config scope), `PeerAuthentication`(inbound mTLS filter), `Service` port
- **고장 증상**: listener 자체가 없거나 잘못된 filter chain → 트래픽이 protocol을 잘못 판단(HTTP인데 TCP proxy로 처리), inbound 정책 미적용
**RDS — Route Discovery Service.** Route는 "이 HTTP 요청을 **어느 cluster로** 보낼 것인가"입니다. ⚠️ RDS의 D는 흔히 "Direct"로 오해되지만 정확히는 **`Route Discovery Service`** 입니다(xDS 전체가 `x + Discovery Service` 패턴 — 이것만 기억하면 안 헷갈림). route configuration 안에는 virtual host(어떤 host 묶음인가), route entry(path/header match → 어느 cluster), header modification이 들어갑니다.
```text
Host: reviews.default.svc.cluster.local
Path: /api/v1/reviews
Header: x-user = jason
→ cluster: outbound|9080|v2|reviews.default.svc.cluster.local
```
route의 destination은 `outbound|PORT|SUBSET|FQDN` 형태의 cluster를 참조합니다. 이 이름 규칙과 subset의 의미는 → [Cluster 해부](xds__src-cluster-anatomy.html).
```bash
istioctl proxy-config route -n
```
- **영향 주는 Istio 리소스**: `VirtualService`, `Gateway`, `Service host`, Gateway API 사용 시 `HTTPRoute`
- **고장 증상**: route 없음 → access log에 `NR`(NoRouteFound). VirtualService `hosts`가 실제 `:authority`와 다르거나, port가 HTTP로 인식 안 되거나, `gateways` 필드 때문에 sidecar에는 rule이 적용 안 되는 경우가 대표적
**CDS — Cluster Discovery Service.** Cluster는 "upstream backend pool", 즉 Envoy가 요청을 보낼 **논리적** 목적지입니다(아직 실제 IP는 모름).
```text
outbound|9080||reviews.default.svc.cluster.local (subset 없음 = 전체 pool)
outbound|9080|v1|reviews.default.svc.cluster.local
outbound|9080|v2|reviews.default.svc.cluster.local
```
CDS에는 cluster 이름, EDS/DNS 사용 여부, connect timeout, circuit breaker, outlier detection, load balancing, upstream TLS 설정이 들어갑니다.
```bash
istioctl proxy-config cluster -n
```
- **영향 주는 Istio 리소스**: `Service`, `ServiceEntry`, `DestinationRule`(subset/LB/TLS/connectionPool/outlier), `Sidecar` egress scope, `PeerAuthentication`/`DestinationRule` TLS
- **고장 증상**: cluster 없음 → `NC`(NoClusterFound). DestinationRule/ServiceEntry 누락, service host 오타, config scope에서 잘려 나간 경우
**EDS — Endpoint Discovery Service.** CDS가 "reviews v1으로 보내라"라는 *논리*라면, EDS는 "reviews v1 Pod IP는 이것들이다"라는 *실체*입니다. **왜 따로 받나?** endpoint는 cluster 정의보다 훨씬 자주 바뀌기 때문입니다 — Pod가 죽고 살 때마다 무거운 cluster 설정(LB/circuit breaker/TLS)을 통째로 다시 보내는 건 낭비라, Istio는 변하지 않는 cluster(CDS)와 자주 변하는 endpoint(EDS)를 **분리**해서 endpoint만 가볍게 갱신합니다.
```text
Cluster: outbound|9080|v1|reviews.default.svc.cluster.local
Endpoints:
10.244.1.12:9080
10.244.2.18:9080
```
```bash
istioctl proxy-config endpoint -n
```
- **영향 주는 정보/리소스**: Kubernetes `EndpointSlice`, Pod readiness, Service selector, `WorkloadEntry`, `ServiceEntry` endpoints, multicluster remote endpoint, locality/zone/region metadata
- **고장 증상**: healthy endpoint 없음 → `UH`(NoHealthyUpstream). readiness 실패, outlier detection으로 전부 eject, selector mismatch
**SDS — Secret Discovery Service.** TLS 인증서, private key, root CA를 내려줍니다. 위 4계층이 "어디로 보내나"의 사슬이라면 SDS는 그 사슬을 **가로지르는** 보안 축입니다. 핵심 설계는 이것 — Istio sidecar mode에서 workload 인증서는 app container가 직접 들고 있지 않습니다. workload 시작 시 Envoy가 **SDS API로 cert/key를 요청**하고, Istio agent가 istiod CA에서 받은 cert를 Envoy 메모리로만 전달합니다(디스크 secret 마운트 없음 = blast radius 축소). 이 cert가 mTLS handshake에서 workload identity를 증명합니다.
```bash
istioctl proxy-config secret -n
```
- **영향 주는 리소스**: istiod CA, `PeerAuthentication`, `DestinationRule` TLS mode
- **고장 증상**: mTLS handshake failure, `503 UF`, upstream reset, AuthorizationPolicy principal match 실패, 인증서 만료/rotation 문제
### 02.2 ADS와 적용 순서 — "route가 빈 cluster를 가리키는 순간"을 없애는 불변식
Envoy 공식 문서 기준 CDS/EDS/LDS/RDS/SDS 각각이 독립 streaming endpoint를 가지지만, **ADS(Aggregated Discovery Service)** 는 이 여러 xDS resource를 **하나의 bidirectional gRPC stream**으로 묶어 전달합니다. Istio가 ADS를 쓰는 이유는 단 하나, **적용 순서 보장**입니다.
왜 순서가 생명인가. route를 cluster X에서 cluster Y로 바꿀 때, Envoy가 cluster Y와 그 endpoint를 **먼저** 알아야 route가 안 깨집니다. 채널이 5개로 따로 놀면 RDS가 CDS보다 먼저 도착해 "존재하지 않는 cluster를 가리키는 route"가 생기는 race가 발생합니다. ADS는 하나의 stream에서 CDS→EDS→RDS→LDS를 순서대로 흘려 이 race를 원천 차단합니다.
```mermaid
flowchart LR
K8S[K8s API + Istio CRD]
ISTIOD[istiod compiler]
STREAM[ADS single gRPC stream]
ENVOY[Envoy proxy]
K8S --> ISTIOD
ISTIOD --> STREAM
STREAM --> ENVOY
ENVOY --> APPLY["apply order: CDS"]
APPLY --> EDS2[EDS]
EDS2 --> RDS2[RDS]
RDS2 --> LDS2[LDS]
```
순서에는 **비대칭**이 있고, 이게 진짜 포인트입니다. **add(추가)** 는 `CDS→EDS→RDS→LDS`로 **bottom-up**(참조 대상부터 만들고 나중에 참조)으로 적용됩니다. 반대로 **remove(삭제)나 warm shutdown**은 `LDS/RDS 먼저 → 그 다음 CDS/EDS`로 **top-down**(참조하는 쪽을 먼저 떼고 나중에 대상 제거)으로 적용됩니다.
```text
add : CDS → EDS → RDS → LDS (bottom-up, 대상 먼저)
remove : LDS / RDS → CDS / EDS (top-down, 참조자 먼저)
```
방향은 정반대지만 두 경우 모두 같은 **불변식** 하나를 지키기 위함입니다 — **"route가 아직 없거나 이미 사라진 cluster를 가리키는 순간이 절대 생기지 않는다."** 추가든 삭제든 이 불변식이 유지되므로 트래픽이 깨지지 않습니다. Envoy는 update를 받은 뒤 **ACK/NACK**로 적용 여부를 control plane에 알리고, 이 ACK/NACK가 `proxy-status`의 `SYNCED`/`STALE`로 드러납니다(§04).
### 02.3 응답 플래그 = 사슬의 끊긴 고리 좌표
위 사슬의 각 고리가 끊기면 Envoy access log에 고유한 response flag가 찍힙니다. 이게 진단의 출발점입니다 — flag만 보면 5계층 중 몇 번째부터 보면 되는지 바로 압니다(flag 전체표는 → [Envoy 응답 플래그](xds__src-envoy-response-flags.html)).
| flag | 끊긴 고리 | 먼저 볼 계층 |
|---|---|---|
| `NR` (NoRoute) | RDS | route (1단계) |
| `NC` (NoCluster) | CDS | cluster (2단계) |
| `UH` (NoHealthyUpstream) | EDS | endpoint (3단계) |
| `UF` (UpstreamFailure) | SDS/mTLS | secret (4단계) |
---
## 03. 적용 예시 — 끊긴 고리 찾아 내려가기 (worked diagnosis)
이제 멘탈모델을 실제 명령으로 돌립니다. 시나리오: "reviews로 보낸 요청이 503이다." 분석은 **사슬을 위에서 아래로** 내려가며 빈 칸을 찾는 일입니다.
### 03.1 0단계 게이트 — 먼저 "전달은 됐나"부터
layer trace **전에** `proxy-status`로 설정이 Envoy에 도착했는지부터 확인합니다. `NOT SENT`/`STALE`이면 설정이 아직 프록시에 없는 것이라, 그 위에서 layer를 따라가 봐야 의미가 없습니다([sync 상태](xds__note-data-plane-sync-state.html)의 1단계 게이트).
```bash
istioctl proxy-status
```
예상 출력:
```text
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
details-v1-558b8b4b76-qzqsg.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-6cf8d4f9cb-wm7x6 1.30.1
productpage-v1-6987489c74-nc7tj.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-6cf8d4f9cb-wm7x6 1.30.1
istio-ingressgateway-66c994c45c-cmb7x.istio-system Kubernetes SYNCED SYNCED SYNCED NOT SENT NOT SENT istiod-6cf8d4f9cb-wm7x6 1.30.1
reviews-v1-7f99cc4496-rtsqn.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-6cf8d4f9cb-wm7x6 1.30.1
```
여기서 모두 `SYNCED`(또는 정상 `NOT SENT`)이고 reviews proxy가 **목록에 보이면** 전달은 정상 — 이제 layer를 내려가도 됩니다. 만약 reviews proxy가 목록에서 **빠졌다면** 그건 istiod 미연결이고, 그 즉시 원인이 좁혀집니다(layer 추적 불필요).
### 03.2 1~4단계 — 사슬을 따라 내려가기
```bash
# 1. route가 원하는 cluster를 가리키는지 (NR이면 여기)
istioctl proxy-config route -n
# 2. 그 cluster가 존재하는지 (NC이면 여기)
istioctl proxy-config cluster -n
# 3. 그 cluster에 endpoint가 있는지 (UH이면 여기)
istioctl proxy-config endpoint -n
# 4. mTLS secret이 있는지 (UF이면 여기)
istioctl proxy-config secret -n
# 5. (재동기 확인) proxy가 istiod와 sync됐는지
istioctl proxy-status
```
이 시나리오에서 1·2단계는 정상인데 3단계에서 endpoint가 비어 있다고 합시다 — access log의 `UH`와 일치합니다. cluster `outbound|9080|v1|reviews...`는 존재하지만 endpoint 목록이 0이면 "cluster 있음 ≠ 정상"의 전형입니다. 원인은 readiness 실패 / outlier detection eject / selector mismatch 중 하나로 좁혀지고, `kubectl get endpointslice`로 K8s 쪽 실체를 대조하면 끝납니다.
> [!tip] 시작점 좁히기
> response flag로 trace를 단축할 수 있음. `NR`→1단계, `NC`→2단계, `UH`→3단계, `UF`→4단계부터 보면 빠름. 처음부터 1단계로 내려갈 필요 없이 flag가 가리키는 고리로 점프하면 됨.
### 03.3 한 단계 위 시점 — `istioctl x describe pod`
`proxy-config`가 Envoy raw 설정이라면, `istioctl x describe pod`(정식 `istioctl experimental describe pod`)는 "이 Pod에 영향을 주는 Istio 설정"을 **사람이 읽기 쉽게 요약**합니다. 보통 이걸로 큰 그림을 잡고 `proxy-config`로 내려갑니다.
> 1.30에서 `x describe`는 여전히 **experimental**입니다(deprecated 트랙으로 분류 — 향후 변경/제거 가능). 동작은 하지만 운영 자동화·스크립트의 안정적 기반으로 삼지 말고, 결정적 진단은 `proxy-config`/`analyze`로 하십시오.
장애 분석의 첫 질문 "이 Pod가 mesh 안에 있나?"를 즉답합니다. sidecar 없는 Pod:
```bash
istioctl x describe pod coredns-f9fd979d6-2zsxk -n kube-system
```
```text
Pod: coredns-f9fd979d6-2zsxk
Pod Ports: 53/UDP (coredns), 53 (coredns), 9153 (coredns)
WARNING: coredns-f9fd979d6-2zsxk is not part of mesh; no Istio sidecar
--------------------
Error: failed to execute command on sidecar ...
```
→ Envoy sidecar가 없으면 mesh policy, mTLS, AuthorizationPolicy, routing rule 대부분이 동작하지 않음.
mesh 안 Pod(DestinationRule 포함):
```bash
istioctl x describe pod ratings-v1-7dc98c7588-8jsbw -n default
```
```text
Pod: ratings-v1-7dc98c7588-8jsbw
Pod Ports: 9080 (ratings), 15090 (istio-proxy)
--------------------
Service: ratings
Port: http 9080/HTTP targets pod port 9080
DestinationRule: ratings for "ratings"
Matching subsets: v1
(Non-matching subsets v2,v2-mysql,v2-mysql-vm)
Traffic Policy TLS Mode: ISTIO_MUTUAL
```
→ container port, istio-proxy port(15090), service protocol, DestinationRule, matching subset, TLS mode를 한눈에 요약.
```text
초기 진단 : istioctl x describe pod ← "이 Pod에 적용되는 Istio 설정 요약" (추상화 관점)
깊은 분석 : istioctl proxy-config ... ← "Envoy 내부 실제 설정" (raw)
```
### 03.4 `proxy-status` 상태값을 정확히 읽기
§03.1에서 본 컬럼들의 의미입니다. `CLUSTER`는 proxy가 속한 mesh cluster — 단일 클러스터에서는 모두 `Kubernetes`, multicluster일 때만 갈립니다.
| 상태 | 의미 |
|---|---|
| `SYNCED` | Envoy가 istiod의 마지막 설정을 **ACK**함 (정상) |
| `NOT SENT` | istiod가 보낼 것이 없었음 (예: gateway에 적용할 RDS route 없음 — 정상일 수 있음) |
| `STALE` | istiod가 update를 보냈지만 Envoy ACK를 **못 받음** (네트워크 또는 Istio 자체 문제 의심) |
| (proxy 누락) | 그 proxy가 **현재 istiod에 연결 안 됨** → 설정을 못 받는 중 |
```text
CDS STALE → cluster 설정 sync 문제 LDS STALE → listener 설정 sync 문제
EDS STALE → endpoint 설정 sync 문제 RDS STALE → route 설정 sync 문제
ECDS → WasmPlugin/EnvoyFilter의 extension config sync. extension 안 쓰면 보통 NOT SENT
proxy 없음 → sidecar가 istiod에 연결 안 됨 (injection 누락, agent crash, 네트워크 차단)
```
> `ECDS`(Extension Config Discovery Service)는 `WasmPlugin`이나 extension을 inject하는 `EnvoyFilter`의 확장 설정을 별도로 push하는 채널입니다. extension이 없는 일반 proxy는 `NOT SENT`가 정상이고, 사용 중인데 `STALE`이면 해당 WasmPlugin/EnvoyFilter sync 문제로 좁혀집니다.
> [!warning] 함정
> `NOT SENT`는 장애가 아닐 수 있음. ingress gateway의 RDS가 `NOT SENT`인 건 그 gateway에 적용할 HTTP route가 아직 없다는 뜻일 수 있음. 반면 **proxy가 목록에서 아예 빠진 것**은 거의 항상 문제(istiod 미연결).
### 03.5 diff 훈련 — "YAML이 아니라 Envoy 설정으로 사고하기"
xDS를 진짜로 이해하는 가장 빠른 길은 리소스 apply **전후**로 `proxy-config ... -o json`을 dump해 `diff`를 떠 보는 것입니다. CRD 한 줄이 어느 Envoy 설정으로 컴파일되는지가 눈에 보입니다.
```bash
istioctl proxy-config route -n -o json > before-route.json
kubectl apply -f virtualservice.yaml
istioctl proxy-config route -n -o json > after-route.json
diff -u before-route.json after-route.json
```
§03.2의 layer-trace와 이 diff 워크플로를 한 번에 자동화한 스크립트가 있습니다(listeners/routes/clusters/endpoints/secret 일괄 덤프).
- 📎 [proxy-dump.sh (proxy-config 일괄 덤프)](attachment/scripts/proxy-dump.sh)
---
## 04. CRD ↔ Envoy 번역표 + 매일 쓰는 명령 세트
운영자는 늘 "이 YAML이 어느 Envoy 설정으로 바뀌었나"를 묻습니다. 외우는 게 아니라 매번 `proxy-config`로 확인하는 용도의 매핑입니다.
| Istio/K8s 리소스 | Envoy 관점 | 장애 시 보는 곳 |
|---|---|---|
| `Service`, `EndpointSlice`, `ServiceEntry` | service registry / endpoint source | `proxy-config endpoint`, `proxy-config cluster` |
| `VirtualService` | HTTP/TCP/TLS route (RDS) | `proxy-config route` |
| `DestinationRule` | LB, subset, TLS, connection pool, outlier detection (CDS) | `proxy-config cluster` |
| `Gateway` | edge listener + server + TLS (LDS) | `proxy-config listener`, `proxy-config route` |
| `PeerAuthentication` | inbound mTLS requirement (LDS filter + SDS) | `proxy-config listener`, `proxy-config secret` |
| `AuthorizationPolicy` | Envoy RBAC filter | `proxy-config listener -o json` |
| `Sidecar` | config scope / import boundary | `proxy-config cluster`, `proxy-config listener` |
| `EnvoyFilter` | raw Envoy patch | `proxy-config listener/cluster/route -o json` |
매일 쓰는 디버깅 명령 10종:
```bash
# 1. 전체 proxy sync 상태
istioctl proxy-status
# 2. 특정 pod가 어떤 istiod에 붙었는지
istioctl proxy-status | grep
# 3. pod 관점 설명 (mesh 소속 + 적용 설정 요약) — experimental, 향후 변경/제거 가능
istioctl x describe pod -n
# 4. Envoy listener 확인 (어디서 받나)
istioctl proxy-config listener -n
# 5. HTTP/gRPC route 확인 (어느 cluster로)
istioctl proxy-config route -n
# 6. upstream cluster 확인 (backend pool)
istioctl proxy-config cluster -n
# 7. endpoint 확인 (실제 Pod IP)
istioctl proxy-config endpoint -n
# 8. mTLS secret 확인 (cert/key/CA)
istioctl proxy-config secret -n
# 9. bootstrap 확인 (어느 istiod, 어떤 metadata로 시작했는지)
istioctl proxy-config bootstrap -n
# 10. 구성 오류 사전 분석 (host mismatch, injection 누락, subset 불일치 등)
istioctl analyze -A
```
---
## 05. EnvoyFilter는 언제 쓰나 — escape hatch의 비용
`EnvoyFilter`는 istiod가 생성한 Envoy 설정을 직접 patch하는 **escape hatch**입니다. 특정 field를 바꾸거나, filter를 추가하거나, listener/cluster를 새로 추가할 수 있습니다. 존재 이유는 단순합니다 — **Istio API가 Envoy의 모든 기능을 추상화하지는 않기 때문**입니다. 그래서 운영 기준으로 **일반 application team은 거의 안 쓰는 게 정상**이고, platform team도 신중히 제한적으로만 씁니다.
쓰게 되는 경우:
```text
- 특정 Envoy HTTP filter 삽입
- Lua/Wasm/ext_authz 같은 고급 확장
- mesh-wide idle timeout / connection option 세밀 조정
- 특정 vendor agent / observability / security filter 연동
- Istio API로 아직 지원 안 하는 Envoy 기능 임시 사용 / 긴급 workaround
```
우선순위 원칙은 "위에서부터 가능하면 그걸로, EnvoyFilter는 최후"입니다. EnvoyFilter는 **컴파일러의 출력물을 손으로 후패치하는 것**이라, 컴파일러(istiod)가 다음 버전에서 출력 형태를 바꾸면 조용히 깨집니다.
```text
VirtualService로 가능하면 → VirtualService
DestinationRule로 가능하면 → DestinationRule
Telemetry API로 가능하면 → Telemetry
WasmPlugin으로 가능하면 → WasmPlugin
마지막에만 → EnvoyFilter
```
> [!warning] 함정
> 잘못된 EnvoyFilter는 mesh 전체를 불안정하게 만들 수 있음. Istio networking 내부 구현 및 Envoy xDS API에 깊게 묶여 있어 **proxy version upgrade마다 재검증 필요**. 여러 EnvoyFilter가 충돌하면 동작은 **undefined**.
성숙도 신호와 가드레일:
```text
[성숙도 신호]
Application namespace에 EnvoyFilter가 많다 → 운영 성숙도 낮음 또는 platform abstraction 부족 가능성
Root namespace의 global EnvoyFilter가 많다 → upgrade risk 큼. owner/test/rollback/version guard 필수
[운영 가드레일]
- EnvoyFilter는 platform team만 merge 가능
- workloadSelector 없는 root namespace EnvoyFilter 금지 또는 강한 승인
- 적용 전후 config_dump diff 필수
- canary namespace에서 먼저 검증
- Istio minor upgrade마다 재검증
```
---
## 핵심 정리
```text
멘탈모델 : istiod=컴파일러. K8s/CRD → Envoy 설정 5계층 → xDS로 push. 장애 = 사슬의 끊긴 고리 찾기.
5계층 : LDS(어디서 받나) → RDS(어느 cluster) → CDS(어떤 pool) → EDS(실제 Pod IP) (+SDS mTLS cert)
ADS 적용 : add=CDS→EDS→RDS→LDS(bottom-up) / remove=top-down — 불변식: route가 빈 cluster를 안 가리킴
분석 순서 : (0) proxy-status 게이트 → route → cluster → endpoint → secret. flag로 시작점 점프(NR/NC/UH/UF)
proxy-status : SYNCED(ACK) / NOT SENT(보낼 것 없음) / STALE(보냈지만 ACK 못 받음) / 누락(미연결) / ECDS=확장설정
x describe : 초기 진단(experimental) | proxy-config : 깊은 분석(Envoy raw) | diff로 CRD→Envoy 번역 체득
EnvoyFilter : 컴파일러 출력 후패치(escape hatch). VS/DR/Telemetry/WasmPlugin 우선, EnvoyFilter 최후
```
RDS의 D는 Discovery(Direct 아님). 요청은 항상 `Listener → Route → Cluster → Endpoint (+Secret)` 순으로 resolve됨.
## What you might be missing
- **`NOT SENT`을 장애로 오해하지 말 것.** gateway의 RDS가 `NOT SENT`인 건 적용할 route가 없다는 뜻일 수 있음 — 정상. 진짜 위험 신호는 `STALE`(ACK 실패)과 **proxy 목록에서의 누락**(istiod 미연결)임.
- **cluster는 CDS, endpoint는 EDS로 따로 온다(분리는 의도된 설계).** cluster가 존재해도 endpoint가 비어 있으면 `503 UH`가 남. "cluster 있음 = 정상"이 아니라 endpoint까지 봐야 함 — endpoint를 분리한 이유 자체가 "자주 바뀌니까 가볍게 갱신"이라, readiness probe와 outlier detection이 언제든 endpoint를 비울 수 있음.
- **`proxy-config`는 그 proxy가 실제로 받은 설정이지, istiod가 의도한 설정이 아님.** push가 안 갔거나 ACK가 안 됐으면(STALE) `proxy-config` 출력이 옛날 설정일 수 있음. 그래서 `analyze`(의도 검증)와 `proxy-status`(전달 검증)를 같이 봐야 함.
- **EnvoyFilter는 Istio/Envoy upgrade의 숨은 지뢰.** 컴파일러 출력물에 후패치를 박는 것이라 minor upgrade에서 출력 형태가 바뀌면 조용히 깨짐. root namespace의 workloadSelector 없는 EnvoyFilter는 blast radius가 mesh 전체이므로 owner/rollback/version guard 없으면 upgrade 전 반드시 재검증 대상으로 분류할 것.
## 관련 파일 · 참조
- 📎 [proxy-dump.sh (proxy-config 일괄 덤프)](attachment/scripts/proxy-dump.sh) — §03.2 layer-trace와 §03.5 diff 워크플로를 자동화한 listeners/routes/clusters/endpoints/secret 일괄 덤프 스크립트
- [xDS API 계층](xds__note-xds-api-layers.html) — 본 진단 상세판의 개념 note
- [데이터플레인 sync 상태](xds__note-data-plane-sync-state.html) — proxy-status SYNCED/STALE/NOT SENT 상태 개념
- [Cluster 해부](xds__src-cluster-anatomy.html) · [Sidecar 트래픽 캡처](xds__src-sidecar-traffic-capture.html) · [Envoy 응답 플래그](xds__src-envoy-response-flags.html)