homelab89 Docs Logs Legacy Files ☰ TOC 🌓
noteistio 2026-06-07istioeastwest-gatewaysnimulticlustermtls

east-west gateway는 목적지 클러스터를 SNI에 인코딩해, mTLS를 복호화하지 않고 암호화된 채로 원격 워크로드까지 프록시한다

ABSTRACT

east-west gateway는 mTLS를 풀지 않고 봉투 겉면(ClientHello의 SNI)에 적힌 목적지만 읽어 다음 hop으로 넘기는 L4 SNI 라우터다. 멀티클러스터(network 분리) 메시에서 한 클러스터의 sidecar가 다른 클러스터의 워크로드를 부를 때, sidecar는 목적지 식별자를 SNI 필드에 인코딩해 보내고, 게이트웨이는 그 SNI만 읽어(AUTO_PASSTHROUGH) 암호 바이트를 그대로 흘린다. 종단을 안 하므로 워크로드↔워크로드 mTLS가 관통해 보존된다. 이 문서는 그 메커니즘과 왜를 다룬다(운영 매니페스트는 위임).

대상환경: Istio 1.30, multi-network 멀티클러스터 · 대상독자: 멀티클러스터 mTLS 신원 보존이 왜·어떻게 동작하는지 알고 싶은 SRE · 선행개념: SPIFFE/mTLS 신원, Cluster 해부


1. 배경: 단일 클러스터 mTLS가 멀티클러스터에서 깨지는 두 지점

east-west gateway가 왜 존재하는지는, 그것이 없을 때 무엇이 깨지는지에서 나온다. 먼저 정상 상태부터.

단일 클러스터에서는 sidecar가 목적지 pod IP를 직접 알고, Envoy cluster가 그 endpoint로 mTLS를 맺는다. 워크로드 A의 SPIFFE 신원이 워크로드 B에 그대로 제시되고, B의 AuthorizationPolicy는 “A가 부른 게 맞다"를 그 신원으로 판정한다(SPIFFE/mTLS 신원). 이 그림에서 sidecar↔sidecar는 1-hop이고, 중간에 끼어드는 자가 없다.

멀티클러스터 — 특히 network가 분리된 경우(클러스터 간 pod CIDR이 서로 라우팅 불가) — 에서는 이 그림이 두 군데서 깨진다.

  • 연결성: cluster1의 sidecar가 cluster2의 pod IP로 직접 TCP를 못 연다. 두 network는 L3에서 서로의 pod CIDR을 모른다.
  • 신원 보존: 누군가 두 클러스터 사이에서 TLS를 종단(복호화)하면, 그 지점에서 peer 신원이 그 중간자의 신원으로 바뀐다. B가 보는 건 더 이상 A가 아니다. end-to-end SPIFFE가 끊긴다.

순진한 해법(클러스터 경계에 보통 게이트웨이를 세워 TLS 종단 후 재암호화)은 연결성은 풀지만 신원을 죽인다. 신원을 살리려면 중간 게이트웨이가 절대 TLS를 풀지 않아야 한다. 그런데 풀지 않으면 게이트웨이는 안을 못 보는데, 어떻게 “어디로 보낼지"를 정하나? 이 긴장이 east-west gateway 설계 전체를 결정한다.


2. 핵심 아키텍처: 봉투 겉면(SNI)에 목적지를 적어 라우터에게 읽힌다

머릿속에 담을 한 장면(anchor): 게이트웨이가 편지 을 못 열게 하려면, 받는 사람을 봉투 겉면에 적으면 된다. TLS에서 그 겉면이 평문으로 노출되는 유일한 칸이 ClientHello의 SNI다. 그래서 sidecar는 목적지 식별자를 SNI에 인코딩해 보내고, 게이트웨이는 그 한 줄만 읽어 라우팅한다 — 복호화 0.

이 한 문장에서 나머지 모든 디테일이 따라 나온다. 메커니즘을 세 부품으로 분해하면.

