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

DestinationRule subset × port 교차 생성 — 원리와 포트 스코핑 전략

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

이 글의 원문은 port×subset 곱 다이어그램, 동일-selector 두 Service 공유 구조도, 전략 선택 결정 트리 SVG 3종을 포함한 HTML 레퍼런스다.

→ 전문 보기 (istio-dr-subset-port-scoping.html)

이 페이지는 같은 내용의 markdown 정리본. 전편: Vol.1 — VS ↔ DR 매핑 구조와 디버깅 플레이북. 출발점이 된 상황: egress 설계에서 “3128용 subset·443용 subset"으로 나눠 등록했는데 istioctl pc cluster에 모든 포트 × 모든 subset 조합이 보였다.

0. 핵심 요약

  • 원리 — subset은 label 축, port는 Service 축. subset 스키마에 port 필드가 없어 istiod는 host의 port × subset 곱으로 cluster를 생성한다. 관찰된 현상은 버그가 아닌 설계상 정상.
  • EDS — endpoint는 label로만 필터된다. Pod가 해당 포트를 실제 listen하는지 검사하지 않으며, HEALTHY는 “readiness 통과 + ejection 아님"의 의미다.
  • 정석 — 포트 스코핑은 Service의 책임: 포트당 Service(host) 분리로 곱 자체를 제거한다. Pod는 나눌 필요 없다 (같은 Deployment를 두 Service가 공유 가능).
  • 위험 — 잘못된 (port, subset) 조합으로 라우팅하면 cluster·endpoint 모두 정상으로 보이지만 503 UF로 실패한다 — NC/UH 진단 축에 안 잡히는 구간.

1. 왜 교차 생성되는가

DR subset의 스키마는 name · labels · trafficPolicy 세 필드가 전부다. “이 subset은 3128 전용"이라고 선언할 자리가 없다. 포트별 subset이라는 의도는 리소스에 표현되지 않고, istiod는 다음 규칙으로 CDS를 생성한다:

CDS 생성 규칙

host의 port마다: 기본 cluster 1개 + subset마다 1개. cluster 이름 = outbound|<port>|<subset>|<FQDN>

 Service axis (ports)          DR axis (subsets, label only)
 proxy-svc: [3128, 443]    x   squid  {app: squid}
                               tls-gw {app: tls-gw}
            |
            v  cross product (istiod)
 CDS output: 2 ports x (1 default + 2 subsets) = 6 clusters
   outbound|3128||proxy-svc...
   outbound|3128|squid|proxy-svc...
   outbound|3128|tls-gw|proxy-svc...   <- unintended
   outbound|443||proxy-svc...
   outbound|443|squid|proxy-svc...     <- unintended
   outbound|443|tls-gw|proxy-svc...

pc cluster에 443용으로 만든 subset이 3128 포트 행으로도 보이는 것은 정확히 이 규칙의 결과다. “의도 밖” 조합도 문법상 유효한 cluster로 생성된다 — istiod는 의도를 모른다. 문제는 존재 자체가 아니라, 이 중 일부가 “설정상 존재하지만 실제로는 동작 불가능한 조합"이라는 점이다 (→ §6).

2. EDS는 label만 본다 — HEALTHY의 실제 의미

pc endpoint에서 의도 밖 조합의 endpoint까지 HEALTHY로 보이는 이유도 같은 축 분리에서 나온다.

단계 동작 검사하는 것 검사하지 않는 것
EDS endpoint 선정 Service endpoint 중 subset labels 일치 Pod 선택 label 일치 여부 해당 포트 listen 여부
endpoint port 결정 그 Service port의 targetPort를 그대로 사용 Service 선언 Pod의 실제 open port
HEALTHY 판정 readiness probe 통과 + outlier ejection 아님 probe 결과 이 cluster의 포트로 통신 가능한지
  • outbound|3128|tls-gw|...의 endpoint = “tls-gw label Pod들 + 3128의 targetPort”. 그 Pod가 443만 listen해도 목록에 들어간다.
  • readiness probe가 앱 포트(예: 443)만 체크하면, 3128 조합 cluster에서도 같은 Pod가 HEALTHY로 표시된다. **“HEALTHY ≠ 이 cluster로 통신 가능”**이 핵심 구분이다.

3. Service selector vs subset labels — Pod를 나눠야 하는가

둘 다 “Pod를 고르는 조건"일 뿐 Pod 배치를 강제하지 않는다. 한 Pod가 여러 Service에 잡히는 것도, 여러 subset에 잡히는 것도 정상이다. 따라서 “label이 다르니 Pod를 나눠야 한다"는 순서가 반대다 — Pod가 실제로 다른 워크로드인지가 먼저이고, label은 그 사실의 표현 수단이다.

