개요
사내에서 진행하는 Istio Study 마지막 세션. 내 주요 업무는 클라우드 서비스와 연관되어 있기에, Istio와 클라우드 서비스를 엮어서 마지막 발표를 준비했다. 지금까지는 클러스터 내부에서 Pod 간 통신에서의 Istio/envoy를 살펴봤다면, 이번 글에서는 (클라우드 파트 업무와 엮어서) Istio 외부 즉, Mesh 밖으로 나가는 트래픽을 위주로 학습해보려고 한다.
1. Mesh의 경계는 Service Registry가 정한다.
1.1. Service Registry
Envoy는 istiod가 내려주는 설정 즉, Service Registry에 있는 목적지만 알아본다.
쿠버네티스 Service를 만들면 이 Service Registry라는 목록에 자동으로 올라간다. 그래서 cobinding.demo.svc로 요청하면 Envoy는 "아, cobinding 앱이구나." 하고 이름 붙은 목적지로 라우팅하고 메트릭도 남긴다.
반면 ElastiCache 엔드포인트는 쿠버네티스 Service가 아니어서 목록에 없다. Envoy 입장에서는 정체를 모르는 Endpoint라 그냥 통과 시키고, 메트릭의 목적지도 unknown으로 찍힌다.
1.2. Service Registry에 있는지 확인하는 과정 (패킷이 흘러가는 경로)

virtualOutbound:istio-proxy컨테이너에서 envoy 프로세스가 띄운 리스닝 소켓- virtual! 패킷을 받아 진짜 처리할 리스너로 넘기는 역할만 수행한다.
- 모든 아웃바운드 트래픽이 처음으로 들어오는 리스너다.
- Envoy가 여기로 들어온 패킷에 대해
SO_ORIGINAL_DST로 원래 목적지를 복원하고 이 목적지가 Service Registry에 있는지 조회한다.- 있으면 리스너로 위임
- 없으면 Passthrough
Istio를 사용하면 Service Registry에 등록되어 있지 않은 host 요청은 기본적으로 Passthrough(패쓰쓰루, 트래픽이 지나가긴 하지만 매트릭을 남기진 않음!)라고 하는 Cluster로 인식되며, Passthrough 클러스터는 단순한 TCP Proxy로 동작한다. 즉, 해당 TCP 패킷에 대해 아무런 부가 기능을 수행해주지 않고 그냥 패킷을 전달할 뿐이다. 이 경우 L7인 HTTPS를 바탕으로 한 요청에 대해 Istio는 L4인 TCP 수준의 메트릭밖에 기록하지 못하며, TCP 메트릭 또한 정보가 매우 빈약하게 된다.
정리
- ServiceRegistry
- istiod가 들고 있는 '메시지 목록' 그 자체
- ServiceEntry CRD를 통해 등록
- ServiceEntry
- k8s Service가 아닌 외부 대상을 Service처럼 정의해서, istiod의 Service Registry에 등록하는 Istio의 Custom Resource 중 하나
- 당근에서는 SRE팀이 만든 ServiceEntry Controller를 통해 자동 등록
- istiod가 목적지를 알고 있다면 → 해당 목적지로 패킷 라우팅
- VirtualService, DR이 동작하는 구간
- istiod가 목적지를 모른다면 →
outboundTrafficPolicy.mode가 결정- ALLOW_ANY : PassthroughCluster 그대로 내보냄
- REGISTRY_ONLY : BlackHoleCluster 드롭
2. Mesh를 타지 않는 Outbound: PassthroughCluster와 BlackHoleCluster
apiVersion: networking.istio.io/v1
kind: Sidecar
metadata:
...
outboundTrafficPolicy:
mode: ALLOW_ANY
| Cluster 정의 | ALLOW_ANY | REGISTRY_ONLY |
| 앱 | 평소처럼 동작한다. | HTTP는 502, TCP는 연결이 맺히지 않음 |
| 메트릭 | destination_service_name="PassthroughCluster" | destination_service_name="BlackHoleCluster" |
| 실질적 의미 | 나가긴 하는데 unknown으로 잡혀서 이름이 남지 않음 | 등록하지 않은 목적지는 전부 사고 |

