Egress Gateway 리소스 관리 모델 — exportTo 중앙 관리와 목적별 Service 분리
이 글의 원문은 비교 다이어그램 SVG 2종(분산 vs 중앙, fat Service vs 목적별 분리)을 포함한 HTML 설계 문서다.
→ 전문 보기 (egress-gateway-resource-management.html)
이 페이지는 같은 내용의 markdown 정리본이다. 선행 문서: Outbound 집약 설계서 (4.2 addresses·4.7 exportTo) · SE 리스너 매칭 재현 시나리오 S1~S5.
0. 요약 — 두 개의 결정
- egress 관련 CR(ServiceEntry·VirtualService·DestinationRule·Gateway)은
istio-egress에 한 벌만 둔다. 소비 네임스페이스는 세 객체의exportTo리스트에 이름을 추가하는 것으로 연결되며, 그 리스트가 곧 “누가 egress를 쓰는가"의 감사 가능한 계약이 된다. - 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. 도입 절차와 측정
-
현재 상태 측정 (기준선) — 대표 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 # 푸시 동기화 상태 -
istio-egress에 CR 한 벌 정리 — 기존 분산 사본이 있다면 중앙 벌 생성 → exportTo에 해당 ns 추가 → 사본 삭제 순으로 네임스페이스별 이관 (동시 존재 구간에서 같은 host 중복이 생기므로
istioctl analyze로 충돌 감시, 이관은 ns 단위로 짧게). -
목적별 Service·Gateway server 구성 — 기존 fat Service가 있다면 새 목적 Service를 병행 생성 → 소비자를 VS destination 전환 → fat Service 축소 (파드 무변경이므로 트래픽 전환은 라우팅 객체만의 일).
-
사후 측정 — 1의 커맨드 재실행으로 per-proxy cluster/listener 수·config 크기 감소를 기록.
istioctl proxy-config endpoints <pod> | grep egressgw로 목적 몫만 남았는지 확인. -
CI 게이트 — exportTo 네 곳 동일성 검사 스크립트 +
istioctl analyze를 리스트 변경 PR의 필수 체크로.
5. 참고 자료
- Istio 공식 · Configuration Scoping — exportTo(공급자측)와 Sidecar(소비자측)의 대칭 정의, gateway의 exportTo 존중, 스케일 목적의 범위 축소 권고. istio.io/latest/docs/ops/configuration/mesh/configuration-scoping/
- Istio 공식 · Sidecar reference — “configuration scoping은 불필요한 설정을 제거해 mesh 확장성을 개선"한다는 명시. istio.io/latest/docs/reference/config/networking/sidecar/
- Istio 소스 · mesh 기본값 —
DefaultServiceExportTo / DefaultVirtualServiceExportTo / DefaultDestinationRuleExportTo = ["*"]의 존재. github.com/istio/istio — pkg/config/mesh/mesh.go - Tetrate · Service Mesh Best Practices — default*ExportTo를
"."로 좁히고 네임스페이스별 Sidecar를 관리하는 운영 권고. tetrate.io/blog/optimize-traffic-management… - Google Cloud Community · 중앙 스코프 관리 — 멀티테넌트 클러스터에서 Istio 리소스 스코프를 중앙 통제하는 접근. medium.com/google-cloud/centrally-manage-the-scope…
- 대규모 운영 사례 — 설정 범위 축소를 1순위 최적화로 꼽는 스케일 자료들 (per-proxy 설정 70~90% 축소 보고). americanexpress.io/optimizing-istio-for-large-scale…
3.4의 수치는 예시 규모의 산술 모델이며 실측이 아니다. §4의 기준선 측정 커맨드로 실제 감소율을 기록해 이 문서를 갱신하는 것을 도입 완료 조건에 포함할 것.
관련 문서
- Outbound 집약 설계서 — egress gateway로 Squid 경로 일원화 — 이 문서의 선행 설계서. 4.2(addresses·정의 충돌 C 상태)·4.7(exportTo 세 객체 정합)이 본문의 전제
- ServiceEntry 리스너 매칭 — 다섯 명제와 재현 시나리오 S1~S5 — 1.2 “충돌” 행의 재현 근거
- Egress route 스코핑 — exportTo 가시성 — exportTo 필드 자체의 동작 정리
- Sidecar scope 개념 노트 — exportTo(공급자측)의 반대 축인 소비자측 통제
- SE와 Envoy 반영 범위 — sidecar vs gateway·DR — 3.5 “gateway 파드 자신의 설정은 별개 축"의 상세
- Egress 4-CRD 멘탈모델 — curl 한 번 = 두 hop — 1.1 객체 배선의 원리