homelab89 Docs Logs Legacy Files ☰ TOC 🌓
guideistio 2026-08-03istioegressexporttogatewayxdsedsscalingoperations

Egress Gateway 리소스 관리 모델 — exportTo 중앙 관리와 목적별 Service 분리

원본은 스타일링된 HTML 설계 문서

이 글의 원문은 비교 다이어그램 SVG 2종(분산 vs 중앙, fat Service vs 목적별 분리)을 포함한 HTML 설계 문서다.

→ 전문 보기 (egress-gateway-resource-management.html)

이 페이지는 같은 내용의 markdown 정리본이다. 선행 문서: Outbound 집약 설계서 (4.2 addresses·4.7 exportTo) · SE 리스너 매칭 재현 시나리오 S1~S5.

0. 요약 — 두 개의 결정

  1. egress 관련 CR(ServiceEntry·VirtualService·DestinationRule·Gateway)은 istio-egress에 한 벌만 둔다. 소비 네임스페이스는 세 객체의 exportTo 리스트에 이름을 추가하는 것으로 연결되며, 그 리스트가 곧 “누가 egress를 쓰는가"의 감사 가능한 계약이 된다.
  2. egress gateway 파드는 하나의 Deployment로 유지하되, Service는 목적 단위로 분리한다. 분리의 기준은 포트 번호가 아니라 노출 범위(소비자 집합)가 다른 목적이다 — 분리와 exportTo 축소가 결합될 때만 Envoy 리소스가 실제로 줄어든다.
한 문장 원리

sidecar가 짊어지는 설정의 양은 (보이는 서비스 × 포트) × 그것이 보이는 sidecar 수의 곱이다. 중앙 관리는 곱의 앞항을 한 벌로 고정하고, exportTo와 목적별 분리는 뒷항(누구에게 보이는가)을 목적별 최소로 줄인다.

1. 배경 — 문제의 정확한 정의

1.1 이 구성에 필요한 객체들

egress 경로는 sidecar → egress gateway(pod) → Squid(:3128, CONNECT 터널) 두 홉이고, 이를 성립시키려면 CR이 최소 네 종 필요하다 — ServiceEntry(Squid 등재와 리스너 매칭), DestinationRule(홉 1 mTLS 발신), Gateway(수신 리스너), VirtualService(두 홉의 배선). 여기에 AuthorizationPolicy(신원 심사)와 gateway용 Service/Deployment까지 더하면 한 목적지를 잇는 데 예닐곱 개의 객체가 움직인다. 문제는 이 세트를 누가, 어디에, 몇 벌 두느냐다.

1.2 개별 CR 반복 생성의 문제 — 구체적으로

각 앱 네임스페이스가 자기 사본을 만드는 분산 모델(네임스페이스 40개 기준)의 실제 사고 형태:

문제 예시 시나리오
드리프트 GSLB VIP가 추가된다. 40개 네임스페이스의 SE 사본 중 37개만 갱신된다 — 남은 3개 네임스페이스만 새 VIP를 받은 파드에서 리스너 미스가 나고, “우리 팀만 간헐적으로 안 돼요” 티켓이 원인 불명으로 떠돈다. 사본이 N개라는 것은 진실이 N개라는 뜻이다
충돌 한 팀이 addresses를 빼먹은 SE를 만든다 — 같은 호스트·포트를 주장하는 정의 충돌(설계서 4.2의 C 상태)로 istiod가 비결정적으로 하나만 반영하고, 다른 팀 트래픽이 엉뚱한 목적지로 오배송된다. 같은 host의 VirtualService 중복도 병합 순서가 보장되지 않는다
변경 증폭 Squid 도메인 변경 1건 = 40개 리포지토리에 40개 PR. 롤백도 40번. 절차가 무거우니 “일부만 먼저"가 되고, 그것이 곧 드리프트의 씨앗이 된다
감사 불가 “egress를 쓰는 네임스페이스 목록"이라는 감사 질문에 답하려면 전 네임스페이스의 CR을 스캔해야 한다. PCI 심사에서 이 목록은 반복적으로 요구된다
RBAC 표면 확대 모든 앱 팀에 networking.istio.io CR 생성 권한이 필요해진다 — 그런데 이 CR들은 기본 exportTo: "*"라 한 팀의 실수(와일드카드 SE 등)가 mesh 전체에 영향을 줄 수 있는 권한이다
xDS 비대 기본값에서 모든 CR은 모든 sidecar에 계산·푸시된다 — 40벌의 사본은 400개 sidecar 각각의 설정에 40벌 몫의 항목을 만든다 (1.3에서 정량화)