부품 그게 답하는 질문 어떻게
SNI 인코딩 (sidecar 쪽) “게이트웨이가 안을 못 보는데 목적지를 어떻게 전달하나?” 목적지를 outbound_.PORT_.SUBSET_.FQDN로 SNI에 적음
AUTO_PASSTHROUGH (게이트웨이 listener) “게이트웨이가 어떻게 복호화 없이 SNI만 읽나?” tls_inspector로 ClientHello의 SNI만 추출, TLS는 안 풂
sni-dnat (게이트웨이 라우팅) “추출한 SNI를 어디로 보내나?” SNI를 동일 이름 내부 cluster로 역매핑해 tcp_proxy

전체 흐름을 한 장에 담으면.

flowchart LR
  subgraph C1["cluster1 / network1"]
    A["workload A<br/>sidecar"]
    EW1["east-west GW<br/>(AUTO_PASSTHROUGH)"]
  end
  subgraph C2["cluster2 / network2"]
    EW2["east-west GW<br/>(AUTO_PASSTHROUGH)"]
    B["workload B<br/>sidecar"]
  end
  A -- "mTLS, SNI=outbound_.8080._.B.ns.svc" --> EW2
  EW2 -- "same encrypted bytes,<br/>routed by SNI" --> B
  EW1 -. "symmetric path<br/>for C2 to C1 -> A" .-> A
NOTE

A의 sidecar는 cluster2의 east-west GW(EW2)로 곧장 mTLS를 맺는다 — 자기 클러스터 EW1을 거치지 않는다. EW1은 반대 방향(cluster2 → cluster1의 워크로드) 트래픽의 진입점이다. 게이트웨이는 network마다 하나 있는 양방향 관문이고, 트래픽은 항상 목적지 network의 게이트웨이로 들어간다.

2.1 부품 ①: SNI 인코딩 — sidecar가 목적지를 봉투에 적는다

평범한 Envoy cluster의 transport socket은 TLS를 맺을 때 SNI를 목적지 hostname(또는 비움)으로 채운다 — “어떤 server 인증서를 줄까"용이다. east-west 경로용 cluster는 이 SNI를 목적지 식별자 문자열로 덮어쓴다. istiod가 멀티클러스터 sidecar에 내려주는 cluster의 모양은 이렇다.

# istioctl proxy-config cluster deploy/workload-a --fqdn B.ns.svc.cluster.local -o json 의 발췌
{
  "name": "outbound|8080||B.ns.svc.cluster.local",
  "transportSocket": {
    "name": "envoy.transport_sockets.tls",
    "typedConfig": {
      "sni": "outbound_.8080_._.B.ns.svc.cluster.local"   # ← 목적지를 SNI에 인코딩
    }
  },
  "loadAssignment": { "endpoints": [
    { "lbEndpoints": [ { "endpoint": { "address": {
        "socketAddress": { "address": "<EW2 gateway IP>", "portValue": 15443 }
    }}}]}
  ]}
}

이 한 객체에 멀티클러스터의 본질 두 개가 다 들어 있다.

  1. endpoint가 원격 pod가 아니라 east-west gateway IP:15443이다. network 분리로 pod에 직접 못 가니, istiod가 endpoint를 게이트웨이로 치환해 내려준다. (cluster 구조 일반론은 Cluster 해부.)
  2. SNI가 hostname이 아니라 outbound_.{port}_.{subset}_.{fqdn} 인코딩 문자열이다. 일반 TLS의 SNI는 인증서 선택용이지만, 여기 SNI는 “원격 게이트웨이가 어느 내부 cluster로 보낼까“의 라우팅 키다. cluster 이름 outbound|8080||B..._로 평탄화한 게 SNI라는 점을 보면, 둘이 같은 식별자임을 알 수 있다 — 이게 뒤의 역매핑을 공짜로 만든다.
구분 일반 cluster의 SNI east-west용 cluster의 SNI
목적지 hostname 또는 없음 outbound_.PORT_.SUBSET_.FQDN
용도 server 인증서 선택(SAN 매칭) 원격 게이트웨이의 라우팅 키
endpoint 실제 목적지 IP east-west gateway IP:15443
누가 읽나 목적지 워크로드 east-west gateway(passthrough)
IMPORTANT

포트 15443은 east-west gateway의 TLS auto-passthrough listener다. 메시 내부 inbound(15006)·outbound(15001)와 별개의, 멀티클러스터 전용 포트다.

