homelab89 Docs Logs Legacy Files ☰ TOC 🌓
guideistio 2026-06-07xdsenvoyistioctlproxy-statusenvoyfilter

xDS 5계층과 istioctl 진단 — LDS/RDS/CDS/EDS/SDS (출처: Istio 1.30 공식 문서 + 홈랩 검증)

이 문서가 다루는 것

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 계층, sync 상태값은 데이터플레인 sync 상태. 본 문서는 그 둘의 운영·진단 상세판이다.

⚠️ 버전 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)가 잘못됐거나, 컴파일이 안 됐거나, 로드가 안 됐거나 셋 중 하나이고, 그걸 가르는 게 아래 도구들입니다.

한 문장 멘탈모델

xDS = istiod가 Kubernetes/Istio 상태를 Envoy 설정(Listener/Route/Cluster/Endpoint/Secret)으로 컴파일해서 각 프록시에 push하는 5개 API. 진단 = 이 5계층을 순서대로 따라가며 빈 칸을 찾는 일.


02. 아키텍처 — 5계층이 “요청 처리 경로” 그대로인 이유

핵심 통찰은 이것입니다. 5개 계층은 임의로 나눈 게 아니라, 요청 하나가 Envoy 내부에서 resolve되는 경로 그 자체입니다. 요청이 들어오면 Envoy는 항상 같은 질문을 순서대로 던지고, 각 질문에 답하는 설정 조각이 곧 xDS 한 계층입니다.

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)
flowchart TD
  L["Listener (LDS)<br/>어디서 받나"]
  R["Route (RDS)<br/>어느 cluster로"]
  C["Cluster (CDS)<br/>upstream pool"]
  E["Endpoint (EDS)<br/>실제 Pod IP"]
  S["Secret (SDS)<br/>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.

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 트래픽 캡처). LDS가 반영되면 Envoy 안에 listener, filter chain, protocol inspector, HTTP connection manager, TCP proxy 같은 처리 구조가 생깁니다.

istioctl proxy-config listener <pod> -n <namespace>
  • 영향 주는 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이 들어갑니다.

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 해부.

istioctl proxy-config route <pod> -n <namespace>
  • 영향 주는 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는 모름).

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 설정이 들어갑니다.

istioctl proxy-config cluster <pod> -n <namespace>
  • 영향 주는 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만 가볍게 갱신합니다.

Cluster: outbound|9080|v1|reviews.default.svc.cluster.local
Endpoints:
  10.244.1.12:9080
  10.244.2.18:9080
istioctl proxy-config endpoint <pod> -n <namespace>
  • 영향 주는 정보/리소스: 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를 증명합니다.

istioctl proxy-config secret <pod> -n <namespace>
  • 영향 주는 리소스: istiod CA, PeerAuthentication, DestinationRule TLS mode
  • 고장 증상: mTLS handshake/설정 불일치(흔히 UC나 일반 503으로 나타나며, UF가 반드시 뜨는 것은 아님 — §02.3 참고), 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→LDS→RDS를 순서대로 흘려 이 race를 원천 차단합니다.

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 --> LDS2[LDS]
  LDS2 --> RDS2[RDS]

순서에는 비대칭이 있고, 이게 진짜 포인트입니다. add(추가)CDS→EDS→LDS→RDSbottom-up(참조 대상부터 만들고 나중에 참조)으로 적용됩니다 — Envoy 공식 xDS protocol 문서(“eventual consistency considerations”)가 명시하는 순서로, 직관적으로는 참조하는 쪽인 LDS가 나중에 올 것 같지만 실제로는 LDS가 RDS보다 먼저 도착해야 합니다. 반대로 remove(삭제)나 warm shutdownLDS/RDS 먼저 → 그 다음 CDS/EDStop-down(참조하는 쪽을 먼저 떼고 나중에 대상 제거)으로 적용됩니다.

add    : CDS → EDS → LDS → RDS   (bottom-up, 대상 먼저)
remove : LDS / RDS → CDS / EDS   (top-down, 참조자 먼저)

