🏠 목록 Sidecar 리소스는 '좁은 범위가 넓은 범위를 통째로 이기는' override 계층으로, 사이드카에 푸시할 설정을 좁혀 성능과 egress 거버넌스를 동시에 얻는다 📄 MD 원본 🌓 테마
istiosidecaregressperformance

Sidecar 리소스는 '좁은 범위가 넓은 범위를 통째로 이기는' override 계층으로, 사이드카에 푸시할 설정을 좁혀 성능과 egress 거버넌스를 동시에 얻는다

NOTE

Istio의 기본 가정은 "메시 안 모든 워크로드가 다른 모든 워크로드에 접근 가능"이다. 그래서 istiod는 각 Envoy에 메시 전체 설정을 푸시하고, 규모가 커지면 이것이 메모리·xDS push·istiod CPU를 동시에 압박한다. Sidecar 리소스는 두 개의 서로 다른 레버로 이를 푼다 — egress.hosts(이 워크로드가 알아야 할 설정의 범위를 축소 → 성능)와 outboundTrafficPolicy(레지스트리 밖 트래픽의 차단 정책 → 거버넌스). 이 문서가 세우려는 단 하나의 멘탈모델: 세 scope(Mesh / Namespace / Workload)는 merge가 아니라 가장 좁은 하나가 통째로 이기는 override 의미론이다. 운영 detail·YAML 전문은 Sidecar scope 운영 가이드 참조.

1. 배경 — Istio의 "모두가 모두를 안다"는 기본 가정과 그 비용

Istio를 쓰는 순간 service registry(Service / ServiceEntry로 채워지는 메시의 주소록)에 등록된 모든 서비스가 그 워크로드의 잠재적 upstream으로 취급된다. 이건 편의성의 산물이다 — 개발자가 아무 서비스나 이름으로 부르면 곧장 라우팅되니까. 대가는 데이터 평면 쪽 Envoy 하나하나가 메시 전체의 cluster/listener/route/endpoint를 통째로 들고 있어야 한다는 것이다.

규모가 작을 땐 안 보이지만, 워크로드가 N개로 늘면 사이드카 하나가 보유할 설정은 대략 O(N)으로 커지고, push 비용은 변경 빈도와 곱해져 더 빠르게 나빠진다. 구체적으로 세 곳을 동시에 누른다.

여기서 던질 질문은 "왜 Envoy는 자기가 부르지도 않는 서비스의 설정까지 받아야 하나?"이다. 받을 이유가 없다 — istiod가 그 워크로드의 실제 의존 대상을 알 길이 없어서 보수적으로 전부 보낼 뿐이다. Sidecar 리소스는 바로 그 정보를 운영자가 명시적으로 주입하는 통로다. "이 워크로드가 실제로 호출하는 대상만 알면 된다"고 선언해 위 곱셈을 끊는다. 이는 control-plane 성능 튜닝의 1순위 권고이며, 동일 맥락의 다른 요인(스코핑 외의 push 빈도·proxy 수 등)은 control plane 성능 요인에서 다룬다.

No Sidecar (default)istiodEnvoy AEnvoy BEnvoy Cfull meshWith Sidecar egress.hostsistiodEnvoy AEnvoy Bonly A depsonly B deps
그림 1. Sidecar 리소스가 없으면 istiod가 모든 Envoy에 전체 메시 설정을 push. egress.hosts로 스코프를 좁히면 각 워크로드는 자기 의존 대상만 받음 → 설정 크기·push 부하 급감.

전제 개념 정리: registry(메시가 아는 목적지 목록), xDS(istiod가 Envoy에 설정을 밀어 넣는 푸시 프로토콜 — CDS/LDS/RDS/EDS), PassthroughCluster / BlackHoleCluster(registry에 매칭 안 되는 outbound를 각각 "통과" / "차단"으로 처리하는 두 합성 cluster). 이 세 가지가 아래 메커니즘의 부품이다.

2. 핵심 멘탈모델 — Sidecar는 'override 계층'이고, 그 위에 두 개의 독립 레버가 얹힌다

머릿속에 그릴 단 하나의 그림은 이것이다.

Sidecar는 "이 Pod가 받을 설정"을 정하는 override 계층이다. 어느 Pod에든 적용되는 Sidecar는 항상 정확히 하나뿐이며 — 가장 좁은 범위가 이긴다 — 그 하나가 egress.hosts(무엇을 알게 할지=범위)와 outboundTrafficPolicy(모르는 곳을 어떻게 처리할지=차단)라는 서로 독립된 두 손잡이를 함께 든다.

