Eureka는 서비스를 등록하는 서비스 레지스트리로 애플리케이션에 있는 모든 마이크로서비스의 중앙 집중 레지스트리로 작동한다. 서비스들이 서로를 찾는데 도움을 주는 역할을 한다. 원하는 서비스를 찾았을 때, 어떤 인스턴스를 사용해야 할 지 결정해야하는데 매번 이런 선택을 피하기 위해 로드 밸런싱을 사용한다
여러 개의 유레카 서버가 있을 경우, 하나에 문제가 발생하더라도 문제의 발생을 막을 수 있으므로 실무에서는 여러 개의 유레카 서버들이 클러스터로 구성되어 유레카는 다른 유레카 서버로부터 서비스 레지스트리를 가져오거나 다른 유레카 서버의 서비스로 자신을 등록하기도 함
모든 서비스는 Eureka에 등록되며, 하나의 서비스는 여러 개의 인스턴스로 분산이 가능하다. 그러나 여러 개의 인스턴스들은 모두 서비스와 같은 이름으로 Eureka에 등록된다
server:
port: 8761
spring:
application:
name: discorveryservice // 유레카 서버의 이름 (프로젝트 명과는 관련없음)
eureka:
//instance:
//hostname: localhost -> 생략가능하고 생략하면 유레카가 환경변수를 참고하여 결정. 하지만 명시적으로 지정해주는 것이 좋음(여기서는 생략함)
client:
register-with-eureka: false
// 이 서비스를 다른 eureka 서버에 등록하겠는가 -> 단일 유레카서버 자체로 기동만 하면 되므로, 등록할 필요X
fetch-registry: false
// 다른 Eureka 서버로부터 인스턴스들의 정보를 주기적으로 가져올 것인지 설정
단일 유레카서버 자체로 기동만 하면 되므로, 등록할 필요X
//defaultZone:
http://${eureka.instance.hostname}:${server.port}/eureka
Eureka Client로써 Eureka Server에 등록하기 위해 사용되는 Endpoint가 http://localhost:8761/eureka이다. application.yml 파일에 /eureka를 빼고 설정하면, Eureka Server 정보를 전달할 수가 없기 때문에, 오류가 발생하고, Service Registry에 등록되지 않는다.
이렇게 포트번호를 다르게 설정

인텔리제이 terminal 또는 리눅스 등에서 작업
java -version / javac -version / mvn --version 으로 설치확인 후 진행
mvn spring-boot:run '-Dspring-boot.run.jvmArguments="-Dserver.port=9003"'
실행한 서비스를 중지하려면 Ctrl + C
위처럼 서비스의 포트번호를 정적으로 할당하면 굉장히 번거로움
포트번호를 0으로 설정하면 동적으로 랜덤한 번호를 할당함
인텔리제이 terminal에서 랜덤포트를 부여하려면 mvn spring-boot:run 까지만 입력
server:
port: 0 // 0으로 할당하면 실행할 때마다 랜덤포트번호 부여
spring:
application:
name: user-service -> 유레카 서버에 등록되는 서비스 이름
eureka:
instance:
instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
//포트 번호를 랜덤으로 주기 위해 0으로 설정해 놓으면 유레카 서버에서 인스턴스를 구분하지 못함
따라서, 랜덤으로 포트번호가 부여된 인스턴스가 표시되도록 설정
(하나의 서비스에 다수의 인스턴스가 있을 때, Gateway가 어떤 인스턴스를 가져왔는지 알 수 있음)
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:8761/eureka
//마지막에 적은 eureka는 엔드포인트
// 해당하는 주소의 유레카 서버에 서비스가 등록되도록 구성됨
// 서버.yml eureka: client: register-with-eureka: false fetch-registry: false server: #레지스트리 삭제옵션 enable-self-preservation: false #개발일때만 사용, 운영시 삭제해야함 eviction-interval-timer-in-ms: 3000 #하트비트 수신점검
//클라이언트.yml eureka: instance: lease-renewal-interval-in-seconds: 1 #하트비트 인터벌 lease-expiration-duration-in-seconds: 2 # 디스커버리는 서비스 등록 해제 하기 전에 마지막 하트비트에서부터 2초 기다림
Netty 서버로 실행되어, 비동기 처리도 가능해짐Route : Predicate 와 Filter를 조건을 충족하여 만들어진 요청 URI
Predicate : 요청을 처리하기전에 수행되는 로직, path 혹은 리퀘스트 헤더에 포함된 조건
Filter

클라이언트가 Gateway에 요청을 전달
Gateway Handler Mapping이 요청 정보를 Gateway의 설정된 Route로 전달하는 것을 결정하고, Gateway Web Handler에게 전송한다.
Gateway Web Handler는 만들어진 필터들을 거쳐 조건에 맞는 요청을 보낸다.
(Pre Filter 과정)
요청의 결과로, proxied server에 원하는 정보를 얻고 사후 필터들에 대한 로직이 동작하여 클라이언트에게 응답이 간다. (Post Filter 을 거쳐 클라이언트에게 응답)

exchange로 request 및 response를 가져올 수 있고 동기 방식인 Tomcat이 아닌 비동기 방식인 Netty를 사용하고 있기 때문에, 서블릿이 아닌 serverHttpRequest와 serverHttpResponse를 사용.chain을 이용하여 post filter를 추가할 수 있음.webflux에서도 Mono와 Flux로 나뉘게 됨
Mono는 0~1개의 결과만을 처리하기 위한 Reactor 객체Flux는 0~N개의 결과물을 처리하기 위한 Reactor 객체Route, Filter 기능을 application.yml에서 구현
예제로 쓸 마이크로서비스 2개 생성

