homelab89 Docs Logs Legacy Files ☰ TOC 🌓
guideistio 2026-08-01istiovirtualservicedestinationrulesubsetxdsistioctldiagnosistroubleshooting

Istio VirtualService ↔ DestinationRule — 매핑 구조와 디버깅 플레이북

원본은 스타일링된 HTML 레퍼런스 (Vol.1)

이 글의 원문은 매핑 와이어 다이어그램·xDS 파이프라인 SVG 2종과 8단계 플레이북 카드를 포함한 HTML 레퍼런스다.

→ 전문 보기 (istio-vs-dr-mapping.html)

이 페이지는 같은 내용의 markdown 정리본이며, 원문 작성 이후 대화에서 나온 교정 1건(NR flag의 응답 코드 — §7 참조)과 보충(맹점·확인 질문)을 함께 반영했다. 후속편: Vol.2 — DR subset × port 교차 생성.

0. 핵심 요약

  • 발동 조건 — VS spec.hosts = 요청의 목적지 이름과 매칭 (HTTP는 :authority 헤더, TLS는 SNI, plain TCP는 Service VIP). DR과는 무관하다.
  • 매핑 키 ① — VS route.destination.host ⟷ DR spec.host. FQDN 정규화 후 문자열 비교.
  • 매핑 키 ② — VS destination.subset ⟷ DR subsets[].name. subset labels가 Pod label과 일치해야 endpoint가 채워진다.
  • 진단 축 — response flag로 원인 계층을 가른다: NR=route 부재(VS/hosts, 404), NC=cluster 부재(DR/host, 503), UH=endpoint 부재(label/Pod, 503).

1. 왜 두 리소스로 쪼개져 있나 — 역할 분담

Istio는 트래픽 제어를 라우팅 결정목적지 정책으로 분리한다. 변경 주기와 소유자가 다른 관심사를 리소스 단위로 나눈 설계다.

리소스 책임 답하는 질문 Envoy 대응
VirtualService L7 라우팅 규칙 (match/rewrite/weight/retry/timeout/fault) 이 요청을 어디로 보낼까 Route (RDS)
DestinationRule 목적지 정책 (LB, connection pool, outlier detection, mTLS 모드) + subset 정의 그 목적지로 가는 트래픽을 어떻게 다룰까 Cluster (CDS)
subset 같은 Service 뒤 Pod들을 label로 나눈 논리적 버전 그룹 목적지 안에서 어느 그룹인가 별도 Cluster + endpoint 필터 (EDS)
Service / ServiceEntry service registry 등록 — VS·DR이 참조 가능한 이름의 원천 mesh가 아는 이름인가 Cluster·Endpoint의 기반
Gateway edge에서 수신할 port·protocol·host 선언 외부 트래픽을 어디서 받을까 Listener (LDS)

subset은 라우팅 대상 그룹의 “정의"만 담당하고, 그 그룹을 “선택"하는 것은 VS의 몫이다. 정의(DR)와 선택(VS)이 분리되어 이름 참조로만 연결된다 — 이 참조가 끊기는 것이 대부분의 장애 원인이다.

2. spec.hosts는 무엇과 매칭되는가

spec.hosts는 **“요청이 향하는 목적지 이름”**과 매칭되어 이 VS의 발동 여부를 결정한다. DR의 host와 비교되는 필드가 아니다 — 여기가 가장 많이 헷갈리는 지점이다. 매칭은 두 단계로 일어난다.

단계 1 — 컴파일 타임 (istiod). istiod가 hosts:를 보고 이 VS의 규칙을 어느 Envoy virtualHost에 부착할지 결정한다. sidecar(mesh) 트래픽의 경우 host는 service registry에 등록된 이름(K8s Service 또는 ServiceEntry)이어야 한다. 미등록 이름을 쓰면 규칙이 어디에도 부착되지 않고 조용히 무시된다 — 에러가 없어 발견이 늦다.

단계 2 — 런타임 (Envoy). 프로토콜에 따라 매칭 재료가 다르다. L7 정보가 있는 프로토콜만 이름 기반 매칭이 가능하다.

프로토콜 매칭 재료 비고
HTTP/1.1, HTTP/2, gRPC :authority (Host) 헤더 virtualHost domains에 FQDN·short name·Service VIP 변형이 모두 등록됨
TLS (passthrough) SNI tls: 라우트 블록 사용 시
Plain TCP 목적지 IP (Service VIP) L7 헤더가 없어 이름 매칭 불가

부착 위치(gateways 필드)에 따른 차이:

gateways 필드 적용 범위 hosts 매칭 대상
미지정 또는 mesh 모든 sidecar의 outbound mesh 내부 요청의 목적지 이름 (registry 등록 필수)
[my-gateway] 해당 ingress gateway Gateway servers[].hosts와의 교집합 + 외부 인입 Host/SNI. 외부 도메인 사용 가능
[mesh, my-gateway] 둘 다 각각 위 규칙 적용
정리

spec.hosts = “요청의 목적지 이름과 매칭해 이 VS를 발동시킬지”, route.destination.host = “발동 후 실제로 보낼 곳(= DR 매칭 키)”. 단순 케이스에선 둘이 같지만, hosts: [reviews]로 받아 destination.host: reviews-canary로 보내는 식으로 다를 수 있다.

3. 매핑 키 — 무엇이 무엇과 연결되는가

VS와 DR을 잇는 키는 정확히 두 개다. 그 외 필드는 매핑에 관여하지 않는다.

 Client request (Host: reviews)
        |
        v
+---------------------------+
| VirtualService            |
|  hosts: [reviews]  <------+-- matches request :authority
|  http.route.destination:  |
|    host: reviews   -------+--+  KEY 1: host (FQDN normalized)
|    subset: v2      -------+--+  KEY 2: subset name
+---------------------------+  |
                               v
+---------------------------+
| DestinationRule           |
|  host: reviews     <------ KEY 1 match
|  subsets:                 |
|   - name: v2       <------ KEY 2 match
|     labels:               |
|       version: v2  -------+--> endpoint filter
+---------------------------+
        |
        v
 Envoy cluster: outbound|9080|v2|reviews.default.svc.cluster.local
        |  (EDS: Service endpoint among label version=v2 only)
        v
 Pod reviews-v2-xxxxx

FQDN 정규화 규칙host: reviews처럼 short name을 쓰면 그 리소스가 정의된 namespace 기준으로 reviews.<ns>.svc.cluster.local로 확장된다. 호출자 namespace 기준이 아니다. VS와 DR이 서로 다른 namespace에 있으면 같은 short name이 다른 FQDN으로 확장되어 매핑이 조용히 끊어진다. 운영 환경에서는 FQDN 명시가 원칙이다.

4. istiod → Envoy xDS 변환 경로

디버깅에서 “어느 계층에서 끊겼는지"를 판단하려면 각 리소스가 어떤 xDS로 변환되는지 알아야 한다. istioctl proxy-config(이하 pc)의 서브커맨드가 이 계층과 1:1 대응이다.

Istio API Envoy xDS 내용 조회
Gateway LDS Listener 수신 port·protocol pc listener
VirtualService RDS Route virtualHost.domains ← hosts, route → cluster 이름 pc route
DestinationRule CDS Cluster outbound|port|subset|fqdn pc cluster
Service/Endpoint EDS Endpoint label 필터된 Pod IP pc endpoint
  • RDS — VS의 hosts가 virtualHost의 domains가 되고, route 규칙이 cluster 이름 문자열을 가리킨다.
  • CDS — DR의 subset마다 outbound|<port>|<subset>|<FQDN> 이름의 cluster가 생성된다. subset 없는 기본 cluster는 subset 자리가 빈 문자열이다.
  • EDS — 해당 cluster의 endpoint 목록. Service endpoint 중 subset labels와 일치하는 Pod만 포함된다.
  • 즉 VS→DR 연결의 실체는 **“route가 참조하는 cluster 이름 문자열”**이며, 이 문자열을 만드는 재료가 매핑 키 두 개다.

5. 완전한 예제 — v1/v2 가중치 분배

# DestinationRule — subset 정의 + 목적지 정책
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
  namespace: default
spec:
  host: reviews.default.svc.cluster.local   # KEY 1
  trafficPolicy:
    loadBalancer:
      simple: LEAST_REQUEST
  subsets:
  - name: v1                                # KEY 2
    labels:
      version: v1                           # Pod label과 일치 필요
  - name: v2
    labels:
      version: v2
---
# VirtualService — 라우팅 규칙
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
  namespace: default
spec:
  hosts:
  - reviews.default.svc.cluster.local        # 요청 매칭용 (DR과 무관)
  http:
  - route:
    - destination:
        host: reviews.default.svc.cluster.local  # KEY 1 → DR.host
        subset: v1                               # KEY 2 → subsets[].name
      weight: 90
    - destination:
        host: reviews.default.svc.cluster.local
        subset: v2
      weight: 10