방향은 정반대지만 두 경우 모두 같은 불변식을 지키려는 설계입니다 — “route가 아직 없거나 이미 사라진 cluster를 가리키는 순간이 생기지 않게 한다.” 다만 이 불변식은 동일 리소스 하나가 새로 만들어질 때의 add 순서에 대한 보장이지, 서로 다른 두 CRD(예: DestinationRule과 VirtualService)를 한 번에 동시 전환하는 경우까지 원자적으로 묶어 주지는 않습니다 — istiod가 K8s 오브젝트의 watch 이벤트를 리소스 종류별로 독립적으로 재계산·push하기 때문입니다. 실제로 홈랩에서 DestinationRule(신규 subset 추가)과 VirtualService(그 subset으로 라우팅 전환)를 단일 kubectl apply로 동시에 바꿔보면, RDS가 아직 해당 Envoy에 CDS/EDS로 생성되지 않은 신규 subset cluster를 가리키는 약 23~33ms의 짧은 창에서 NC(cluster_not_found)가 실제로 관측됐습니다(400회 중 3회, T25). 창이 매우 짧고 자동 회복되지만, “불변식이 절대 깨지지 않는다"는 것은 과장이고 크로스 리소스(다른 종류의 CRD) 동시 전환에는 원자성이 없다는 게 더 정확한 서술입니다. Envoy는 update를 받은 뒤 ACK/NACK로 적용 여부를 control plane에 알리고, 이 ACK/NACK가 proxy-statusSYNCED/STALE로 드러납니다(§04).

02.3 응답 플래그 = 사슬의 끊긴 고리 좌표

위 사슬의 각 고리가 끊기면 Envoy access log에 고유한 response flag가 찍힙니다. 이게 진단의 출발점입니다 — flag만 보면 5계층 중 몇 번째부터 보면 되는지 바로 압니다(flag 전체표는 → Envoy 응답 플래그).

flag 끊긴 고리 먼저 볼 계층
NR (NoRoute) RDS route (1단계)
NC (NoCluster) CDS cluster (2단계)
UH (NoHealthyUpstream) EDS endpoint (3단계)
UF (UpstreamFailure) 특정 계층 전용 아님 — TCP 연결 자체 실패 endpoint 도달성(3단계) 우선 의심 → 안 풀리면 secret(4단계)

UF는 예외로 다뤄야 합니다. Envoy 공식 문서상 UF(Upstream connection Failure)는 “TCP 연결 자체의 실패"를 뜻하는 범용 플래그이고, TLS/mTLS/SDS를 특정하지 않습니다. mTLS handshake 실패가 UF로 잡히는 경우도 있지만, 실무에서 UF의 훨씬 흔한 원인은 endpoint 미기동/포트 오류/NetworkPolicy 차단 같은 EDS·네트워크 계층 문제입니다. 그래서 “UF를 보면 곧장 secret(SDS)부터 본다"는 매핑은 과잉단순화이고, UF를 보면 먼저 EDS/네트워크 도달성(엔드포인트가 실제로 열려 있는지)을 의심하고, 그래도 안 풀리면 mTLS/SDS를 보는 순서가 더 정확합니다. 홈랩 실측(T20)에서도 순수 TCP 연결 실패와 mTLS mode mismatch(서버 STRICT + 클라이언트 tls.mode: DISABLE) 재현 모두에서 UF 판별에 쓸 만한 신호(response flag·UPSTREAM_TRANSPORT_FAILURE_REASON)가 동일하게 비어 있거나 겹쳐, 로그만으로 “UF=mTLS 문제"를 단정할 수 없음이 확인됐습니다.


03. 적용 예시 — 끊긴 고리 찾아 내려가기 (worked diagnosis)

이제 멘탈모델을 실제 명령으로 돌립니다. 시나리오: “reviews로 보낸 요청이 503이다.” 분석은 사슬을 위에서 아래로 내려가며 빈 칸을 찾는 일입니다.

03.1 0단계 게이트 — 먼저 “전달은 됐나"부터

layer trace 전에 proxy-status로 설정이 Envoy에 도착했는지부터 확인합니다. NOT SENT/STALE이면 설정이 아직 프록시에 없는 것이라, 그 위에서 layer를 따라가 봐야 의미가 없습니다(sync 상태의 1단계 게이트).

istioctl proxy-status

예상 출력:

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단계 — 사슬을 따라 내려가기

# 1. route가 원하는 cluster를 가리키는지   (NR이면 여기)
istioctl proxy-config route <pod> -n <ns>

# 2. 그 cluster가 존재하는지              (NC이면 여기)
istioctl proxy-config cluster <pod> -n <ns>

# 3. 그 cluster에 endpoint가 있는지        (UH이면 여기 / UF도 우선 여기부터 의심)
istioctl proxy-config endpoint <pod> -n <ns>

# 4. mTLS secret이 있는지                  (endpoint까지 정상인데 UF면 여기)
istioctl proxy-config secret <pod> -n <ns>

# 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 쪽 실체를 대조하면 끝납니다.

시작점 좁히기

