Skip to content
Published on

Istio 아키텍처 내부 분석: 컨트롤 플레인과 데이터 플레인

Authors

들어가며

Istio는 Kubernetes 환경에서 가장 널리 사용되는 서비스 메시입니다. 하지만 "VirtualService를 만들면 트래픽이 라우팅된다"는 수준을 넘어, 내부에서 실제로 어떤 일이 일어나는지 이해하는 것은 운영과 트러블슈팅에 큰 차이를 만듭니다.

이 글에서는 Istio의 컨트롤 플레인과 데이터 플레인이 어떻게 상호작용하고, 사용자가 정의한 CRD가 어떻게 Envoy 구성으로 변환되는지 내부 메커니즘을 분석합니다.

Istio 아키텍처 개요

Istio는 크게 두 부분으로 구성됩니다:

┌─────────────────────────────────────────────────┐
Control Plane│  ┌───────────────────────────────────────────┐  │
│  │              istiod                        │  │
│  │  ┌─────────┐ ┌─────────┐ ┌──────────┐   │  │
│  │  │  Pilot  │ │ Citadel │ │  Galley  │   │  │
│  │    (xDS)  (CA)(Validate)│   │  │
│  │  └─────────┘ └─────────┘ └──────────┘   │  │
│  └───────────────────────────────────────────┘  │
├─────────────────────────────────────────────────┤
Data Plane│  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│  │ App + EP │  │ App + EP │  │ App + EP │     │
 (Pod A) (Pod B) (Pod C)  │     │
│  └──────────┘  └──────────┘  └──────────┘     │
EP = Envoy Proxy (istio-proxy sidecar)└─────────────────────────────────────────────────┘

istiod: 통합된 컨트롤 플레인

역사적 배경

Istio 1.5 이전에는 Pilot, Citadel, Galley, Mixer가 각각 별도의 마이크로서비스로 배포되었습니다. Istio 1.5부터 이들이 istiod라는 단일 바이너리로 통합되었고, Mixer는 1.8에서 완전히 제거되었습니다.

Pilot: 트래픽 관리 엔진

Pilot은 istiod의 핵심으로, 다음 역할을 수행합니다:

  1. 서비스 디스커버리: Kubernetes API 서버를 감시하여 Service, Endpoint, Pod 변경을 추적
  2. 구성 변환: Istio CRD(VirtualService, DestinationRule 등)를 Envoy 구성으로 변환
  3. xDS 서버: 변환된 구성을 gRPC 스트림으로 각 Envoy 프록시에 푸시
Kubernetes API Server
   ┌─────────┐
Pilot  │ ← Istio CRD 감시 (VirtualService, DestinationRule, Gateway...)
   │         │ ← Kubernetes 리소스 감시 (Service, Endpoints, Pod...)
   └────┬────┘
xDS (gRPC stream)
   ┌─────────┐
Envoy  │ ← LDS, RDS, CDS, EDS, SDS 수신
   └─────────┘

Citadel: 인증서 관리

Citadel(현재 istiod에 통합)은 메시 내 워크로드의 아이덴티티와 인증서를 관리합니다:

  • CA(Certificate Authority) 역할 수행
  • 각 워크로드에 X.509 인증서 발급
  • SPIFFE 표준 기반 아이덴티티 부여
  • 인증서 자동 로테이션 (기본 24시간)

Galley: 구성 검증

Galley는 Istio 구성의 유효성을 검증합니다:

  • Kubernetes Admission Webhook을 통한 구성 검증
  • CRD 스키마 검증
  • 구성 간 참조 무결성 확인 (예: VirtualService가 참조하는 Gateway 존재 여부)

Envoy 사이드카 주입 메커니즘

MutatingWebhookConfiguration

Istio의 사이드카 주입은 Kubernetes의 MutatingAdmissionWebhook을 활용합니다:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-sidecar-injector
webhooks:
  - name: sidecar-injector.istio.io
    namespaceSelector:
      matchLabels:
        istio-injection: enabled
    rules:
      - apiGroups: ['']
        apiVersions: ['v1']
        operations: ['CREATE']
        resources: ['pods']

