마이크로서비스 패턴 (Microservices Patterns)
마이크로서비스 패턴 (Microservices Patterns)¶
난이도: ⭐⭐⭐⭐
개요¶
마이크로서비스 아키텍처를 성공적으로 운영하기 위해서는 다양한 패턴과 도구가 필요합니다. 이 장에서는 서비스 디스커버리, 서킷 브레이커, Bulkhead 패턴, 서비스 메시, 그리고 분산 추적에 대해 학습합니다.
목차¶
1. 서비스 디스커버리¶
왜 서비스 디스커버리가 필요한가?¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서비스 디스커버리의 필요성 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 전통적 방식 (하드코딩): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Order Service │ │
│ │ config: │ │
│ │ user_service: http://10.0.1.5:8080 │ │
│ │ product_service: http://10.0.1.10:8080 │ │
│ │ payment_service: http://10.0.1.15:8080 │ │
│ │ │ │
│ │ 문제: │ │
│ │ - IP 변경 시 설정 수정 필요 │ │
│ │ - 스케일 아웃 시 수동 업데이트 │ │
│ │ - 장애 인스턴스 자동 제외 불가 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 동적 환경에서의 문제: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 시간 0: User Service @ 10.0.1.5 │ │
│ │ 시간 1: 스케일 아웃 → 10.0.1.5, 10.0.1.6, 10.0.1.7 │ │
│ │ 시간 2: 10.0.1.5 장애 → 10.0.1.6, 10.0.1.7 │ │
│ │ 시간 3: 새 배포 → 10.0.1.8, 10.0.1.9 │ │
│ │ │ │
│ │ → IP가 계속 변경! 어떻게 추적할 것인가? │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
클라이언트 사이드 디스커버리¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Client-Side Discovery │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Service Registry │ │ │
│ │ │ (Eureka, Consul, etcd) │ │ │
│ │ │ │ │ │
│ │ │ user-service: │ │ │
│ │ │ - 10.0.1.5:8080 │ │ │
│ │ │ - 10.0.1.6:8080 │ │ │
│ │ │ - 10.0.1.7:8080 │ │ │
│ │ │ │ │ │
│ │ └─────────────┬──────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────1.Query────────┘ │ │
│ │ │ 2.Return instances │ │
│ │ ▼ │ │
│ │ ┌───────────────┐ 3.Direct call ┌───────────────┐ │ │
│ │ │ Order Service │─────────────────────────│ User Service │ │ │
│ │ │ (Client) │ (Load Balancing) │ (10.0.1.5) │ │ │
│ │ │ + LB Logic │─────────────────────────│ (10.0.1.6) │ │ │
│ │ └───────────────┘ │ (10.0.1.7) │ │ │
│ │ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 동작 방식: │
│ 1. 클라이언트가 Registry에 서비스 인스턴스 조회 │
│ 2. 클라이언트가 로드밸런싱 수행 (Round Robin, Random 등) │
│ 3. 직접 인스턴스 호출 │
│ │
│ 예: Netflix Eureka + Ribbon │
│ │
│ 장점: 단점: │
│ - 단순한 인프라 - 클라이언트 복잡 │
│ - Registry 부하 분산 - 언어별 구현 필요│
│ │
└─────────────────────────────────────────────────────────────────────────┘
서버 사이드 디스커버리¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Server-Side Discovery │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Service Registry │ │ │
│ │ └─────────────┬──────────────────┘ │ │
│ │ │ │ │
│ │ │ 인스턴스 정보 동기화 │ │
│ │ ▼ │ │
│ │ ┌───────────────┐ ┌───────────────────┐ ┌───────────────┐ │ │
│ │ │ Order Service │─►│ Load Balancer │──►│ User Service │ │ │
│ │ │ (Client) │ │ / API Gateway │ │ (10.0.1.5) │ │ │
│ │ │ 단순 호출 │ │ │ │ (10.0.1.6) │ │ │
│ │ └───────────────┘ │ - 라우팅 │ │ (10.0.1.7) │ │ │
│ │ │ - 로드밸런싱 │ └───────────────┘ │ │
│ │ GET /user-service │ - 헬스체크 │ │ │
│ │ /users/123 └───────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 동작 방식: │
│ 1. 클라이언트는 LB/Gateway에 요청 (서비스 이름 사용) │
│ 2. LB가 Registry에서 인스턴스 조회 │
│ 3. LB가 로드밸런싱 후 인스턴스 호출 │
│ │
│ 예: AWS ELB + Route 53, Kubernetes Service, Nginx + Consul │
│ │
│ 장점: 단점: │
│ - 클라이언트 단순화 - LB가 SPOF │
│ - 언어 독립적 - 추가 홉 (지연) │
│ - 중앙 집중 관리 - LB 운영 비용 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
서비스 등록¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서비스 등록 패턴 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Self-Registration: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ User Service Service Registry │ │
│ │ │ │ │ │
│ │ 시작 │─────── Register(ip, port) ─────────►│ │ │
│ │ │ │ │ │
│ │ 주기적│─────── Heartbeat ─────────────────►│ │ │
│ │ │ │ │ │
│ │ 종료 │─────── Deregister ─────────────────►│ │ │
│ │ │ │ │ │
│ │ 예: Eureka Client, Consul Agent │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Third-Party Registration (Registrar): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ User Service Registrar Service Registry │ │
│ │ │ │ │ │ │
│ │ 시작 │ │ │ │ │
│ │ │◄── 감지 ────────│ │ │ │
│ │ │ │── Register ──────────►│ │ │
│ │ │ │ │ │ │
│ │ 종료 │◄── 감지 ────────│ │ │ │
│ │ │ │── Deregister ────────►│ │ │
│ │ │ │
│ │ 예: Netflix Prana, Kubernetes, Docker Swarm │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
주요 도구 비교¶
| 도구 | 타입 | 특징 |
|---|---|---|
| Consul | CP | 헬스체크, KV 스토어, DNS 인터페이스 |
| Eureka | AP | Netflix OSS, Spring Cloud 통합 |
| etcd | CP | Raft 합의, Kubernetes 기반 |
| ZooKeeper | CP | 분산 조정, 복잡한 API |
| Kubernetes | - | Service + DNS 내장, 클라우드 네이티브 |
2. 서킷 브레이커¶
서킷 브레이커 패턴¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서킷 브레이커 패턴 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 문제: Cascading Failure (연쇄 장애) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Order Service → Payment Service → Bank API │ │
│ │ │ │ ✗ (장애) │ │
│ │ │ │ │ │
│ │ │ 타임아웃 대기... │ │
│ │ │ 스레드 고갈 │ │
│ │ │ ✗ │ │
│ │ │ │ │
│ │ 타임아웃 대기... │ │
│ │ 스레드 고갈 │ │
│ │ ✗ │ │
│ │ │ │
│ │ → 하나의 장애가 전체 시스템으로 전파! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 해결: 서킷 브레이커 (전기 차단기처럼 동작) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Order Service → [CB] → Payment Service → [CB] → Bank API │ │
│ │ ┌─────────┐ ✗ (장애) │ │
│ │ │ Circuit │ │ │
│ │ │ OPEN │ ──► 즉시 실패 반환 │ │
│ │ └─────────┘ │ │
│ │ │ │
│ │ → 빠른 실패로 자원 보호 │ │
│ │ → 장애 격리 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
상태 전이¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서킷 브레이커 상태 전이 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 실패율 임계값 초과 │
│ ┌────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────────────┐ ┌──────┴───────────────┐ │
│ │ │ │ │ │
│ │ CLOSED │───────────►│ OPEN │ │
│ │ │ │ │ │
│ │ - 정상 동작 │ │ - 요청 차단 │ │
│ │ - 모든 요청 허용 │ │ - 즉시 실패 반환 │ │
│ │ - 실패율 모니터링 │ │ - 폴백 실행 │ │
│ │ │ │ │ │
│ └──────────────────────┘ └──────────┬───────────┘ │
│ ▲ │ │
│ │ │ 타임아웃 후 │
│ │ ▼ │
│ │ ┌──────────────────────┐ │
│ │ │ │ │
│ └──────────────────────────│ HALF-OPEN │ │
│ 성공 │ │ │
│ │ - 제한된 요청 허용 │ │
│ 실패 │ - 상태 확인 중 │ │
│ ┌─────────────────│ │ │
│ │ └──────────────────────┘ │
│ │ │ │
│ └────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 타임라인 예시: │
│ │
│ CLOSED ────────────────────────────► OPEN ──────► HALF-OPEN │
│ │ │ │ │
│ │ ✓ ✓ ✓ ✗ ✗ ✗ ✗ ✗ │ │ ✓ │
│ │ 실패율 50% 초과 │ │ 성공! │
│ │ │ 대기 10초 │ │
│ │ │ ▼ │
│ │ │ CLOSED ───────► │
│ │ │
└─────────────────────────────────────────────────────────────────────────┘
주요 설정 파라미터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서킷 브레이커 설정 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CircuitBreaker: │
│ failureRateThreshold: 50 # 실패율 임계값 (%) │
│ slowCallRateThreshold: 100 # 느린 호출 비율 임계값 (%) │
│ slowCallDurationThreshold: 2s # 느린 호출 기준 시간 │
│ minimumNumberOfCalls: 10 # 최소 호출 수 (통계 계산용) │
│ slidingWindowSize: 100 # 슬라이딩 윈도우 크기 │
│ slidingWindowType: COUNT_BASED # COUNT or TIME_BASED │
│ waitDurationInOpenState: 10s # OPEN 유지 시간 │
│ permittedNumberOfCallsInHalfOpen: 3 # HALF-OPEN에서 허용 호출 수 │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 슬라이딩 윈도우: │
│ │
│ COUNT_BASED (최근 N개 요청 기준): │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ ✓ │ ✓ │ ✗ │ ✓ │ ✗ │ ✗ │ ✓ │ ✗ │ ✗ │ ✗ │ → 실패율 60% │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ ◄────────────── 최근 10개 ─────────────► │
│ │
│ TIME_BASED (최근 N초 기준): │
│ ┌───────────────────────────────────────┐ │
│ │ ✓ ✓ ✗ ✓ ✗ ✗ ✓ ✗ ✗ ✗ │ → 실패율 60% │
│ └───────────────────────────────────────┘ │
│ ◄────────────── 최근 10초 ─────────────► │
│ │
└─────────────────────────────────────────────────────────────────────────┘
폴백 전략¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 폴백 (Fallback) 전략 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 기본값 반환 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ def get_user_profile(user_id): │ │
│ │ try: │ │
│ │ return user_service.get(user_id) │ │
│ │ except CircuitOpenException: │ │
│ │ return {"name": "Guest", "avatar": "default.png"} │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 캐시된 데이터 반환 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ def get_product_price(product_id): │ │
│ │ try: │ │
│ │ price = price_service.get(product_id) │ │
│ │ cache.set(product_id, price) │ │
│ │ return price │ │
│ │ except CircuitOpenException: │ │
│ │ return cache.get(product_id) # 마지막 캐시값 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 대체 서비스 호출 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ def send_notification(user_id, message): │ │
│ │ try: │ │
│ │ return push_service.send(user_id, message) │ │
│ │ except CircuitOpenException: │ │
│ │ return email_service.send(user_id, message) # 대체 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. 큐에 저장 후 나중에 처리 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ def process_order(order): │ │
│ │ try: │ │
│ │ return order_service.process(order) │ │
│ │ except CircuitOpenException: │ │
│ │ retry_queue.enqueue(order) # 나중에 재시도 │ │
│ │ return {"status": "pending"} │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
구현 도구¶
| 도구 | 언어 | 특징 |
|---|---|---|
| Resilience4j | Java | 경량, 함수형, 모듈러 |
| Hystrix | Java | Netflix OSS (유지보수 모드) |
| Polly | .NET | 풍부한 기능, 정책 조합 |
| go-kit | Go | 미들웨어 기반 |
| opossum | Node.js | Promise 지원 |
3. Bulkhead 패턴¶
Bulkhead 개념¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Bulkhead (격벽) 패턴 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 선박의 격벽 (Bulkhead): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ┌─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │ │ │ 침수│ │ │ ← 격벽으로 분리 │ │
│ │ │ A │ B │ C │ D │ E │ │ │
│ │ │ │ │~~~~~│ │ │ → C 구역 침수해도 │ │
│ │ └─────┴─────┴─────┴─────┴─────┘ 다른 구역은 안전! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 소프트웨어 적용: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 격벽 없음 (공유 리소스): │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Thread Pool (10 threads) │ │ │
│ │ │ [Payment][Payment][Payment][Payment][Payment]... │ │ │
│ │ │ 모든 스레드가 느린 Payment 호출에 점유됨 │ │ │
│ │ │ → Order, User 서비스 호출도 불가! │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 격벽 적용 (분리된 리소스): │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Payment Pool │ │ Order Pool │ │ User Pool │ │ │
│ │ │ (5 threads) │ │ (3 threads) │ │ (2 threads) │ │ │
│ │ │ [P][P][P] │ │ [O][O] │ │ [U] │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ Payment 지연되어도 Order, User는 정상 동작! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Bulkhead 유형¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Bulkhead 구현 방식 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Thread Pool Bulkhead │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ Service A Thread Pool │ │ │
│ │ │ maxThreads: 10 │ │ │
│ │ │ queueCapacity: 100 │ │ │
│ │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │
│ │ │ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │ │ │ │
│ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 장점: 완전한 격리, 타임아웃 제어 용이 │ │
│ │ 단점: 오버헤드 (컨텍스트 스위칭) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. Semaphore Bulkhead │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ Service A Semaphore │ │ │
│ │ │ permits: 10 (현재 사용: 7, 대기: 0) │ │ │
│ │ │ │ │ │
│ │ │ Request → acquire() → Call → release() │ │ │
│ │ │ │ │ │
│ │ │ permits 초과 시 → 즉시 거부 또는 대기 │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 장점: 가벼움, 호출 스레드에서 실행 │ │
│ │ 단점: 타임아웃 제어 어려움 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 3. 프로세스 수준 격리 (컨테이너) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Container │ │ Container │ │ Container │ │ │
│ │ │ Payment │ │ Order │ │ User │ │ │
│ │ │ API calls │ │ API calls │ │ API calls │ │ │
│ │ │ CPU: 2 │ │ CPU: 1 │ │ CPU: 1 │ │ │
│ │ │ Mem: 4GB │ │ Mem: 2GB │ │ Mem: 2GB │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ 가장 강력한 격리, Kubernetes Pod 리소스 제한 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4. 서비스 메시¶
서비스 메시 개념¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 서비스 메시 (Service Mesh) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 문제: 서비스별로 횡단 관심사 구현 필요 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 각 서비스에서 직접 구현: │ │
│ │ - 서비스 디스커버리 │ │
│ │ - 로드밸런싱 │ │
│ │ - 서킷 브레이커 │ │
│ │ - 재시도/타임아웃 │ │
│ │ - TLS/인증 │ │
│ │ - 메트릭/추적 │ │
│ │ │ │
│ │ → 언어/프레임워크마다 다른 구현 │ │
│ │ → 일관성 유지 어려움 │ │
│ │ → 개발자 부담 증가 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 해결: 인프라 레이어로 분리 (Service Mesh) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 네트워크 프록시가 모든 횡단 관심사 처리 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ Control Plane │ │ │
│ │ │ (설정, 정책, 인증서 관리) │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ ▲ ▲ ▲ │ │
│ │ │ │ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ │
│ │ │ │Proxy│◄──┼──┼──►│Proxy│◄──┼──┼──►│Proxy│ │ │ │
│ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ │
│ │ │ ▲ │ │ ▲ │ │ ▲ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ ┌─┴─┐ │ │ ┌─┴─┐ │ │ ┌─┴─┐ │ │ │
│ │ │ │App│ │ │ │App│ │ │ │App│ │ │ │
│ │ │ └───┘ │ │ └───┘ │ │ └───┘ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ App은 비즈니스 로직에만 집중! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
사이드카 패턴¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 사이드카 (Sidecar) 패턴 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Pod (Kubernetes) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │
│ │ │ │ Application │ │ Sidecar │ │ │ │
│ │ │ │ Container │ │ Proxy │ │ │ │
│ │ │ │ │ │ (Envoy) │ │ │ │
│ │ │ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │ │ │
│ │ │ │ │ App Process │◄─┼───┼─►│ Proxy Process │◄─┼─── 외부 │ │ │
│ │ │ │ │ │ │ │ │ │ │ 트래픽 │ │ │
│ │ │ │ │ Port 8080 │ │ │ │ Port 15001 │ │ │ │ │
│ │ │ │ └───────────────┘ │ │ └───────────────┘ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ └─────────────────────┘ └─────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ 공유: Network Namespace, Volume │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 사이드카가 처리하는 기능: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Inbound Traffic: Outbound Traffic: │ │
│ │ - TLS 종료 - 서비스 디스커버리 │ │
│ │ - 인증/인가 - 로드밸런싱 │ │
│ │ - 레이트 리미팅 - 서킷 브레이커 │ │
│ │ - 메트릭 수집 - 재시도/타임아웃 │ │
│ │ - mTLS │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Istio 아키텍처¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Istio 아키텍처 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Control Plane (istiod) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Pilot │ │ Citadel │ │ Galley │ │ │
│ │ │ (설정 배포) │ │ (인증서) │ │ (설정 검증) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ └────────────────┼────────────────┘ │ │
│ │ │ │ │
│ │ │ xDS API (설정 푸시) │ │
│ │ ▼ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Data Plane │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Service A │ │ Service B │ │ │
│ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │
│ │ │ │ Envoy │◄─┼───────────┼─►│ Envoy │ │ │ │
│ │ │ │ Proxy │ │ mTLS │ │ Proxy │ │ │ │
│ │ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌─────┴─────┐ │ │ ┌─────┴─────┐ │ │ │
│ │ │ │ App │ │ │ │ App │ │ │ │
│ │ │ └───────────┘ │ │ └───────────┘ │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ 주요 기능: │
│ - Traffic Management: 라우팅, 카나리 배포, A/B 테스트 │
│ - Security: mTLS, RBAC │
│ - Observability: 메트릭, 로그, 분산 추적 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
서비스 메시 도구 비교¶
| 도구 | 프록시 | 특징 |
|---|---|---|
| Istio | Envoy | 풍부한 기능, 복잡도 높음 |
| Linkerd | linkerd2-proxy | 경량, Rust 기반, 단순함 |
| Consul Connect | Envoy/내장 | HashiCorp 생태계 통합 |
| AWS App Mesh | Envoy | AWS 서비스 통합 |
5. 분산 추적¶
분산 추적의 필요성¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 분산 추적 (Distributed Tracing) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 문제: 마이크로서비스에서 요청 추적 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Client → API GW → Order → Inventory → Payment → Shipping │ │
│ │ ↓ │ │
│ │ User Service │ │
│ │ │ │
│ │ "주문 API가 느린데, 어디서 지연이 발생하지?" │ │
│ │ "에러가 발생했는데, 어느 서비스에서 시작된 거지?" │ │
│ │ │ │
│ │ 로그를 봐도: │ │
│ │ - 각 서비스 로그가 분산되어 있음 │ │
│ │ - 어떤 로그가 같은 요청인지 알 수 없음 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 해결: Trace ID로 전체 요청 추적 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Trace ID: abc123 │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ API GW [──────────────────────────────────────────] │ │ │
│ │ │ Order │ [─────────────────────────────────] │ │ │
│ │ │ Inventory │ [────────────────] │ │ │
│ │ │ Payment │ [──────────] │ │ │
│ │ │ User │ [────] │ │ │
│ │ │ 0ms 100ms 200ms 300ms 400ms 500ms │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ → 한눈에 전체 흐름과 병목 지점 파악! │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Trace, Span, Context¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 추적 구성 요소 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Trace: 전체 요청의 여정 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Trace ID: abc-123-def-456 │ │
│ │ │ │
│ │ Span: 하나의 작업 단위 │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ Span A (Root Span) │ │ │
│ │ │ service: api-gateway │ │ │
│ │ │ operation: handle_request │ │ │
│ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Span B (Child of A) │ │ │ │
│ │ │ │ service: order-service │ │ │ │
│ │ │ │ operation: create_order │ │ │ │
│ │ │ │ ┌────────────────────────┐ ┌────────────────────┐ │ │ │ │
│ │ │ │ │ Span C (Child of B) │ │ Span D (Child of B)│ │ │ │ │
│ │ │ │ │ service: inventory │ │ service: user │ │ │ │ │
│ │ │ │ │ operation: reserve │ │ operation: get │ │ │ │ │
│ │ │ │ └────────────────────────┘ └────────────────────┘ │ │ │ │
│ │ │ └──────────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Span 구조: │
│ { │
│ "traceId": "abc-123-def-456", │
│ "spanId": "span-789", │
│ "parentSpanId": "span-456", │
│ "operationName": "create_order", │
│ "serviceName": "order-service", │
│ "startTime": "2024-01-15T10:30:00.000Z", │
│ "duration": 150, // ms │
│ "tags": { "http.status": 200, "user.id": "123" }, │
│ "logs": [ { "event": "order_created", "orderId": "ord-999" } ] │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Context Propagation¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Context 전파 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ HTTP 헤더를 통한 전파: │
│ │
│ Order Service Inventory Service │
│ │ │ │
│ │ POST /inventory/reserve │ │
│ │ Headers: │ │
│ │ X-B3-TraceId: abc123 │ │
│ │ X-B3-SpanId: span456 │ │
│ │ X-B3-ParentSpanId: span123 │ │
│ │ X-B3-Sampled: 1 │ │
│ │───────────────────────────────────────►│ │
│ │ │ │
│ │ │ 새 Span 생성: │
│ │ │ spanId: span789 │
│ │ │ parentSpanId: span456 │
│ │ │ traceId: abc123 │
│ │ │ │
│ │
│ 표준: │
│ - B3 Propagation (Zipkin) │
│ - W3C Trace Context (표준) │
│ - Jaeger Propagation │
│ │
│ W3C Trace Context: │
│ traceparent: 00-abc123def456-span789-01 │
│ tracestate: vendor=custom_value │
│ │
└─────────────────────────────────────────────────────────────────────────┘
추적 도구¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 분산 추적 도구 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Jaeger: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - CNCF 프로젝트 │ │
│ │ - Uber에서 개발 │ │
│ │ - Cassandra, Elasticsearch 백엔드 │ │
│ │ - 강력한 UI │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Zipkin: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - Twitter에서 개발 │ │
│ │ - 다양한 스토리지 지원 │ │
│ │ - 가벼움, 설치 쉬움 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ OpenTelemetry (OTel): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - 표준화된 관측성 프레임워크 │ │
│ │ - Traces + Metrics + Logs 통합 │ │
│ │ - 벤더 중립적 │ │
│ │ - Jaeger, Zipkin 등으로 내보내기 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 상용: │
│ - Datadog APM │
│ - New Relic │
│ - AWS X-Ray │
│ - Google Cloud Trace │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6. 기타 중요 패턴¶
API Gateway¶
┌─────────────────────────────────────────────────────────────────────────┐
│ API Gateway 패턴 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Clients API Gateway │ │
│ │ ┌──────┐ ┌─────────────────┐ │ │
│ │ │ Web │──────────────────►│ │ │ │
│ │ └──────┘ │ - 라우팅 │ ┌─────────┐ │ │
│ │ ┌──────┐ │ - 인증/인가 │───►│ User Svc│ │ │
│ │ │Mobile│──────────────────►│ - 레이트리밋 │ └─────────┘ │ │
│ │ └──────┘ │ - 캐싱 │ ┌─────────┐ │ │
│ │ ┌──────┐ │ - 요청 변환 │───►│Order Svc│ │ │
│ │ │ IoT │──────────────────►│ - 로깅/메트릭 │ └─────────┘ │ │
│ │ └──────┘ │ - SSL 종료 │ ┌─────────┐ │ │
│ │ │ │───►│Prod Svc │ │ │
│ │ └─────────────────┘ └─────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 도구: Kong, AWS API Gateway, Nginx, Envoy, Spring Cloud Gateway │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Retry Pattern¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Retry 전략 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Exponential Backoff with Jitter: │
│ │
│ 시도 1: ──X (실패) │
│ 대기: 100ms + random(0-50ms) │
│ 시도 2: ────X (실패) │
│ 대기: 200ms + random(0-100ms) │
│ 시도 3: ──────X (실패) │
│ 대기: 400ms + random(0-200ms) │
│ 시도 4: ────────✓ (성공) │
│ │
│ config: │
│ maxRetries: 5 │
│ initialDelay: 100ms │
│ maxDelay: 10s │
│ multiplier: 2 │
│ jitter: 0.5 # 50% 랜덤 │
│ retryableExceptions: │
│ - ConnectionException │
│ - TimeoutException │
│ # 4xx 에러는 재시도 안함! │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Health Check Pattern¶
┌─────────────────────────────────────────────────────────────────────────┐
│ Health Check │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Liveness Probe (살아있는가?): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ GET /health/live │ │
│ │ │ │
│ │ 200 OK → 프로세스 정상 │ │
│ │ 5xx → 프로세스 재시작 필요 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Readiness Probe (요청 처리 가능한가?): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ GET /health/ready │ │
│ │ │ │
│ │ 체크 항목: │ │
│ │ - DB 연결 │ │
│ │ - 캐시 연결 │ │
│ │ - 의존 서비스 │ │
│ │ - 초기화 완료 │ │
│ │ │ │
│ │ 200 OK → 트래픽 라우팅 │ │
│ │ 503 → 트래픽 제외 (재시작 안함) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7. 연습 문제¶
연습 1: 서킷 브레이커 설계¶
결제 서비스에 대한 서킷 브레이커를 설계하세요: - 적절한 임계값 설정 - 폴백 전략 정의 - 상태 전이 시나리오 작성
연습 2: 서비스 메시 선택¶
다음 요구사항에 적합한 서비스 메시를 선택하고 이유를 설명하세요: - 10개의 마이크로서비스 - Kubernetes 환경 - mTLS 필수 - 카나리 배포 필요 - 팀의 Kubernetes 경험은 중급
연습 3: 분산 추적 구현¶
주문 처리 시스템의 분산 추적을 설계하세요: - 추적해야 할 주요 스팬 정의 - 중요한 태그/메타데이터 정의 - 샘플링 전략 수립
다음 단계¶
15_Distributed_Systems_Concepts.md에서 분산 시스템의 기본 개념과 시간, 리더 선출 알고리즘을 배워봅시다!
참고 자료¶
- "Release It!" - Michael Nygard
- "Building Microservices" - Sam Newman
- Istio Documentation
- Envoy Proxy Documentation
- OpenTelemetry Documentation
- Netflix Tech Blog: Hystrix
- Resilience4j Documentation