4. ServiceEntry를 통해 목적지에 이름 붙이기
4.1. ServiceEntry가 하는 역할
L4, L7 가시성 확보 방법!
ServiceEntry라는 CRD를 통해 외부 도메인을 명시하면, Passthrough로 빠지지 않고 Istio Proxy는 해당 도메인을 식별 가능한 대상으로 인식하여 트래픽을 제어하고 메트릭을 남길 수 있게 된다.
- 내부 서비스 간 통신은 암호화되지 않은 HTTP(80 포트) 기반 통신을 주로 사용하여 Istio Proxy가 패킷 내부를 쉽게 해석하고 L7 메트릭을 남길 수 있다.
- 그러나, 보안상 대개 HTTPS로 암호화되어 통신하는 외부서비스는,
- L7 가시화가 어렵다.
- 다만, TLS 과정에서의 SNI를 통해 도메인 정보를 얻어올 수 있다!
karin의 ServiceEntry Controller 명강의 😎 (link)

HTTPS는 암호화되어있기 때문에 istio-proxy가 패킷을 뜯어보기 어렵다. 그래서 SNI를 통해 요청 도메인을 알아내고, 목적지에 대한 TCP 연결 정보를 알아 낸다.
4.2. SNI (Server Name Indication)
SNI는 TLS HandShake의 첫 패킷(ClientHello)에 평문으로 실려 나가는 '접속하려는 도메인 이름'이다. 그래서 istio-proxy가 암호는 풀지 못해도 목적지 호스트는 읽을 수 있다.

SNI는 왜 평문인가?
- 한 IP/포트에 여러 도메인이 올라가 있을 때, 서버는 어떤 인증서를 줘야 할지 알아야 한다. 그런데 인증서 교환은 아직 안 끝난 상태 → 암호화 전에 도메인을 먼저 알려줄 수밖에 없다. 그게 SNI.

- HTTP의 Host 헤더와 목적은 같지만, Host는 TLS 안쪽(암호화됨)이고 SNI는 TLS 바깥쪽(평문)
정리
ServiceEntry에 openai.com을 등록해두면 Envoy가 그 SNI에 매칭되는 listener/cluster를 만들어서, 해당 세션을 PassthroughCluster(정체불명 외부 트래픽)가 아니라 이름 붙은 목적지로 기록한다.
참고로 최신 TLS에는 ECH(Encrypted Client Hello)가 있어 SNI까지 암호화되는데, 이게 퍼지면 이 방식의 가시성은 사라진다. 아직은 대부분 평문 SNI라 동작이다.
5. ServiceEntry 작성하는 방법
5.1. 일반적인 방식
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: elasticache-valkey
spec:
hosts:
- my-cache.abc123.ng.0001.apn2.cache.amazonaws.com
location: MESH_EXTERNAL # 메시 밖 서비스
resolution: DNS # 목적지 IP를 사이드카가 직접 해석
ports:
- number: 6379
name: tcp-valkey # 이름의 접두사가 프로토콜을 결정
protocol: TCP
exportTo:
- "." # 이 ServiceEntry가 있는 NS에만 적용
resolution- NONE
- Envoy가 IP를 직접 찾지 않고, 앱이 DNS로 찾은 IP를 그대로 쓴다.
- 와일드카드 호스트(
*.amazonaws.com)를 쓸 수 있고, 가장 무난한 방식이다. (당근 컨트롤러도 이 방식)
- STATIC
endpoints필드에 적은 고정 IP로만 보낸다.- IP가 바뀌지 않는 대상에 맞고, IP가 수시로 바뀌는 AWS 매니지드 서비스에는 맞지 않다.
- DNS
- Envoy가 호스트 이름을 직접 DNS로 조회해서, 나온 IP들로 로드밸런싱 한다.
- 앱이 어떤 IP로 접속하려 했든 Envoy가 자기가 조회한 IP로 보낸다.
- 와일드카드 호스트 x
- NONE
5.2. 당근에서 사용하는 Service Entry Controller (밋업 발표)
당근에서는 CoreDNS 로그 기반으로 이 Entry 등록을 자동화해두었다.
동작 원리
- 내부 Pod들은 통신을 위해 통신하기 전에 반드시 CoreDNS에 질의를 한다.
- 어떤 IP가 어떤 도메인을 질의했는지 로그가 남는다.

