과거의 온프레미스 환경에서는 서버 IP가 고정(192.168.0.10)되어 있어 설정 파일에 박아두면 그만이었습니다.
하지만 클라우드(AWS, K8s) 환경에서는 컨테이너가 수시로 죽고 살아나며 IP가 **동적(Dynamic)**으로 바뀝니다.
그래서 **"현재 살아있는 서버들의 목록(Service Registry)"**을 관리하는 전화번호부가 필요합니다. 중요한 건 이 전화번호부를 누가 보고 전화를 거느냐입니다.
1. 방식 1: 클라이언트 사이드 디스커버리 (Client-side Discovery) - "능동적인 손님"
Netflix Eureka가 대표적인 예시입니다. API를 호출하는 **클라이언트(요청자)**가 서비스 레지스트리(전화번호부)를 직접 조회해서, 사용 가능한 인스턴스 중 하나를 골라 호출합니다.
특징
- 똑똑한 클라이언트: 클라이언트가 로드 밸런싱 알고리즘(Round Robin 등)을 직접 수행합니다.
- 직접 통신: 중간에 거치는 녀석 없이, 알아낸 IP로 바로 꽂습니다.
코드 예시 (Spring Cloud Netflix)
// 1. 클라이언트(주문) 설정
@EnableDiscoveryClient // "나 전화번호부 볼 수 있어"
@SpringBootApplication
public class OrderServiceApplication { ... }
// 2. 호출 로직 (Ribbon/Feign)
@FeignClient(name = "payment-service") // "결제 서비스 IP 좀 알려줘"
public interface PaymentClient {
@GetMapping("/pay")
void pay();
}
동작: 주문 서비스가 Eureka 서버에게 "Payment 서비스 IP 목록 줘"라고 물어보고, 받은 목록(10.1, 10.2) 중 하나를 골라 직접 호출합니다.
장단점
- 장점: 로드 밸런서(LB)라는 병목 지점이 없습니다. 클라이언트가 상황에 맞춰 유연하게 서버를 선택할 수 있습니다.
- 단점: 클라이언트 코드에 디스커버리 로직(라이브러리)이 침투합니다. (Java면 Java용 라이브러리, Node.js면 Node용 라이브러리를 다 따로 붙여야 함)
2. 방식 2: 서버 사이드 디스커버리 (Server-side Discovery) - "안내 데스크 위임"
Kubernetes(K8s) Service나 AWS ELB가 사용하는 방식입니다. 클라이언트는 아무것도 모른 채 그냥 **로드 밸런서(VIP)**로 요청을 보냅니다. 그러면 로드 밸런서가 레지스트리를 조회해서 실제 서버로 토스합니다.
특징
- 단순한 클라이언트: 클라이언트는 디스커버리 로직을 몰라도 됩니다. 그냥 http://payment-service로 요청하면 끝입니다.
- 인프라 위임: 위치 찾기와 로드 밸런싱을 인프라(K8s, Nginx)가 전담합니다.
개념 예시 (Kubernetes)
# 클라이언트는 그냥 이 DNS 이름만 알면 됨
apiVersion: v1
kind: Service
metadata:
name: payment-service # 호출 주소: http://payment-service
spec:
selector:
app: payment # 실제 Pod(컨테이너)들을 찾아서 연결해줌
ports:
- port: 80
동작: 주문 서비스는 http://payment-service로 요청을 보냅니다. 쿠버네티스의 Kube-Proxy(Server-side LB)가 요청을 가로채서, 현재 살아있는 Payment 파드 중 하나로 트래픽을 넘겨줍니다.
장단점
- 장점: **언어 독립적(Language Agnostic)**입니다. Java든 Python이든 Go든 상관없이 그냥 HTTP 요청만 날리면 됩니다. 클라이언트 코드가 매우 깔끔해집니다.
- 단점: 로드 밸런서(LB)를 한 번 거쳐야 하므로 미세한 **네트워크 홉(Hop)**이 추가됩니다. LB가 죽으면 통신이 마비될 수 있습니다(SPOF).
3. 실무 비교: 언제 무엇을 쓰는가?
| 구분 | Client-side Discovery (Netflix) | Server-side Discovery (K8s/AWS) |
| 위치 결정 주체 | 클라이언트 (서비스 A가 직접 찾음) | 라우터/LB (중간 관리자가 찾음) |
| 의존성 | 언어별 라이브러리 필요 (Ribbon 등) | 인프라 의존적 (K8s, ELB) |
| 네트워크 경로 | Direct (Service A -> Service B) | Hop (Service A -> LB -> Service B) |
| 구현 난이도 | 코드 복잡도 증가 | 인프라 설정 필요 |
| 추천 상황 | Spring Cloud 레거시, 인프라 제어가 힘들 때 | Kubernetes 환경, 컨테이너 기반 최신 아키텍처 |
결론
Spring Cloud 초창기(2015~2018)에는 넷플릭스 스택인 **클라이언트 사이드 디스커버리(Eureka)**가 표준이었습니다.
하지만 Kubernetes가 세상을 지배한 지금은, 굳이 애플리케이션 코드에 무거운 디스커버리 라이브러리를 넣을 필요가 없어졌습니다. 쿠버네티스가 알아서 다 해주니까요(서버 사이드 디스커버리).
따라서 **"맨땅에 MSA를 구축한다"**면 Eureka를 고려해야 하지만, **"쿠버네티스나 AWS 위에 올린다"**면 인프라가 제공하는 DNS/Service 기능을 쓰는 것이 훨씬 세련되고 관리하기 편합니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 사이드카 패턴(Sidecar Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 벌크헤드 패턴(Bulkhead Pattern) 완벽 정리 (0) | 2025.12.07 |
| 재시도 패턴(Retry Pattern) 완벽 정리 (0) | 2025.12.07 |
| 헬스 체크 패턴(Health Check Pattern) 완벽 정리 (0) | 2025.12.07 |
| 처리율 제한 패턴(Rate Limiter Pattern) 완벽 정리 (0) | 2025.12.07 |