homelab89 Docs Logs Legacy Files ☰ TOC 🌓
guideistio 2026-07-03istiodestinationruleconnectionpool

DestinationRule 만들기 — 기초부터 심화까지

ABSTRACT

DR은 “어디로 보낼지”(VS의 일)가 아니라 **“도착이 결정된 목적지와 어떻게 통신할지”**를 정하는 리소스다. host 문자열이 서비스 레지스트리와 매칭되어 Envoy cluster에 컴파일되는 전 과정, trafficPolicy 필드가 cluster의 어느 칸으로 흩어지는지, 레벨 3층(top/subset/port)의 병합 규칙(“통째 교체”), 같은 host 다중 DR의 조용한 병합, 그리고 만들었으면 반드시 도는 검증 3단계까지. 모든 예시는 홈랩 실측(2026-07-03, mesh-test namespace)이다.

선행 문서: Egress 4-CRD 직관 — DR이 4-CRD 중 어느 질문을 담당하는지.


01. DR의 자리 — 세 리소스의 분업

host라는 단어가 세 리소스에 다 나오지만 역할이 전부 다르다:

 Service / ServiceEntry hosts    "이 목적지가 존재한다"      -> istiod가 cluster 생성
 DestinationRule host            "그 cluster에 정책 부착"    -> pool/LB/TLS가 cluster에 컴파일
 VirtualService hosts+dest       "트래픽을 그 cluster로"     -> 어느 cluster를 탈지 결정

istiod가 만드는 cluster의 이름이 이 구조를 그대로 담는다:

 outbound | 443 | httpbin | istio-egressgateway.istio-system.svc.cluster.local
 (방향)    (포트)  (subset)   (레지스트리에 등록된 host — DR.host의 매칭 대상)

DR은 VS 없이도 동작한다 — 레지스트리 host와 매칭만 되면 cluster에 정책이 박힌다. 반대로 DR 없이도 트래픽은 흐른다 — 기본 cluster(무제한 풀, keepalive off, plaintext 그대로)로. DR은 “흐르게 하는” 리소스가 아니라 “흐르는 방식을 통제하는” 리소스다.


02. 기초 — 최소 DR과 host 매칭 규칙

최소 형태

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: { name: httpbin-ext, namespace: mesh-test }
spec:
  host: httpbin.org            # <- 이 문자열이 전부를 결정한다
  trafficPolicy:
    connectionPool:
      tcp: { maxConnections: 100 }

host 매칭 규칙 — “명확해야 한다"의 정확한 의미

DR의 host서비스 레지스트리(k8s Service / ServiceEntry)에 등록된 host 문자열과 매칭된다. VS와 매칭되는 것이 아니다.

대상 레지스트리 등록 형태 DR host에 적을 것
k8s Service <svc>.<ns>.svc.cluster.local (자동) FQDN 전체 권장
ServiceEntry spec.hosts의 문자열 그대로 그 문자열과 정확 일치 (또는 *.example.com 접두 wildcard)
WARNING

매칭 실패는 조용하다. DR은 선언적 정책이라 대상 host가 “나중에 생길 수도” 있으므로, istiod는 매칭 안 되는 DR을 에러로 취급하지 않는다. 오타 하나로 정책 전체가 소리 없이 무시되고, 증상은 “설정했는데 적용 안 됨"으로만 나타난다. 그래서 §06의 검증이 필수다.

short-name 확장 함정: host: istio-egressgateway처럼 짧게 적으면 DR이 있는 namespace 기준으로 확장된다. DR이 mesh-test에 있으면 istio-egressgateway.mesh-test.svc.cluster.local로 풀리는데, 실제 gateway는 istio-system에 있으므로 존재하지 않는 host가 되어 조용히 무시된다. 항상 FQDN 전체를 적는 것이 규칙이다.

만들었으면 바로 확인 — 결부(attribution) 검증

istioctl proxy-config cluster deploy/sleep -n mesh-test --fqdn httpbin.org -o json | \
  jq '.[] | {name, dr: .metadata.filterMetadata.istio.config}'
# "dr": ".../namespaces/mesh-test/destination-rule/httpbin-ext"  <- 결부 성공
# "dr": null                                                     <- host 매칭 실패

03. trafficPolicy 해부 — 필드 4+1과 컴파일 위치

trafficPolicy의 최상위 필드는 사실상 4+1개이고, 각각 cluster JSON의 다른 칸으로 컴파일된다. 이 매핑을 모르면 검증에서 “한 칸만 보고 통과” 판정을 내리는 사고가 난다.

필드 제어 대상 cluster JSON 위치
connectionPool 연결 풀 한도·타임아웃·keepalive 한도 4종→circuitBreakers.thresholds, keepalive→upstreamConnectionOptions, connectTimeout→connectTimeout, HTTP 공통→typedExtensionProtocolOptions[...].commonHttpProtocolOptions
loadBalancer 엔드포인트 선택 알고리즘 lbPolicy (+ 관련 config)
outlierDetection 이상 엔드포인트 자동 격리 outlierDetection
tls 이 목적지로 나갈 때의 TLS 모드/SNI transportSocket
portLevelSettings 위 4개를 포트 단위로 override 해당 포트 cluster에만