6. 디버깅 플레이북 — 설정이 꼬였을 때

원칙: 정적 검증 → 동기화 확인 → xDS 계층 순회(Route → Cluster → Endpoint) → 런타임 관측 순으로 내려간다. 각 단계는 “여기까지는 정상"을 확정하고 다음 계층으로 넘어가는 이분 탐색이다.

# 1. 정적 검증 — 리소스 간 참조 무결성
istioctl analyze -n default
#  IST0101 referenced DestinationRule subset not found → subset 오타/DR 누락
#  IST0132 host not found in registry → hosts에 미등록 이름

# 2. 동기화 확인 — STALE이면 이후 단계는 의미 없음 (istiod부터)
istioctl proxy-status
# 모든 컬럼(CDS/LDS/EDS/RDS)이 SYNCED 여야 함

# 3. 대상 요약 — 이 Pod에 내 설정이 붙긴 했나
istioctl experimental describe pod reviews-v2-abc -n default

# 4. Route 계층 — VS가 라우트로 변환됐는가
istioctl proxy-config route deploy/productpage -n default --name 9080 -o json \
  | jq '.[0].virtualHosts[] | select(.name | startswith("reviews"))'
# 기대: weightedClusters에 outbound|9080|v1|... =90, |v2|... =10
# 규칙이 안 보임 → hosts가 registry 미등록이거나 exportTo로 가려짐

# 5. Cluster 계층 — DR·subset이 cluster로 변환됐는가
istioctl proxy-config cluster deploy/productpage -n default \
  --fqdn reviews.default.svc.cluster.local
# 기대: SUBSET 컬럼에 v1, v2 행이 각각 존재
# subset 행 없음 → DR 미적용 (host FQDN 불일치, exportTo, namespace)

# 6. Endpoint 계층 — label 필터 결과가 비어있지 않은가
istioctl proxy-config endpoint deploy/productpage -n default \
  --cluster "outbound|9080|v2|reviews.default.svc.cluster.local"
# 기대: version=v2 Pod IP들이 HEALTHY. 0개면 아래로 교차 확인:
kubectl get pods -n default -l app=reviews --show-labels
kubectl get endpointslices -n default -l kubernetes.io/service-name=reviews

# 7. 런타임 관측 — 호출자 sidecar의 access log response flag
kubectl logs deploy/productpage -n default -c istio-proxy --tail=100 | grep -E ' 50[03] | 404 '
# "... 503 NC ..." → cluster 부재   "... 503 UH ..." → endpoint 부재
# "... 404 NR ..." → route 부재

# 8. 최후 수단 — config dump / Envoy 로그 레벨
kubectl exec deploy/productpage -n default -c istio-proxy -- \
  curl -s localhost:15000/config_dump > dump.json
istioctl proxy-config log deploy/productpage -n default --level router:debug,upstream:debug

증상 → 진입 단계 매핑

증상 진입 단계 전형적 원인
배포 직후 전면 503 1 → 7 (flag 확인) subset 오타(NC), DR host 불일치(NC)
특정 namespace에서만 503 4, 5 VS/DR exportTo 제한, short name의 namespace 확장 차이
가중치가 반영 안 됨 4 다른 VS가 같은 host 선점, match 순서
일부 요청만 간헐 503 6, 7 일부 Pod not-ready(UH 간헐), outlier ejection, rollout 중 label 전환
설정 바꿨는데 반영 안 됨 2 proxy STALE, webhook 오류로 리소스 미반영
mTLS 관련 연결 실패 7 (UF) DR tls 모드 ↔ PeerAuthentication 불일치

7. 진단 축 — 계층별 response flag

Flag 의미 응답 코드 의심 계층 1차 확인
NR NoRoute — 매칭 route/filter chain 없음 404 (TCP는 코드 없이 연결 종료) VS / hosts pc route, hosts 철자·registry 등록
NC NoCluster — route가 가리키는 cluster 없음 503 DR / host·subset pc cluster, DR host FQDN·exportTo
UH NoHealthyUpstream — cluster는 있으나 endpoint 0개 503 subset labels / Pod pc endpoint, Pod label·readiness
UF UpstreamConnectionFailure — 연결 실패 503 mTLS / NetworkPolicy / 포트 DR tls 모드 ↔ PeerAuthentication 정합
UO UpstreamOverflow — circuit breaker 발동 503 DR connectionPool pc cluster -o json thresholds, upstream_rq_pending_overflow stat
  • NR vs NC — NR은 “라우팅 테이블에 규칙 자체가 없음”(VS 계층), NC는 “규칙은 있는데 목적지 cluster가 없음”(DR 계층). 고칠 리소스가 다르다.
  • NC vs UH — NC는 cluster 생성 실패(DR host/subset 참조 문제), UH는 cluster는 정상이나 label 필터 결과가 빈 경우. pc clusterpc endpoint 순 확인으로 즉시 구분된다.
  • UF — 설정 매핑이 아니라 연결 자체의 문제. Vol.2의 port×subset 오조합이 전형이다.