이 한 문장에서 나머지가 전부 따라 나온다. 좁은 범위가 넓은 범위에 "합쳐지는" 게 아니라 통째로 갈아치운다는 것, 그리고 범위(성능)와 차단(거버넌스)이 같은 리소스 안의 다른 손잡이라는 것 — 이 둘이 거의 모든 오해의 진원지다.

2-1. 두 개의 레버: egress.hosts vs outboundTrafficPolicy

가장 흔한 오해는 "egress.hosts에 안 적으면 차단된다"는 것이다. 아니다. 둘은 독립된 레버이고, 답하는 질문 자체가 다르다.

레버 답하는 질문 빼면 일어나는 일
egress.hosts "이 사이드카가 무엇을 알게 할까?" (푸시할 cluster/listener의 범위) 범위 축소 안 됨 = 메시 전체 설정 유지(성능 이득 없음). 차단과는 무관
outboundTrafficPolicy.mode "registry에 없는 호스트로 가는 트래픽을 어떻게 처리할까?" 기본값 ALLOW_ANY → 미등록 외부 호출이 PassthroughCluster로 그냥 통과

왜 굳이 둘을 쪼갰나? "범위를 줄이는 일"과 "모르는 목적지를 막는 일"은 본질적으로 다른 결정이기 때문이다. 설정을 가볍게 하고 싶다고 해서 반드시 외부를 차단하고 싶은 건 아니다(그 반대도 마찬가지). 그래서 egress.hosts만 좁히고 outboundTrafficPolicy를 생략하면, 설정은 가벼워져도 "기본 deny" 거버넌스는 생기지 않는다. zero-trust egress("등록 안 된 외부는 막는다")를 원하면 반드시 mode: REGISTRY_ONLY를 함께 둬야 한다.

미등록 목적지의 처리는 결국 어느 합성 cluster로 보내느냐로 갈린다.

outbound request registry에 host 존재? matched cluster → upstream PassthroughCluster 통과 (ALLOW_ANY) BlackHoleCluster 502 차단 (REGISTRY_ONLY) yes no, ALLOW_ANY no, REGISTRY_ONLY
그림 3. outbound 해소 분기. registry(=Sidecar scope로 좁혀진 cluster 목록)에 host가 있으면 정상 라우팅, 없으면 outboundTrafficPolicy에 따라 ALLOW_ANY면 Passthrough로 통과, REGISTRY_ONLY면 BlackHole로 502 차단된다.

2-2. 세 scope와 override 우선순위 — 왜 'merge 아님'이 함정인가

Sidecar는 적용 범위가 셋이며, 좁은 범위 하나가 넓은 범위를 통째로 덮어쓴다(병합이 아니다).

범위 우선순위 적용 대상 판정 키
Mesh-wide ① 가장 낮음 메시 안 모든 Pod rootNamespace + name: default + selector 없음
Namespace-wide 해당 NS 모든 Pod 대상 NS + workloadSelector 없음
Workload-specific ③ 가장 높음 selector 일치 Pod workloadSelector.labels 지정

override 의미론이 왜 위험한가: workload-specific Sidecar가 붙은 Pod에는 NS-wide나 mesh-wide가 추가로 합쳐지지 않는다. 그 Pod에는 가장 좁은 정책 하나만 유효하다. 직관적으로는 "더 구체적인 규칙이 일반 규칙 위에 얹힌다(덮어쓴다)"고 기대하기 쉬운데, 여기선 일반 규칙이 통째로 사라진다. 따라서 workload Sidecar를 쓸 때는 그 워크로드가 필요로 하는 egress를 빠짐없이 다시 나열해야 한다(istio-system 모니터링·제어 트래픽 포함). 빠뜨리면 "갑자기 어떤 호출만 502" 류 장애가 난다 — 이게 실무에서 가장 자주 밟는 지뢰다.

Mesh-widerootNs/defaultNamespace-widens/defaultWorkloadselectortarget Podoverrideoverride이것만 적용
그림 3. Sidecar 적용 우선순위: Mesh-wide < Namespace-wide < Workload-specific(workloadSelector). selector가 매칭되는 Pod엔 그 Sidecar 하나만 적용되고 상위는 merge되지 않음.

2-3. Mesh-wide 판정 조건의 정확한 근거

"mesh-wide는 istio-system에 두면 된다"는 흔한 요약은 부정확하다. 실제 판정 조건은 세 가지 동시 충족이다.

  1. metadata.namespacemeshConfig.rootNamespace(기본값 istio-system)와 일치
  2. metadata.name: default
  3. workloadSelector 없음