2.2 부품 ②③: AUTO_PASSTHROUGH로 SNI만 읽고, sni-dnat으로 내부 cluster에 꽂는다

원격 east-west gateway의 listener는 tls.mode: AUTO_PASSTHROUGH로 떠 있다. 동작은 egress passthroughprotocol: TLS + tls_inspector와 같은 계열 — TLS를 풀지 않고 ClientHello의 SNI만 추출한다.

protocol: TLS + AUTO_PASSTHROUGH  →  listener:[ tls_inspector → tcp_proxy ]
   ClientHello의 SNI 추출 → SNI 문자열을 cluster 이름으로 해석 → 그 cluster로 tcp_proxy
   복호화 0, L7 0 (암호 바이트 그대로 통과)

게이트웨이가 하는 일은 SNI 디코딩 → 매칭되는 내부 cluster 선택뿐이다.

받은 SNI:  outbound_.8080_._.B.ns.svc.cluster.local
             │        │    │   └── fqdn      → 어느 서비스
             │        │    └────── subset    → 어느 DestinationRule subset
             │        └─────────── port      → 어느 포트
             └──────────────────── direction(outbound)
                  ▼ 이 인코딩을 cluster "outbound|8080||B.ns.svc.cluster.local" 로 역매핑
       → 그 cluster의 endpoint(= cluster2 내부 B의 실제 pod들)로 tcp_proxy

이 “SNI를 보고 동일 이름의 내부 cluster로 dnat"이 sni-dnat 라우터 모드다. Istio 1.11부터는 Gateway 리소스에 tls.mode: AUTO_PASSTHROUGH가 선언된 것만으로 istiod가 이를 자동 감지해, 게이트웨이에 각 서비스마다 outbound_.* SNI에 매칭되는 passthrough cluster를 생성해준다(과거처럼 게이트웨이 deployment에 ISTIO_META_ROUTER_MODE=sni-dnat 환경변수를 수동으로 얹을 필요가 없다 — 이 변수 자체가 이후 코드베이스에서 정리됐다). 게이트웨이는 받은 암호 바이트를 건드리지 않고 그 내부 cluster의 실제 pod endpoint로 tcp_proxy할 뿐이다. SNI 문자열과 cluster 이름이 같은 식별자(§2.1)이므로 이 역매핑은 문자열 변환 한 번이다 — L7 파싱도, 세션 키도 필요 없다.

sequenceDiagram
  participant A as workload A sidecar (c1)
  participant GW as east-west GW (c2, AUTO_PASSTHROUGH)
  participant B as workload B (c2)
  A->>GW: TLS ClientHello, SNI=outbound_.8080_._.B.ns.svc
  Note over GW: tls_inspector reads SNI only<br/>NO decryption
  Note over GW: sni-dnat: SNI -> cluster<br/>outbound|8080||B.ns.svc
  GW->>B: forward SAME encrypted bytes (tcp_proxy)
  Note over A,B: mTLS handshake completes A<->B end-to-end<br/>SPIFFE identity of A presented to B
  B-->>A: encrypted response (via GW)

결정적 결과: TLS 핸드셰이크는 A와 B 사이에서 완성된다. 게이트웨이는 그 핸드셰이크의 ClientHello만 엿보고 바이트를 옮겼을 뿐, 세션 키를 모른다. 따라서 B는 A의 진짜 SPIFFE 신원을 받고, AuthorizationPolicy가 그 신원으로 정상 평가된다 — 게이트웨이가 신원을 “삼키지” 않는다.

2.3 왜 종단하면 안 되나 — 대안과의 대조로 설계를 못 박기

AUTO_PASSTHROUGH의 당위는 그 반대를 그려보면 확정된다. 만약 east-west gateway가 ISTIO_MUTUAL/SIMPLE로 TLS를 종단한다면.

  • 게이트웨이가 A의 mTLS를 풀고 → 자기 신원으로 B에 새 mTLS를 맺는다(2-hop).
  • B가 보는 peer 신원은 east-west gateway의 SPIFFE가 된다 — A의 신원이 사라진다.
  • AuthorizationPolicy from.source.principals: [A]가 깨진다(게이트웨이 신원만 보임).
  • 게이트웨이 내부 평문 구간 노출 + 복호화 비용 발생.