1.3 “리소스 포화"의 리소스란 정확히 무엇인가

막연히 “리소스"라 부르는 것의 실체는 istiod가 각 프록시에 xDS로 밀어넣는 Envoy 설정 객체다. 세 종류가 각각 다른 공식으로 증식한다:

Envoy 리소스 무엇인가 per-proxy 증식 공식
CDS (cluster) 보낼 곳의 정의 — outbound|포트||호스트 하나씩 Σ(보이는 서비스 × 그 서비스의 포트 수)
EDS (endpoint) 각 cluster의 실제 주소 목록 포트 수 × 파드 수 (cluster마다 파드 전체가 반복 수록)
LDS (listener) 잡을 문 — 포트·프로토콜 조합별 보이는 포트·프로토콜 조합 수

그리고 이 per-proxy 양에 × 그것이 보이는 프록시 수가 곱해진 것이 istiod의 유지·푸시 비용이다. 변경(파드 롤링, 포트 추가)이 일어날 때마다 이 곱만큼의 갱신이 mesh를 흐른다. 쿠버네티스 쪽에도 대응물이 있다 — Service마다 EndpointSlice 세트가 있고, Service의 어떤 변경(포트 하나 추가)도 그 Service의 모든 슬라이스 갱신 → 모든 워처(kube-proxy·istiod)의 재계산을 유발한다.

예시 — fat Service의 계산

egressgateway 파드 6개에 목적이 뒤섞인 포트 8개(3128, 8888, 443, 5432, 3306, 6379, 9092, 587)를 하나의 Service로 노출하고 mesh의 sidecar 400개 전부가 보는 경우:

per-proxy:  cluster  +8         (포트마다 outbound|PORT||egressgateway…)
            EDS 항목 +48        (8 포트 × 6 파드)
            listener +8
mesh 전체:  istiod가 유지·푸시하는 EDS 항목  = 48 × 400 = 19,200
변경 파급:  gateway 파드 1회 롤링  → 8개 cluster의 EDS 갱신이 400개 프록시 전부에게
            DB 포트 1개 추가       → DB와 무관한 400개 프록시 전원이 Service 갱신을 수신
정직한 명제

여기서 중요한 사실 — Service를 목적별로 나누는 것 “단독"으로는 총량이 줄지 않는다. 포트 8개를 Service 1개에 두든 4개로 나누든, 전부가 모든 네임스페이스에 노출되면 per-proxy cluster는 똑같이 8개다. 감소는 분리가 노출 범위 축소를 가능하게 만들 때 일어난다 — fat Service의 노출은 all-or-nothing이라 축소가 원천 불가능하고, 목적별 Service는 목적별 exportTo를 가질 수 있다는 것. 이것이 §3의 논리 전체다.

2. 모델 1 · exportTo 중앙 관리

2.1 모델 정의

egress 관련 CR 한 벌을 istio-egress에만 두고, 소비는 세 객체(SE·VS·DR)의 exportTo 리스트로 제어한다. 앱 네임스페이스에는 아무 CR도 생기지 않는다 — 연결은 리스트에 이름을 추가하는 PR 하나다.

  Distributed (copy per ns)              Central (istio-egress + exportTo)

+-------------+  +-------------+       +----------------------------------+
| ns:payments |  | ns:settle.. |       | ns: istio-egress                 |
|  SE VS DR   |  |  SE VS DR   |       |  SE VS DR Gateway   (1 set)      |
|  copy v1.3  |  |  copy v1.1  |       |  exportTo: [".", payments, ...]  |
+-------------+  +-------------+       +---------+--------+--------+------+
+-------------+  +-------------+                 |        |        |
| ns:fraud    |  | ns: ... x40 |                 v        v        v
|  SE VS DR   |  |  SE VS DR   |         +----------+ +----------+ +-------+
| no addrs!   |  | 40 truths   |         | payments | | settle.. | | fraud |
+-------------+  +-------------+         +----------+ +----------+ +-------+

 VIP change = 40 PRs                     app ns has no CR
 audit = scan every ns                   join = add name to list (1 PR)