파드가 생성될 때 다음 과정이 진행됩니다:

1. kubectl apply -f deployment.yaml
2. Kubernetes API Server가 Admission Webhook 호출
3. istiod의 Sidecar Injector가 파드 스펙 수정
4. 수정된 파드 스펙이 반환
5. 수정된 스펙으로 파드 생성

주입되는 컨테이너

사이드카 주입 시 두 개의 컨테이너가 추가됩니다:

1. istio-init (Init Container)

initContainers:
  - name: istio-init
    image: proxyv2
    command:
      - istio-iptables
      - '-p'
      - '15001' # Envoy outbound 포트
      - '-z'
      - '15006' # Envoy inbound 포트
      - '-u'
      - '1337' # istio-proxy UID (이 UID의 트래픽은 리다이렉트 제외)
      - '-m'
      - 'REDIRECT'
    securityContext:
      capabilities:
        add: ['NET_ADMIN', 'NET_RAW']

istio-init 컨테이너는 iptables 규칙을 설정하여 모든 인바운드/아웃바운드 트래픽을 Envoy 프록시로 리다이렉트합니다.

2. istio-proxy (Sidecar Container)

containers:
  - name: istio-proxy
    image: proxyv2
    ports:
      - containerPort: 15090 # Prometheus 메트릭
      - containerPort: 15021 # 헬스 체크
    env:
      - name: ISTIO_META_CLUSTER_ID
        value: 'Kubernetes'
      - name: PILOT_CERT_PROVIDER
        value: 'istiod'

iptables 트래픽 리다이렉트

istio-init이 설정하는 iptables 규칙의 흐름:

[인바운드 트래픽]
외부 → Pod IP:Port
  → iptables PREROUTING
ISTIO_INBOUND 체인
REDIRECT to 15006 (Envoy inbound listener)
Envoy가 처리 후 localhost:AppPort로 전달

[아웃바운드 트래픽]
App → 외부 서비스 IP:Port
  → iptables OUTPUT
ISTIO_OUTPUT 체인
REDIRECT to 15001 (Envoy outbound listener)
Envoy가 처리 후 실제 목적지로 전달

[예외]
UID 1337 (istio-proxy)의 트래픽은 리다이렉트 제외 → 무한 루프 방지

xDS 프로토콜 상세

xDS(x Discovery Service)는 Envoy가 동적으로 구성을 수신하는 API 프로토콜입니다.

xDS API 종류

API전체 이름역할
LDSListener Discovery Service리스너 구성 (포트, 프로토콜)
RDSRoute Discovery ServiceHTTP 라우팅 규칙
CDSCluster Discovery Service업스트림 클러스터 정의
EDSEndpoint Discovery Service클러스터 내 실제 엔드포인트 목록
SDSSecret Discovery ServiceTLS 인증서 및 키

구성 푸시 흐름

[1] 사용자가 VirtualService 생성
[2] Pilot이 Kubernetes API 감시를 통해 변경 감지
[3] Pilot이 VirtualService를 Envoy RDS 구성으로 변환
[4] 관련 CDS, EDS 구성도 함께 생성
[5] gRPC 스트림으로 해당 워크로드의 Envoy에 푸시
[6] Envoy가 새 구성을 핫 리로드 (연결 끊김 없음)

ADS (Aggregated Discovery Service)

Istio는 ADS를 사용하여 모든 xDS 응답을 단일 gRPC 스트림으로 통합합니다. 이는 구성 일관성을 보장합니다:

  • CDS와 EDS 간의 순서 보장 (클러스터 정의 먼저, 엔드포인트 나중)
  • LDS와 RDS 간의 순서 보장
  • 원자적 구성 업데이트

구성 확인 방법

# 특정 파드의 Envoy 리스너 확인
istioctl proxy-config listeners PODNAME.NAMESPACE

# 라우트 구성 확인
istioctl proxy-config routes PODNAME.NAMESPACE

# 클러스터 구성 확인
istioctl proxy-config clusters PODNAME.NAMESPACE