판별은 실측 하나로 끝난다:

kubectl get pods -n egress -l app=squid --show-labels
kubectl get pods -n egress -l app=tls-gw --show-labels
# 같은 Pod가 나옴 → 상황 2 (단일 워크로드, 포트 구분 목적의 label이었음)
# 다른 Pod가 나옴 → 상황 1 (이미 분리된 워크로드)
상황 실체 조치 Pod 변경
상황 1 labels가 서로 다른 Pod 그룹을 가리킴 Service 2개, selector 각각 (그룹별) 없음 — 이미 나뉘어 있음
상황 2 labels가 결국 같은 Pod들에 매칭됨 Service 2개, selector 동일, 포트만 다름 없음 — subset이 잘못된 도구였을 뿐
 상황 2 — 같은 Deployment를 두 Service가 공유 (Pod 분리 불필요)

 +---------------------------+   +---------------------------+
 | Service: squid-path       |   | Service: tls-path         |
 | selector: {app: egress-gw}|   | selector: {app: egress-gw}|  <- 동일
 | ports: [3128]             |   | ports: [443]              |
 +------------+--------------+   +------------+--------------+
              |                               |
              v                               v
        +--------------------------------------+
        | Deployment: egress-gw (한 벌)         |
        | labels: {app: egress-gw}             |
        | listens: 3128, 443                   |
        +--------------------------------------+

Service는 논리 객체다 — selector가 같아도 host가 다르면 cluster 곱의 축이 분리된다. 각 host에 자기 port cluster만 생성된다: outbound|3128||squid-path... / outbound|443||tls-path... — subset 불필요.

4. 해결 전략 3안

방법 cross product Pod 변경 적합 조건
1안 Service만 분리 (Pod 공유) 제거됨 없음 대부분의 경우 — 정석
2안 Pod까지 분리 (워크로드 격리) 제거됨 Deployment 분리 격리가 별도로 필요할 때만
3안 수용 + VS destination.port로 정밀 라우팅 존재 (무해화) 없음 규모 작음 · 무장애 우선 초기 단계

1안 — Service(host) 분리, Pod 공유. 포트 스코핑은 Service의 책임이므로 “포트당 하나의 host"로 모델링한다. cluster는 host 단위로 생성되므로 host가 갈라지는 순간 각 host엔 자기 포트만 존재하고, 그룹 구분이 host로 표현되므로 subset 자체가 불필요해진다. 상황 1이면 selector를 그룹별로, 상황 2면 동일 selector로 작성한다 (→ §3). 리소스 추가는 Service 오브젝트뿐이라 무장애 전환과도 충돌 없다.

2안 — Pod까지 분리. cross product 해소만이 목적이면 과잉이다. 선택 기준은 별개의 운영 요구 — 리소스 경합 분리, 장애 반경 축소, HPA 개별 스케일링, 노드 배치(egress 노드 집약) 차이 — 가 있는지다. Squid 경유 TCP 터널 트래픽과 TLS 트래픽의 부하 특성이 크게 다르면 고려한다.

3안 — 수용 + 라우팅 통제. 조합 cluster는 트래픽에 무해하다 — 라우팅되지 않는 cluster는 config로만 존재한다. VS에서 destination.port.number를 항상 명시해 유효한 (port, subset) 조합만 참조하면 된다. 비용은 두 가지: ① proxy 메모리·xDS push 크기 증가(Service·subset 수에 곱으로 비례), ② 사람 실수로 잘못된 조합을 라우팅할 위험 — 이 위험을 설정 리뷰(GitOps PR)로 막을 수 있는지가 채택 기준이다. 포트별 정책 차이는 subset이 아니라 trafficPolicy.portLevelSettings로 표현한다:

trafficPolicy:
  portLevelSettings:
  - port: { number: 3128 }
    tls: { mode: DISABLE }         # CONNECT 평문 터널
  - port: { number: 443 }
    tls: { mode: ISTIO_MUTUAL }
운영 경로 제안

무장애 최우선이면 변경 최소인 3안으로 시작 → SE·경로 수 증가 시점에 1안으로 정리. 판단 기준: “잘못된 (port, subset) 조합 라우팅을 리뷰로 차단 가능한가”. VS를 ArgoCD 한벌 단위로 관리하면 통제 가능한 위험이다.