response flag로 trace를 단축할 수 있음. NR→1단계, NC→2단계, UH→3단계로 점프하면 빠름. UF는 SDS 전용 신호가 아니라 TCP 연결 실패 일반이므로 3단계(EDS/네트워크 도달성)부터 보고, 그래도 안 풀리면 4단계(mTLS/secret)로 내려가는 순서가 더 정확함.

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:

istioctl x describe pod coredns-f9fd979d6-2zsxk -n kube-system
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 포함):

istioctl x describe pod ratings-v1-7dc98c7588-8jsbw -n default
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를 한눈에 요약.

초기 진단 : 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에 연결 안 됨 → 설정을 못 받는 중
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 문제로 좁혀집니다.

함정

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 설정으로 컴파일되는지가 눈에 보입니다.

istioctl proxy-config route <pod> -n <ns> -o json   > before-route.json
kubectl apply -f virtualservice.yaml
istioctl proxy-config route <pod> -n <ns> -o json   > after-route.json
diff -u before-route.json after-route.json

§03.2의 layer-trace와 이 diff 워크플로를 한 번에 자동화한 스크립트가 있습니다(listeners/routes/clusters/endpoints/secret 일괄 덤프).


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종:

# 1. 전체 proxy sync 상태
istioctl proxy-status
# 2. 특정 pod가 어떤 istiod에 붙었는지
istioctl proxy-status | grep <pod>
# 3. pod 관점 설명 (mesh 소속 + 적용 설정 요약) — experimental, 향후 변경/제거 가능
istioctl x describe pod <pod> -n <ns>
# 4. Envoy listener 확인 (어디서 받나)
istioctl proxy-config listener <pod> -n <ns>
# 5. HTTP/gRPC route 확인 (어느 cluster로)
istioctl proxy-config route <pod> -n <ns>
# 6. upstream cluster 확인 (backend pool)
istioctl proxy-config cluster <pod> -n <ns>
# 7. endpoint 확인 (실제 Pod IP)
istioctl proxy-config endpoint <pod> -n <ns>
# 8. mTLS secret 확인 (cert/key/CA)
istioctl proxy-config secret <pod> -n <ns>
# 9. bootstrap 확인 (어느 istiod, 어떤 metadata로 시작했는지)
istioctl proxy-config bootstrap <pod> -n <ns>
# 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도 신중히 제한적으로만 씁니다.

쓰게 되는 경우:

- 특정 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)가 다음 버전에서 출력 형태를 바꾸면 조용히 깨집니다.

VirtualService로 가능하면  → VirtualService
DestinationRule로 가능하면 → DestinationRule
Telemetry API로 가능하면   → Telemetry
WasmPlugin으로 가능하면    → WasmPlugin
마지막에만                  → EnvoyFilter
함정

잘못된 EnvoyFilter는 mesh 전체를 불안정하게 만들 수 있음. Istio networking 내부 구현 및 Envoy xDS API에 깊게 묶여 있어 proxy version upgrade마다 재검증 필요. 여러 EnvoyFilter가 충돌하면 동작은 undefined.

성숙도 신호와 가드레일:

[성숙도 신호]
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마다 재검증

핵심 정리

멘탈모델 : istiod=컴파일러. K8s/CRD → Envoy 설정 5계층 → xDS로 push. 장애 = 사슬의 끊긴 고리 찾기.

5계층   : LDS(어디서 받나) → RDS(어느 cluster) → CDS(어떤 pool) → EDS(실제 Pod IP) (+SDS mTLS cert)
ADS 적용 : add=CDS→EDS→LDS→RDS(bottom-up) / remove=top-down — 불변식은 "동일 리소스" add에 한정,
           cross-resource(다른 CRD 동시 전환) 시 수십ms대 NC 예외가 실측됨(T25)
분석 순서 : (0) proxy-status 게이트 → route → cluster → endpoint → secret. flag로 시작점 점프
           (NR/NC/UH는 그대로 매핑, UF는 SDS 전용 아님 — EDS/네트워크부터 의심)

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(전달 검증)를 같이 봐야 함.
  • ADS의 순서 보장은 “동일 리소스 add” 범위에 한정된다. DestinationRule과 VirtualService처럼 서로 다른 CRD를 한 번에 동시 전환하면, istiod가 두 오브젝트를 독립적으로 재계산·push하기 때문에 수십ms대의 짧은 NC 창이 생길 수 있음(실측 T25). 트래픽이 아주 짧게라도 민감한 전환(예: canary 100% 컷오버)은 이 창을 감안해야 함.
  • UF는 SDS/mTLS 전용 신호가 아니다. Envoy 공식 정의는 “TCP 연결 실패"일 뿐이라 endpoint 미기동·NetworkPolicy 차단 같은 네트워크 문제로도 흔히 뜬다. UF를 보면 EDS/네트워크 도달성부터 의심하고, 그래도 안 풀릴 때 mTLS/SDS로 내려가는 순서가 안전하다(실측 T20).
  • EnvoyFilter는 Istio/Envoy upgrade의 숨은 지뢰. 컴파일러 출력물에 후패치를 박는 것이라 minor upgrade에서 출력 형태가 바뀌면 조용히 깨짐. root namespace의 workloadSelector 없는 EnvoyFilter는 blast radius가 mesh 전체이므로 owner/rollback/version guard 없으면 upgrade 전 반드시 재검증 대상으로 분류할 것.