- 위 로그를 메트릭으로 변환해서 Thanos에 저장:
log:coredns:...:sum - 이 레코딩룰을 k8s_info 메트릭과 join해서 원하는 정보 GET
⇒ NS별 호출 도메인 목록 집계 ⇒ ServiceEntry 생성!
자동 등록 과정
- NS 조회: istio-proxy가 주입된 NS 중, DNS 요청이 있는 NS 탐색
- 메트릭 조회: 호출 도메인 목록 조회
- 필터링: 내부 도메인은 제외하고 외부 도메인만 선별!
- ServiceEntry: 기존 리소스 확인 후 생성 또는 갱신
한계: 프로토콜별 가시성 차이로 자동화는 443 포트 한정
- HTTPS (DynamoDB, STS 등 AWS API)
- TLS ClientHello에 평문 SNI가 실려 있어서, 같은 443 포트를 쓰는 호스트가 여럿이어도 이름으로 구분된다.
- 그래서 호스트 이름만 모아서 ServiceEntry를 만드는 자동화가 잘 동작한다.
- 평문 TCP (ElastiCache 6379, RDS 3306)
- 전송 중 암호화를 끈 Redis에는 SNI도, Host 헤더도 없다. Envoy가 알 수 있는 건 IP와 포트뿐이다.
- 암호화를 켠 Redis는 처음부터 TLS로 연결하므로 SNI가 있다.
- MySQL은 평문으로 시작했다가 중간에 TLS로 바꾸는 방식이라, 첫 패킷에서 SNI를 읽을 수 없다.
- 그래서 호스트별로 포트와 프로토콜을 정확히 선언해야 Cluster가 생긴다.
- 전송 중 암호화를 끈 Redis에는 SNI도, Host 헤더도 없다. Envoy가 알 수 있는 건 IP와 포트뿐이다.
6. 결론: Mesh밖 클라우드 서비스와의 통신
Mesh 밖으로 나가는 트래픽을 Istio가 어디까지 다룰 수 있는지 정리하면 다음과 같다.
- Service Registry에 없는 목적지
ALLOW_ANY면 PassthroughCluster로 그대로 나가고, 목적지는unknown으로 남는다.REGISTRY_ONLY면 BlackHoleCluster로 막힌다.
- ServiceEntry로 등록한 목적지
- Passthrough로 빠지지 않고, 이름 붙은 목적지로 라우팅되며 지표가 남는다.
- 다만 Envoy가 그 이름을 알아볼 수 있는지는 프로토콜에 달려 있다.
- HTTPS (일반 요청, DDB나 STS와 같은 API 등) : 평문 SNI 덕분에 같은 포트에서도 호스트별로 구분된다.
- 평문 TCP (Redis, MySQL 등) : 이름 정보가 없어서 IP와 포트로만 구분된다. 포트와 프로토콜을 정확히 선언해야 한다.
'DevOps > DevOps' 카테고리의 다른 글
| [AWS] VPC CNI와 Node/Pod IP 할당, Subnet IP 소비 이해하기 (0) | 2026.10.11 |
|---|---|
| [Istio] GW/VS/DR을 apply하면 Envoy에는 무슨 일이 생길까? (1) | 2026.08.04 |
| [Istio] Service Mesh를 탈 때/타지 않을 때 요청 흐름 비교 (0) | 2026.07.21 |
| [Istio] Sidecar (Native, Ambient) (0) | 2026.07.07 |
| [Istio] Service Mesh와 Istio (0) | 2026.07.05 |