Istio VirtualService ↔ DestinationRule — 매핑 구조와 디버깅 플레이북
이 글의 원문은 매핑 와이어 다이어그램·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⟷ DRspec.host. FQDN 정규화 후 문자열 비교. - 매핑 키 ② — VS
destination.subset⟷ DRsubsets[].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 cluster→pc endpoint순 확인으로 즉시 구분된다. - UF — 설정 매핑이 아니라 연결 자체의 문제. Vol.2의 port×subset 오조합이 전형이다.
원문 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 불일치. clusteroutbound|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가 유일한 확정 수단이다.
관련 문서
- DR subset × port 교차 생성 — 원리와 포트 스코핑 전략 — 이 문서의 후속편(Vol.2). “설정은 전부 정합한데 UF"인 네 번째 진단 축
- Response flag 실전 판독 — 식별 팁·계층별 카탈로그·조합 해석 — §7 진단 축의 확장판(Vol.3 격). details 필드·집계·디코더
- DestinationRule 기초→심화 — DR 필드 전반의 정본
- Envoy Response Flags 운영 레퍼런스 — 28-flag 정본·파이프라인 멘탈모델 (검증 문서)
- xDS 계층과 진단 — §4·§6의 계층 순회를 xDS 관점에서 정리한 정본
- SE와 Envoy 반영 범위 — sidecar vs gateway·DR — 같은 매핑이 ServiceEntry·gateway에서 어떻게 달라지는가