보조 수단 — Sidecar 리소스의 egress[](host+port 단위 import 제한)로 소비자 proxy에 밀리는 cluster 수를 줄일 수 있다. 단 이는 config push 절감 수단이지 subset-포트 매핑 수단이 아니며 관리 복잡도가 오르므로 1·3안으로 해결 안 될 때만 검토한다.

5. 완전한 예제 — 1안 (상황 2: 동일 selector)

# 같은 Deployment(egress-gw, 3128·443 모두 listen)를 두 Service가 공유
apiVersion: v1
kind: Service
metadata:
  name: squid-path
  namespace: egress
spec:
  selector: { app: egress-gw }         # 동일 selector
  ports:
  - name: tcp-proxy
    port: 3128
    targetPort: 3128
---
apiVersion: v1
kind: Service
metadata:
  name: tls-path
  namespace: egress
spec:
  selector: { app: egress-gw }         # 동일 selector
  ports:
  - name: tls
    port: 443
    targetPort: 8443
---
# DR은 host별로 — subset 없이 정책만
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: squid-path
  namespace: egress
spec:
  host: squid-path.egress.svc.cluster.local
  trafficPolicy:
    tls: { mode: DISABLE }
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: tls-path
  namespace: egress
spec:
  host: tls-path.egress.svc.cluster.local
  trafficPolicy:
    tls: { mode: ISTIO_MUTUAL }

검증:

# 1. host별로 자기 포트 cluster만 존재하는지
istioctl proxy-config cluster deploy/<caller> -n <ns> --fqdn squid-path.egress.svc.cluster.local
# 예상: PORT=3128 행만 존재. 443 행 없음

istioctl proxy-config cluster deploy/<caller> -n <ns> --port 443
# 예상: tls-path만 표시, squid-path 미표시

# 2. 두 host의 endpoint가 같은 Pod IP를 가리키는지 (Pod 공유 확인)
istioctl proxy-config endpoint deploy/<caller> -n <ns> \
  --cluster "outbound|3128||squid-path.egress.svc.cluster.local"
istioctl proxy-config endpoint deploy/<caller> -n <ns> \
  --cluster "outbound|443||tls-path.egress.svc.cluster.local"
# 예상: 동일한 Pod IP 집합, 포트만 3128 / 8443으로 다름

# 3. 정적 검증
istioctl analyze -n egress
# 예상: No validation issues found

6. 실패 케이스 — 의도 밖 조합으로 라우팅 시 503 UF

VS에서 실수로 port: 3128 + 443만 listen하는 Pod의 subset으로 라우팅한 경우의 관측 시그니처:

진단 단계 결과 함정
pc cluster cluster 존재 — 정상으로 보임 NC 아님 — DR 계층 무혐의로 판단하기 쉬움
pc endpoint endpoint HEALTHY — 정상으로 보임 UH 아님 — label 필터는 통과했기 때문
실제 요청 connection refused → 503 UF Pod가 그 포트를 listen하지 않음
kubectl logs deploy/<caller> -c istio-proxy | grep ' 503 '
# "... 503 UF ..." — UpstreamConnectionFailure

# cluster·endpoint가 모두 정상으로 보이면 listen 포트를 실측함
kubectl exec <pod> -n egress -c <app> -- ss -tlnp
# 예상: 443(8443)만 LISTEN, 3128 없음 → 조합 오류 확정
진단 축 확장

Vol.1의 NR(라우트 부재)/NC(cluster 부재)/UH(endpoint 부재)에 더해, **UF는 “설정은 전부 정합한데 실체와 불일치”**하는 구간이다. 설정 계층 순회가 전부 통과하면 마지막으로 워크로드 실체(listen 포트, NetworkPolicy, mTLS 정합)를 실측해야 한다.

7. 판단 플로우

 Q1. 두 subset의 labels가 같은 Pod를 가리키는가?
     (kubectl get pods -l ... 로 실측)
        |
        v
 Q2. 워크로드 격리(리소스·장애 반경·HPA·노드 배치)가
     별도로 필요한가?
        |
   YES  |  NO                     무장애 우선·변경 최소가 우선이면
    +---+---+                              |
    v       v                              v
 [2안]   [1안 — 정석]                  [3안 — 전환 초기]
 Pod까지  Service만 분리               수용 + VS destination.port 필수
 분리     포트당 host, Pod 공유        GitOps 리뷰로 오조합 차단 가능 시
          상황1: selector 각각         규모 증가 시 1안으로 이행
          상황2: selector 동일

어느 경로든 Pod를 새로 띄우는 것은 “cross product 해소” 목적이 아니라 “워크로드 격리” 목적일 때만 정당화된다.

관련 문서

Files