K8s Networking - 11. istio
Istiod가 수천 개의 Envoy를 어떻게 조율하는가 — 그리고 Ambient가 바꾸는 것
한 팀이 Istio를 도입했다. 500개의 Pod에 사이드카(Envoy)가 하나씩 들어갔다. 그 500개의 Envoy에게 "서비스 간 mTLS, 카나리 규칙, 분산 추적"을 어떻게 일관되게 적용할까? 사람이 500대를 돌며 설정할 수는 없다. Istiod가 그 조율을 담당한다 — 컨트롤플레인이 500개의 사이드카를 watch 모델로 지휘한다. 그런데 최근 Istio에 Ambient mesh라는 사이드카 없는 모드가 들어왔다. 이것이 메시의 판도를 어떻게 바꾸나?
이 글이 푸는 것은: Istio의 아키텍처(Istiod + Envoy 사이드카), 트래픽 관리 객체(VirtualService/DestinationRule), 그리고 Ambient 모드가 왜 사이드카 모델을 재검토하게 했는가다.
10장의 컨트롤플레인이 Istio에선 Istiod다
10장에서 서비스 메시가 "데이터플레인(사이드카) + 컨트롤플레인(정책·인증서 배포)" 두 층이라고 했다. Istio에서 그 컨트롤플레인이 Istiod다. (Istio docs - Architecture)
flowchart TD
ISTIOD["Istiod (컨트롤플레인)<br/>설정·인증서·디스커버리"] -. xDS 배포 .-> E1["Envoy 사이드카 (Pod1)"]
ISTIOD -. xDS .-> E2["Envoy 사이드카 (Pod2)"]
ISTIOD -. 인증서 발급 .-> E1
ISTIOD -. 인증서 .-> E2
E1 == mTLS ==> E2
Istiod가 담당하는 세 가지:
- 서비스 디스커버리 — 클러스터에 어떤 서비스/Pod가 있는지 파악.
- 설정 배포(xDS 프로토콜) — VirtualService/DestinationRule 같은 트래픽 규칙을 사이드카에 밀어 넣음.
- 인증서 발급(CA) — mTLS용 인증서를 각 서비스에 발급·갱신.
이것이 03장 list-watch와 같은 패턴이다 — "중앙이 결정, 분산이 구독". 차이는 Kubernetes 컨트롤러는 etcd revision 기반 watch를 쓰고, Istiod는 Envoy 전용 프로토콜인 xDS(gRPC 스트림)를 쓴다는 것. 결과는 같다 — 500개 사이드카가 일관되게 갱신된다.
사이드카 자동 주입 — namespace 라벨 하나로
10장에서 사이드카가 iptables로 트래픽을 가로챈다고 했다. 그 사이드카는 어떻게 Pod에 들어가나? 수동으로 각 Pod에 넣는 게 아니다. Istio는 namespace에 라벨(istio-injection=enabled)을 주면, 그 namespace의 Pod에 자동으로 istio-proxy(Envoy) 사이드카를 주입한다(Webhook 어드미션 컨트롤러). (Istio docs - Sidecar injection)
# Kubernetes 1.36
kubectl label namespace default istio-injection=enabled
kubectl deploy web --image=nginx:1.27 # 이제 Pod에 Envoy 사이드카가 자동 포함
Pod의 컨테이너가 2개(앱 + istio-proxy)로 늘어난다 — 04장 사이드카 패턴. 이미 떠 있는 Pod에는 라벨을 줘도 사이드카가 안 붙는다(주입은 Pod 생성 시점에만). 재생성해야 적용.
VirtualService와 DestinationRule — 트래픽 제어의 두 객체
Istio의 트래픽 제어는 두 CRD로 나뉜다. 이 분리를 처음에 이해하면 Istio 설정이 보인다:
- VirtualService: 어떻게 라우팅할까 — 경로/헤더 기반 매칭, 가중치 분배, 카나리 규칙.
- DestinationRule: 라우팅 대상의 속성 — 로드밸런싱 정책, 서브셋(v1/v2 같은 버전 그룹), mTLS 모드.
(Istio docs - Traffic Management)
# VirtualService — v2에 10% 카나리
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata: {name: app-vs}
spec:
hosts: [app]
http:
- route:
- destination: {host: app, subset: v1, weight: 90}
- destination: {host: app, subset: v2, weight: 10} # 카나리 10%
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: {name: app-dr}
spec:
host: app
subsets:
- name: v1
labels: {version: "1"} # version=1 라벨 Pod 그룹
- name: v2
labels: {version: "2"}
trafficPolicy: {loadBalancer: {simple: LEAST_REQUEST}}
이 두 객체로 "v2에 10% 카나리, 최소요청 로드밸런싱"을 선언한다. Envoy 사이드카들이 이 규칙을 받아 실제 분배. 분리의 의미: "라우팅 결정(VS)"과 "대상 속성(DR)"을 나눠 재사용성을 높인다 — 하나의 DR을 여러 VS가 참조 가능.
xDS — Istiod가 500개 Envoy에 설정을 배포하는 프로토콜
도입의 질문으로 돌아가자. 500개의 Envoy에게 어떻게 일관되게 설정을? 핵심은 xDS 프로토콜이다. (Envoy xDS)
- Envoy가 Istiod에 구독(subscribe) — "이 서비스의 리스너/라우팅/클러스터를 알려줘".
- Istiod가 VirtualService/DestinationRule을 Envoy가 이해하는 xDS 메시지로 번역해 밀어 넣음.
- 설정이 바뀌면 Istiod가 새 xDS를 보내고, Envoy가 핫 리로드 (재시작 없이 설정 갱신).
flowchart LR
VS["VirtualService YAML<br/>(사용자 작성)"] --> ISTIOD["Istiod"]
ISTIOD --> COMPILE["xDS 번역<br/>(LDS/RDS/CDS/EDS)"]
COMPILE -. grpc 스트림 .-> E1["Envoy1 (Pod1)"]
COMPILE -. grpc 스트림 .-> E2["Envoy2 (Pod2)"]
xDS의 네 가지: LDS(리스너), RDS(라우팅), CDS(클러스터), EDS(엔드포인트). VirtualService가 RDS로, DestinationRule이 CDS로 번역되는 식이다. 이 매핑을 알면 "VirtualService를 고쳤는데 왜 안 될까"를 Envoy 설정 레벨에서 디버깅할 수 있다(istioctl proxy-config).
가중치 분배는 어떻게 보장되나
VirtualService로 v2에 10%를 보낸다고 했다. Envoy가 어떻게 10%를 보장하는가? 핵심은 Envoy의 라우팅 규칙: 매 요청마다 가중치(90/10) 기반 무작위 선택을 한다. 장기 평균은 90/10에 수렴하지만 — 단기(소수 요청)는 편차가 있다. 10개 요청 중 정확히 1개가 v2가 아닐 수 있다.
이것이 카나리 배포의 미묘함 — "10% 카나리"가 트래픽 양에 따라 실제 분배가 다르다. 소규모에선 더 많은 트래픽이 필요해야 통계적으로 의미 있는 10%가 됨. 이것을 모르면 "카나리 켰는데 안 보인다"는 혼란이 생긴다.
mTLS — Istio가 자동으로
10장에서 mTLS가 메시의 가장 가시적 이점이라고 했다. Istio 모드를 켜면 서비스 간 트래픽이 기본적으로 mTLS다. Istiod가 각 서비스의 인증서를 발급/갱신(자동), Envoy가 상호 인증. 앱 코드는 한 줄도 안 바뀐다. (Istio docs - Security)
istioctl authn tls-check app.default.svc.cluster.local
확인할 것: mTLS 적용 여부. Istio 설치 시.
Ambient mesh — 사이드카 비용을 재검토한 모드
10장에서 사이드카 메시의 비용(자원/지연/복잡성)을 봤다. Ambient mesh는 이 비용을 재검토한다 — 사이드카 대신 노드 수준에서 처리한다. (Istio docs - Ambient)
flowchart TD
ZT["ztunnel (노드당, DaemonSet)<br/>L4 mTLS만"] --> P1["Pod1 (사이드카 없음)"]
ZT --> P2["Pod2"]
WP["waypoint (선택, namespace당)<br/>L7 처리"] -. L7 필요 시 .-> P1
- ztunnel: 노드마다 하나(DaemonSet). L4 수준 mTLS만 담당. Pod에 컨테이너가 없다 — 노드 수준에서 트래픽 암호화.
- waypoint: L7(HTTP 경로/헤더) 정책이 필요한 namespace에 추가. Envoy 기반이지만 Pod마다*가 아니라 *namespace당 1개.
이점: 자원 오버헤드 감소, Pod 재생성 없이 메시 적용(L4 mTLS만). 단 L7 고급 기능은 waypoint가 필요해 상황에 따라 복잡. Ambient는 Cilium 메시와 구조적으로 비슷하다 — L4는 노드 수준(ztunnel/eBPF), L7은 별도(waypoint/노드 Envoy). 둘 다 "사이드카 없는 메시"를 추구하되 경로가 다르다.
Ambient의 GA 상태/세부 지원은 Istio docs 실측. 1.36 호환 Istio 버전 명시. Ambient가 사이드카를 완전히 대체한 건 아님 — 전환 진행 중이며 둘 다 지원.
Istio 디버깅 — proxy-config와 proxy-status
사이드카 메시는 "보이지 않는 층"이라 디버깅이 어렵다. Istio가 제공하는 관측 도구:
istioctl proxy-status # 각 사이드카가 Istiod와 동기화됐나?
istioctl proxy-config cluster <pod> # 이 Envoy가 아는 클러스터
istioctl proxy-config route <pod> # 라우팅 규칙
istioctl proxy-config listener <pod> # 리스너
이 명령들이 "VirtualService를 고쳤는데 왜 안 될까"를 Envoy 설정 레벨에서 보게 해 준다. "내가 쓴 VS가 실제 Envoy에 반영됐나?"를 직접 확인. 이것이 사이드카 메시 디버깅의 첫 단계 — 선언(YAML)과 실제(Envoy)의 일치 여부.
Istio vs Linkerd vs Cilium 메시 — 대략 지형
| Istio | Linkerd | Cilium 메시 | |
|---|---|---|---|
| 데이터플레인 | Envoy(사이드카) / ztunnel(Ambient) | 자체 경량 프록시(사이드카) | eBPF |
| 기능 풍부도 | 가장 풍부 | 단순/경량 | eBPF 기반 |
| 복잡성 | 높음 | 낮음 | 중 |
Istio는 "기능 풍부 but 복잡". Linkerd는 "단순/경량 우선". Cilium은 "eBPF로 사이드카 비용 제거". 선택은 요구사항과 조직 숙련도.
직접 확인하기 (Istio 설치 필요)
# Kubernetes 1.36 — Istio 설치 후 사이드카 주입
istioctl install --set profile=demo
kubectl label namespace default istio-injection=enabled
kubectl deploy web --image=nginx:1.27
kubectl get pod <web-pod> -o jsonpath='{.spec.containers[*].name}{"\n"}'
확인할 것: web istio-proxy — 앱 + Envoy 사이드카 두 컨테이너.
istioctl analyze # 설정 검증
istioctl proxy-status # 각 사이드카 동기화 상태
Istio CLI 별도 설치. kind 클러스터에서 동작.
k8s-verify스킬 참조.
흔히 묻는 것, 흔히 틀리는 것
| 오해 | 정정 |
|---|---|
| "Istio는 쿠버네티스 일부다" | 별도 프로젝트. 설치해야 |
| "사이드카는 수동으로 넣는다" | namespace 라벨로 자동 주입(Webhook) |
| "VirtualService와 Service(K8s)는 같다" | 다름. VS는 Istio 트래픽 규칙. K8s Service는 진입점 |
| "Ambient가 사이드카를 완전히 없앴다" | L4(mTLS)는 ztunnel로. L7은 waypoint 옵션. 완전 제거 아님 |
| "mTLS는 Istio만 된다" | Linkerd/Cilium도. Istio가 가장 널리 쓰일 뿐 |
| "Istiod가 다운되면 트래픽이 멈춘다" | 멈추진 않음(이미 배포된 설정으로 사이드카 동작). 단 갱신/인증서 발급 정지 |
요약 — 이 글의 결론
- Istio = Istiod(컨트롤플레인) + Envoy 사이드카(데이터플레인). 10장의 메시 컨트롤플레인이 Istio에선 Istiod. xDS로 설정·인증서를 배포, 사이드카가 구독. 03장 list-watch와 같은 "중앙 결정-분산 watch" 패턴.
- 사이드카 자동 주입(namespace 라벨 + Webhook). Pod에 Envoy가 자동 포함. 단 Pod 생성 시점에만 — 기존 Pod는 재생성 필요.
- VirtualService(라우팅 규칙) + DestinationRule(대상 속성/서브셋)로 트래픽 제어. 카나리/가중치를 선언적으로. 가중치는 장기 평균 수렴이지 정확한 절대 수가 아님.
- mTLS가 기본 — Istiod가 인증서 발급, Envoy가 상호 인증. 앱 코드 한 줄 안 바뀜.
- Ambient mesh가 사이드카 모델을 재검토 — 노드 수준 ztunnel(L4) + 옵션 waypoint(L7). 자원 비용 감소. 단 완전 대체 진행 중. Cilium 메시와 같은 "사이드카리스" 방향.
- 디버깅은 proxy-config/proxy-status로 "선언(YAML) vs 실제(Envoy)" 일치 확인.
- Istio vs Linkerd vs Cilium: 풍부/복잡(Istio), 단순/경량(Linkerd), eBPF 사이드카리스.
생각해 볼 문제
- Istiod가 다운됐다. 이미 설정을 받은 사이드카들은 트래픽을 계속 처리하는가? 인증서는?
- VirtualService로 v2에 10%를 보냈다. 어떻게 10%가 보장되는가? (Envoy 라우팅 관점, 그리고 소규모 트래픽에서의 함정)
- namespace에 istio-injection 라벨을 줬더니 기존 Pod엔 사이드카가 안 붙는다. 왜?
- Ambient에서 L7 정책(헤더 기반 라우팅)을 쓰려면 무엇이 추가로 필요한가?
- Istio mTLS를 켰다. 외부(Ingress로 들어오는) 트래픽도 mTLS인가?
- Cilium 메시가 eBPF로 사이드카 없이 메시를 구현하면, Istio Ambient와 어떻게 다른가?
참고
- Istio docs - Architecture - 접근 2026-07-13 (Istiod/Envoy)
- Istio docs - VirtualService/DestinationRule - 접근 2026-07-13
- Istio docs - Ambient mesh - 접근 2026-07-13 (사이드카 없는 모드)
- Istio docs - Sidecar injection - 접근 2026-07-13
- Istio docs - Security (mTLS) - 접근 2026-07-13
- Envoy xDS - 접근 2026-07-13 (설정 배포 프로토콜)
'Tech Artifacts > K8s Networking' 카테고리의 다른 글
| K8s Networking - 13. troubleshooting (0) | 2026.07.14 |
|---|---|
| K8s Networking - 12. cilium-service-mesh (0) | 2026.07.14 |
| K8s Networking - 10. service-mesh concepts (0) | 2026.07.14 |
| K8s Networking - 09. gateway-api (0) | 2026.07.14 |
| K8s Networking - 08. ingress (1) | 2026.07.14 |