마이크로서비스 환경에서는 수십, 수백 개의 서비스가 끊임없이 생겨나고 사라집니다.
서비스들은 서로를 어떻게 찾아서 통신할까요? 그 답이 바로 서비스 디스커버리입니다.
서비스 디스커버리(Service Discovery) 란, 네트워크 상에서 서비스의 위치(IP 주소, 포트 등)를 자동으로 탐색하고 연결하는 메커니즘입니다.
전통적인 모놀리식(Monolithic) 아키텍처에서는 서비스가 고정된 서버에 올라가 있어서, IP 주소와 포트를 미리 알고 하드코딩해도 큰 문제가 없었습니다.
하지만 마이크로서비스 아키텍처(MSA) 환경에서는 상황이 달라집니다.
| 상황 | 문제 |
|---|---|
| 서비스 인스턴스가 수시로 추가/제거됨 | IP 주소가 계속 바뀜 |
| 컨테이너(Docker, Kubernetes) 환경 | 재시작할 때마다 IP가 달라짐 |
| 오토 스케일링 | 인스턴스 수가 동적으로 증가/감소 |
이처럼 서비스의 위치가 동적으로 변하는 환경에서 IP를 하드코딩하는 것은 불가능합니다.
→ 서비스가 서로를 자동으로 찾을 수 있는 체계가 필요합니다.
💡 쉽게 이해하기
전화번호부가 없던 시절에는 상대방 번호를 직접 외워야 했습니다. 전화번호부(114)가 생기면서 이름만 알면 번호를 찾을 수 있게 됐죠.
서비스 디스커버리는 마이크로서비스 세계의 전화번호부 역할을 합니다.
서비스 디스커버리는 크게 세 가지 요소로 이루어집니다.
모든 서비스의 위치 정보(IP, 포트, 상태)를 저장하는 중앙 저장소입니다.
서비스가 시작되면 자신의 정보를 레지스트리에 등록하고, 종료되면 제거합니다.
Service Registry (예: Consul, Eureka, etcd)
┌────────────────────────────────────┐
│ 서비스명 IP 포트 │
│ order-service 10.0.0.1 8080 │
│ payment-service 10.0.0.2 8081 │
│ user-service 10.0.0.3 8082 │
└────────────────────────────────────┘
서비스가 시작될 때 자신의 정보를 레지스트리에 등록하는 과정입니다.
서비스가 종료되거나 장애가 발생하면 레지스트리에서 제거됩니다.
서비스가 다른 서비스를 호출할 때, 레지스트리에서 해당 서비스의 위치를 조회하는 과정입니다.
클라이언트가 직접 서비스 레지스트리에 쿼리하여 사용 가능한 서비스 인스턴스 목록을 가져오고, 직접 로드 밸런싱하여 호출하는 방식입니다.
흐름
[클라이언트]
├── (1) 레지스트리에 "order-service 어디 있어?" 쿼리
├── (2) 레지스트리 → IP 목록 반환 [10.0.0.1, 10.0.0.4, 10.0.0.7]
└── (3) 클라이언트가 직접 로드 밸런싱 후 호출
대표 도구: Netflix Eureka + Ribbon
| 장점 | 단점 |
|---|---|
| 클라이언트가 직접 인스턴스 선택 → 유연한 로드 밸런싱 가능 | 클라이언트 코드가 복잡해짐 |
| 중간 단계가 없어 네트워크 홉(hop)이 적음 | 각 클라이언트마다 디스커버리 로직 구현 필요 |
| 언어/프레임워크마다 별도 구현 필요 |
클라이언트는 로드 밸런서나 API 게이트웨이에만 요청을 보내고, 로드 밸런서가 레지스트리에 쿼리하여 적절한 서비스 인스턴스로 라우팅하는 방식입니다.
흐름
[클라이언트]
└── (1) 로드 밸런서에 요청
[로드 밸런서 / API Gateway]
├── (2) 레지스트리에 "order-service 어디 있어?" 쿼리
├── (3) 레지스트리 → IP 목록 반환
└── (4) 적절한 인스턴스로 라우팅 후 응답 반환
대표 도구: AWS ALB, Kubernetes, Nginx + Consul
| 장점 | 단점 |
|---|---|
| 클라이언트 코드가 단순해짐 (디스커버리 로직 불필요) | 로드 밸런서가 SPOF가 될 수 있음 |
| 언어/프레임워크에 독립적 | 네트워크 홉이 하나 더 추가됨 |
| 중앙에서 라우팅 정책 관리 용이 | 로드 밸런서 자체의 이중화 필요 |
클라이언트 사이드 서버 사이드
클라이언트 ──→ 레지스트리 클라이언트 ──→ 로드 밸런서 ──→ 서비스
클라이언트 ──→ 서비스 로드 밸런서 ──→ 레지스트리
(클라이언트가 직접 선택) (로드 밸런서가 대신 처리)
서비스를 레지스트리에 등록하는 방법도 두 가지가 있습니다.
서비스 인스턴스가 직접 레지스트리에 자신을 등록하고 해제합니다.
주기적으로 헬스체크(Health Check) 신호를 보내 살아있음을 알립니다.
// Spring Boot + Eureka 예시
// application.yml 설정만으로 자동 등록
eureka:
client:
serviceUrl:
defaultZone: http://eureka-server:8761/eureka/
instance:
preferIpAddress: true
| 장점 | 단점 |
|---|---|
| 구현이 단순 | 서비스 코드에 레지스트리 의존성이 생김 |
| 서비스가 자신의 상태를 직접 관리 | 언어/프레임워크마다 구현 방식이 다름 |
서비스 자신이 아닌 외부 시스템(registrar)이 등록과 해제를 담당합니다.
Kubernetes처럼 플랫폼이 직접 서비스의 생명주기를 관리할 때 이 방식을 사용합니다.
Kubernetes 환경 예시
Pod 시작 → Kubernetes가 자동으로 Service Registry에 등록
Pod 종료 → Kubernetes가 자동으로 Service Registry에서 제거
| 장점 | 단점 |
|---|---|
| 서비스 코드에 레지스트리 의존성 없음 | 별도의 registrar 시스템 필요 |
| 언어/프레임워크에 독립적 | registrar 자체가 SPOF가 될 수 있음 |
서비스 레지스트리는 단순히 주소만 저장하는 것이 아닙니다.
등록된 서비스가 실제로 살아있는지 주기적으로 확인합니다. 이를 헬스 체크라고 합니다.
헬스 체크에 실패한 서비스는 레지스트리에서 자동으로 제거되어, 장애 서비스로 요청이 라우팅되는 것을 방지합니다.
헬스 체크 흐름
레지스트리 ──→ "살아있니?" (주기적 ping)
서비스 ──→ "응, 살아있어!" (200 OK)
응답 없음 or 오류 → 레지스트리에서 해당 인스턴스 제거
| 방식 | 설명 |
|---|---|
| HTTP 체크 | /health 엔드포인트에 GET 요청을 보내 200 응답 확인 |
| TCP 체크 | 지정된 포트로 TCP 연결 시도 |
| TTL 체크 | 서비스가 주기적으로 레지스트리에 "살아있음" 신호를 보냄 |
# Consul 헬스 체크 설정 예시
service:
name: "order-service"
port: 8080
check:
http: "http://localhost:8080/health"
interval: "10s" # 10초마다 체크
timeout: "3s" # 3초 안에 응답 없으면 실패
| 도구 | 특징 | 주요 사용처 |
|---|---|---|
| Netflix Eureka | REST 기반, Spring Cloud와 궁합 좋음 | Spring Boot MSA |
| HashiCorp Consul | DNS 및 HTTP API 지원, 다양한 환경에서 사용 가능 | 범용 MSA |
| etcd | 분산 키-값 저장소, 가볍고 빠름 | Kubernetes 내부 사용 |
| Apache Zookeeper | 분산 코디네이션, 안정성 높음 | Kafka, Hadoop 등 |
| Kubernetes DNS | k8s 환경에서 서비스명으로 자동 라우팅 | Kubernetes 환경 |
주문 서비스가 결제 서비스를 호출하는 상황을 예로 들어보겠습니다.
[서버 사이드 디스커버리 전체 흐름]
1. payment-service 시작
└── 레지스트리에 자신을 등록
{ name: "payment-service", ip: "10.0.0.2", port: 8081 }
2. order-service가 결제 요청
└── API Gateway에 요청 전달
POST /payment/process
3. API Gateway가 레지스트리 조회
└── "payment-service 인스턴스 목록 줘"
→ [10.0.0.2:8081, 10.0.0.5:8081, 10.0.0.8:8081]
4. API Gateway가 로드 밸런싱
└── 10.0.0.5:8081 선택 후 요청 전달
5. payment-service 응답
└── API Gateway → order-service로 응답 반환
6. payment-service 중 하나 장애 발생
└── 레지스트리가 헬스 체크 실패 감지
→ 해당 인스턴스를 목록에서 자동 제거
서비스 디스커버리
└── 마이크로서비스 환경에서 서비스 위치를 자동으로 찾는 메커니즘
핵심 구성 요소
├── 서비스 레지스트리 → 서비스 위치 정보를 저장하는 중앙 저장소
├── 서비스 등록 → 서비스 시작/종료 시 레지스트리에 등록/해제
└── 헬스 체크 → 장애 서비스를 자동으로 레지스트리에서 제거
디스커버리 방식
├── 클라이언트 사이드 → 클라이언트가 레지스트리 직접 조회 후 호출
└── 서버 사이드 → 로드 밸런서가 대신 조회 후 라우팅
등록 방식
├── 자가 등록 → 서비스가 직접 레지스트리에 등록
└── 제3자 등록 → 외부 시스템(Kubernetes 등)이 대신 등록
주요 도구
├── Eureka → Spring Cloud 환경
├── Consul → 범용 MSA 환경
└── etcd → Kubernetes 내부
| 용어 | 설명 |
|---|---|
| MSA (Microservice Architecture) | 애플리케이션을 작고 독립적인 서비스로 분리한 아키텍처 |
| 서비스 레지스트리 | 서비스의 IP, 포트, 상태를 저장하는 중앙 저장소 |
| 헬스 체크 | 서비스가 정상 동작 중인지 주기적으로 확인하는 것 |
| 로드 밸런싱 | 요청을 여러 서비스 인스턴스에 분산하는 것 |
| 인스턴스 | 실행 중인 서비스의 단일 프로세스 단위 |
| 네트워크 홉 (Hop) | 네트워크에서 패킷이 거치는 중간 장치 수 |
| 오토 스케일링 | 트래픽에 따라 서비스 인스턴스 수를 자동으로 조절하는 것 |
| API 게이트웨이 | 클라이언트와 서비스 사이에서 요청을 중계하는 진입점 |