[Spring Cloud] Microservice(MSA) 구축 : (2) Eureka Server와 Gateway, Microservice 서버들의 연결
이전 포스팅에서 MSA 아키텍쳐의 구조와 Spring Cloud에 대한 소개를 하였었다.
이번 포스팅에서는 이전에 소개했던 아키텍쳐의 구성 요소들인 Gateway, Naming Server, MSA Service들을 만들어보고 연동하는 작업을 해보려고 한다.
https://sjh9708.tistory.com/119
Spring Cloud 아키텍쳐
Spring Service Registry (Naming Server)
MSA에서 서비스 인스턴스의 등록과 검색을 담당하는 서버 역할을 한다.
서비스 인스턴스의 등록과 해제를 관리하며 서비스 디스커버리를 통해 클라이언트는 특정 서비스 인스턴스를 동적으로 찾을 수 있.
대표적인 예로는 Netflix Eureka
아래에서 Eureka Server에 해당하는 DiscoveryService 프로젝트를 생성할 예정이다.
Microservices
각각의 비즈니스 로직을 담당하는 서비스 인스턴스
아래에서 FirstService / SecondService 프로젝트를 생성하고 비즈니스 로직을 작성하여 Eureka Client로 등록할 예정이다.
Spring Cloud API Gateway
마이크로서비스 아키텍처에서 Gateway로서 클라이언트의 단일 진입점 역할을 하는 서버 역할을 한다.
인증, 권한 부여, 로드 밸런싱, 라우팅, 트래픽 제어 등의 기능을 제공한다.
클라이언트는 API Gateway를 통해 여러 서비스에 대한 요청을 보내고, API Gateway는 이를 해당 서비스로 라우팅해준다.
아래에서 GatewayService 프로젝트를 생성하고 라우팅 설정을 한 후 Eureka Client로 등록할 예정이다.
Eureka Server 생성하기
Spring Service Registry (Naming Server)에 해당하는 Eureka Server 역할을 해 줄 프로젝트를 생성해주도록 하자.
필자는 JDK 11, Maven, Spring Boot 2.7.18을 선택해주었으며 의존성으로 Eureka Server를 추가해주자.
▶ DiscoveryserviceApplication.java
package com.example.discoveryservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryserviceApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryserviceApplication.class, args);
}
}
메인에서 @EnableEurekaServer Annotation을 추가하여 유레카 서버로서 사용할 수 있도록 하자.
▶ application.yml
server:
port: 8761
spring:
application:
name: discoveryservice
eureka:
client:
register-with-eureka: false
fetch-registry: false
register-with-eureka와 fetch-registry는 Eureka Naming Server에 Register과 Fetch를 수행하는 것의 여부인데
자기 자신이 Eureka Server이므로 FALSE로 둔 것이다.
서버를 가동시켰을 때의 모습니다.
Eureka Server는 이처럼 등록된 클라이언트 인스턴스들의 Namespace를 관리해주는 Naming Server의 역할을 한다.
USER-SERVICE라는 인스턴스가 Naming Server에 등록된 것을 확인할 수 있다.
USER-SERVICE라는 인스턴스가 등록되어 있지만, 지금까지는 Eureka Client 서버를 실행시키지 않았으므로 아무것도 뜨지 않은 것이 정상이다.
Microservice 생성하기
이제 MSA를 구성할 서버들을 생성해줄 것이다.
FirstService와 SecondService 두 개의 프로젝트를 생성하여보자.
FirstService 생성
Dependencies로는 Eureka Discovery Client, Spring Boot DevTools, Lombok, Spring Web을 함께 사용하려고 한다.
Eureka Discovery Client는 Eureka Discovery Server(Naming Server)가 관리하게 될 인스턴스 대상으로 등록되게 하기 위해서 의존성으로 등록할 것이다.
▶ application.yml
server:
port: 0 # Random Port
spring:
application:
name: my-first-service
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
1. fetch-registry: true / register-with-eureka: true
Eureka Discovery Server(Naming Server)가 관리하게 될 인스턴스 대상으로 등록되게 하도록 TRUE
2. service-url
등록될 Eureka Discovery Server(Naming Server)의 Location
3. port
포트번호를 0으로 하였다. 이는 랜덤포트를 사용하겠다는 뜻으로, Scale-out 시 여러개의 인스턴스가 어떤 포트에서 열릴 지 모르는 상황을 가정하고, 여러개의 인스턴스를 가동시킬 수 있도록 하기 위해서 사용하였다.
Random Port는 임의의 포트로 서버 인스턴스를 실행시킨다.
4. instance-id
인스턴스 ID를 지정하지 않는다면, Eureka Server는 여러개의 인스턴스가 랜덤포트로 작동하게 되더라도 항상 0으로 인식하게 된다.
왼쪽은 instance-id를 지정하였을 때, 오른쪽은 그렇지 않았을 때 Eureka server의 모습이다. 오른쪽은 인스턴스가 두 개가 실행된 상태임에도 포트번호 0번인 것 한 개밖에 나타나지 않는다.
▶ FirstController.java
@RestController
@RequestMapping("/first-service")
@Slf4j
public class FirstController {
Environment env;
public FirstController(Environment env){
this.env = env;
}
@GetMapping("/welcome")
public String welcome(){
return "Welcome First Service";
}
@GetMapping("/message")
public String message(@RequestHeader("first-request") String header){
log.info(header);
return "Hello First Service";
}
@GetMapping("/check")
public String check(HttpServletRequest request){
// 포트를 가져오는 방법 두 가지
log.info("Server port={}", request.getServerPort());
return String.format("Check First Service on PORT %s", env.getProperty("local.server.port"));
}
}
간단한 테스트를 위해서 컨트롤러를 작성해 주었다.
/message는 Request Header에 first-request가 있다면 로그를 출력해 준다.
/check는 응답을 해주는 PORT번호를 반환해주는 역할을 할 것이다.
SecondService 생성
FirstService와 유사하게 SecondService 프로젝트를 생성하여 다음 내용들을 작성해주었다.
▶ SecondController.java
@RestController
@RequestMapping("/second-service")
@Slf4j
public class SecondController {
@GetMapping("/welcome")
public String welcome(){
return "Welcome Second Service";
}
@GetMapping("/message")
public String message(@RequestHeader("second-request") String header){
log.info(header);
return "Hello Second Service";
}
@GetMapping("/check")
public String check(){
return "Check Second Service";
}
}
▶ application.yml
server:
port: 0
spring:
application:
name: my-second-service
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
Spring Cloud Gateway 생성하기
의존성으로 Gateway와 Eureka Discovery Client를 등록해주었다. Gateway 또한 Nameserver에 연결하고 등록하기 위해서이다.
▶ application.yml
server:
port: 8000
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: apigateway-service
cloud:
gateway:
routes:
- id : first-service
uri : lb://MY-FIRST-SERVICE
predicates:
- Path=/first-service/**
- id: second-service
uri : lb://MY-SECOND-SERVICE
predicates:
- Path=/second-service/**
spring.cloud.gateway,routes에서 Microservice들에 해당하는 Eureka Client들을 등록해 줄 것이다. 여기서 Gateway와 Load Balancing 역할을 수행하는 설정을 하게 될 것이다.
ID는 라우터의 식별자, URI에는 연결해줄 Service Location, Predicates에는 라우팅의 조건을 정의한다.
첫 번째 first-service의 경우 Path에 명시된 /first-service로 시작하는 요청들을 MY-FIRST-SERVICE의 Name에 해당하는 인스턴스들로 연결해주겠다는 뜻이다.
uri를 살펴보도록 하자.
lb://: 이는 Load Balancer를 가리키는 프로토콜을 나타낸다. Spring Cloud Gateway에서는 "lb"라는 프로토콜을 사용하여 Load Balancer를 참조한다. 이것은 일반적으로 서비스 디스커버리나 로드 밸런싱을 통해 서비스를 찾고 분산된 방식으로 트래픽을 라우팅할 때 사용된다
MY-FIRST-SERVICE: 이는 실제로 호출하려는 서비스의 이름이나 ID이다. 이 식별자를 통해 Eureka Server에서 서비스를 찾고 로드 밸런싱을 수행한다
일단 이 외에 다른 것은 작성하지 않고 테스트를 진행해보자.
실행 테스트
Spring Service Registry (Naming Server) -> DiscoveryService 프로젝트를 생성할 예정이다.
Microservices -> FirstService / SecondService 프로젝트를 생성하고 비즈니스 로직을 작성하여 Eureka Client로 등록
Spring Cloud API Gateway -> GatewayService 프로젝트를 생성하고 라우팅 설정을 한 후 Eureka Client로 등록
지금까지 Eureka Server (DiscoveryService), Microservice(FirstService, SecondService), Gateway(GatewayService)를 생성하였다.
그렇다면 Eureka Server 프로젝트 -> Gateway 프로젝트 -> FirstService/SecondService 인스턴스 2개씩을 순서대로 실행시켜서 테스트를 진행해보겠다.
Eureka server에는 first-service, second-service 인스턴스가 2개씩 등록되어 있으며, API Gateway 또한 등록되어 있는 것을 확인할 수 있다.
게이트웨이인 localhost:8000을 통해서 같은 진입점으로 다른 서비스들에 접근을 할 수 있었다.
각각 아래의 과정을 거쳐서 라우팅이 수행된 것을 확인할 수 있다.
localhost:8000/first-service/welcome -> lb://MY-FIRST-SERVICE/first-service/welcome -> localhost:랜덤포트/first-service/welcome
localhost:8000/second-service/welcome -> lb://MY-SECOND-SERVICE/second-service/welcome -> localhost:랜덤포트/second-service/welcome
check 요청을 날려보면 각각 다른 포트에서 응답 메세지를 생성한 것을 확인할 수 있다.
이는 로드 밸런서가 같은 서비스의 두 개의 인스턴스에 트래픽을 분산해주고 요청을 처리해주었다는 것을 확인 가능하다.