AUTO_PASSTHROUGH는 이 모두를 피한다 — L4 SNI 라우터로만 동작하므로 신원이 게이트웨이를 투명하게 관통한다. 이것이 ingress gateway와 갈리는 본질이다. ingress는 외부→메시 진입이라 종단·L7 라우팅이 목적이지만, east-west는 메시 내부 mTLS를 그대로 다른 network로 운반하는 게 목적이다.

구분 ingress gateway east-west gateway
TLS 처리 종단(복호화), L7 라우팅 AUTO_PASSTHROUGH, SNI만
신원 클라이언트→GW에서 끝, GW가 새 신원으로 mesh 진입 A→B end-to-end 보존
라우팅 키 host/path(L7) SNI 인코딩 문자열(L4)
포트 80/443 등 15443
목적 외부 트래픽 진입 network 간 mTLS 운반

3. 무엇이 이 라우팅을 켜고 끄나 — network 식별자가 방아쇠

위 메커니즘은 istiod가 어떤 endpoint가 어느 network에 있는지 알 때만 성립한다. 그 지식은 세 식별자에서 온다.

식별자 어디서 정의 역할
meshID install values (global.meshID) 여러 클러스터를 하나의 신뢰·메시 단위로 묶음. 같은 meshID + 공유 root CA여야 mTLS 신뢰가 클러스터를 횡단
clusterName install values (global.multiCluster.clusterName) 각 클러스터에 고유 이름 부여. endpoint 출처 식별·메트릭 라벨(source_cluster)에 쓰임
network namespace label topology.istio.io/network + gateway 설정 endpoint가 어느 network에 속하는지. 이게 핵심 스위치

istiod의 endpoint discovery(EDS) 분기는 단순하다 — 이 한 비교가 §2 전체를 켤지 말지 결정한다.

목적지 endpoint의 network == 호출자 sidecar의 network ?
├─ 같다  → endpoint를 pod IP 그대로 내려줌(직접 연결, east-west GW 불필요)
└─ 다르다 → endpoint를 그 network의 east-west GW IP:15443으로 치환 + SNI 인코딩

east-west gateway 경유는 network가 다를 때만 일어난다. 같은 network면(예: flat pod network를 공유하는 멀티클러스터) sidecar가 원격 pod로 직접 mTLS를 맺고 게이트웨이를 안 거친다. 트리거는 클러스터 경계가 아니라 network 경계다. (어떤 endpoint가 sidecar에 보이는가의 일반 메커니즘은 data-plane sync state, 가시성 범위 한정은 sidecar scope.)


4. 떴는지 확인 — SNI 인코딩과 endpoint 치환을 한 번에 본다

클러스터 간 호출이 동작한다면, 호출자 sidecar의 cluster에 §2.1의 두 흔적이 찍혀 있어야 한다. 한 명령으로 둘 다 본다.

istioctl proxy-config cluster deploy/workload-a \
  --fqdn B.ns.svc.cluster.local -o json \
  | jq '.[0] | {name, sni: .transportSocket.typedConfig.sni,
                ep: .loadAssignment.endpoints[0].lbEndpoints[0].endpoint.address.socketAddress}'

기대 출력(멀티클러스터·network 분리가 정상일 때):

{
  "name": "outbound|8080||B.ns.svc.cluster.local",
  "sni": "outbound_.8080_._.B.ns.svc.cluster.local",
  "ep": { "address": "<EW2 gateway IP>", "portValue": 15443 }
}

판정 기준 두 줄:

  • snioutbound_.* 인코딩이면 ✓ — SNI 라우팅용 cluster가 맞다. 평범한 hostname이면 멀티클러스터 EDS가 안 걸린 것.
  • ep.address가 원격 GW IP, portValue15443이면 ✓ — endpoint 치환 성공. 여전히 원격 pod IP면 network label(topology.istio.io/network) 미설정이다.

신원이 정말 보존됐는지는 도착지에서 본다: B의 AuthorizationPolicy 로그/메트릭에서 peer principal이 A의 SPIFFE(게이트웨이가 아니라)로 찍히면 end-to-end가 살아있다는 증거다.


정리

