TL;DR
VirtualService와 트래픽 라우팅의 핵심 개념과 범위를 간단히 정의하고, 왜 이 문서가 필요한지 요점을 잡습니다.
이 주제가 등장하게 된 배경과 문제 상황, 기술적 맥락을 짚습니다.
왜 이 접근이 필요한지, 기존 대안의 한계나 목표를 설명합니다.
문서에서 다루는 주요 구성요소와 실전 적용 포인트를 정리합니다.
작성일: 2026-03-03 카테고리: Cloud / Kubernetes / Istio / Traffic Management 포함 내용: VirtualService, DestinationRule, Gateway, Traffic Routing, Canary, A/B Testing, Fault Injection, Traffic Mirroring, Kubernetes Gateway API
┌─────────────────────────────────────────────────────────────────┐
│ VirtualService │
├─────────────────────────────────────────────────────────────────┤
│ │
│ VirtualService = Istio의 CRD (Custom Resource Definition) │
│ networking.istio.io API 그룹 │
│ │
│ 핵심 질문: │
│ "트래픽이 host X에 도달하면 실제로 무엇이 일어나야 하는가?" │
│ │
│ 이름의 의미: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ "Virtual" = 클라이언트가 사용하는 논리적 이름과 │ │
│ │ 실제 물리적 백엔드를 분리한다 │ │
│ │ │ │
│ │ 클라이언트 → "reviews" 호출 (논리적 이름) │ │
│ │ 실제 라우팅 → reviews-v1 (90%), reviews-v2 (10%) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌──────────────────────┬──────────────────────────┬────────────────────────────────┐
│ 비교 항목 │ K8s Service │ VirtualService │
├──────────────────────┼──────────────────────────┼────────────────────────────────┤
│ OSI Layer │ L3/L4 (IP + port) │ L7 (HTTP, gRPC, headers) │
│ 라우팅 구현 │ kube-proxy / iptables │ Envoy 프록시 │
│ 트래픽 분배 │ Round-robin (pod 수) │ 가중치 기반 (정확한 %) │
│ 버전 인식 │ 없음 │ 라벨 기반 subset 라우팅 │
│ 재시도 / 타임아웃 │ 없음 │ HTTP 시맨틱 지원 │
│ 장애 주입 │ 없음 │ delay / abort 주입 가능 │
│ 헤더 기반 라우팅 │ 불가 │ 헤더, 쿠키, URI 모두 가능 │
│ 트래픽 미러링 │ 불가 │ mirror + mirrorPercentage │
│ URL 리라이팅 │ 불가 │ rewrite.uri, redirect │
│ 적용 범위 │ 클러스터 내 DNS │ Mesh 내 + Gateway 외부 트래픽 │
└──────────────────────┴──────────────────────────┴────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Service의 구조적 한계 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ kube-proxy 동작 방식: │
│ │
│ 클라이언트 → iptables 규칙 → Pod 선택 (랜덤 Round-robin) │
│ │
│ 한계 1: L4 전용 │
│ ├── TCP/UDP 포트만 이해 │
│ ├── HTTP 헤더 검사 불가 │
│ └── /api/v1 vs /api/v2 경로 구분 불가 │
│ │
│ 한계 2: 버전 인식 없음 │
│ ├── version: v1 라벨이 있어도 라우팅에 사용 불가 │
│ └── 카나리: 9개 v1 + 1개 v2 Pod으로 10% 분배 (부정확) │
│ │
│ 한계 3: 재시도 / 타임아웃 시맨틱 없음 │
│ ├── 503 응답 받아도 그냥 클라이언트에 전달 │
│ └── 각 서비스 코드에서 직접 구현해야 함 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Ingress의 구조적 한계 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 한계 1: Annotation 지옥 │
│ ├── 모든 고급 기능 → 컨트롤러별 비표준 annotation │
│ ├── nginx: nginx.ingress.kubernetes.io/... │
│ ├── traefik: traefik.ingress.kubernetes.io/... │
│ └── 이식성 없음. 컨트롤러 교체 = 전체 재작성 │
│ │
│ 한계 2: North-South 전용 │
│ ├── 외부(인터넷) → 클러스터 내부 트래픽만 처리 │
│ └── East-West (서비스 간) 트래픽은 관리 불가 │
│ │
│ 한계 3: 기능 부재 │
│ ├── 트래픽 분할 없음 (가중치 기반) │
│ ├── 재시도 / 타임아웃 없음 │
│ └── 장애 주입 없음 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ VirtualService 진화 역사 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1 (2017.05) - Istio 0.1 │
│ ├── Google + IBM + Lyft 공동 출시 │
│ ├── RouteRule 리소스 사용 │
│ └── 문제: 여러 RouteRule이 precedence 순서로 분산 │
│ → 설정 간 충돌, 에러 발생 쉬움 │
│ │
│ Phase 2 (2018) - Istio 0.8, v1alpha3 API 도입 │
│ ├── VirtualService, DestinationRule, Gateway, ServiceEntry 등장 │
│ ├── 설계 원칙: │
│ │ ├── Producer-Oriented: 서비스 오너가 정책 정의 │
│ │ ├── 인프라 명시적 모델링 │
│ │ └── 라우팅 / 정책 분리 (VS vs DR) │
│ └── 안정적이지만 "alpha" 딱지로 오랜 기간 유지 │
│ │
│ Phase 3 (2024.05) - Istio 1.22 │
│ └── v1 API로 승격 → GA (Generally Available, 안정 버전) │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Envoy 사이드카의 트래픽 가로채기 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Pod 내부 구조: │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Pod │ │
│ │ ┌─────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ App 컨테이너 │ │ istio-proxy (Envoy) │ │ │
│ │ │ │ │ │ │ │
│ │ │ localhost:8080 │ │ iptables 규칙으로 │ │ │
│ │ │ 에만 바인딩 │ │ 모든 인/아웃바운드 가로채기 │ │ │
│ │ └────────┬────────┘ └──────────────┬───────────────┘ │ │
│ │ │ 평문 통신 │ 외부 통신 │ │
│ │ └─────────────────────────────┘ │ │
│ │ initContainer가 iptables 규칙 설정 │ │
│ │ → 앱이 네트워크와 직접 통신하지 않음 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 💡 핵심 포인트: │
│ 애플리케이션 코드는 전혀 수정하지 않아도 된다! │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Istiod 설정 변환 파이프라인 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ VirtualService YAML │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Istiod (Control Plane) │ │
│ │ 1. K8s API 감시 (Watch) │ │
│ │ 2. 내부 모델 구축 (Service Discovery) │ │
│ │ 3. Envoy 설정 생성 (Config Generation) │ │
│ │ 4. xDS 프로토콜로 배포 (Push to Envoy) │ │
│ └───────────────────────┬──────────────────────────────────┘ │
│ │ xDS (gRPC 스트리밍) │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Envoy 사이드카들 │ │
│ │ 각 Pod의 Envoy가 실시간으로 설정을 받아 적용 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ xDS 변환 매핑: │
│ ┌──────────┬──────────────────────────────────────────────┐ │
│ │ xDS 타입│ 내용 │ │
│ ├──────────┼──────────────────────────────────────────────┤ │
│ │ LDS │ 포트 수준 리스너 (Listener Discovery) │ │
│ │ RDS │ HTTP 라우팅 규칙 ← VirtualService 주 대상 │ │
│ │ CDS │ upstream 서비스 풀 (DestinationRule subset) │ │
│ │ EDS │ 실제 Pod IP:port (Endpoint Discovery) │ │
│ └──────────┴──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# VirtualService YAML (사람이 작성)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
# Istiod가 위 YAML을 Envoy Route Configuration으로 변환
VirtualService 필드 → Envoy 설정
─────────────────────────────────────────────────────
http[].match.headers → route matcher (header_matcher)
http[].match.uri → route matcher (prefix_rewrite)
destination.host + subset → cluster name
(outbound|80|v2|reviews.default.svc.cluster.local)
timeout → route_config.timeout
retries → route_config.retry_policy
fault.delay → route_config.typed_per_filter_config (fault filter)
┌─────────────────────────────────────────────────────────────────┐
│ 소스 프록시 실행 원칙 (매우 중요!) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ service-a가 service-b를 호출할 때: │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ service-a Pod │ │ service-b Pod │ │
│ │ ┌──────────────┐ │ │ ┌──────────────────────────┐ │ │
│ │ │ App │ │ │ │ App │ │ │
│ │ └──────┬───────┘ │ │ └──────────────────────────┘ │ │
│ │ │ │ │ ┌──────────────────────────┐ │ │
│ │ ┌──────▼───────┐ │ │ │ Envoy │ │ │
│ │ │ Envoy │─┼────────►│ │ (수신만 할 뿐) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ← 여기서! │ │ │ └──────────────────────────┘ │ │
│ │ │ service-b의 │ │ └──────────────────────────────┘ │
│ │ │ VS 규칙이 │ │ │
│ │ │ 평가됨 │ │ │
│ │ └──────────────┘ │ │
│ └──────────────────┘ │
│ │
│ 💡 핵심 포인트: │
│ 라우팅 규칙은 목적지 프록시가 아닌 │
│ 소스 프록시(호출하는 쪽의 Envoy)에서 평가됨! │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Gateway - VirtualService - DestinationRule 3계층 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 외부 트래픽 │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Gateway │ │
│ │ "어떤 연결을 받을지" │ │
│ │ ├── L4/L5 설정 (포트, TLS 모드, SNI) │ │
│ │ └── hosts: ["*.example.com"] │ │
│ └─────────────────────────┬──────────────────────────────────┘ │
│ │ 연결 허용됨 │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ VirtualService │ │
│ │ "어디로, 어떻게" │ │
│ │ ├── L7 라우팅 규칙 (URI, 헤더, 가중치) │ │
│ │ └── gateways: [my-gateway, mesh] │ │
│ └─────────────────────────┬──────────────────────────────────┘ │
│ │ subset: v2 로 라우팅 │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ DestinationRule │ │
│ │ "도착 후 정책" │ │
│ │ ├── subset 정의 (v1이 무엇인지 = labels: version: v1) │ │
│ │ ├── 로드밸런싱 알고리즘 (ROUND_ROBIN, LEAST_CONN) │ │
│ │ └── Circuit Breaker, Connection Pool 설정 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 카나리 배포 비교 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ❌ K8s Service만 사용: │
│ 9개 v1 Pod + 1개 v2 Pod = 약 10% (부정확, Pod 수에 의존) │
│ 트래픽 조정 = Pod 수 조정 (느리고 비경제적) │
│ │
│ ✅ VirtualService 사용: │
│ 1개 v1 Pod + 1개 v2 Pod → weight: 90/10 (정확한 %) │
│ 트래픽 조정 = YAML 한 줄 변경 (즉각적) │
│ │
│ 트래픽 흐름: │
│ │
│ 클라이언트 │
│ │ │
│ ▼ │
│ VirtualService │
│ ├── 90% ────────────────────► reviews-v1 Pod │
│ └── 10% ────────────────────► reviews-v2 Pod │
│ │
└─────────────────────────────────────────────────────────────────┘
# 카나리 배포 - VirtualService
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
---
# 카나리 배포 - DestinationRule (subset 정의)
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
┌─────────────────────────────────────────────────────────────────┐
│ A/B 테스트 라우팅 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 요청 A: X-User-Group: beta 헤더 포함 │
│ └──────────────────────────────────────► 신규 버전 (v2) │
│ │
│ 요청 B: 헤더 없음 (일반 사용자) │
│ └──────────────────────────────────────► 안정 버전 (v1) │
│ │
│ 쿠키 기반 예시: │
│ 요청 C: Cookie: user=beta_tester_123 │
│ └──────────────────────────────────────► v2 │
│ │
└─────────────────────────────────────────────────────────────────┘
# A/B 테스트 - 헤더 + 쿠키 기반 라우팅
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
# 조건 1: 특정 헤더 값 매칭
- match:
- headers:
X-User-Group:
exact: beta
route:
- destination:
host: reviews
subset: v2
# 조건 2: 쿠키 regex 매칭
- match:
- headers:
Cookie:
regex: ".*user=beta.*"
route:
- destination:
host: reviews
subset: v2
# 기본: 나머지 모든 트래픽
- route:
- destination:
host: reviews
subset: v1
┌─────────────────────────────────────────────────────────────────┐
│ 트래픽 미러링 동작 방식 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 실제 요청 │
│ │ │
│ ├──────── 실제 처리 ────────► v1 (응답 반환) │
│ │ │
│ └──────── 복사본 전송 ───────► v2 (fire-and-forget) │
│ ├── v2 응답을 기다리지 않음│
│ ├── v2 에러는 무시 │
│ └── 성능 영향 최소화 │
│ │
│ 활용: v2를 실제 프로덕션 트래픽으로 검증 │
│ (사용자 영향 없이 새 버전 테스트) │
│ │
└─────────────────────────────────────────────────────────────────┘
# 트래픽 미러링 설정
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 100
# v1으로 가는 트래픽의 20%를 v2로도 미러링
mirror:
host: reviews
subset: v2
mirrorPercentage:
value: 20.0
┌─────────────────────────────────────────────────────────────────┐
│ 장애 주입 - Fault Injection │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 두 가지 장애 유형: │
│ │
│ 1. delay (지연 주입) │
│ └── 특정 % 요청에 인위적 지연 추가 │
│ → 네트워크 지연, 서비스 느려짐 시뮬레이션 │
│ │
│ 2. abort (에러 주입) │
│ └── 특정 % 요청에 HTTP 에러 코드 반환 │
│ → 서비스 다운, 에러 응답 시뮬레이션 │
│ │
│ ⚠️ 주의: │
│ fault injection과 retry를 같은 route에 동시 사용 불가 │
│ (설정 자체가 거부됨) │
│ │
└─────────────────────────────────────────────────────────────────┘
# 장애 주입 설정
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
X-Test-Fault:
exact: "true"
fault:
# 지연 주입: 50% 요청에 7초 지연
delay:
percentage:
value: 50.0
fixedDelay: 7s
# 에러 주입: 10% 요청에 503 반환
abort:
percentage:
value: 10.0
httpStatus: 503
route:
- destination:
host: ratings
subset: v1
# 정상 트래픽
- route:
- destination:
host: ratings
subset: v1
# 타임아웃 + 재시도 설정
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
# 요청 타임아웃: 3초
timeout: 3s
# 재시도 정책
retries:
attempts: 3 # 최대 3회 재시도
perTryTimeout: 1s # 1회 시도당 타임아웃
retryOn: "gateway-error,connect-failure,retriable-4xx"
# retriable-4xx: 429 Too Many Requests 등 재시도 가능 4xx
┌─────────────────────────────────────────────────────────────────┐
│ 타임아웃 + 재시도 시나리오 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 총 타임아웃: 3s │
│ 시도 1: 0.0s ~ 1.0s → 503 응답 → 재시도 │
│ 시도 2: 1.0s ~ 2.0s → 503 응답 → 재시도 │
│ 시도 3: 2.0s ~ 3.0s → 성공 → 응답 반환 │
│ │
│ 💡 retryOn 값 설명: │
│ ├── gateway-error: 502, 503, 504 │
│ ├── connect-failure: 연결 실패 │
│ └── retriable-4xx: 429 (Rate Limit) 등 │
│ │
└─────────────────────────────────────────────────────────────────┘
# URL 리라이팅 설정
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts:
- api.example.com
gateways:
- api-gateway
http:
# URI 리라이팅: /v1/users → /users (백엔드에는 /users로 전달)
- match:
- uri:
prefix: /v1/users
rewrite:
uri: /users
route:
- destination:
host: user-service
port:
number: 80
# HTTP → HTTPS 리다이렉트
- match:
- uri:
prefix: /old-path
redirect:
uri: /new-path
authority: api.example.com
redirectCode: 301
┌─────────────────────────────────────────────────────────────────┐
│ Blue-Green 배포 전환 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 단계 1: Blue(v1) 운영 중 │
│ weight: v1=100, v2=0 │
│ │
│ 단계 2: Green(v2) 배포 및 검증 (트래픽 0%) │
│ weight: v1=100, v2=0 │
│ (내부 테스트 트래픽은 헤더 기반으로 v2 접근 가능) │
│ │
│ 단계 3: 전환 │
│ weight: v1=0, v2=100 │
│ (YAML 변경 한 줄 → 즉각 전환) │
│ │
│ 단계 4: 문제 발생 시 즉시 롤백 │
│ weight: v1=100, v2=0 │
│ (K8s Deployment 롤백보다 훨씬 빠름) │
│ │
└─────────────────────────────────────────────────────────────────┘
# Blue-Green 배포: 전환 전
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: my-service
spec:
hosts:
- my-service
http:
- route:
- destination:
host: my-service
subset: blue # v1
weight: 100
- destination:
host: my-service
subset: green # v2 (새 버전)
weight: 0
# 전환 후: weight만 변경
# - destination:
# subset: blue
# weight: 0
# - destination:
# subset: green
# weight: 100
┌─────────────────────────────────────────────────────────────────┐
│ 모놀리스를 마이크로서비스로 분해하는 패턴 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 클라이언트 요청 │
│ │ │
│ ▼ │
│ VirtualService (api.example.com) │
│ ├── /users/* ──────────────────► user-service │
│ ├── /orders/* ─────────────────► order-service │
│ ├── /products/* ───────────────► product-service │
│ └── /* (catch-all) ────────────► legacy-monolith │
│ │
│ 효과: 새 마이크로서비스를 점진적으로 분리 │
│ 모놀리스는 나머지 경로를 처리하면서 단계적 전환 │
│ │
└─────────────────────────────────────────────────────────────────┘
# 모놀리스 분해 - Path 기반 멀티서비스 라우팅
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: monolith-decomp
spec:
hosts:
- api.example.com
gateways:
- main-gateway
http:
- match:
- uri:
prefix: /users
route:
- destination:
host: user-service
port:
number: 80
- match:
- uri:
prefix: /orders
route:
- destination:
host: order-service
port:
number: 80
- match:
- uri:
prefix: /products
route:
- destination:
host: product-service
port:
number: 80
# 분리 안 된 기능은 여전히 모놀리스로
- route:
- destination:
host: legacy-monolith
port:
number: 8080
┌──────────────────────┬──────────────────────────┬────────────────────────────────┐
│ 비교 항목 │ K8s Service │ Istio VirtualService │
├──────────────────────┼──────────────────────────┼────────────────────────────────┤
│ OSI Layer │ L3/L4 │ L7 │
│ 라우팅 구현 │ kube-proxy + iptables │ Envoy (사이드카) │
│ 트래픽 분배 │ Round-robin │ 가중치, 헤더, URI 등 │
│ 버전별 라우팅 │ 불가 │ subset으로 가능 │
│ 타임아웃 │ 없음 │ timeout 필드 │
│ 재시도 │ 없음 │ retries 필드 │
│ 장애 주입 │ 불가 │ fault 필드 │
│ 헤더 기반 라우팅 │ 불가 │ match.headers │
│ 트래픽 미러링 │ 불가 │ mirror + mirrorPercentage │
│ URL 리라이팅 │ 불가 │ rewrite.uri │
│ 추가 의존성 │ 없음 (K8s 기본) │ Istio 설치 필요 │
│ 성능 오버헤드 │ 거의 없음 │ 사이드카 오버헤드 있음 │
└──────────────────────┴──────────────────────────┴────────────────────────────────┘
┌──────────────────────┬──────────────────────────┬────────────────────────────────┐
│ 비교 항목 │ K8s Ingress │ Gateway + VirtualService │
├──────────────────────┼──────────────────────────┼────────────────────────────────┤
│ 트래픽 방향 │ North-South 전용 │ North-South + East-West │
│ 경로 매칭 │ prefix, exact │ prefix, exact, regex │
│ 헤더 매칭 │ annotation에 의존 │ 네이티브 지원 │
│ 트래픽 분할 │ annotation (비표준) │ weight 필드 (표준) │
│ 재시도 / 타임아웃 │ annotation (비표준) │ 네이티브 지원 │
│ 이식성 │ 컨트롤러 종속 │ Istio 표준 │
│ TLS 설정 │ annotation에 의존 │ Gateway에서 명시적 설정 │
│ 장애 주입 │ 불가 │ 가능 │
│ East-West 트래픽 │ 불가 │ mesh 게이트웨이로 가능 │
└──────────────────────┴──────────────────────────┴────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Gateway API 개요 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 탄생 배경: │
│ ├── K8s SIG-Network 프로젝트 │
│ ├── Ingress의 annotation 지옥을 해결 │
│ ├── Istio VirtualService에서 영감 받아 설계 │
│ └── 포터블: Istio, Contour, NGINX 등 모든 구현체 지원 │
│ │
│ 역할 기반 분리 (RBAC 친화적): │
│ ├── GatewayClass: 인프라 팀이 관리 │
│ ├── Gateway: 클러스터 운영자가 관리 │
│ └── HTTPRoute: 개발 팀이 관리 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────┬──────────────────────────────────────┐
│ Istio 리소스 │ Gateway API 리소스 │
├──────────────────────────────────────┼──────────────────────────────────────┤
│ Gateway │ Gateway │
│ VirtualService │ HTTPRoute / TCPRoute / TLSRoute │
│ DestinationRule │ BackendLBPolicy (실험적) │
│ ServiceEntry │ 별도 CRD 논의 중 │
└──────────────────────────────────────┴──────────────────────────────────────┘
┌──────────────────────┬──────────────────────────┬────────────────────────────────┐
│ 기능 │ Gateway API (HTTPRoute) │ Istio VirtualService │
├──────────────────────┼──────────────────────────┼────────────────────────────────┤
│ 헤더 기반 라우팅 │ 지원 │ 지원 │
│ 가중치 기반 라우팅 │ 지원 │ 지원 │
│ URL 리라이팅 │ 지원 │ 지원 │
│ 재시도 │ 지원 (BackendLBPolicy) │ 지원 │
│ 장애 주입 │ 미지원 │ 지원 │
│ 트래픽 미러링 │ 미지원 │ 지원 │
│ Circuit Breaking │ 미지원 │ DestinationRule │
│ EnvoyFilter │ 미지원 │ 지원 │
│ 이식성 │ 높음 (표준 K8s API) │ 낮음 (Istio 종속) │
│ RBAC 친화성 │ 높음 (역할 기반 분리) │ 중간 │
└──────────────────────┴──────────────────────────┴────────────────────────────────┘
💡 권장 전략:
├── 새로운 배포: Gateway API (HTTPRoute) 우선 고려
├── 고급 기능 필요시: VirtualService 추가 사용
└── 두 방식은 Istio에서 공존 가능
# gateways 필드 생략 = mesh 내부 트래픽에만 적용
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts:
- reviews # K8s Service 이름
# gateways 없음 = 기본값: ["mesh"]
# mesh = 모든 사이드카에서의 호출에 적용
http:
- route:
- destination:
host: reviews
subset: v1
weight: 80
- destination:
host: reviews
subset: v2
weight: 20
# Gateway + VirtualService 조합
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "bookinfo.example.com"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "bookinfo.example.com"
# gateways 명시: 외부 + 내부 모두 적용
gateways:
- bookinfo-gateway # 외부 Gateway에서 오는 트래픽
- mesh # 내부 서비스 간 트래픽
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
route:
- destination:
host: productpage
port:
number: 9080
┌─────────────────────────────────────────────────────────────────┐
│ Delegate VirtualService - 팀별 라우팅 분리 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 대규모 조직에서: │
│ ├── 인프라 팀: Root VS (도메인 수준 라우팅) 관리 │
│ ├── 팀 A: /users/* 라우팅 관리 (user-ns) │
│ └── 팀 B: /orders/* 라우팅 관리 (order-ns) │
│ │
│ Root VS │
│ ├── /users/* ──► delegate → user-vs (user-ns) │
│ └── /orders/* ─► delegate → order-vs (order-ns) │
│ │
└─────────────────────────────────────────────────────────────────┘
# Root VirtualService (인프라 팀 관리)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: root-vs
namespace: istio-system
spec:
hosts:
- "api.example.com"
gateways:
- main-gateway
http:
- match:
- uri:
prefix: /users
delegate:
name: user-vs
namespace: user-ns # 다른 namespace로 위임
- match:
- uri:
prefix: /orders
delegate:
name: order-vs
namespace: order-ns
---
# Child VirtualService (팀 A가 관리)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: user-vs
namespace: user-ns
spec:
http:
- match:
- uri:
prefix: /users/v2
route:
- destination:
host: user-service-v2
- route:
- destination:
host: user-service-v1
# 헤더 조작 - 요청/응답 헤더 추가/변경/삭제
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
headers:
request:
set:
X-Custom-Header: "injected-by-istio" # 헤더 설정 (덮어쓰기)
add:
X-Request-Id: "generated" # 헤더 추가 (중복 허용)
remove:
- X-Internal-Debug # 헤더 제거
response:
set:
Cache-Control: "no-cache" # 응답 헤더 설정
remove:
- X-Internal-Error-Details # 내부 정보 제거
# Cross-Namespace 라우팅 - exportTo 설정
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: team-a
spec:
# 반드시 FQDN 사용 (cross-namespace에서 짧은 이름은 위험)
hosts:
- reviews.team-a.svc.cluster.local
exportTo:
- "." # 현재 namespace만
# - "*" # 모든 namespace (기본값, 권장하지 않음)
# - "team-b" # 특정 namespace만
http:
- route:
- destination:
host: reviews.team-a.svc.cluster.local
subset: v1
┌─────────────────────────────────────────────────────────────────┐
│ exportTo 값 설명 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ "." → 현재 namespace에만 적용 (가장 안전) │
│ "*" → 모든 namespace에 적용 (기본값, 주의 필요) │
│ "foo" → foo namespace에서만 접근 가능 │
│ │
│ 💡 핵심 포인트: │
│ cross-namespace 라우팅 시 짧은 hostname은 잘못된 서비스로 │
│ 라우팅될 수 있음. FQDN 사용 필수! │
│ │
└─────────────────────────────────────────────────────────────────┘
# TCP 라우팅 (MongoDB 등 non-HTTP 서비스)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: mongodb
spec:
hosts:
- mongodb
tcp:
- match:
- port: 27017
route:
- destination:
host: mongodb
port:
number: 27017
subset: v1
---
# TLS 라우팅 (SNI 기반)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: tls-routing
spec:
hosts:
- "*.example.com"
tls:
- match:
- port: 443
sniHosts:
- service-a.example.com
route:
- destination:
host: service-a
- match:
- port: 443
sniHosts:
- service-b.example.com
route:
- destination:
host: service-b
┌─────────────────────────────────────────────────────────────────┐
│ 프로덕션 Best Practices (8가지) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 기본 route 반드시 정의 │
│ ├── match 없는 catch-all route가 없으면 → 404/503 │
│ └── http[] 배열의 마지막 항목은 항상 match 없이 │
│ │
│ 2. 프로덕션에서 FQDN 사용 │
│ ├── ❌ reviews │
│ └── ✅ reviews.default.svc.cluster.local │
│ │
│ 3. DestinationRule을 VirtualService보다 먼저 배포 │
│ ├── VS가 참조하는 subset이 없으면 503 발생 │
│ └── DR 먼저 → VS 배포 순서 준수 │
│ │
│ 4. 보수적 retry 설정 │
│ ├── 최대 3회 + 짧은 perTryTimeout │
│ └── Circuit Breaker와 함께 사용 (DestinationRule) │
│ │
│ 5. exportTo로 가시성 제어 │
│ ├── 기본값 "*"는 모든 namespace에 적용 │
│ └── 팀 namespace에는 "." 사용 권장 │
│ │
│ 6. hostname당 하나의 catch-all rule │
│ └── 동일 host에 여러 VS가 있으면 설정 충돌 발생 │
│ │
│ 7. DestinationRule은 대상 서비스 namespace에 배치 │
│ └── 서비스와 같은 namespace에 DR 위치시켜 관리 명확화 │
│ │
│ 8. route에 name 필드 사용 │
│ ├── 모니터링/트레이싱에서 route 이름 표시됨 │
│ └── 디버깅 시 어떤 rule이 적용됐는지 바로 파악 │
│ │
└─────────────────────────────────────────────────────────────────┘
# Best Practice 예시: name 필드 + FQDN + catch-all
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: default
spec:
hosts:
- reviews.default.svc.cluster.local # FQDN 사용
http:
- name: "beta-users" # route 이름
match:
- headers:
X-User-Group:
exact: beta
route:
- destination:
host: reviews.default.svc.cluster.local
subset: v2
- name: "default" # catch-all에도 이름
route:
- destination:
host: reviews.default.svc.cluster.local
subset: v1 # 기본 route 반드시 정의
┌──────────────────────────────┬──────────────────────┬──────────────────────────────┐
│ 실수 │ 증상 │ 해결책 │
├──────────────────────────────┼──────────────────────┼──────────────────────────────┤
│ 기본 route 없음 │ 404 / 503 │ 마지막 match 없는 route 추가│
│ VS 먼저, DR 나중 배포 │ 503 (subset 없음) │ DR → VS 배포 순서 준수 │
│ 짧은 hostname cross-ns │ 잘못된 서비스 라우팅│ FQDN 사용 필수 │
│ fault injection + retries │ 설정 자체 거부됨 │ 둘 중 하나만 사용 │
│ 과도한 retry + no CB │ 장애 증폭 (Cascade) │ DestinationRule CB 추가 │
│ 동일 host에 여러 VS │ 예측 불가 라우팅 │ VS 통합 또는 delegate 사용 │
│ exportTo: "*" (기본) │ 의도치 않은 노출 │ exportTo: ["."] 사용 │
└──────────────────────────────┴──────────────────────┴──────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 가상 시나리오: 신규 서비스의 트래픽 오라우팅 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 상황: │
│ ├── 멀티 서비스 환경 (dev.example.com) │
│ ├── main-portal VirtualService가 wildcard로 설정됨 │
│ │ hosts: ["*.dev.example.com"] │
│ │ route: main-portal-service (catch-all) │
│ └── 새 서비스 order-service 배포 완료 │
│ │
│ 문제: │
│ order.dev.example.com 접속 시 │
│ ↓ │
│ Gateway: *.dev.example.com 에 매칭 │
│ ↓ │
│ main-portal VirtualService가 포착 (wildcard) │
│ ↓ │
│ main-portal 서비스로 라우팅 → 404 또는 잘못된 응답 │
│ │
│ 원인: │
│ order-service의 VirtualService가 존재하지 않음 │
│ → Gateway는 더 구체적인 VS를 찾지 못하고 wildcard VS 적용 │
│ │
│ 해결: │
│ 1. order-service VirtualService 작성 │
│ hosts: ["order.dev.example.com"] │
│ 2. kustomization.yaml에 추가 │
│ 3. 구체적인 host 매칭이 wildcard보다 우선 적용됨 │
│ │
└─────────────────────────────────────────────────────────────────┘
# 문제: main-portal wildcard VirtualService
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: main-portal-wildcard
spec:
hosts:
- "*.dev.example.com" # 모든 서브도메인을 포착
gateways:
- main-gateway
http:
- route:
- destination:
host: main-portal-service
---
# 해결: order-service 전용 VirtualService 추가
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-service-vs
spec:
hosts:
- "order.dev.example.com" # 구체적 host가 wildcard보다 우선
gateways:
- main-gateway
http:
- route:
- destination:
host: order-service
port:
number: 80
┌─────────────────────────────────────────────────────────────────┐
│ 글로벌 기업들의 Istio / VirtualService 활용 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Airbnb │
│ ├── 수만 개 Pod, 수십 개 클러스터 │
│ ├── 초당 수천만 QPS 처리 │
│ └── Hub-and-spoke 모델로 트래픽 중앙 관리 │
│ │
│ Salesforce │
│ └── 하루 수조 건의 요청 처리 │
│ → 세밀한 트래픽 제어 없이는 운영 불가능 │
│ │
│ eBay │
│ └── 프로덕션과 유사한 격리 환경에서 마이크로서비스 테스트 │
│ → 트래픽 미러링으로 실제 트래픽 기반 검증 │
│ │
│ T-Mobile │
│ └── 100개 이상의 Istio 인스턴스 배포 │
│ → 글로벌 통신사 인프라 관리 │
│ │
│ 기타 │
│ ├── Atlassian: 마이크로서비스 전환에 활용 │
│ ├── Splunk: 데이터 파이프라인 트래픽 제어 │
│ ├── FICO: 금융 서비스 안정성 확보 │
│ ├── Intuit (TurboTax): 세금 신고 시즌 트래픽 관리 │
│ └── Databricks: 데이터 플랫폼 서비스 메시 구성 │
│ │
│ 공통 사용 패턴: │
│ ├── 카나리 배포 (안전한 점진적 릴리즈) │
│ ├── 트래픽 미러링 (신버전 검증) │
│ └── Circuit Breaking (연쇄 장애 방지) │
│ │
└─────────────────────────────────────────────────────────────────┘
namespace/gateway-name 형식이란?┌─────────────────────────────────────────────────────────────────┐
│ VirtualService의 gateways 필드 해석 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ gateways: │
│ - istio-ingress/main-gateway │
│ ─────────────┬────────────── │
│ │ │ │
│ ▼ ▼ │
│ namespace Gateway 리소스 이름 │
│ (Gateway가 (.metadata.name) │
│ 위치한 곳) │
│ │
│ 의미: "istio-ingress 네임스페이스에 있는 │
│ main-gateway라는 Gateway 리소스를 통해 │
│ 들어오는 트래픽에 이 라우팅 규칙을 적용하라" │
│ │
│ ⚠ namespace를 생략하면? │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ gateways: │ │
│ │ - main-gateway ← VirtualService와 같은 namespace │ │
│ │ 에서 찾음 │ │
│ │ │ │
│ │ VirtualService가 my-app namespace에 있는데 │ │
│ │ Gateway는 istio-ingress에 있다면? │ │
│ │ → 바인딩 실패! (조용히 무시됨, 에러 없음) │ │
│ │ → istioctl analyze로 감지 가능 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# ✅ 올바른 사용: Gateway가 다른 namespace에 있을 때 반드시 prefix
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-service
namespace: my-app # VirtualService는 my-app에 있음
spec:
hosts:
- order.dev.example.com
gateways:
- istio-ingress/main-gateway # Gateway는 istio-ingress에 있음 → prefix 필수
- mesh # 내부 sidecar 트래픽에도 적용
http:
- route:
- destination:
host: order-service
port:
number: 80
# ❌ 잘못된 사용: namespace prefix 누락
# spec:
# gateways:
# - main-gateway ← my-app namespace에서 찾으려 하지만 없음 → 실패
mesh 예약어┌─────────────────────────────────────────────────────────────────┐
│ gateways 필드의 특수 값: "mesh" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ gateways: │
│ - istio-ingress/main-gateway ← 외부 트래픽 (North-South) │
│ - mesh ← 내부 트래픽 (East-West) │
│ │
│ "mesh"는 Istio의 예약어: │
│ ├── 메시 내 모든 사이드카 프록시를 의미 │
│ ├── gateways 필드를 아예 생략하면 기본값이 "mesh" │
│ └── Named Gateway만 지정하면 외부 트래픽에만 적용 │
│ │
│ 동작 정리: │
│ ┌────────────────────────────────┬───────────────────────────┐ │
│ │ gateways 값 │ 적용 범위 │ │
│ ├────────────────────────────────┼───────────────────────────┤ │
│ │ (생략) │ mesh 내부 트래픽만 │ │
│ │ ["mesh"] │ mesh 내부 트래픽만 │ │
│ │ ["istio-ingress/gw"] │ 외부 트래픽만 │ │
│ │ ["istio-ingress/gw", "mesh"] │ 외부 + 내부 모두 │ │
│ └────────────────────────────────┴───────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Istio Namespace 분리 아키텍처 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ istio-system (Control Plane) │ │
│ │ ├── istiod (통합 컨트롤 플레인) │ │
│ │ │ ├── Pilot: 서비스 디스커버리, xDS 설정 배포 │ │
│ │ │ ├── Citadel: 인증서 발급 (CA Private Key 보관) │ │
│ │ │ ├── Galley: 설정 검증 │ │
│ │ │ └── Sidecar Injector: Mutating Webhook │ │
│ │ └── 관리 주체: 클러스터 관리자만 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↕ xDS (gRPC) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ istio-ingress (Data Plane - Gateway) │ │
│ │ ├── istio-ingressgateway Pod (Envoy 프록시) │ │
│ │ ├── Gateway 리소스들 (main-gateway, dev-gateway 등) │ │
│ │ └── TLS 인증서 Secret │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 왜 분리하는가? │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 보안: 최소 권한 원칙 (Least Privilege) │ │
│ │ │ │
│ │ ingress gateway는 인터넷에서 오는 신뢰할 수 없는 │ │
│ │ 트래픽을 직접 처리하는 Pod임 │ │
│ │ │ │
│ │ 만약 istio-system에 같이 있다면? │ │
│ │ → 공격자가 gateway 취약점 악용 시 │ │
│ │ → 같은 namespace의 Secret에 접근 가능 │ │
│ │ → CA Private Key 탈취 → 전체 메시 인증 체계 붕괴 │ │
│ │ │ │
│ │ 분리하면? │ │
│ │ → gateway Pod는 istio-ingress의 Secret만 접근 가능 │ │
│ │ → TLS 인증서만 노출, CA 키는 안전 │ │
│ │ → Kubernetes RBAC으로 namespace 경계 강제 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 레거시 vs 권장 방식: │
│ ┌────────────────────────┬────────────────────────────────┐ │
│ │ 레거시 (istioctl 기본) │ 권장 (Helm 기반) │ │
│ ├────────────────────────┼────────────────────────────────┤ │
│ │ istio-system에 │ istio-system: istiod만 │ │
│ │ istiod + gateway 같이 │ istio-ingress: gateway 분리 │ │
│ │ │ istio-egress: egress 분리 │ │
│ │ 문제: 라이프사이클 │ 장점: 독립 업그레이드/스케일 │ │
│ │ 결합, 보안 경계 없음 │ 보안 경계 분리 │ │
│ └────────────────────────┴────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# 권장 설치 순서 (Helm)
helm install istio-base istio/base -n istio-system --create-namespace
helm install istiod istio/istiod -n istio-system
helm install istio-ingressgateway istio/gateway -n istio-ingress --create-namespace
┌─────────────────────────────────────────────────────────────────┐
│ Gateway 이름 규칙 (Naming Convention) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Gateway 이름은 목적/환경/대상을 반영: │
│ │
│ ┌──────────────────────┬────────────────────────────────────┐ │
│ │ 이름 패턴 │ 용도 │ │
│ ├──────────────────────┼────────────────────────────────────┤ │
│ │ main-gateway │ 주 프로덕션 게이트웨이 │ │
│ │ dev-gateway │ 개발 환경 전용 │ │
│ │ staging-gateway │ 스테이징 환경 │ │
│ │ public-gateway │ 인터넷 접근용 (외부) │ │
│ │ private-gateway │ 내부 네트워크 전용 (인트라넷) │ │
│ │ api-gateway │ API 트래픽 전용 │ │
│ │ team-a-gateway │ 팀/테넌트별 전용 │ │
│ └──────────────────────┴────────────────────────────────────┘ │
│ │
│ 💡 왜 같은 클러스터에 여러 Gateway가 필요한가? │
│ │
│ 1. 환경 분리: dev.example.com vs example.com │
│ 2. TLS 분리: 환경별 다른 인증서 사용 │
│ 3. 보안 정책 분리: 개발은 HTTP 허용, 프로덕션은 HTTPS 강제 │
│ 4. 팀 자율성: 개발자가 dev-gateway 설정을 직접 관리 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Gateway selector의 동작 원리 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Gateway 리소스: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ spec: │ │
│ │ selector: │ │
│ │ istio: ingressgateway ← 라벨 셀렉터 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ │ "이 라벨을 가진 Pod에 설정을 적용" │
│ ▼ │
│ istio-ingressgateway Deployment의 Pod: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ metadata: │ │
│ │ labels: │ │
│ │ app: istio-ingressgateway │ │
│ │ istio: ingressgateway ← 이 라벨이 매칭됨 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 핵심: Gateway 리소스 자체는 설정일 뿐 │
│ 실제 트래픽을 처리하는 건 selector로 선택된 Envoy Pod │
│ │
│ Istiod의 처리 흐름: │
│ 1. Gateway 리소스 감시 │
│ 2. selector 라벨로 매칭되는 Pod 검색 (기본: 전체 namespace) │
│ 3. 해당 Pod의 Envoy에 LDS 설정(포트, TLS) 푸시 │
│ 4. VirtualService가 이 Gateway를 참조하면 RDS(라우팅)도 푸시 │
│ │
│ ⚠ 멀티 Gateway 배포 시 라벨 분리 필수: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ # 공개 Gateway → 공개용 Envoy Pod │ │
│ │ Gateway "public-gw": │ │
│ │ selector: { istio: public-ingressgateway } │ │
│ │ │ │
│ │ # 내부 Gateway → 내부용 Envoy Pod │ │
│ │ Gateway "private-gw": │ │
│ │ selector: { istio: private-ingressgateway } │ │
│ │ │ │
│ │ → 라벨이 같으면 두 Gateway 설정이 같은 Pod에 합쳐져 │ │
│ │ 예상치 못한 리스너 충돌 발생 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 패턴 A: 공유 Gateway (Shared) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ istio-ingress namespace: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [Envoy Pod Fleet] ← 하나의 Pod 그룹 │ │
│ │ label: istio=ingressgateway │ │
│ │ │ │
│ │ Gateway "dev-gateway" │ │
│ │ hosts: [dev.example.com], port: 80 │ │
│ │ │ │
│ │ Gateway "main-gateway" │ │
│ │ hosts: [example.com], port: 443 + TLS │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↑ ↑ │
│ │ │ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ dev namespace │ │ prod namespace │ │
│ │ VirtualService │ │ VirtualService │ │
│ │ gateways: │ │ gateways: │ │
│ │ [istio-ingress/ │ │ [istio-ingress/ │ │
│ │ dev-gateway] │ │ main-gateway] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 장점: LoadBalancer 1개, 비용 절약, DNS 단순 │
│ 단점: 단일 장애점, 테넌트 간 격리 없음 │
│ 적합: 단일 팀, 소규모 클러스터, 같은 도메인 공유 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 패턴 B: 전용 Gateway (Dedicated) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────┐ ┌────────────────────────────┐ │
│ │ team-a-ingress namespace │ │ team-b-ingress namespace │ │
│ │ [Envoy Pod] │ │ [Envoy Pod] │ │
│ │ label: app=team-a-gw │ │ label: app=team-b-gw │ │
│ │ Gateway "team-a-gateway" │ │ Gateway "team-b-gateway" │ │
│ │ Service: LB → IP 1.2.3.4 │ │ Service: LB → IP 5.6.7.8│ │
│ └─────────────────────────────┘ └────────────────────────────┘ │
│ │
│ 장점: 완전 격리, 독립 스케일링, 팀별 TLS 관리 │
│ 단점: LB 여러 개 (비용 증가), 운영 복잡도 증가 │
│ 적합: 멀티 테넌트, 규제 산업, 내부/외부 분리 필요 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 패턴 C: 하이브리드 (가장 일반적) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 하나의 Envoy Pod Fleet + 여러 Gateway 리소스 │
│ │
│ istio-ingress: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [Envoy Pod Fleet] │ │
│ │ label: istio=ingressgateway │ │
│ │ │ │
│ │ Gateway "dev-gateway" │ │
│ │ hosts: ["*.dev.example.com"], port: 80 │ │
│ │ │ │
│ │ Gateway "main-gateway" │ │
│ │ hosts: ["*.example.com"], port: 443 + TLS │ │
│ │ │ │
│ │ Gateway "internal-gateway" │ │
│ │ hosts: ["*.internal.example.com"], port: 443 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ → 하나의 LoadBalancer IP로 여러 도메인/환경 처리 │
│ → Gateway 리소스가 논리적 분리 담당 │
│ → 가장 비용 효율적이면서도 유연한 패턴 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 가상 시나리오: cross-namespace Gateway 참조 누락 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 인프라 구성: │
│ ├── istio-ingress namespace에 "main-gateway" 존재 │
│ ├── payment 팀이 payment namespace에 VirtualService 배포 │
│ └── payment 팀이 gateways: ["main-gateway"]로 참조 (prefix 누락)│
│ │
│ 발생한 문제: │
│ payment.example.com 접속 시 → 404 Not Found │
│ │
│ 원인 분석: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ VirtualService (namespace: payment): │ │
│ │ gateways: ["main-gateway"] │ │
│ │ │ │
│ │ Istio 해석: "payment namespace에서 main-gateway 검색" │ │
│ │ → payment namespace에는 Gateway가 없음 │ │
│ │ → 바인딩 실패 (조용히 무시됨, 에러 로그 없음!) │ │
│ │ → main-gateway의 Envoy에 라우팅 규칙이 푸시되지 않음 │ │
│ │ → 해당 host로 들어오는 트래픽에 매칭 규칙 없음 → 404 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 해결: │
│ gateways: ["istio-ingress/main-gateway"] ← namespace prefix │
│ │
│ 진단 도구: │
│ $ istioctl analyze -n payment │
│ Warning: VirtualService references gateway not found: │
│ gateway "main-gateway" not found in namespace "payment" │
│ │
└─────────────────────────────────────────────────────────────────┘
# ❌ 잘못된 VirtualService (payment namespace)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
namespace: payment
spec:
hosts:
- payment.example.com
gateways:
- main-gateway # ← 잘못됨! payment ns에서 찾음
http:
- route:
- destination:
host: payment-service
---
# ✅ 올바른 VirtualService
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
namespace: payment
spec:
hosts:
- payment.example.com
gateways:
- istio-ingress/main-gateway # ← 올바름! namespace prefix 포함
http:
- route:
- destination:
host: payment-service
┌─────────────────────────────────────────────────────────────────┐
│ VirtualService 핵심 정리 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ VirtualService란? │
│ ├── Istio CRD (networking.istio.io/v1) │
│ ├── L7 트래픽 라우팅 규칙 정의 │
│ └── 클라이언트의 논리적 이름과 실제 백엔드를 분리 │
│ │
│ 왜 필요한가? │
│ ├── K8s Service: L4만 지원, 버전/헤더 인식 불가 │
│ └── K8s Ingress: North-South 전용, annotation 지옥 │
│ │
│ 내부 원리: │
│ YAML 작성 → Istiod(Pilot) 변환 → xDS(RDS) 배포 │
│ → Envoy Route Configuration 적용 │
│ │
│ 핵심 원칙: │
│ ├── 소스 프록시 실행: 호출하는 쪽 Envoy에서 규칙 평가 │
│ └── 3계층 분리: Gateway(L4) → VS(L7 라우팅) → DR(도착 정책) │
│ │
│ 미래 방향: │
│ ├── Gateway API(HTTPRoute)가 표준으로 부상 │
│ ├── VirtualService는 고급 기능(fault, mirror)용으로 공존 │
│ └── 두 방식은 Istio에서 동시 사용 가능 │
│ │
│ 애플리케이션 코드 수정 없이 가능한 것들: │
│ ├── 카나리 배포 (가중치 기반) │
│ ├── A/B 테스트 (헤더/쿠키 기반) │
│ ├── 트래픽 미러링 (shadow testing) │
│ ├── 장애 주입 (chaos engineering) │
│ ├── Blue-Green 배포 (즉각 전환) │
│ └── 모놀리스 분해 (path 기반 라우팅) │
│ │
│ 💡 핵심 포인트: │
│ "VirtualService는 Kubernetes 네트워킹의 L7 계층을 │
│ 인프라 레벨에서 제어하는 유일한 표준 수단이다" │
│ │
└─────────────────────────────────────────────────────────────────┘
VirtualService, DestinationRule, Gateway, Istio, Service Mesh, Envoy, Traffic Routing, Canary Deployment, A/B Testing, Traffic Mirroring, Fault Injection, Blue-Green, xDS, Pilot, Istiod, Kubernetes Gateway API, HTTPRoute, East-West Traffic, North-South Traffic, Circuit Breaker, Weighted Routing, Subset, istio-system, istio-ingress, Gateway Namespace, selector, Least Privilege, Shared Gateway, Dedicated Gateway, istioctl analyze, mesh, 트래픽 라우팅, 카나리 배포, 장애 주입, 트래픽 미러링