Istio egress 구성(sidecar → egress gateway → Squid CONNECT 터널)에 필요한 CR 세트를
네임스페이스마다 복제하지 않고 전용 네임스페이스 istio-egress에 한 벌만 두고
exportTo로 노출을 제어하는 관리 모델, 그리고 목적별 Service 분리로
Envoy 리소스(LDS·CDS·EDS) 비대화를 예방하는 구성 방안을 정의합니다.
istio-egress에 한 벌만 둔다. 소비 네임스페이스는 세 객체의
exportTo 리스트에 이름을 추가하는 것으로 연결되며, 그 리스트가 곧
"누가 egress를 쓰는가"의 감사 가능한 계약이 된다.sidecar가 짊어지는 설정의 양은 (보이는 서비스 × 포트) × 그것이 보이는 sidecar 수의 곱이다. 중앙 관리는 곱의 앞항을 한 벌로 고정하고, exportTo와 목적별 분리는 뒷항(누구에게 보이는가)을 목적별 최소로 줄인다.
egress 경로는 sidecar → egress gateway(pod) → Squid(:3128, CONNECT 터널)
두 홉이고, 이를 성립시키려면 CR이 최소 네 종 필요합니다 — ServiceEntry(Squid 등재와
리스너 매칭), DestinationRule(홉 1 mTLS 발신), Gateway(수신 리스너), VirtualService(두 홉의
배선). 여기에 AuthorizationPolicy(신원 심사)와 gateway용 Service/Deployment까지 더하면
한 목적지를 잇는 데 예닐곱 개의 객체가 움직입니다. 문제는 이 세트를 누가, 어디에,
몇 벌 두느냐입니다.
각 앱 네임스페이스가 자기 사본을 만드는 분산 모델(네임스페이스 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에서 정량화) |
막연히 "리소스"라 부르는 것의 실체는 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)의 재계산을 유발합니다.
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를 가질 수 있다는 것. 이것이 Part 03의 논리 전체입니다.
egress 관련 CR 한 벌을 istio-egress에만 두고, 소비는 세 객체(SE·VS·DR)의
exportTo 리스트로 제어합니다. 앱 네임스페이스에는 아무 CR도 생기지
않습니다 — 연결은 리스트에 이름을 추가하는 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객체 동시 강제
이 모델은 관습이 아니라 Istio가 설계한 두 방향의 가시성 통제 중 한쪽을 그대로 쓰는 것입니다. 공식 Configuration Scoping 문서의 정의가 정확히 이 구도입니다:
".")와 앱 소비(나열)를 하나의 메커니즘으로 다룰 수 있는
근거입니다.defaultServiceExportTo / defaultVirtualServiceExportTo /
defaultDestinationRuleExportTo(기본값 "*")가 존재합니다. "기본 전체
공개를 뒤집을 수 있는 전역 스위치"가 코어에 있다는 것 자체가, 가시성 축소가 의도된
운영 모델이라는 뜻입니다.mesh 전역 기본값(default*ExportTo)을 "."로 뒤집는 강수는
구버전에서 구체 네임스페이스 나열 미지원, delegate VS 병합 등 이슈 이력이 있습니다.
이 문서의 모델은 전역 기본값은 건드리지 않고 egress 객체의 exportTo만 나열하는
보수적 형태이며, 전역 뒤집기는 mesh 성숙 후 별도 검토 대상입니다.
| 중앙 (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로 흡수합니다.
"."은 불변 멤버 — egressgateway 자신이 SE의 두 번째
소비자(홉 2 클러스터)이기 때문입니다. 빠지면 "mTLS 성공 직후 끊김" 시그니처.쿠버네티스 Service는 파드의 소유물이 아니라 파드를 가리키는 독립 객체입니다. 같은 selector를 가진 Service를 몇 개 만들든 전부 같은 egressgateway 파드들을 가리키고, 파드는 아무것도 모릅니다. 새 목적의 수신 포트가 필요하면 Gateway CR에 server를 추가하는데, 이는 xDS로 gateway Envoy에 핫 반영됩니다(리스너 추가에 재시작 불요, Deployment의 containerPort 선언은 정보성이라 무변경). 즉 파드 재생성 없이 Service와 Gateway server만으로 목적을 증설할 수 있고, 이것이 이 모델의 전제입니다.
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 |
# 같은 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 목록 변수 하나를 두고 네 곳에 주입하는 구조를 권장합니다.
| 지표 | 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)의 산술이며, 요점은 절대값이 아니라 변경 파급의 분모가 "전원"에서 "해당 목적의 소비자"로 바뀐다는 구조입니다.
tcp-,
tls-)가 Istio의 프로토콜 판정 입력입니다. 목적별 Service는 이 명명을
목적 단위로 명확하게 만들어 주는 부수 효과가 있습니다.to.ports 목적별)·Calico·방화벽의 일입니다. 목적별
포트는 그 정책들의 좌표가 되어 줍니다.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 # 푸시 동기화 상태istioctl analyze로 충돌 감시, 이관은 ns 단위로 짧게).istioctl proxy-config endpoints <pod> | grep egressgw로
목적 몫만 남았는지 확인.istioctl analyze를
리스트 변경 PR의 필수 체크로.DefaultServiceExportTo /
DefaultVirtualServiceExportTo / DefaultDestinationRuleExportTo = ["*"]의 존재."."로 좁히고 네임스페이스별 Sidecar를 관리하는 운영 권고.3.4의 수치는 예시 규모의 산술 모델이며 실측이 아닙니다. Part 04의 기준선 측정 커맨드로 실제 감소율을 기록해 이 문서를 갱신하는 것을 도입 완료 조건에 포함하세요.