# istio-egress에만 존재하는 한 벌 — 세 객체의 exportTo는 항상 동일 리스트
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata: {name: squid-gw, namespace: istio-egress}
spec:
  hosts: ["squid.gw-zone.example"]
  addresses: ["<SQUID_VIP_1>/32", "<SQUID_VIP_2>/32"]
  ports: [{number: 3128, name: tcp-squid, protocol: TCP}]
  location: MESH_EXTERNAL
  resolution: DNS
  exportTo: [".", "payments", "settlement"]     # "."=gateway 자신(불변 멤버)
# VirtualService·DestinationRule에도 같은 리스트 — kustomize patch로 3객체 동시 강제

2.2 왜 이것이 정석인가 — 공식 근거

이 모델은 관습이 아니라 Istio가 설계한 두 방향의 가시성 통제 중 한쪽을 그대로 쓰는 것이다. 공식 Configuration Scoping 문서의 정의가 정확히 이 구도다:

  • exportTo = 공급자의 통제 — “설정을 특정 워크로드 집합에만 내보내는 메커니즘"이며, Sidecar 리소스(소비자가 자기 의존성을 통제)와 정확히 반대 방향의 쌍으로 명시되어 있다. 우리는 egress라는 공급자측 설정을 다루므로 exportTo가 맞는 축이다.
  • gateway도 exportTo를 존중 — 공식 문서 명시. exportTo 리스트가 gateway 소비(".")와 앱 소비(나열)를 하나의 메커니즘으로 다룰 수 있는 근거다.
  • 일급 설계의 증거 — meshConfig에 defaultServiceExportTo / defaultVirtualServiceExportTo / defaultDestinationRuleExportTo(기본값 "*")가 존재한다. “기본 전체 공개를 뒤집을 수 있는 전역 스위치"가 코어에 있다는 것 자체가, 가시성 축소가 의도된 운영 모델이라는 뜻이다.
  • 스케일 문서의 일관된 권고 — 대규모 mesh 운영 자료들은 설정 전파 범위 축소(exportTo + Sidecar)를 프록시 메모리·푸시 시간 절감의 1순위 수단으로 권고한다 (per-proxy 설정 70~90% 축소 사례 보고).
버전 주의

mesh 전역 기본값(default*ExportTo)을 "."로 뒤집는 강수는 구버전에서 구체 네임스페이스 나열 미지원, delegate VS 병합 등 이슈 이력이 있다. 이 문서의 모델은 전역 기본값은 건드리지 않고 egress 객체의 exportTo만 나열하는 보수적 형태이며, 전역 뒤집기는 mesh 성숙 후 별도 검토 대상이다.

2.3 중앙 vs 분산 — 정직한 비교

중앙 (istio-egress 한 벌 + exportTo) 분산 (네임스페이스별 사본)
장점 진실 1벌(드리프트 원천 차단) · 변경 1 PR · exportTo 리스트 = 감사 문서 · 앱팀에 networking CR 권한 불필요 · 충돌 계급 소멸(같은 host의 SE가 1개) · per-proxy 설정 최소 팀 자율성·속도(플랫폼팀 대기 없음) · 네임스페이스 삭제 시 자동 정리 · 팀별 커스텀(타임아웃 등) 자유
단점 플랫폼팀이 병목(리스트 PR 승인 대기) · 리스트 오타 하나의 파급이 여러 ns · exportTo는 라벨 셀렉터 미지원(명시 나열만)이라 ns 수십 개면 리스트가 길어짐 · 팀별 커스텀 요구는 별도 협의 필요 1.2의 여섯 문제 전부 · 특히 RBAC 표면(기본 “*“인 CR 권한을 전 팀에)과 감사 불가가 규제 환경에서 치명적
완화책 리스트 관리 자동화 — kustomize 변수/생성기로 ns 목록을 한 곳에서 세 객체에 주입, PR 템플릿·CODEOWNERS로 승인 지연 최소화, istioctl analyze CI 게이트 (사실상 없음 — 정책·리뷰로 여섯 문제를 개별 방어해야 함)

규제 환경(PCI)의 결정 가중치는 감사 가능성과 권한 최소화에 있으므로 중앙 모델을 채택한다. 분산의 유일한 실질 장점(팀 속도)은 “리스트 추가 PR"의 승인 SLA로 흡수한다.