# 엔드포인트 확인
istioctl proxy-config endpoints PODNAME.NAMESPACE

# 전체 Envoy 구성 덤프
istioctl proxy-config all PODNAME.NAMESPACE -o json

Envoy 필터 체인 아키텍처

Envoy 프록시는 계층적 필터 체인으로 요청을 처리합니다:

[요청 흐름]

Listener (포트 바인딩)
Filter Chain (일치하는 필터 체인 선택)
    ├── Network Filters
    │   ├── TCP Proxy Filter (L4)
    │   └── HTTP Connection Manager (L7)
    │       │
    │       ├── HTTP Filters
    │       │   ├── RBAC Filter (인가)
    │       │   ├── JWT Authn Filter (인증)
    │       │   ├── Fault Injection Filter
    │       │   ├── CORS Filter
    │       │   ├── Stats Filter (메트릭)
    │       │   └── Router Filter (최종 라우팅)
    │       │
    │       └── Route Configuration
    │           ├── Virtual Host 선택
    │           └── Route 매칭 및 Cluster 결정
Cluster (업스트림 선택)
Endpoint (실제 대상 파드)

Listener 구조

Listener 0.0.0.0:15006 (Inbound)
├── FilterChain: App 포트 (: 8080)
│   ├── TLS Inspector
│   ├── HTTP Connection Manager
│   │   ├── istio_authn filter
│   │   ├── envoy.filters.http.rbac
│   │   └── envoy.filters.http.router
│   └── Route: inbound|8080|http|service.ns.svc.cluster.local
└── FilterChain: Default (passthrough)

Listener 0.0.0.0:15001 (Outbound)
├── FilterChain: 서비스별 매칭
│   ├── HTTP Connection Manager
│   │   ├── envoy.filters.http.fault
│   │   ├── envoy.filters.http.cors
│   │   ├── istio.stats
│   │   └── envoy.filters.http.router
│   └── Route: 서비스별 VirtualHost
└── FilterChain: PassthroughCluster (매칭되지 않는 트래픽)

워크로드 아이덴티티: SPIFFE

SPIFFE ID 체계

Istio는 SPIFFE(Secure Production Identity Framework For Everyone) 표준을 사용합니다:

spiffe://TRUST_DOMAIN/ns/NAMESPACE/sa/SERVICE_ACCOUNT

예시:
spiffe://cluster.local/ns/production/sa/frontend

인증서 발급 흐름 (CSR Flow)

[1] istio-agent (파드 내)가 키 쌍 생성
[2] CSR(Certificate Signing Request) 생성
[3] CSR을 istiod에 전송 (gRPC, 이미 부트스트랩 토큰으로 인증)
[4] istiod가 CSR 검증:
    - ServiceAccount 토큰 유효성
    - 네임스페이스 소속 확인
[5] istiod의 CAX.509 인증서 서명
[6] 서명된 인증서를 istio-agent에 반환
[7] istio-agent가 SDS를 통해 Envoy에 인증서 전달
[8] Envoy가 mTLS에 인증서 사용

SDS (Secret Discovery Service)

인증서는 파일 시스템이 아닌 SDS를 통해 Envoy에 전달됩니다:

  • istio-agent가 로컬 SDS 서버 역할
  • Envoy가 SDS API로 인증서 요청
  • 인증서 로테이션 시 Envoy 재시작 불필요
  • 인증서가 디스크에 기록되지 않아 보안성 향상

구성 변환: Istio CRD에서 Envoy 구성으로

VirtualService 변환 예시

사용자가 정의한 VirtualService:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: reviews-route
spec:
  hosts:
    - reviews
  http:
    - match:
        - headers:
            end-user:
              exact: jason
      route:
        - destination:
            host: reviews
            subset: v2
    - route:
        - destination:
            host: reviews
            subset: v1

이것이 Envoy RDS 구성으로 변환됩니다:

{
  "name": "reviews.default.svc.cluster.local:9080",
  "virtual_hosts": [
    {
      "name": "reviews.default.svc.cluster.local:9080",
      "domains": ["reviews.default.svc.cluster.local"],
      "routes": [
        {
          "match": {
            "prefix": "/",
            "headers": [
              {
                "name": "end-user",
                "string_match": {
                  "exact": "jason"
                }
              }
            ]
          },
          "route": {
            "cluster": "outbound|9080|v2|reviews.default.svc.cluster.local"
          }
        },
        {
          "match": {
            "prefix": "/"
          },
          "route": {
            "cluster": "outbound|9080|v1|reviews.default.svc.cluster.local"
          }
        }
      ]
    }
  ]
}

DestinationRule 변환 예시

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: reviews-destination
spec:
  host: reviews
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100

이것은 Envoy CDS 구성으로 변환됩니다:

{
  "name": "outbound|9080|v1|reviews.default.svc.cluster.local",
  "type": "EDS",
  "eds_cluster_config": {
    "service_name": "outbound|9080|v1|reviews.default.svc.cluster.local"
  },
  "circuit_breakers": {
    "thresholds": [
      {
        "max_connections": 100
      }
    ]
  },
  "transport_socket": {
    "name": "envoy.transport_sockets.tls",
    "typed_config": {
      "common_tls_context": {
        "tls_certificate_sds_secret_configs": [
          {
            "name": "default",
            "sds_config": {
              "api_config_source": {
                "api_type": "GRPC",
                "grpc_services": [
                  {
                    "envoy_grpc": {
                      "cluster_name": "sds-grpc"
                    }
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}

구성 동기화 및 디버깅

proxy-status로 동기화 상태 확인

$ istioctl proxy-status
NAME                    CDS    LDS    EDS    RDS    ECDS   ISTIOD
frontend-v1-xxx.prod    SYNCED SYNCED SYNCED SYNCED        istiod-abc
reviews-v1-yyy.prod     SYNCED SYNCED SYNCED SYNCED        istiod-abc
ratings-v1-zzz.prod     STALE  SYNCED SYNCED SYNCED        istiod-abc

상태 코드:

  • SYNCED: 프록시가 최신 구성을 받음
  • NOT SENT: istiod가 아직 구성을 보내지 않음
  • STALE: istiod가 구성을 보냈지만 ACK를 받지 못함

구성 차이 비교

# istiod가 보낸 구성과 프록시가 가진 구성 비교
istioctl proxy-config all PODNAME -o json > proxy-config.json
istioctl proxy-status PODNAME --diff

성능 고려사항

컨트롤 플레인 확장

  • istiod는 수평 확장 가능 (여러 복제본)
  • 각 Envoy는 하나의 istiod 인스턴스에 연결
  • istiod 장애 시 Envoy는 마지막으로 받은 구성으로 계속 동작

대규모 메시에서의 최적화

  1. Sidecar 리소스 사용: 각 워크로드가 알아야 할 서비스 범위 제한
  2. exportTo 설정: CRD의 가시성 범위 제한
  3. 이벤트 배치 처리: istiod는 짧은 기간 내 여러 변경을 묶어서 푸시
  4. 증분 xDS(Delta xDS): 변경된 부분만 전송
# Sidecar 리소스로 Envoy 메모리 최적화
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
  name: default
  namespace: production
spec:
  egress:
    - hosts:
        - './*' # 같은 네임스페이스 서비스
        - 'istio-system/*' # Istio 시스템 서비스
        - 'monitoring/prometheus' # 특정 외부 서비스

마무리

Istio의 내부 아키텍처를 이해하면 다음과 같은 이점이 있습니다:

  1. 트러블슈팅 능력 향상: xDS 동기화 문제, 사이드카 주입 실패, 인증서 만료 등을 빠르게 진단
  2. 성능 최적화: Sidecar 리소스, 연결 풀 튜닝, 구성 범위 제한 등을 적절히 활용
  3. 보안 강화: mTLS 동작 원리를 이해하고 올바른 PeerAuthentication 설정

다음 글에서는 트래픽 관리 엔진의 내부 동작을 더 깊이 살펴보겠습니다.