한 문장 멘탈 모델: east-west gateway는 편지를 열지 않는다 — sidecar가 봉투 겉면(SNI)에 적은 목적지만 읽어 암호 바이트를 그대로 다음 hop으로 넘기는 L4 라우터이고, 그래서 A↔B mTLS 신원이 게이트웨이를 투명하게 관통한다.

핵심 정리

  • east-west gateway는 멀티클러스터(network 분리 시)의 클러스터 간 진입점이며, mTLS를 절대 종단하지 않는 L4 SNI 라우터다.
  • sidecar는 목적지를 SNI에 outbound_.PORT_.SUBSET_.FQDN으로 인코딩하고, endpoint는 원격 east-west gateway IP:15443으로 치환된다. SNI 문자열 = cluster 이름의 평탄화 — 그래서 게이트웨이의 역매핑이 문자열 변환 한 번이다.
  • 게이트웨이는 AUTO_PASSTHROUGH(= tls_inspector로 SNI만 추출) + sni-dnat(Gateway의 tls.mode: AUTO_PASSTHROUGH 선언만으로 istiod가 1.11부터 자동 생성 — ROUTER_MODE 환경변수 불필요)로 그 SNI를 동일 이름 내부 cluster로 역매핑해 암호 바이트를 그대로 tcp_proxy한다.
  • TLS 핸드셰이크는 A↔B 사이에서 완성 → A의 SPIFFE 신원이 B까지 보존 → AuthorizationPolicy 정상 평가.
  • 분기 스위치는 network 식별자(topology.istio.io/network): 같으면 직접 연결, 다르면 east-west GW 경유. meshID(신뢰 단위)·clusterName(출처)이 이를 뒷받침.

What you might be missing

  • east-west GW 경유는 network 분리 시에만. 같은 flat network를 공유하는 멀티클러스터(primary-remote with shared network)는 sidecar가 원격 pod로 직접 가고 게이트웨이를 안 거친다. “멀티클러스터면 무조건 east-west GW"가 아니다 — 트리거는 클러스터 경계가 아니라 network 경계다.
  • 공유 root CA가 전제. SNI 인코딩·passthrough가 다 동작해도, 두 클러스터가 같은 trust domain의 공유 root CA를 안 쓰면 A의 SVID를 B가 검증 못 해 핸드셰이크가 깨진다. east-west 라우팅은 신뢰 체인을 운반할 뿐 생성하지 않는다(SPIFFE/mTLS 신원).
  • passthrough라 L7 관측이 0. 게이트웨이가 복호화를 안 하므로 클러스터 간 트래픽은 게이트웨이에서 istio_tcp_*(L4)만 잡히고 method/path/status(istio_requests_total)는 출발지/도착지 sidecar에서만 보인다. 멀티클러스터 트래픽을 게이트웨이 메트릭으로 L7 분석하려 하면 빈손이다.
  • SNI 인코딩 디버깅. 클러스터 간 호출이 안 되면 §4의 cluster dump로 (1) endpoint가 원격 GW IP:15443인지, (2) transportSocket.snioutbound_.* 형태인지 먼저 확인하라. endpoint가 여전히 원격 pod IP면 network label 미설정, SNI가 평범한 hostname이면 멀티클러스터 EDS가 안 걸린 것이다.
  • ROUTER_MODE — 옛 이야기다(1.11 이전). east-west gateway와 일반 게이트웨이를 가르는 결정적 차이는 더 이상 ISTIO_META_ROUTER_MODE=sni-dnat 환경변수가 아니다 — 이건 Istio 1.11(2021-08 릴리스) 이전 기준이다. 1.11부터 istiod는 Gateway 리소스의 tls.mode: AUTO_PASSTHROUGH 선언만 보고 필요한 passthrough cluster를 자동 생성하도록 개선됐고, 이후 ISTIO_META_ROUTER_MODE 변수 자체가 코드베이스에서 제거됐다(istio/istio#34848, PR #37508). 1.30 기준으로 east-west gateway를 일반 ingress gateway와 가르는 결정적 차이는 ROUTER_MODE 환경변수가 아니라 (1) Gateway 리소스의 tls.mode: AUTO_PASSTHROUGH 선언과 (2) 게이트웨이 Service의 topology.istio.io/network 레이블(어느 network의 게이트웨이인지 지정)이다. istioctl x create-remote-secret는 원격 클러스터 API 서버 접근용 kubeconfig 자격증명을 담은 Secret을 생성하는 명령일 뿐, 게이트웨이의 라우터 모드와는 관심사 자체가 다르다(원격 API 서버 인증 vs 게이트웨이 라우팅 모드) — 멀티클러스터 install이 “ROUTER_MODE를 깔아준다"는 서술도 1.11 이후로는 대상이 사라진 진술이다.

검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6)

