--- type: guide tags: [istio, egress, gateway, virtualservice, serviceentry, destinationrule, mental-model, how-to] created: 2026-06-09 --- # Egress 4-CRD 직관 — "한 번의 curl = 두 hop", 4개를 어떤 순서로 어떻게 채우나 > [!abstract] > egress gateway 설정이 "Gateway·VirtualService·ServiceEntry·DestinationRule을 왜 4개나, 어느 필드를 어디에"로 > 막히는 이유는 **멘탈모델 없이 필드부터 보기 때문**이다. 이 문서는 거꾸로 간다 — 먼저 "한 번의 외부 호출이 > 두 hop으로 쪼개지고, 4개 CRD는 각자 다른 hop의 다른 질문에 답하는 부품"이라는 그림을 세운 뒤, **실제로 > 두 패턴(passthrough / outer-mTLS)을 어떤 순서로 어떻게 만들었는지**를 그때의 YAML과 함께 따라간다. > 결론: 4개는 따로 노는 게 아니라 **두 hop을 굴리기 위한 질문 4개**이고, 패턴 전환은 "3개 델타"일 뿐이다. > 필드 단위 레퍼런스(=사전)는 [Egress Gateway 정본](ref__src-egress-gateway.html)·[HTTPS over mTLS 해부](id__src-https-over-mtls.html), 검증 랩은 [이중 gateway 가이드](cfg__guide-dual-gateway.html). **대상 환경:** homelab (kubespray bare-metal, k8s v1.30.6, CNI Calico), Istio **1.30.0**, Helm gateway chart **대상 독자:** egress gateway config를 *직접 만들어야* 하는데 4개 CRD 관계가 안 잡히는 사람 **선행 개념:** sidecar 트래픽 캡처(15001 outbound), Envoy listener/cluster, mTLS·SNI 기초 **다루는 것:** ① 왜 egress·왜 4개 ② 멘탈모델·부품표 ③ 구성 순서대로 실제 YAML과 이유 ④ 필드 정렬 지도 ⑤ 단 하나의 비대칭(tls vs tcp) --- ## 1. 배경 — 왜 egress gateway이고, 왜 하필 CRD가 4개인가 mesh 안의 pod가 `curl https://example.org`를 하면, sidecar가 outbound 트래픽(15001)을 가로챈다. 여기서 두 가지를 "통제하고 싶다"는 욕구가 egress gateway의 존재 이유다: - **관측·정책의 단일 통로.** 외부로 나가는 트래픽을 pod마다 제각각 내보내면 "누가 어디로 나갔나"를 한 곳에서 볼 수 없다. egress gateway는 **모든 외부 호출을 한 노드(pod)로 모으는 깔때기**다 — 거기서 egress IP 고정, 로그·메트릭 집중, 정책 한 점 적용이 가능해진다. - **신원 있는 외부 호출.** "어느 app이 이 외부 host로 나갔는가"를 mesh mTLS(SPIFFE)로 증명하려면, sidecar→gateway 구간을 mTLS로 감싸고 gateway에서 그 신원을 검증해야 한다. 이게 outer-mTLS 패턴이다. 문제는, 이 "통로"를 만들려면 **단 한 번의 외부 호출이 두 개의 구간으로 쪼개진다**는 점이다. app은 한 번 curl했지만 물리적으로는 `app→gateway`, `gateway→외부` 두 개의 hop이 생긴다. 두 hop을 각각 "어디로 보낼지·어떻게 말 걸지·어느 문으로 받을지·이 host가 존재하긴 하는지"로 설정해야 하니, 답해야 할 질문이 자연히 여러 개가 된다. Istio는 이 질문들을 **관심사별로 다른 CRD에 분산**시켰다 — 그래서 4개다. 4개라서 어려운 게 아니라, **두 hop × 관심사 분리**의 결과가 4개일 뿐이다. 이 문서는 그 4개를 "질문 4개"로 되돌려 직관을 세운다. > 같은 4-CRD 골격은 ingress가 아니라 **외부로 나가는 egress** 시나리오 전용 멘탈모델이다. 더 넓은 운영 맥락은 > [egress operations](ref__src-operations.html), 라우팅 스코핑은 [VS 스코핑](mm__note-vs-scoping.html). --- ## 2. 모든 걸 푸는 한 문장 (멘탈모델 anchor) > **`curl https://example.org` 한 번은 Istio 안에서 "두 개의 hop"으로 쪼개진다. > CRD 4개는 각자 다른 hop의 다른 질문에 답하는 부품일 뿐이다.** 이 한 문장만 머리에 박으면 나머지는 다 따라온다. 물리적으로 일어나는 일은 이게 전부다: ```mermaid flowchart LR app[app + sidecar] -->|hop1: sidecar가 나가는 길| gw[egress gateway pod] gw -->|hop2: listener가 받아 외부로| ext[example.org:443] VS1[VS leg-1: host -> gw] -.batches.-> app DR[DR: hop1을 어떻게 plain/mTLS] -.batches.-> app GW[Gateway: 문 열고 보안 모드] -.batches.-> gw VS2[VS leg-2: 받은 걸 외부로] -.batches.-> gw SE[ServiceEntry: example.org는 존재함] -.전제, hop 무관.-> app ``` ASCII로 같은 그림 — "두 hop"의 골격만 먼저: ``` app ───hop1───> egress gateway pod ───hop2───> example.org (sidecar가 (listener가 (외부 서버) 나가는 길) 받는 자리) ``` 나머지 모든 설정은 "이 두 hop을 어떻게 동작시킬까"를 채우는 것이다. 리소스가 4개인 이유는 **두 hop을 굴리려면 답해야 할 질문이 4개**이기 때문이다 — §1에서 본 "관심사 분리"가 여기서 4개의 부품으로 떨어진다. --- ## 3. 부품표 — 각 CRD = 하나의 질문에 대한 답 | 답해야 할 질문 | 답 = CRD | 한 줄 직관 | |---|---|---| | "이 외부 host가 존재하긴 하나? 메시가 알아도 되나?" | **ServiceEntry** | 메시의 **주소록에 등록**. 없으면 unknown(REGISTRY_ONLY면 차단). | | "트래픽이 뜨면 어디로?(hop1) / gateway 도착 뒤 어디로?(hop2)" | **VirtualService** | **라우팅 규칙**. 4개 중 **유일하게 두 hop에 다 걸친다** → route 블록 2개. | | "sidecar가 gateway에게 *어떻게* 말 거나? 평문? mTLS로 감싸서?" | **DestinationRule** | hop1 목적지(gateway)에게 **말 거는 방식**. "mTLS로 감싸라"를 여기서 지정. | | "gateway는 어느 *문(listener)* 을 열어 hop1을 받나? cert를 요구하나?" | **Gateway** | egress pod에 **포트를 열고** 보안 모드 설정(ISTIO_MUTUAL = cert 요구·검증). | 이 표가 핵심이다. **"필드 4묶음"이 아니라 "질문 4개"** 로 보면, 작성은 질문에 순서대로 답하는 일이 된다. 교환원(switchboard)을 거쳐 외부로 전화 거는 비유: - **ServiceEntry** = 그 외부 번호가 회사 전화번호부에 있어야 함(없는 번호는 차단) - **Gateway** = 교환원 책상 + "신분증 보여주는 사람 전화만 받음" 규칙 - **DestinationRule** = 내 전화기 지시 "교환원한테 걸 땐 네 신분증을 제시해" - **VirtualService** = 통화 라우팅 스크립트 "cnn행 → 교환원에게(leg1)", "교환원아, 이걸 진짜 cnn으로(leg2)" > 비유의 한계: mTLS 패턴에서 교환원(gateway)은 **바깥 봉투(outer mTLS)만 뜯고 안쪽 편지(inner 앱 TLS)는 > 못 읽은 채** 그대로 넘긴다. 전화 비유엔 "이중 봉투"가 없다 — 그 부분은 §7. ### 공간 지도 — 어느 부품이 경로의 어디에 앉나 부품을 질문으로 알았으면, 이번엔 그 부품이 **두 hop의 어느 쪽에 매달리는지**를 본다. hop1은 sidecar 쪽이 만들고, hop2는 gateway 쪽이 받는다 — 4개가 이 두 진영으로 깔끔히 갈린다: ``` app sidecar ════ hop1 ════> egress gw ════ hop2 ════> example.org:443 │ │ VS leg-1 ─────┤ "host -> gw 로 보내" │ DR ───────────┘ "그 길을 어떻게(plain/mTLS)" │ ├──── Gateway "포트 열고, 보안 모드" └──── VS leg-2 "받은 걸 외부로 흘려" ServiceEntry ── "example.org 는 존재함" (그림 전체에 적용 — hop 무관) ``` - **hop1을 만드는 건 sidecar 쪽**: VS leg-1(어디로) + DR(어떻게). - **hop1을 받는 건 gateway 쪽**: Gateway(문) + VS leg-2(받은 뒤 어디로). - ServiceEntry는 양쪽 어디에도 안 붙는 "전제" — host 등록. --- ## 4. 구성 따라하기 — 실제로 이렇게 만들었다 홈랩에 **두 패턴을 별도 pod로** 띄웠다. 핵심은 **포트·구조를 똑같이 맞춰** 두 패턴의 차이를 *최소한*으로 드러낸 것이다 — 둘은 **3곳만 다르고 나머지는 동일**하다. 그래서 "공통 뼈대를 만들고, mTLS는 거기에 3개 델타를 얹는다"로 읽으면 된다. 부품 의존 순서대로 `SE → Gateway → DR → VS`로 채운다(VS가 나머지를 가리키므로 마지막). Helm으로 gateway pod부터 두 개 띄운다(label만 다름, 포트는 둘 다 443→8443으로 통일): ```bash helm upgrade --install egw-pt istio/gateway -n egress-pt --version 1.30.0 -f install/helm/values-egw-pt.yaml --wait helm upgrade --install egw-mtls istio/gateway -n egress-mtls --version 1.30.0 -f install/helm/values-egw-mtls.yaml --wait # 둘 다 Service: tls 443 -> targetPort 8443 (443은 privileged라 Envoy는 8443에 bind) # 다른 점은 label뿐: istio=egw-pt / istio=egw-mtls -> 각 Gateway 리소스 selector가 자기 pod만 잡음 ``` ### 4.1 ServiceEntry — "이 host가 존재한다" (두 패턴 동일) ```yaml kind: ServiceEntry metadata: { name: pt-ext, namespace: mesh-test } # mtls는 name: wiki-ext, host만 다름 spec: exportTo: [".", "egress-pt"] # client sidecar(.) + 해당 egress ns 에만 노출(메시 전역 누수 금지) hosts: [example.org] # mtls: www.wikipedia.org ports: - number: 443 name: tls protocol: TLS # ★ TLS 라야 sniHosts 라우팅이 붙는다(TCP면 SNI 못 봄). gateway가 L7 안 봄. resolution: DNS # STRICT_DNS — multi-IP A record를 다 endpoint로 펼침(LOGICAL_DNS 단일-endpoint 함정 회피) ``` 왜 이렇게: `protocol: TLS`는 "gateway가 앱 payload를 복호화하지 않는다(L4)"는 선언이다. origination(앱 평문화)이면 여기가 `HTTP`였을 것. `exportTo`는 host 정보를 **필요한 곳에만** 흘려 다른 gateway가 잘못 받아 NACK 나는 사고를 막는다 ([스코핑 §5](mm__note-vs-scoping.html)). ### 4.2 Gateway — "egress pod에 문 열기" (★ 델타 1: tls.mode) ```yaml kind: Gateway metadata: { name: egw-pt-gateway, namespace: egress-pt } # Gateway 리소스는 workload와 같은 ns spec: selector: { istio: egw-pt } # 이 ns의 egw-pt pod만 선택 — 멀티-gateway 격리의 핵심 servers: - port: { number: 443, name: tls, protocol: TLS } # CRD엔 443, Envoy는 Service targetPort 8443에 bind hosts: [example.org] tls: mode: PASSTHROUGH # ◀── 델타 1 ``` mTLS는 **이 한 줄만** 바뀐다: ```yaml tls: mode: ISTIO_MUTUAL # ◀── 델타 1 (mtls): outer 메시 mTLS를 *종단* + client cert 강제·검증 ``` 왜 이렇게: `PASSTHROUGH`는 복호화 없이 SNI로 라우팅만. `ISTIO_MUTUAL`은 listener를 "client cert 강제 (`requireClientCertificate: true`) + mesh CA로 SPIFFE 검증 + outer 종단"으로 만든다 — "누가 나가는가"가 여기서 확정된다. 포트(443)는 둘이 같아도 된다: **다른 pod·다른 ns라 충돌이 없다.** (같은 pod에 두 모드를 얹을 때만 포트를 갈라야 했다.) ### 4.3 DestinationRule — "그 문을 어떻게 두드리나" (★ 델타 2: trafficPolicy.tls) ```yaml kind: DestinationRule metadata: { name: egw-pt-dr, namespace: mesh-test } spec: exportTo: ["."] host: egw-pt.egress-pt.svc.cluster.local # = Helm release name = Service name subsets: - name: pt # passthrough: subset 식별만, TLS 정책 없음(앱 TLS 그대로 전달) ``` mTLS는 그 subset에 **trafficPolicy가 붙는다**(이게 hop1을 mTLS로 감싸는 트리거): ```yaml host: egw-mtls.egress-mtls.svc.cluster.local subsets: - name: mtls trafficPolicy: portLevelSettings: - port: { number: 443 } # sidecar가 dial하는 gateway Service 포트(= Gateway server port) tls: mode: ISTIO_MUTUAL # ◀── 델타 2: sidecar가 SPIFFE client cert 제시 + outer mTLS 생성 sni: www.wikipedia.org # ◀── gateway가 filter chain 고르는 키. Gateway hosts·VS sniHosts와 일치 필수 ``` 왜 이렇게: passthrough는 sidecar가 앱 TLS를 *그대로* 넘기면 되니 DR에 tls가 없다. mTLS는 sidecar가 **한 겹 더(outer)** 를 만들어야 하므로 DR에서 `ISTIO_MUTUAL`을 명시한다. `sni`는 outer TLS의 SNI를 박는 것 — 이게 어긋나면 gateway listener 매칭 실패로 연결이 끊긴다. ### 4.4 VirtualService — "두 leg을 배선" (★ 델타 3: leg-2 tcp) VS는 **하나에 두 leg**이 들어간다. leg-1(sidecar→gw)은 두 패턴 동일, leg-2(gw→외부)만 다르다. ```yaml # (1) client용 VS — mesh-test에 둠. 두 패턴 구조 동일. kind: VirtualService metadata: { name: pt-client, namespace: mesh-test } # mtls: wiki-client spec: exportTo: ["."] hosts: [example.org] gateways: [mesh] # leg-1은 sidecar(mesh)에 붙음 tls: - match: [{ gateways: [mesh], port: 443, sniHosts: [example.org] }] route: - destination: host: egw-pt.egress-pt.svc.cluster.local # 어느 gateway로 subset: pt # 어떤 말투로(DR subset) port: { number: 443 } ``` leg-2(gateway용 VS)는 **여기서 갈린다**: ```yaml # (2-passthrough) gateway용 VS — egress-pt에 둠 metadata: { name: pt-gateway, namespace: egress-pt } spec: gateways: [egw-pt-gateway] tls: # 종단 안 함 → tls/sniHosts - match: [{ gateways: [egw-pt-gateway], port: 443, sniHosts: [example.org] }] route: [{ destination: { host: example.org, port: { number: 443 } } }] ``` ```yaml # (2-mtls) gateway용 VS — egress-mtls에 둠 metadata: { name: wiki-gateway, namespace: egress-mtls } spec: gateways: [egw-mtls-gateway] tcp: # ◀── 델타 3: 종단함 → tcp (이유는 §7) - match: [{ gateways: [egw-mtls-gateway], port: 443 }] route: [{ destination: { host: www.wikipedia.org, port: { number: 443 } } }] ``` **정리 — 두 패턴은 딱 3곳만 다르다:** ``` passthrough outer-mTLS Gateway tls.mode: PASSTHROUGH tls.mode: ISTIO_MUTUAL (델타 1) DR subset만 subset + trafficPolicy(mTLS,sni) (델타 2) VS leg-2 tls / sniHosts tcp (델타 3) ---------------------------------------------------------------------- 나머지(SE, 포트 443->8443, leg-1 구조, ns 분리, exportTo)는 전부 동일 ``` --- ## 5. 필드 정렬 지도 — "왜 같은 문자열이 여기저기?"의 해소 4개가 따로 노는 게 아니라 **특정 필드가 서로 같아야** 연결된다. 여기서 길을 잃기 쉽다. 묶음으로 보면 끝난다: ``` ┌─ 외부 host 이름 (example.org) — 같아야 함 ───────────────────┐ │ SE.hosts · Gateway.servers.hosts · VS.hosts │ │ VS leg-1 sniHosts · VS leg-2 dest.host │ │ (mTLS만) DR.tls.sni ← gateway filter chain 고르는 키 │ └─────────────────────────────────────────────────────────────┘ ┌─ gateway Service FQDN (egw-pt.egress-pt.svc) — 2곳 ─┐ │ DR.host == VS leg-1 destination.host │ "교환원이 누구" └─────────────────────────────────────────────────────┘ ┌─ subset 이름 (pt / mtls) — 2곳 ────────────────────┐ │ DR.subsets.name == VS leg-1 destination.subset │ "어떤 말투로" └─────────────────────────────────────────────────────┘ ┌─ 포트 443 — 전부 443으로 통일 ──────────────────────┐ │ SE.port · Gateway.port · DR.portLevelSettings.port │ │ VS leg-1 dest.port · VS leg-2 match.port · 외부 dest.port │ │ (Envoy 실제 listener는 Service targetPort 8443에 bind) │ └─────────────────────────────────────────────────────┘ ┌─ gateway 바인딩 ────────────────────────────────────┐ │ Gateway.selector(istio:egw-pt) == egress pod label │ │ VS.gateways == [mesh(leg1), (leg2)] │ └─────────────────────────────────────────────────────┘ ``` 포트를 둘 다 443으로 통일한 효과가 여기서 드러난다 — "443이냐 15443이냐"라는 혼란 축이 통째로 사라지고, **443은 다 같은 443**으로 읽힌다. 실측 함정도 전부 이 정렬 위반이다: `sni↔hosts↔sniHosts` 어긋남 = SSL 오류. --- ## 6. 적용·결과 — 떴는지 한 번만 본다 구성이 맞으면 listener가 뜨고 호출이 통한다. 길게 검증하지 않고 "listener 존재 + 호출 성공"만 확인한다: ```bash istioctl proxy-config listener deploy/egw-mtls.egress-mtls # PORT 8443, SNI www.wikipedia.org 행이 보이면 OK # (ISTIO_MUTUAL이면) --port 8443 -o json | grep requireClientCertificate -> true SLEEP=$(kubectl -n mesh-test get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}') kubectl -n mesh-test exec $SLEEP -c sleep -- curl -sS -o /dev/null -w '%{http_code}\n' https://www.wikipedia.org # 200 ``` - `proxy-config listener`에 **8443 / SNI www.wikipedia.org 행이 보이면** Gateway·VS leg-2 배선이 산 것이다. - `requireClientCertificate: true`면 ISTIO_MUTUAL listener가 client cert를 강제하도록 떴다는 뜻(델타 1이 먹은 증거). - 마지막 curl `200`이면 두 hop이 끝까지 이어졌다 — hop1(mTLS 감쌈) → hop2(종단 후 외부) 완주. listener 행이 **안 보이면** 십중팔구 §7(종단인데 leg-2를 tls로) 또는 §5(정렬 어긋남)다. --- ## 7. 단 하나의 비대칭 — 왜 leg-1은 `tls`, leg-2는 `tcp`인가 (mTLS 한정) passthrough는 양쪽 leg 모두 `tls`/sniHosts다. mTLS만 leg-2가 `tcp`로 갈린다. 이유: ``` passthrough : sidecar [SNI 봄] ──tls──> gw [SNI 봄, 종단 안 함] ──tls──> 외부 outer-mTLS : sidecar [outer 생성] ─tls─> gw [outer 종단! SNI 소비] ─tcp─> 외부 └ 종단 뒤엔 SNI가 없다 → tcp_proxy로 흘릴 수밖에 ``` `ISTIO_MUTUAL` gateway는 outer 메시 mTLS를 **종단**한다. 종단된 listener에 `tls`/sniHosts route를 걸면 **network filter가 안 생겨** listener가 통째로 누락된다(istiod: `must have more than 0 chains`로 omit). 종단 뒤 남은 inner 앱 TLS 바이트는 L4 `tcp_proxy`로 흘려야 하므로 leg-2는 **반드시 `tcp`**. 이게 mTLS egress의 가장 흔한 함정이다. (filter chain 원리: [Envoy filter chain](../istio/xds__note-envoy-filter-chain-extension.html)) --- ## 핵심 정리 - **한 번의 외부 curl = 두 hop**(`app→gw`, `gw→외부`). 4개 CRD는 두 hop × 관심사 분리의 결과 — "필드 4묶음"이 아니라 **질문 4개**. - **질문 매핑**: SE="host 존재하나"(주소록) · VS="어디로"(두 leg, 유일하게 양 hop) · DR="hop1을 어떻게(plain/mTLS)" · Gateway="어느 문·보안 모드". - **공간 배치**: hop1은 sidecar 쪽(VS leg-1 + DR), hop2는 gateway 쪽(Gateway + VS leg-2), SE는 hop 무관 전제. - **passthrough → outer-mTLS = 델타 3개뿐**: Gateway `tls.mode`(PASSTHROUGH→ISTIO_MUTUAL), DR `trafficPolicy`(subset만→mTLS+sni), VS leg-2(`tls`→`tcp`). 나머지는 전부 동일. - **정렬이 곧 정상 동작**: 외부 host 이름·gateway FQDN·subset·포트 443·selector가 여러 곳에서 일치해야 연결된다. 어긋남 = SSL 오류·listener 누락. - **비대칭 1곳**: ISTIO_MUTUAL은 outer를 종단 → SNI 소비 → leg-2는 반드시 `tcp`(tls면 `0 chains`로 listener omit). --- ## What you might be missing - **VS의 `gateways` 리스트가 leg을 가른다.** `mesh`가 leg-1(sidecar), ``이 leg-2(gateway). 한 VS에 둘을 같이 넣으면 한 리소스가 두 hop을 다 배선하지만, 소유권/스코핑이 섞인다. 그래서 본 랩은 client용·gateway용 VS를 **별도 ns로 분리**했다 — 이유는 [스코핑 노트](mm__note-vs-scoping.html). - **포트를 통일할 수 있는 건 "다른 pod"이기 때문이다.** 한 egress pod에 passthrough와 ISTIO_MUTUAL을 같이 얹으면 같은 포트 공존이 불가(merge 충돌)라 15443 등으로 갈라야 한다. dual-pod로 쪼개면 그 제약이 사라져 443으로 통일 가능 → 설정이 직관적이 된다. 즉 **pod를 나누는 결정이 포트 설계를 단순하게 만든다.** - **이 4-CRD 골격은 세 패턴(passthrough/mTLS/origination)에 공통이다.** 바뀌는 건 Gateway `tls.mode`, DR `trafficPolicy`, VS leg-2 route 타입 — 골격을 외우면 패턴 전환은 "델타 몇 줄"이다. origination(gateway가 외부로 TLS 시작)은 SE가 `protocol: HTTP`가 되고 DR에 외부용 TLS가 붙는 또 다른 델타다([HTTP vs HTTPS](ref__src-http-vs-https.html)). - **Istio 라우팅 ≠ 강제.** 이 골격은 "어떻게 나가는가"를 정의할 뿐, sidecar 우회를 막지 못한다. 진짜 강제는 Calico NetworkPolicy로 egress pod 외 직접 송신을 차단해야 선다([정본](ref__src-egress-gateway.html) §02). --- ## 8. 참조 **아카이브 내부** - [Egress Gateway 도입 가이드 (사내 공유본)](cfg__guide-adoption-passthrough-vs-mtls.html) — 이 멘탈모델이 실전 의사결정·표준 1벌로 압축된 형태 - [Egress TCP 병목 정본](ref__src-tcp-bottlenecks.html) — 4-CRD 다음에 만나는 운영 한계(연결·포트·conntrack) - [이중 gateway 검증 랩](cfg__guide-dual-gateway.html) · [egress route 스코핑](mm__note-vs-scoping.html) - [Egress Gateway 개념 정본(필드 사전)](ref__src-egress-gateway.html) · [HTTPS over mTLS 해부](id__src-https-over-mtls.html) - [HTTPS passthrough 가이드](cfg__guide-gateway-https.html) · [HTTP vs HTTPS](ref__src-http-vs-https.html) · [egress operations](ref__src-operations.html) - [Envoy filter chain](../istio/xds__note-envoy-filter-chain-extension.html) · [SPIFFE 신원](../istio/sec__note-mtls-spiffe-identity.html) **관련 IaC (실제 manifest)** - 📎 [10-passthrough.yaml](../istio/attachment/scenarios/20-egress/dual-gateway/10-passthrough.yaml) · 📎 [20-mtls.yaml](../istio/attachment/scenarios/20-egress/dual-gateway/20-mtls.yaml) - 📎 [values-egw-pt.yaml](../istio/attachment/install/helm/values-egw-pt.yaml) · 📎 [values-egw-mtls.yaml](../istio/attachment/install/helm/values-egw-mtls.yaml)