모놀리식 아키텍처에서는 하나의 애플리케이션이 단일 배포 단위로 묶여 있다.
애플리케이션 프로세스가 하나고, 보통 고정된 포트에서 동작하기 때문에 “서비스 위치”를 관리하는 것은 어렵지 않다.
[단일 애플리케이션] → http://app.example.com:8080
반면 MSA(Microservices Architecture)에서는 도메인 중심으로 여러 개의 독립적인 서비스가 분리되고, 각 서비스가 각각의 배포 단위를 가진다. 여기에 클라우드 컴푸팅 기술이 더해지면 다음과 같은 특징이 생긴다.
서비스 인스턴스 수가 동적으로 증가하거나 감소
각 인스턴스의 IP / Port가 고정되어 있지 않음
배포·롤링 업데이트마다 인스턴스 구성이 지속적으로 변경

규모가 작은 환경에서는 환경 변수나 설정 파일에 서비스 URL을 직접 적어 두는 방식으로 어느 정도 버틸 수 있다.
하지만 실제 아마존이나 넷플릭스처럼 수백~수천 개의 마이크로서비스를 운영하는 환경에서는
“모든 서비스 인스턴스의 IP/Port를 사람이 직접 관리한다” 는 것은 사실상 불가능하다.
이런 배경 때문에 등장한 것이 Service Registry / Service Discovery 패턴이다.
Service Registry(서비스 레지스트리)는 마이크로서비스 인스턴스의
를 중앙에서 저장하고, 각 인스턴스를 서비스 이름으로 식별할 수 있도록 매핑 정보를 관리하는 컴포넌트다.
간단히 말해:
“catalog-service라는 이름으로 어떤 인스턴스들이 어디에서 떠 있는지”
를 알고 있는 전화번호부 역할을 한다.
Service Discovery(서비스 디스커버리)는 클라이언트가
“order-service야, catalog-service로 요청 하나 보내줘.”
라고 서비스 이름만으로 요청을 보냈을 때,
Service Registry에서 실제 인스턴스 목록을 조회하고
헬스 체크 / 로드밸런싱 정책을 적용해
어느 인스턴스로 보낼지 결정하는 메커니즘을 의미한다.
즉, 클라이언트 입장에서는 더 이상 http://10.0.1.23:8080 같은 구체적인 주소를 알 필요가 없고, “catalog-service에게 보내라” 는 논리적인 이름만 알고 있으면 된다.
넷플릭스는 자사 인프라에서 이 문제를 해결하기 위해 Eureka라는 이름의 Service Registry & Service Discovery 서버를 개발해 사용해 왔다.
이후 Spring 진영에서는 이를 손쉽게 사용할 수 있도록 Spring Cloud Netflix 프로젝트에 통합(2015.03)했고, Spring Boot 애플리케이션에서도 Eureka 기반의 서비스 등록·조회 기능을 쉽게 사용할 수 있게 되었다.
Eureka는 크게 두 가지 역할로 나뉜다.
Eureka Server
Service Registry 역할
서비스 인스턴스 정보(IP, Port, 상태 등)를 저장하고 관리
Eureka Client
실제 비즈니스 로직을 수행하는 각 마이크로서비스
기동 시 자신을 Eureka Server에 등록하고, 다른 서비스 위치가 필요할 때 Eureka로부터 조회
Spring Boot에서의 의존성은 대략 다음과 같이 추가한다.
// Eureka Server
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
// Eureka Client
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
먼저 Eureka Server로 동작할 Spring Boot 애플리케이션을 하나 만든다.
여기에 @EnableEurekaServer 애노테이션을 추가하면 된다.
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
이제 설정 파일(application.yml)에서 서버 포트와 기본 설정을 잡아준다.
server:
port: 8761
spring:
application:
name: eureka-server
Eureka Server는 내부적으로도 Eureka Client 역할을 수행한다. 즉, 다른 Eureka Server에 자신을 등록하거나, 레지스트리를 조회할 수 있다.
하지만 단일 레지스트리 서버로만 사용할 때는 자기 자신을 다시 Eureka에 등록할 필요가 없기 때문에 Client 기능을 끄는 것이 일반적이다.
eureka:
client:
# 서버 자신을 Eureka에 등록하지 않음
register-with-eureka: false
# 다른 레지스트리로부터 레지스트리를 가져오지 않음
fetch-registry: false
register-with-eureka: false
fetch-registry: false
그렇다면 왜 Eureka Server도 Client 역할을 할 수 있도록 설계되어 있을까?
고가용성(HA)이 필요한 환경에서는 Eureka Server 역시 다중 인스턴스로 구성하고, 각 서버가 서로를 peer로 인식해서 레지스트리를 복제한다.
서버가 1대만 있으면 장애 시 전체 시스템에 큰 영향을 미친다.
서버를 2대 이상 두고 서로 레지스트리를 동기화하면,
한 대가 장애가 나더라도 나머지 서버가 계속 Service Registry 역할을 수행할 수 있다.
이를 위해 Eureka Server도 Eureka Client로서 동작하면서 서로를 service-url.defaultZone에 등록하는 구조를 사용한다.
Eureka Server 자체도 “하나의 서비스 인스턴스”이기 때문에,
다른 서비스들과 마찬가지로 eureka.instance 설정을 사용할 수 있다.
eureka:
instance:
hostname: eureka-server-1 # 실제 환경에 맞는 호스트/DNS
# prefer-ip-address: true # 필요 시 IP 기반으로 등록
health-check-url-path: /actuator/health
status-page-url-path: /actuator/info
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
hostname
Eureka에 등록될 때 사용할 호스트 이름
실제로는 보통 클라이언트 서비스에서 도메인 기반 등록을 할 때 더 많이 활용
health-check-url-path
Eureka가 인스턴스 상태를 확인하기 위해 호출하는 URL
Spring Boot Actuator를 사용하면 /actuator/health로 지정하는 것이 일반적
status-page-url-path
lease-renewal-interval-in-seconds
클라이언트가 Eureka에 하트비트(heartbeat)를 보내는 주기
기본 30초 수준. 너무 짧게 줄이면 트래픽 부담 증가
lease-expiration-duration-in-seconds
Eureka Server는 네트워크 이슈 등으로 인해 하트비트가 갑자기 줄어드는 경우에도,
정상 인스턴스를 함부로 레지스트리에서 제거하지 않도록 자기 보호 모드(Self-Preservation)를 제공한다.
eureka:
server:
enable-self-preservation: true
renewal-percent-threshold: 0.85
eviction-interval-timer-in-ms: 60000
enable-self-preservation
기본값은 true이며, 자기 보호 모드를 활성화한다.
네트워크 장애 등으로 하트비트가 일시적으로 급감하더라도 일정 기준까지는 인스턴스를 바로 제거하지 않는다.
운영 환경에서는 보통 true 유지가 일반적이고, 개발 환경에서는 디버깅을 위해 false로 두기도 한다.
renewal-percent-threshold
eviction-interval-timer-in-ms
개발 단계에서 인스턴스를 자주 재기동하면, “이미 죽은 인스턴스가 대시보드에서 한동안 남아 있는” 현상을 볼 수 있는데, 대부분 이 self-preservation과 eviction 주기 설정 때문에 발생한다.
Eureka Server를 2대로 구성하는 예시를 보자.
각 인스턴스는 서로를 peer로 등록해 레지스트리를 복제한다.
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
instance:
hostname: eureka-server-1
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
client:
# peer 구성이므로 두 옵션 모두 true
register-with-eureka: true
fetch-registry: true
service-url:
# "내가 등록/조회할 대상" → 반대편 서버 주소
defaultZone: http://eureka-server-2:8762/eureka/
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 60000
spring:
application:
name: eureka-server
server:
port: 8762 # 1번과 다른 포트 (LB 앞에 두면 동일 포트도 가능)
eureka:
instance:
hostname: eureka-server-2
lease-renewal-interval-in-seconds: 30
lease-expiration-duration-in-seconds: 90
client:
register-with-eureka: true
fetch-registry: true
service-url:
# 이 서버는 1번 서버를 peer로 본다
defaultZone: http://eureka-server-1:8761/eureka/
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 60000
두 설정의 핵심은 다음 한 줄이다.
eureka.client.service-url.defaultZone
“이 인스턴스가 등록/조회할 Eureka Server는 어디인가?” 를 지정한다.
peer 구성에서는 서로의 주소를 defaultZone에 등록하여 레지스트리 정보를 양방향으로 동기화한다.
여기까지 정리한 내용을 다시 한 번 요약해보면:
MSA & 클라우드 환경에서는 서비스 인스턴스의 IP/Port가 동적으로 변하기 때문에 사람이나 설정 파일만으로는 서비스 위치 관리가 불가능에 가깝다.
이를 해결하기 위해 Service Registry / Service Discovery 패턴이 등장했고, 넷플릭스는 이를 구현한 Eureka를 운영해 왔다.
Spring에서는 Spring Cloud Netflix Eureka를 통해 Spring Boot 애플리케이션에서도 손쉽게 Eureka 기반 서비스 등록/조회 기능을 제공한다.
Eureka는
Eureka Server는
실제 운영 환경에서는