
오랜만에 포스팅을 쓰는 것 같다 😅
최근 MSA(마이크로서비스 아키텍처) 관련 강의를 들으면서 여러 흥미로운 개념들을 접하게 되었고, 이전보다 더 명확하게 이해하게 된 것 같아 이제부터 정리해보려고 한다.
이번 포스팅에서는 MSA의 기본 개념을 다시 한번 살펴보고, 이 아키텍처에서 핵심적인 역할을 하는 서비스 디스커버리(Service Discovery)에 대해 알아보자 👀
MSA 아키텍처는 알다시피, 하나의 서비스로 구현된 모놀리식 서비스와는 다르게 여러개의 서비스로 구성된 아키텍처를 의미한다.
물론, MSA 아키텍처가 모놀리식 아키텍처보다 항상 좋다는 것은 절대 아니다.
상황에 따라서, 모놀리식 아키텍처로 서비스를 구현해야하는 경우도 분명히 존재한다.
하지만, Cloud Native 환경에서 서비스를 구성하는 경우, 주로 MSA 아키텍처가 사용된다.

위 그림에서 볼 수 있듯이, MSA 아키텍처는 각각의 독립적인 서비스(Instance 1, Instance n)들이 API를 통해 통신하며 전체 서비스를 구성한다.
MSA 아키텍처를 사용하면 서비스를 독립적으로 개발, 배포, 확장할 수 있다는 장점이 있다.
이러한 장점은 당연히 필요한 서비스만 선택적으로 스케일링할 수 있으므로 리소스 효율성이 높아진다는 것을 의미한다.
또한, 장애가 발생하더라도 전체 시스템으로 확산되지 않는다는 장점이 존재한다.
하지만 위에서 언급한 장점들 이면에는 다음과 같은 문제를 고려해야 한다.
고려해야할 내용
- 분산 시스템 복잡성: 서비스 간 통신, 일관성 유지 등의 문제가 발생한다.
- 네트워크 지연: 서비스 간 통신이 네트워크를 통해 이루어지므로 지연이 발생할 수 있다.
- 데이터 일관성: 각 서비스가 독립적인 데이터베이스를 가질 경우, 데이터 일관성 유지가 어려워진다.
- 운영 복잡성: 다수의 서비스를 모니터링하고 관리하는 것이 복잡해진다.
이는 하나의 서비스로 운영하는 모놀리식과 여러 개의 서비스로 운영하는 MSA 간에 발생할 수 있는 주요 차이점이다.
사실, 이런 문제들을 해결하기 위해 서비스 디스커버리, API 게이트웨이, 로드 밸런싱, 서킷 브레이커 등의 다양한 패턴과 기술이 등장하게 되었고, 위에서 살펴본 사진에는 이러한 솔루션들이 모두 포함되어 있는 것이다.
(그래서 복잡해보일 수 있다)
이제부터 포스팅을 통해 이러한 기술들을 하나씩 정리해나갈 예정이다!

이번 포스팅에서는 서비스 디스커버리라는 개념에 대해서 정리하고 코드로 살펴보려고 한다.
사진을 기준으로 보면 빨간색으로 표시된 부분이다.
그렇다면 서비스 디스커버리는 뭘까?
MSA 아키텍처 특성상 여러개의 서비스가 클라우드 내에서 동작하고 있을 것이다.
만약, 클라이언트로부터 API 요청이 들어온다면 누군가는 이 요청을 어느 서비스로 보내야할지 알고있어야 한다.
여기서 알고 있어야하는 정보는 다시말하면 라우팅 정보(IP주소 + 포트넘버)를 의미하게 된다.
즉, 서비스 디스커버리는 현재 동작하는 서비스들의 라우팅 정보를 관리하는 역할을 수행하는 서비스이다.
Spring 프레임워크에서 Service Discovery로는 Eureka를 사용한다.