관련 파일 · 참조

검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6)

검증 방법: Envoy/Istio 공식 문서 대조 + homelab k8s 클러스터 실측(T20, T24, T25, T44)을 병행. 15개 주장 중 2건(C6 ADS 적용 순서, C8 응답 플래그 UF 매핑)이 오류로 확정돼 본문을 교정했고, 그중 C6은 문헌 정정에 더해 실측이 원 correction의 “불변식은 유지된다"는 판단까지 반박해 그 부분도 다시 고쳤다. C4는 문헌만으로는 단일 canonical 근거를 못 찾은 주장이었으나 클러스터 실측으로 확인됐다.

주장 판정 근거
C1. istiod는 K8s 상태+CRD를 Envoy 설정(L/R/C/E/S)으로 컴파일해 xDS로 push하는 컴파일러다 ✅ 실측 확인 istio.io/…/architecture · T44 실측
C2. xDS 5계층은 LDS/RDS/CDS/EDS/SDS이며 각각 listener/route/cluster/endpoint/secret에 대응 ✅ 문헌 확인 envoyproxy.io/…/dynamic_configuration
C3. sidecar는 outbound 15001 / inbound 15006 capture listener를 가지며 iptables가 모든 트래픽을 여기로 redirect ✅ 문헌 확인 istio.io/…/application-requirements
C4. outbound listener는 대부분 wildcard, service-IP별 dedicated listener는 protocol 충돌 회피 등 조건부로만 생성 ✅ 실측 확인 github.com/istio/istio#18684 · T24 실측
C5. ADS는 CDS/EDS/LDS/RDS/SDS를 단일 gRPC stream으로 묶고, 존재 이유는 적용 순서 보장 ✅ 문헌 확인 envoyproxy.io/…/xds_protocol
C6. ADS 적용 순서는 add=CDS→EDS→RDS→LDS이며 불변식이 절대 깨지지 않는다 🔬 실측 반증 — 본문 교정 envoyproxy.io/…/xds_protocol · T25 실측
C7. Envoy는 xDS update에 ACK/NACK로 응답하고, 이것이 proxy-status의 SYNCED/STALE로 드러남 ✅ 문헌 확인 istio.io/…/proxy-cmd
C8. 응답 플래그 대응표(NR/NC/UH는 RDS/CDS/EDS, UF는 SDS/mTLS 전용) 🔬 실측 반증 — 본문 교정 envoyproxy.io/…/access_log/usage · T20 실측
C9. workload 인증서는 Envoy가 SDS로 요청하고, istio agent가 디스크 마운트 없이 메모리로 전달 ✅ 문헌 확인 istio.io/v1.3/…/auth-sds
C10. proxy-status는 CDS/LDS/EDS/RDS/ECDS/ISTIOD/VERSION 컬럼과 SYNCED/NOT SENT/STALE/누락 상태를 보여줌 ✅ 문헌 확인 istio.io/…/proxy-cmd
C11. 1.30에서 istioctl x describe pod는 여전히 experimental ✅ 문헌 확인 istio.io/…/istioctl-describe
C12. CDS/EDS 분리 이유는 endpoint가 cluster보다 훨씬 자주 바뀌기 때문 ✅ 문헌 확인 envoyproxy.io/…/dynamic_configuration
C13. cluster 이름 규칙은 방향|포트|subset|FQDN ✅ 문헌 확인 istio.io/…/proxy-cmd
C14. ECDS는 WasmPlugin/EnvoyFilter 확장 설정 채널이며 미사용 시 NOT SENT가 정상 ✅ 문헌 확인 envoyproxy.io/…/config_discovery.proto
C15. 여러 EnvoyFilter 충돌 시 동작은 undefined이며 proxy 버전 업그레이드마다 재검증 필요 ✅ 문헌 확인 istio.io/…/envoy-filter

Files