일단 전체적인 구조는 Api-Gateway
에 요청을 하게 되면 Api-Gateway
가 Eureka server에 등록된 서비스들을 조회하고 해당 서비스로 라우팅을 해주는 구조라고 보시면 될것 같습니다.
// build.gradle
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
@SpringBootApplication
@EnableEurekaServer
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
// application.properties
server.port=8761
spring.application.name=eureka-server
eureka.instance.hostname=127.0.0.1
// 유레카 서버에 본인 서비스를 등록할 건지 여부 (굳이 등록안해도되긴해요)
eureka.client.register-with-eureka=true
// 유레카 서버로부터 서비스 목록을 로컬 캐시에 저장할 건지 여부, 둘 다 기본값 true라서 지정하지 않아도 상관 없다.
eureka.client.fetchRegistry=true
위와 같이 세팅하고 실행시키면 Eureka 대쉬보드가 나타나게 됩니다.
현재는 아무것도 없어서 서비스가 Eureka 서버밖에 없네요.
이러면 Eureka Server는 구축 다한겁니다. 참쉽죠?
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientDemoApplication {
// spring-start-web이 필수 (아마 마이크로서비스 라서 강제로 mvc, rest 서비스를 제공하게끔 만드는듯)
public static void main(String[] args) {
SpringApplication.run(EurekaClientDemoApplication.class, args);
}
}
@RestController
public class FirstServiceController {
@GetMapping("/product")
public String getFood() {
return "음식";
}
}
// application.properties
server.port=8081
spring.application.name=DEMO-EUREKA-CLIENT
eureka.client.service-url.defaultZone=${EUREKA_URL:http://localhost:8761/eureka/}
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${random.value}}
management.endpoint.shutdown.enabled=true
spring.application.name
: 프로젝트의 애플리케이션 이름을 정해서 입력해줍니다. 해당 이름으로 Gateway 에서 호출을 하게 됩니다. (필수!!)eureka.client.service-url.defaultZone
: 유레카 클라이언트가 서비스를 등록할 서버 주소를 지정하기 위해 사용합니다.eureka.instance.instance-id
: 프로젝트를 유레카 서버에 등록하고 유레카 서버로 접속해보면 아래와 같이 등록된 서비스를 확인할 수 있습니다.
@EnableDiscoveryClient
,@EnableEurekaClient
차이점
서비스 디스커버리 라이브러리는 유레카 외에도 주키퍼, 컨설 등이 존재하며@EnableDiscoveryClient
어노테이션은 모든 라이브러리를 지원하며@EnableEurekaClient
는 유레카 라이브러리만을 지원합니다.
의존성과 Application Class는 First Service와 동일
@RestController
public class SecondServiceController {
@GetMapping("/product")
public String getFood() {
return "컴퓨터";
}
}
server.port=8082
spring.application.name=DEMO-EUREKA-CLIENT2
eureka.client.service-url.defaultZone=${EUREKA_URL:http://localhost:8761/eureka/}
eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${random.value}}
management.endpoint.shutdown.enabled=true
매우 매우 간단한 2가지의 서비스를 구성을 하였습니다.
주의사항
spring.application.name
에서 _ 사용하지 마세요 | 예) DEMO_EUREKA_CLIENT2
이유는 _ 사용 시 API Gateway가 인식을 못해서 Invalid Host라고 에러가 발생합니다.
덕분에 삽질 좀 했습니다 ㅋㅋㅋ
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayDemoApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayDemoApplication.class, args);
}
}
# application.yml
server:
port: 8000
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: ${EUREKA_URL:http://localhost:8761/eureka/}
spring:
application:
name: api-gateway-service
cloud:
gateway:
routes: # 라우팅 설정
- id: first-service # 구분하기 위한 id값으로 임의로 작성해도 무관
predicates: # 라우팅 조건
- Path=/first-service/** # first-service/ 으로 요청이 들어오면
uri: lb://DEMO-EUREKA-CLIENT # 유레카 서버에서 DEMO-EUREKA-CLIENT를 찾아서 그곳으로 요청을 보낸다.
filters:
# url 재정의
# ?<변수명>은 뒤에 나오는 정규식을 변수처럼 사용할 수 있도록 한다. ()는 하나의 묶음 처리 -> segment는 (.*)를 의미
# 콤마(,)를 기준으로 왼쪽 url을 오른쪽 url로 재정의한다.
# 콤마 기준 오른쪽 부분은 ${변수명}으로 url 가져오고 앞에 / 붙여준거라고 보면 된다.
- RewritePath=/first-service/(?<segment>.*), /$\{segment}
- id: second-service
predicates:
- Path=/second-service/**
uri: lb://DEMO-EUREKA-CLIENT2
filters:
- RewritePath=/second-service/(?<segment>.*), /$\{segment}
API Gateway도 하나의 Service 이기 때문에 유레카에 등록을 시켜둬야 합니다.
API Gateway에서 properties가 아닌 yml 형식을 사용한 이유는
설정할 것들이 좀 많길래 가독성있게 보고싶어서 그랬습니다. ( 많은 편도 아니긴해요 ㅋㅋ)
이제 Eureka Server, Microservices , API Gateway를 전부 구축했기 때문에
한번 제대로 동작하는지 확인해보겠습니다.
API Gateway
Service
Eureka Server
Eureka에 제대로 서비스들이 정상적으로 등록이 된것을 확인 하실 수 있습니다.
참고로 같은 어플리케이션 여러개 실행시키면 Availability Zones가 증가합니다.
분명 실제 서비스 포트인 8081 또는 8082 포트로 접근을 한게 아니라 API Gateway인 8000 포트로 접근하여 요청을 하였지만 서비스들의 결과를 불러오는 것을 확인을 하였습니다.
특정 서비스가 갑자기 종료되면 Eureka Server에서는 해당 서비스가 계속 남아있는 상황이 발생합니다.
이는 유레카 서버가 특이한 메커니즘으로 클라이언트를 유지하기 때문인데 일시적 네트워크 장애로 인한 서비스 해제를 막기위해 보호모드를 동작하는데 이 보호모드 동작시간이 60초가 default 값 입니다.
해당 시간안에 갱신 요청이 일정 횟수 이상 들어오지 않아야 서비스를 해제합니다.
다음과 같은 설정으로 해제 가능합니다.
eureka.server.enable-self-preservation=false
하지만 이는 좋지 않은 설정이기 때문에
유레카 클라이언트를 안전하게 종료하기 위해 actuator의 shutdown API를 사용하여,
클라이언트가 유레카 서버에서 서비스를 제거할 수 있도록 하는것이 안정적입니다.
/actuator/shutdown
Actuator가 되게 다양하게 쓰이는 거같은데 나중에 한번 이것에 대해서도
공부해 봐야될 것 같습니다.
서비스 제공해주는 내용은 의미가 1도 없지만 매우 소규모의 MSA를 구축해보았습니다.
물론 다른 구성요소들도 구축을 해야겠지만 이렇게 MSA 구조를 따라 각각의 서비스를 제공하는 시스템을 구현한 것만으로도 되게 유익한 경험이였습니다.
앞으로 프로젝트 규모가 큰것을 만나면 스프링 클라우드로 MSA 구축을 해보고 싶습니다.
물론 실무에서는 이렇게 간단히 구축이 안되고 각종 옵션들 넣어주고 보안적인 것들도 추가를 해줘야겠지만 한번 구축을 해보고 싶습니다.