2.4 운영 규칙

  • 세 객체 동일 리스트 — SE·VS·DR의 exportTo가 어긋나면 각각 차단 / 조용한 직행 / reset이라는 서로 다른 부분 장애가 된다(설계서 4.7의 매트릭스). kustomize에서 세 객체에 같은 patch를 묶어 구조적으로 강제한다.
  • "."은 불변 멤버 — egressgateway 자신이 SE의 두 번째 소비자(홉 2 클러스터)이기 때문이다. 빠지면 “mTLS 성공 직후 끊김” 시그니처.
  • 확대·롤백의 의미 변화 — 노드 ACL 제거 전에는 리스트 제외 = 원래 경로로 안전 복귀, 제거 후에는 = 그 네임스페이스 외부 통신 중단. 확대의 비가역점이 ACL 제거와 묶여 있음을 리스트 PR 템플릿에 명시한다.

3. 모델 2 · 목적별 Service 분리 — 포화 예방

3.1 원리 — Service는 selector일 뿐이다

쿠버네티스 Service는 파드의 소유물이 아니라 파드를 가리키는 독립 객체다. 같은 selector를 가진 Service를 몇 개 만들든 전부 같은 egressgateway 파드들을 가리키고, 파드는 아무것도 모른다. 새 목적의 수신 포트가 필요하면 Gateway CR에 server를 추가하는데, 이는 xDS로 gateway Envoy에 핫 반영된다(리스너 추가에 재시작 불요, Deployment의 containerPort 선언은 정보성이라 무변경). 즉 파드 재생성 없이 Service와 Gateway server만으로 목적을 증설할 수 있고, 이것이 이 모델의 전제다.

3.2 분리 기준 — 포트가 아니라 “노출 범위가 다른 목적”

1.3의 정직한 명제를 다시 소환하면: 분리 단독으로는 총량이 안 줄고, 분리가 목적별 exportTo를 가능하게 만들 때 줄어든다. 따라서 분리 단위의 판정 기준은 하나다 — 이 포트들의 소비자 집합이 서로 다른가? 소비자가 같은 포트들은 한 Service에 묶어도 손해가 없고, 소비자가 다른 포트를 한 Service에 묶는 순간 좁은 쪽이 넓은 쪽의 노출을 강제로 물려받는다. 쿠버네티스 Service의 노출은 Istio 주석 networking.istio.io/exportTo로 제어한다(Service에는 spec 필드가 없음).

목적 (Service) 포트 소비자 집합 노출 (exportTo)
egressgw-squid 3128 외부 API를 쓰는 전 앱 ns 앱 ns 40개 (→ 최종 “*”)
egressgw-squid-alt 8888 테스트·특수 정책 경로 2개 ns 2개 ns만
egressgw-tls-direct 443 직결 허용 대상(파트너 mTLS 등) 6개 ns 6개 ns만
egressgw-db 5432, 3306 외부 DB 연동 4개 ns 4개 ns만 — 두 포트는 소비자가 같아 한 Service
  fat Service (all-or-nothing)          purpose-split (same pods)

      egressgw pods x6                       egressgw pods x6
            |                             |          |          |
            v                             v          v          v
+------------------------+       +-----------+ +----------+ +--------------+
| Service x1 - 8 ports   |       |squid :3128| | alt :8888| |db :5432/3306 |
| 3128 8888 443 5432 ... |       | -> 40 ns  | | -> 2 ns  | |  -> 4 ns     |
+------------------------+       +-----------+ +----------+ +--------------+
            |                                        |
            v                                        v
 all 400 sidecars receive              most sidecars receive
 cluster 8 / EDS 48 each               cluster 1 / EDS 6 only
 add DB port -> push to 400            add DB port -> push to 40

3.3 구성 예시

# 같은 selector — 전부 동일한 egressgateway 파드를 가리킴
apiVersion: v1
kind: Service
metadata:
  name: egressgw-squid
  namespace: istio-egress
  annotations:
    networking.istio.io/exportTo: "payments,settlement,fraud"   # Service의 노출 제어는 주석
spec:
  selector: {istio: egressgateway}
  ports:
  - {name: tls-squid, port: 7777, targetPort: 7777}
---
apiVersion: v1
kind: Service
metadata:
  name: egressgw-db
  namespace: istio-egress
  annotations:
    networking.istio.io/exportTo: "billing,ledger,dw,ml-batch"
spec:
  selector: {istio: egressgateway}                              # 같은 파드
  ports:
  - {name: tls-db-pg, port: 7791, targetPort: 7791}
  - {name: tls-db-my, port: 7792, targetPort: 7792}