원문 HTML의 교정 사항 — NR은 404다

원문 HTML(00 요약)은 NR을 503 계열로 묶었는데, 후속 검토에서 교정됐다. HTTP에서 NR은 404와 함께 남고, TCP·filter chain 미매칭은 응답 코드 없이 연결이 끊어지는 형태로 관측된다. 503 계열은 UH/NC/UF/UC/UR/LR/UO다. flag 판독 전반은 Response flag 실전 판독 참조.

8. 실패 시나리오 — 어떻게 관측되는가

  • A. subset 참조 대상 DR 부재 → 503 NC — VS가 subset: v2를 참조하는데 DR이 없거나 host FQDN 불일치. cluster outbound|9080|v2|... 자체가 생성되지 않아 route가 존재하지 않는 cluster 이름을 가리킨다. istioctl analyze의 IST0101로 사전 탐지 가능.
  • B. subset labels와 일치하는 Pod 없음 → 503 UH — DR은 정상 적용됐으나 해당 label Pod가 0개(오타, rollout 중 label 변경, 전부 not-ready). cluster는 존재하지만 EDS 결과가 빈 목록. 정적 검증으로 안 잡히고 런타임에만 드러난다 — pc endpoint가 유일한 확정 수단.
  • C. short name의 namespace 확장 차이 → 조용한 미적용 — VS는 team-a, DR은 team-b에 있고 둘 다 host: reviews면 서로 다른 FQDN으로 확장되어 KEY ①이 불일치한다. subset까지 참조했다면 NC로 표면화되지만, trafficPolicy만 있는 DR이면 증상 없이 정책만 사라진다.
  • D. exportTo로 가려진 리소스 → 호출자 위치 의존 503 — DR에 exportTo: ["."]가 걸리면 다른 namespace의 sidecar에는 subset cluster가 생성되지 않는다. “같은 namespace에서는 되는데 다른 데선 NC"가 시그니처.
공통 원칙

proxy-config 조회는 항상 호출자(client) Pod 기준으로 수행한다. 라우팅 결정은 호출자 sidecar의 outbound에서 일어나며, exportTo·Sidecar 리소스에 의해 Pod마다 보이는 설정이 다를 수 있다.

What you might be missing

  • DR의 exportTo와 namespace 경계 — DR은 기본적으로 전 mesh에 export되지만(meshConfig.defaultDestinationRuleExportTo에 따라 다름), exportTo: ["."]로 제한하면 다른 namespace의 sidecar에는 subset cluster가 아예 안 생긴다. “내 namespace에선 되는데 다른 데선 503 NC"의 흔한 원인.
  • 같은 host에 DR 여러 개 — 같은 host를 겨냥한 DR이 여러 namespace에 있으면 병합 규칙이 직관적이지 않고(호출자 namespace의 DR 우선 등), trafficPolicy가 예상과 다르게 적용될 수 있다. host당 DR 하나를 원칙으로 하는 게 안전하다.
  • subset 없이도 DR은 의미가 있다 — mTLS 모드, connection pool, outlier detection은 host 레벨에서만 걸어도 동작한다. 반대로 subset 레벨 trafficPolicy는 host 레벨을 상속이 아니라 통째로 override하므로, subset에 정책을 하나라도 쓰면 host 레벨 정책을 다시 명시해야 한다.
확인 질문 — v2 subset의 labels를 version: v2에서 version: v3로 바꾸면 (Pod는 그대로) 무슨 일이 생기나

cluster outbound|9080|v2|...는 그대로 존재한다 — subset 이름(v2)은 안 바뀌었으므로 VS route 참조도 유효하고 NC가 아니다. 대신 그 cluster의 EDS label 필터가 version: v3로 바뀌는데 매칭되는 Pod가 0개이므로 endpoint 목록이 빈다. 결과: v2로 가던 트래픽 전부가 503 UH. istioctl analyze는 못 잡는다(참조 무결성은 멀쩡한, 문법상 유효한 설정이므로) — pc endpoint가 유일한 확정 수단이다.

관련 문서

Files