파일명은 무관하다. 결정하는 것은 리소스의 name/namespace/selector 조합이다. rootNamespace를 별도로 지정한 클러스터라면 istio-system이 아니라 그 NS에 둬야 mesh-wide로 동작한다. 왜 이렇게 빡빡하게 정의했나 — mesh-wide는 메시 전체의 기본값을 갈아치우는 가장 강력한 override라서, 우연히 만들어지면 안 되기 때문이다. 그래서 "특정 이름 + 특정 NS + selector 없음"이라는 명시적 삼중 조건을 요구한다.

2-4. ServiceEntry와의 분업 — '등록'과 '노출'은 다른 일

egress.hosts"이미 registry에 있는 것 중 무엇을 이 사이드카가 알게 할지" 필터일 뿐이다. 외부 도메인(예: Stripe API)을 메시에 등록하는 것은 ServiceEntry의 역할이다. 따라서 외부 호출을 허용하려면 두 단계가 필요하다.

  1. ServiceEntry로 외부 host를 registry에 등록 (*.example.com 또는 <ns>/<name> 포맷)
  2. 해당 워크로드 Sidecaregress.hosts에 그 host(또는 NS)를 포함

REGISTRY_ONLY에서 ServiceEntry 없이 egress.hosts에만 외부 도메인을 적으면 매칭되는 cluster가 없어 여전히 차단된다. 등록(registry에 존재하게 함)과 노출(이 사이드카가 그걸 알게 함)을 분리해서 생각하는 것이 핵심이다 — 둘 다 통과해야 트래픽이 흐른다.

2-5. 성능 경로: 좁은 scope → 작은 xDS → 적은 메모리

좁은 egress.hosts는 istiod가 그 워크로드용으로 계산하는 CDS(cluster)/LDS(listener)/RDS(route)/EDS(endpoint) 집합을 줄인다. 결과적으로:

sync 상태가 실제로 줄었는지는 proxy 동기화 진단으로 확인한다 — SYNCED/NOT SENT/STALE의 의미와 xDS 타입별 보고는 data-plane sync state 참조. (참고: outboundTrafficPolicy로 만든 연결 거버넌스와, DestinationRule 기반의 connection pool·outlier detection으로 만드는 장애 격리는 별개 레버다 — 후자는 circuit breaking 메커니즘.)

3. 떴는지 확인 — mesh-wide zero-trust 예시와 검증

위 멘탈모델을 가장 작은 실물로 굳히는 예시: mesh-wide default Sidecar로 (a) 범위를 istio-system/*로 좁히고 (b) 미등록 외부를 차단한다. 두 손잡이를 한 리소스에서 동시에 쓰는 정석 형태다.

apiVersion: networking.istio.io/v1
kind: Sidecar
metadata:
  name: default              # mesh-wide 필수 조건
  namespace: istio-system    # = meshConfig.rootNamespace
spec:
  egress:
    - hosts:
        - "istio-system/*"   # 모니터링·제어 트래픽만 알림
  outboundTrafficPolicy:
    mode: REGISTRY_ONLY      # 미등록 외부 차단 (zero-trust 기본값)

줄별로 왜: name: default + namespace: istio-system(=rootNamespace) + selector 없음 → §2-3의 삼중 조건을 채워 mesh-wide로 인식된다. egress.hosts: ["istio-system/*"]은 모든 사이드카가 기본적으로 istio-system만 알게 좁힌다(성능 손잡이). mode: REGISTRY_ONLY는 그 외 미등록 목적지를 BlackHole로 보내 차단한다(거버넌스 손잡이). 둘이 함께 있어야 "가볍고 + 막힌" 상태가 된다.

검증:

$ istioctl proxy-config clusters <pod>.<ns> | grep -c outbound
# Sidecar 적용 후 cluster 수가 메시 전체 → 의존 대상만으로 급감하면 OK

$ kubectl exec <pod> -c istio-proxy -- curl -s -o /dev/null -w "%{http_code}\n" http://example.com
502                          # REGISTRY_ONLY + 미등록 호스트 → BlackHole

첫 명령의 cluster 수 급감이 §2-5 성능 경로의 직접 증거이고, 둘째 명령의 502§2-1 차단 레버의 직접 증거다. NS·workload 단위 예시와 ./ 접두사(현재 NS) 의미, 실무 체크리스트는 Sidecar scope 운영 가이드로 위임한다.

핵심 정리

What you might be missing