GateWay Service 에 등록
spring:
application:
name: apigateway-service
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - CustomFilter
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - CustomFilter
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
@Configuration
public class FilterConfig {
@Bean
public RouteLocator gatewayRoute(RouteLocatorBuilder builder, CustomFilter customFilter){
return builder.routes()
.route(r -> r.path("/first-service/**") // 이 경로가 호출되면
.filters(f -> f.addRequestHeader("first-request", "first-request-header") // request 와 responseHeader에 이 값을 넣는다
.addResponseHeader("first-response", "first-response-header")
.filter(customFilter.apply(new CustomFilter.Config()))) // 내가 만든 필터 적용
.uri("http://localhost:8081")) // 여기 uri로 이동한다
.route(r -> r.path("/second-service/**")
.filters(f -> f.addRequestHeader("second-request", "second-request-header")
.addResponseHeader("second-response", "second-response-header")
.filter(customFilter.apply(new CustomFilter.Config())))
.uri("http://localhost:8082"))
.build();
}
}
@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
@Data
public static class Config{
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
public GlobalFilter(){
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
//Custom Pre Filter
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); // Pre Filter
ServerHttpResponse response = exchange.getResponse(); // Post Filter
log.info("Global Pre Filter : {}" , config.getBaseMessage());
if (config.isPreLogger()){ // preFilter이라면 다음의 로그를 호출
log.info("Global Pre Start : request id -> {}" , request.getId());
}
//Custom Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() ->{
if (config.isPostLogger()) { //PostFilter이라면 로그호출
log.info("Global filter End : response code -> {}", response.getStatusCode());
}
}));
};
}
}
spring:
application:
name: apigateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
단, 필터의 적용 순서를 바꾸고 싶을 때에는, new OrderedGatewayFilter를 사용
@Override
public GatewayFilter apply(Config config) {
GatewatFilter filter = new OrderedGatewayFilter((exchange, chain) ->
//Custom Pre Filter
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); // Pre Filter
ServerHttpResponse response = exchange.getResponse(); // Post Filter
log.info("Global Pre Filter : {}" , config.getBaseMessage());
if (config.isPreLogger()){ // preFilter이라면 다음의 로그를 호출
log.info("Global Pre Start : request id -> {}" , request.getId());
}
//Custom Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() ->{
if (config.isPostLogger()) { //PostFilter이라면 로그호출
log.info("Global filter End : response code -> {}", response.getStatusCode());
} Ordered.HIGHEST_PRECEDENCE); // 필터의 적용 순서 설정
return filter;
LB를 사용하게 되면, 유레카에 등록된 서비스가 여러 개인 경우, Load Balancing (부하 분산) 처리를 지원함 routes:
- id: first-service
uri: lb://my-first-service // 등록된 서비스의 포트번호가 아닌 서비스의 이름으로 등록(네이밍 서비스) -> 정해진 포트번호가 아닌 랜덤포트 번호를 사용할 수 있음
predicates:
- Path=/first-service/**
이렇게 gateway에서 서비스의 이동을 직접하는 방식이 아니라, registry service에 전달해서 해야 할 경우(서비스 이름을 사용해서 이동하는 경우?) , spring cloud gateway와 registry service가 서로 연결되어 있어야 한다.
따라서, register-with-eureka와 fetch-registry 코드를 true로 설정해서 Cloud Gateway 애플리케이션을 Eureka Server에 등록하고, 유레카로부터 정보를 받아 주기적으로 서비스 인스턴스들의 정보를 갱신해 줘야 한다.
id 는 Gateway에 등록되는 서비스의 이름
클라이언트가 Path를 입력 시 uri로 이동 (routes의 uri + / + Controller의 uri)
ex) 127.0.0.1:8000/first-service/health_check 입력 -> 127.0.0.1:랜덤포트/my-first-service/health_check 로 이동
등록
eureka:
client:
register-with-eureka: true
fetch-registry: true
routes:
# - id: user-service
# uri: lb://user-service
# predicates:
# - Path=/user-service/**
- id: user-service
uri: lb://USER-SERVICE
redicates:
- Path=/user-service/login
- Method=POST
filters:
- RemoveRequestHeader=Cookie
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
- Method=GET
filters:
- RemoveRequestHeader=Cookie
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
- AuthorizationHeaderFilter
RemoveRequestHeader=Cookie : Request Header에서 Cookie를 삭제하지 않으면, user-service와 같은 애플리케이션에서 Cookies를 추가하여 Response body에 저장된 정보가 있을 경우, 보안에 문제가 된다
RemoveRequestHeader=Cookie를 적용하게 되면, Response Body에 어떤 정보도 남기지 않고, 매번 새로운 Request Header로 요청한다는 의미이다.
RewritePath : RewritePath는 클라이언트로부터 요청된 경로를 다시 다른 경로로 변경할 수 있음. URL을 통한 라우팅 작업을 가능하게 함. 실제 구현된 정보를 노출하지 않을 수 있고, 사용자의 요청 정보를 단일화 (또는 통일화) 한 다음, 내부적으로 사용되는 경로로 라우팅 하기 위해 사용하시면 좋을 것 같다.
ex)127.0.0.1:8000/user-service/login 입력 -> 127.0.0.1:랜덤포트/USER-SERVICE/login 으로 이동 -> RewritePath=/user-service/(?.*), /${segment} 때문에 /user-service/login 경로 전체가 /login 으로 대체되어서 이동 => 127.0.0.1:랜덤포드/login 이 최종 uri