Eureka는 Netflix에서 개발한 서비스 디스커버리 도구로, Spring Cloud에 통합되어 있어 Spring 기반 MSA에서 쉽게 사용할 수 있다.
Eureka를 사용하기 위해서는 우선 위 사진과 같이 Eureka Server 의존성을 추가해야 한다.
다음으로 Eureka Server의 설정 파일(application.yml)을 살펴보자
Sample Code
server: port: 8761 spring: application: name: discoveryservice eureka: client: register-with-eureka: false fetch-registry: false
여기서 중요한 설정을 살펴보면 다음과 같다.
server.port
- Eureka 서버가 실행될 포트 번호이다.
- 기본적으로 8761 포트 번호를 사용한다.
spring.application.name
- 서비스의 이름을 설정하는 부분이다.
eureka.client.register-with-eureka
- Eureka 서버 자신을 Eureka에 등록할지 여부를 결정한다.
- 서버 자체는 등록할 필요 없으므로 false로 설정한다.
eureka.client.fetch-registry
- 레지스트리 정보를 로컬에 캐싱할지 여부를 결정한다.
- 서버 자체는 다른 서비스를 찾을 필요 없으므로 false로 설정한다.
물론 위 설정말고도 설정할 수 있는 옵션은 다양하다.
디스커버리 서버는 서비스들의 정보를 주기적으로 받아와서 정상적으로 동작하는 서비스인지 판단하는 풀링을 진행하게 된다.
이때, 사용할 수 있는 옵션이 client:registry-fetch-interval-seconds: 30 이 있는데 default로 30초마다 서비스의 정보를 계속해서 업데이트한다.
이처럼, 만약 커스텀하고 싶은 부분이 있다면 유레카 설정 정보 페이지를 살펴보고 커스텀하자!
마지막으로 해당 서비스가 명시적으로 Eureka 서버임을 알려주기 위해서는 다음과 같이 코드를 작성하면 된다.
Sample Code
@SpringBootApplication @EnableEurekaServer public class ServiceDiscoveryApplication { public static void main(String[] args) { SpringApplication.run(ServiceDiscoveryApplication.class, args); } }
서비스의 BootApplication에 @EnableEurekaServer 라는 어노테이션을 명시적으로 추가해주면 Eureka 서버 설정은 마무리된다.
위에서 살펴본 것은 Eureka Server이다.
이제부터는 Eureka Client에 대해서 살펴보자
Eureka Client는 Eureka Server에 자신의 정보를 등록하고, 다른 서비스의 정보를 조회하는 역할을 한다.
즉, MSA 환경에서 각 마이크로서비스는 Eureka Client로 동작하게 된다.

각 서비스의 정보를 Eureka Server에 등록하기 위해서는 위 사진과 같이 Eureka Discovery Client 의존성을 각 서비스마다 추가해야 한다.
우선 설정 정보를 살펴보자
Sample Code
server: port: 0 spring: application: name: order-service ... eureka: instance: instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}} client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://127.0.0.1:8761/eureka
여기서 중요한 설정 정보를 정리해보면 다음과 같다.
server.port:0
- 랜덤 포트를 사용하도록 설정한다.
- 이렇게 하면 같은 서버에서 여러 인스턴스를 실행할 때 포트 충돌 문제를 방지할 수 있다.
spring.application.name
- 서비스의 이름을 지정한다.
- 이 이름은 Eureka에 등록될 때 사용되며, 다른 서비스에서 이 서비스를 찾을 때 사용된다.
eureka.instance.instance-id
- Eureka에 등록될 때 사용할 인스턴스 ID를 설정한다.
- ${random.value}를 사용하여 매번 고유한 ID가 생성되도록 한다.
- 이렇게 하면 동일한 서비스의 여러 인스턴스를 구분할 수 있다.
eureka.client.register-with-eureka
- true로 설정하여 이 서비스를 Eureka 서버에 등록한다.
eureka.client.fetch-registry
- true로 설정하여 Eureka 서버로부터 레지스트리 정보를 가져온다.
- 이를 통해 서비스 인스턴스에서도 디스커버리 서버의 정보를 가져오기 때문에다른 서비스의 위치를 알 수 있다.
eureka.client.service-url.defaultZone
- Eureka 서버의 위치를 지정한다.
- 여기서는 로컬 Eureka 서버(127.0.0.1:8761)를 사용한다.
이러한 설정을 통해 MSA 환경에서 서비스 디스커버리가 가능해지며, 같은 서비스의 여러 인스턴스를 쉽게 확장할 수 있게되는 것이다.
결국, 각 인스턴스는 랜덤 포트와 고유한 인스턴스 ID를 가지며, 모두 동일한 서비스 이름으로 Eureka에 등록되어 클라이언트에서는 논리적으로 하나의 서비스로 보이게 된다.

실제로 유레카 서버포트인 localhost:8761 로 접근하면 동일한 서비스가 2개 등록된 모습을 볼 수 있다!
(참고로 스프링부트에서 동일한 서비스를 여러개 띄우기 위해서는 빌드된 jar 파일을 별도로 실행시켜줘야 한다.)
만약 서버 포트를 10000으로 고정하면 어떻게될까?
어차피 서비스는 eureka.instance.instance-id 에서 설정한 바와 같이 랜덤 인스턴스 ID가 할당되어 Eureka 서버에 등록되기 때문에 문제가 없다.
하지만, 서버 포트를 10000으로 고정한다면 동일한 서비스를 동일한 서버에서 2개이상 띄우게 되는 경우 포트 충돌이 발생하게 된다.
따라서, 여러개의 인스턴스를 생성하는 서비스라면 포트 번호를 랜덤으로 설정해주는 0번을 주로 사용한다는 것을 기억하자!
이번 포스팅에서는 서비스 디스커버리의 역할을 수행하는 Eureka Server 설정과 인스턴스를 등록하기 위한 Eureka Client 서비스의 설정 파일을 살펴봤다.
이처럼 Eureka 서버와 클라이언트를 설정하면 서비스 간 자동 검색이 가능해지고, 동적으로 서비스 인스턴스 추가/제거가 가능한 MSA 환경을 구축할 수 있다.
막상 보면 크게 어려운 내용은 없으나, 설정 정보가 어떤 역할을 하는지는 기억해두자 👊