각 필드의 깊은 내용은 전담 문서로: connectionPool 값 도출은 Egress TCP 처방전, tcpKeepalive 3필드는 keepalive 필드 노트, 컴파일 위치 전체는 cluster 해부 정본.

한 가지만 여기서: outlierDetection은 단일 IP 외부 목적지에 켜면 ejection = 전체 차단이다. DNS가 다중 IP를 줄 때만 검토한다.


04. subsets — 이름·labels·라우팅의 3연결

subset은 “한 host의 엔드포인트를 나눠서 각각 다른 정책을 주는” 장치다:

spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn                        # ① VS destination.subset이 참조하는 이름
    # labels: { version: v2 }       # ② 있으면 그 label의 pod만, 없으면 전체 엔드포인트
    trafficPolicy: { ... }           # ③ 이 subset cluster에만 적용되는 정책
  • labels가 없는 subset도 유효하다 — 엔드포인트는 전체 그대로 두고 정책·SNI 앵커로만 쓰는 패턴. egress gateway의 채널별 subset(cnn/httpbin)이 정확히 이것이다: pod는 같은 gateway지만 subset마다 다른 SNI·풀 한도를 준다.
  • subset이 정의되면 istiod는 VS가 참조하든 안 하든 subset cluster를 만든다 (outbound|443|cnn|...). 존재는 결부의 증거일 뿐, 사용의 증거가 아니다 — 사용 여부는 VS의 destination.subset과 stats로 확인(§06).
  • canary/버전 라우팅에서는 labels가 엔드포인트를 실제로 가른다: subset: v1 + labels: {version: v1}.

05. 병합 규칙 (심화 핵심) — 레벨 3층, “최상위 필드 단위 통째 교체”

DR 정책은 세 레벨에 적을 수 있고, 병합은 deep-merge가 아니다:

 top-level trafficPolicy
      | subset에 같은 "최상위 필드"가 있으면 -> 그 필드는 통째로 subset 것으로 교체
      v
 subset trafficPolicy
      | portLevelSettings에 그 포트 엔트리가 있으면 -> 또 한 번 통째 교체
      v
 portLevelSettings (그 포트의 cluster에 최종 적용)

교체 단위는 connectionPool·loadBalancer·outlierDetection·tls 각각이다. subset에 connectionPool.tcp.maxConnections 하나만 적어도 상위의 tcpKeepalive는 그 subset cluster에서 사라진다.

DANGER

실측으로 확인된 2단 함정 (2026-07-03): subset에 connectionPool을 넣고 같은 subset의 portLevelSettingstls만 있는 엔트리(port 15443)를 두면 — 그 포트의 cluster에서는 subset-level connectionPool까지 무시된다. 실측: outbound|443|cnn|...엔 max=100이 박혔는데 정작 VS가 라우팅하는 outbound|15443|cnn|...만 4294967295(기본값)로 남았다. 처방: 트래픽이 실제 타는 포트의 portLevelSettings 엔트리에 필요한 필드를 전부 재기재.

  subsets:
  - name: cnn
    trafficPolicy:
      connectionPool: { tcp: { maxConnections: 100, tcpKeepalive: { time: 300s, interval: 30s, probes: 3 } } }
      portLevelSettings:
      - port: { number: 15443 }
        tls: { mode: ISTIO_MUTUAL, sni: edition.cnn.com }
        connectionPool:              # <- 이 재기재가 없으면 15443 cluster는 기본값
          tcp: { maxConnections: 100, tcpKeepalive: { time: 300s, interval: 30s, probes: 3 } }

meshConfig는 이 병합의 바깥에 있다meshConfig.tcpKeepalive 같은 전역 기본값은 istiod가 DR 병합과 별개 단계에서 cluster에 입히므로, subset 통째-교체에 휩쓸리지 않는다. 전역 기본 + 채널별 DR override의 3층 분업은 keepalive 노트 §05 참조.


06. 같은 host에 DR 여러 벌 — 조용한 병합

한 host에 DR을 여러 벌 만들면 에러가 아니라 병합된다 (실측: egressgateway-cnn + egressgateway-httpbin, 같은 host):

  • subsets는 합집합 — 두 DR의 subset이 모두 cluster로 생성된다.
  • top-level trafficPolicy와 결부 귀속은 최고참(생성시각 가장 오래된 DR)이 승자 — ①의 dr 필드가 모든 cluster에서 오래된 DR만 가리키고, 나중 DR의 top-level 정책은 조용히 무시된다.
  • 같은 이름의 subset이 겹치면 최고참 것만 남는다.