# Gateway CR에 목적별 server(7777/7791/7792)를 추가 — gateway Envoy에 핫 반영
# 각 목적의 SE·VS·DR exportTo 리스트는 해당 Service 주석과 동일하게 유지
정합 규칙 확장

2.4의 “세 객체 동일 리스트"가 목적 단위로 확장된다 — 목적 하나 = {Service 주석, SE·VS·DR exportTo} 네 곳의 동일 리스트. kustomize에서 목적별 디렉토리에 ns 목록 변수 하나를 두고 네 곳에 주입하는 구조를 권장한다.

3.4 효과 — 같은 예시의 재계산

지표 fat Service (before) 목적별 + exportTo (after)
대다수 sidecar의 egress 몫 (cluster / EDS) 8 / 48 1 / 6
istiod 유지 EDS 항목 (egress 몫, mesh 합계) 19,200 ≈ 3,400 (−82%)
DB 포트 추가 시 갱신 대상 프록시 400 (전원) 40 (DB 소비 ns만)
gateway 파드 롤링 시 EDS 갱신 파급 8 cluster × 400 프록시 목적별 cluster × 해당 소비자만

수치는 예시 규모(sidecar 400 · 파드 6 · 포트 8)의 산술이며, 요점은 절대값이 아니라 변경 파급의 분모가 “전원"에서 “해당 목적의 소비자"로 바뀐다는 구조다.

3.5 한계와 주의

  • 과분리 경계 — Service도 공짜가 아니다(목적마다 EndpointSlice 세트, 소비자 sidecar의 cluster). 소비자 집합이 같은 포트를 쪼개는 것은 객체 수만 늘린다. 기준은 언제나 “노출 범위가 다른가"다.
  • gateway 파드 자신의 설정은 별개 축 — 공식 문서가 명시하듯 gateway 프록시에는 cluster 설정 일부가 참조 여부와 무관하게 전 범위로 전송된다. 이 문서의 절감 대상은 앱 sidecar들이며, gateway 파드의 config 크기는 Sidecar 리소스가 아니라 gateway 전용 최적화의 영역이다. (SE와 Envoy 반영 범위의 gateway CDS 조건부 push 참조)
  • 포트 이름 규약 — Service 포트 이름의 프로토콜 접두(tcp-, tls-)가 Istio의 프로토콜 판정 입력이다. 목적별 Service는 이 명명을 목적 단위로 명확하게 만들어 주는 부수 효과가 있다.
  • 보안 통제가 아님 — exportTo(주석 포함)는 가시성이며, 실제 차단은 AuthorizationPolicy(to.ports 목적별)·Calico·방화벽의 일이다. 목적별 포트는 그 정책들의 좌표가 되어 준다.

4. 도입 절차와 측정

  1. 현재 상태 측정 (기준선) — 대표 sidecar에서:

    istioctl proxy-config clusters <pod> | wc -l
    istioctl proxy-config listeners <pod> | wc -l
    kubectl exec <pod> -c istio-proxy -- pilot-agent request GET config_dump | wc -c   # config 크기
    istioctl proxy-status | head    # 푸시 동기화 상태
    
  2. istio-egress에 CR 한 벌 정리 — 기존 분산 사본이 있다면 중앙 벌 생성 → exportTo에 해당 ns 추가 → 사본 삭제 순으로 네임스페이스별 이관 (동시 존재 구간에서 같은 host 중복이 생기므로 istioctl analyze로 충돌 감시, 이관은 ns 단위로 짧게).

  3. 목적별 Service·Gateway server 구성 — 기존 fat Service가 있다면 새 목적 Service를 병행 생성 → 소비자를 VS destination 전환 → fat Service 축소 (파드 무변경이므로 트래픽 전환은 라우팅 객체만의 일).

  4. 사후 측정 — 1의 커맨드 재실행으로 per-proxy cluster/listener 수·config 크기 감소를 기록. istioctl proxy-config endpoints <pod> | grep egressgw로 목적 몫만 남았는지 확인.

  5. CI 게이트 — exportTo 네 곳 동일성 검사 스크립트 + istioctl analyze를 리스트 변경 PR의 필수 체크로.

5. 참고 자료

한계 고지

3.4의 수치는 예시 규모의 산술 모델이며 실측이 아니다. §4의 기준선 측정 커맨드로 실제 감소율을 기록해 이 문서를 갱신하는 것을 도입 완료 조건에 포함할 것.

관련 문서

Files