Tech Artifacts/K8s Networking

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 사이드카리스.

생각해 볼 문제

  1. Istiod가 다운됐다. 이미 설정을 받은 사이드카들은 트래픽을 계속 처리하는가? 인증서는?
  2. VirtualService로 v2에 10%를 보냈다. 어떻게 10%가 보장되는가? (Envoy 라우팅 관점, 그리고 소규모 트래픽에서의 함정)
  3. namespace에 istio-injection 라벨을 줬더니 기존 Pod엔 사이드카가 안 붙는다. 왜?
  4. Ambient에서 L7 정책(헤더 기반 라우팅)을 쓰려면 무엇이 추가로 필요한가?
  5. Istio mTLS를 켰다. 외부(Ingress로 들어오는) 트래픽도 mTLS인가?
  6. Cilium 메시가 eBPF로 사이드카 없이 메시를 구현하면, Istio Ambient와 어떻게 다른가?

참고

은 관리자 편집기 위젯 라벨이 됨 -->