당장은 동작해도, 나중에 어느 한쪽 top-level에 정책을 넣는 순간 “누가 이기는지"가 생성시각이라는 보이지 않는 축으로 결정된다. host당 DR 1벌 + 채널당 subset 1개로 통합하는 것이 원칙이다 (egress 레이어 2 설계가 이 형태다 — 4-CRD 직관).

스코프 규칙 요약 (client 관점에서 어느 DR이 선택되나): 클라이언트 namespace의 DR > 서비스(대상) namespace의 DR > mesh root namespace(istio-system)의 DR. exportTo로 가시성을 좁힐 수 있고, 클라이언트 namespace에 Sidecar 리소스가 있으면 host가 그 egress 스코프 안에 있어야 cluster 자체가 존재한다 (Sidecar scope).


07. tls 블록 — 나가는 연결의 4가지 모드

DR의 tls이 클라이언트가 그 목적지로 나갈 때 어떤 TLS를 입힐지다 (수신 측 설정 아님):

mode 의미 대표 용도
DISABLE 평문 내부 평문 통신
SIMPLE 표준 TLS origination 앱은 HTTP로 보내고 프록시가 HTTPS로 승격
MUTUAL 사용자 인증서 mTLS 외부 파트너가 client cert를 요구할 때
ISTIO_MUTUAL mesh 인증서 mTLS + SPIFFE 신원 sidecar→gateway 구간 (레이어 2 subset)

ISTIO_MUTUAL + sni가 egress 레이어 2의 심장이다: SNI가 gateway의 filter chain 매칭 키가 되어 “어느 채널인가"를 식별한다. 종단 시 SNI가 소비되는 제약은 HTTPS over mTLS 해부.


08. 검증 — 만들었으면 반드시 이 순서로

“적용 안 됨"의 원인 우선순위와 검증 절차의 정본은 keepalive 노트 §04이고, 여기선 순서만:

# 0) 값이 클러스터에 저장은 됐나 (apply 누락·다른 컨텍스트가 0순위 원인)
kubectl config current-context && kubectl get dr <name> -n <ns> -o yaml

# 1) cluster 결부 + 값 (default와 subset을 나란히)
istioctl proxy-config cluster <POD> -n <NS> --fqdn <HOST> -o json | \
  jq '.[] | {name, dr: .metadata.filterMetadata.istio.config,
             max: .circuitBreakers.thresholds[0].maxConnections,
             ka: .upstreamConnectionOptions.tcpKeepalive}'

# 1b) 트래픽이 그 cluster를 타나 — sidecar 기본 stats가 트리밍된 환경에선 /clusters 사용
kubectl exec <POD> -c istio-proxy -- pilot-agent request GET clusters | \
  grep "outbound|443|<SUBSET>|" | grep -E "cx_total|cx_active"

# 2) (keepalive라면) 소켓 레벨
kubectl exec <POD> -c istio-proxy -- ss -tno state established
#   -> timer:(keepalive,4min55sec,0)  # time=300s 카운트다운 (실측값)
TIP

stats 트리밍 주의 (실측): Istio 기본 proxyStatsMatcher는 cluster별 Envoy stats를 수집하지 않는 경우가 있다(pilot-agent request GET stats에 xds-grpc만 보임). 이때 GET clusters 엔드포인트는 트리밍과 무관하게 cluster·엔드포인트별 cx_total/cx_active를 항상 보여준다 — ①-b의 견고한 대안.


09. 함정 모음 — 이 아카이브에서 실제로 밟은 것들

함정 증상 처방
apply 누락 / 다른 kubeconfig 컨텍스트 모든 값 기본(4294967295), dr은 정상일 수도 검증 0단계부터
host 오타·short-name 확장 dr: null, 조용한 무시 FQDN 전체 기재
subset 통째 교체 subset cluster만 일부 필드 소실 subset에 전부 재기재
portLevelSettings 2단 교체 트래픽이 타는 포트만 기본값 그 포트 엔트리에 재기재
같은 host DR 다중 나중 DR의 top-level이 무시 host당 1벌로 통합
단일 IP에 outlierDetection ejection = 전체 차단 다중 IP일 때만
기존 연결 비소급 일부 소켓만 옛 설정 수명 상한으로 자연 교체 or rollout restart

핵심 정리

항목 내용
DR의 정체 라우팅이 아니라 “목적지와의 통신 방식”(풀·LB·격리·TLS)을 cluster에 컴파일하는 리소스
host 규칙 레지스트리(Service/SE) 문자열과 매칭. 실패는 조용함 → FQDN 전체 + 결부 검증 필수
병합 규칙 top→subset→port 3층, 최상위 필드 단위 통째 교체. 재기재가 원칙, meshConfig만 병합 밖
다중 DR subsets 합집합, top-level은 최고참 승 → host당 1벌
검증 0) 저장 확인 → 1) dr+값 → 1b) /clusters 사용 확인 → 2) 소켓(ss)

참조

아카이브 내부

외부

작업 파일 (다운로드)

Files