검증 방법: 공식 문서(Istio/Envoy) 대조 + homelab 클러스터 실측(단일 k8s 클러스터 안에서 topology.istio.io/network 레이블만 다르게 준 echo-netb/crossnet-gw-ews로 “다른 network"를 시뮬레이션, T45).

주장 판정 근거
east-west gateway는 mTLS를 종단하지 않는 L4 SNI 라우터(AUTO_PASSTHROUGH)이며 TLS 핸드셰이크가 A~B 사이에서 완성돼 SPIFFE 신원이 투명하게 보존된다 ✅ 실측 확인 istio.io/…/networking/gateway/ · T45 실측
라우팅 스위치는 클러스터 경계가 아니라 network 경계(topology.istio.io/network)다 ✅ 실측 확인 istio.io/…/reference/config/labels/ · T45 실측
east-west 경로용 cluster는 SNI를 outbound_.PORT_.SUBSET_.FQDN으로 채우고, 같은 network의 평범한 cluster는 hostname/빈 값이다 ✅ 실측 확인 istio.io/…/networking/gateway/ · T45 실측
east-west 경로용 cluster의 endpoint는 원격 pod IP가 아니라 원격 게이트웨이 IP:15443으로 치환된다 ✅ 문헌 확인 istio.io/…/deployment/deployment-models/
AUTO_PASSTHROUGH listener는 tls_inspector로 SNI만 추출하고 TLS 종단·L7 파싱을 하지 않는다 ✅ 문헌 확인 envoyproxy.io/…/tls_inspector
SNI 문자열(outbound_.PORT_.SUBSET_.FQDN)은 Envoy cluster 이름(`outbound PORT SUBSET
east-west gateway가 일반 게이트웨이와 다른 결정적 차이는 ISTIO_META_ROUTER_MODE=sni-dnat 환경변수를 수동 설정해야 한다는 것 ⚠️ 구버전 서술 — 갱신 istio.io/…/1.11.x/announcing-1.11/change-notes/
istioctl x create-remote-secret/멀티클러스터 install이 ISTIO_META_ROUTER_MODE=sni-dnat을 깔아준다 ❌ 오류 — 본문 교정 istio.io/…/multicluster/primary-remote_multi-network/
게이트웨이가 종단(ISTIO_MUTUAL/SIMPLE)하면 B가 보는 peer 신원이 게이트웨이 자신의 SPIFFE로 바뀐다 ✅ 문헌 확인 istio.io/…/concepts/security/
meshID(global.meshID)는 여러 클러스터를 신뢰·메시 단위로 묶으며, 공유 root CA 없이는 mTLS 신뢰가 성립하지 않는다 ✅ 문헌 확인 istio.io/…/multicluster/before-you-begin/
clusterName(global.multiCluster.clusterName)은 endpoint 출처 식별과 메트릭 라벨(source_cluster/destination_cluster)에 쓰인다 ✅ 문헌 확인 istio.io/…/reference/config/metrics/
포트 15443은 east-west gateway TLS auto-passthrough 전용이며 sidecar inbound(15006)·outbound(15001)와 별개다 ✅ 문헌 확인 istio.io/…/deployment/application-requirements/
게이트웨이가 복호화를 안 하므로 클러스터 간 트래픽은 L4 메트릭만 잡히고 L7 메트릭(istio_requests_total)은 게이트웨이에서 관측 불가하다 ✅ 문헌 확인 istio.io/…/reference/config/metrics/
같은 flat network를 공유하는 멀티클러스터는 east-west gateway를 거치지 않고 직접 mTLS를 맺는다 ✅ 문헌 확인 istio.io/…/deployment/deployment-models/

Files