반응형
본 내용은 인프런의 이도원 님의 강의 "Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)" 내용을 바탕으로 정리한 내용입니다.
API Gateway Service란?
- API Gateway는 규모와 관계없이 REST 및 WebSocket API를 생성, 게시, 유지, 모니터링 및 보호하기 위한 AWS 서비스이다.
- 이 서비스를 사용하면 API 개발자가 AWS 또는 다른 웹 서비스를 비롯해 AWS 클라우드에 저장된 데이터에 액세스하는 API를 생성할 수 있다.
API Gateway Service의 역활
- 인증 및 권한 부여
- 서비스 검색 통합
- 응답 캐싱
- 정책, 회로 차단기 및 QoS 다시 시도
- 속도 제한
- 부하 분산
- 로깅, 추적, 상관 관계
- 헤더, 쿼리 문자열 및 청구 변환
- IP 허용 목록에 추가
Custom Filter
Custom Pre filter
- 요청이 들어올 때 요청 id 를 로깅한다.
Custom Post filter
- 요청이 처리된 후 응답 코드를 로깅한다.
@ GateWayService-Project
Custom Filter
package com.example.gateway_service.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
public CustomFilter(){
super(Config.class);
}
/**
* @param config
* @return
*/
@Override
public GatewayFilter apply(Config config){
// Custom Pre Filter
return ((exchange, chain) -> {
// ServletHttp 를 비동기 방식에서는 ServerHttp 로 사용한다.
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Custom PRE filter: request id -> {}", request.getId());
// Custom Post Filter.Suppose we can call error response handler based on error code.
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Custom POST filter: response code -> {}", response.getStatusCode());
}));
});
};
// Mono : 이 객체는 웹 플럭스라고 해서 spring 5에서 추가된 기능, 비동기 방식의 서버 지원 (단일값으로)
public static class Config{
// Put the configuration properties
}
}
apply메서드를 오버라이드하여 요청과 응답을 처리하는 커스텀 로직을 정의- Pre Filter: 요청이 다운스트림 서비스로 전달되기 전에 가로채서 요청 ID를 로그로 남긴다.
log.info("Custom PRE filter: request id -> {}", request.getId());를 사용하여 요청 ID를 출력한다. - Post Filter: 요청이 처리되고 응답이 생성된 후, 응답 상태 코드를 로그로 남긴다.
log.info("Custom POST filter: response code -> {}", response.getStatusCode());를 사용하여 응답 상태 코드를 출력한다.
- Pre Filter: 요청이 다운스트림 서비스로 전달되기 전에 가로채서 요청 ID를 로그로 남긴다.
Config클래스는 빈 상태로 정의되었지만, 필터의 설정을 추가하고자 할 때 확장할 수 있다.
application.yml
- 기존 필터값 주석 처리 후 CutomFilter 추가
server:
port: 8080
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - AddRequestHeader=second-request, second-request-header2
# - AddResponseHeader=second-response, second-response-header2
- CustomFilter
spring.cloud.gateway.routes섹션에서는 두 개의 서비스(first-service와second-service)에 대한 라우팅을 정의한다.- 각 서비스에 대해
CustomFilter를 필터로 추가하며, 기존의 기본 필터들(AddRequestHeader,AddResponseHeader)은 주석 처리되어 있다. - 각 라우트의
uri는 각각http://localhost:8081/과http://localhost:8082/로 설정되어 있다. CustomFilter가 요청을 처리하기 전후로 로그를 남기며, 요청 ID와 응답 상태 코드를 출력한다.
@ FirstService-Project / SecondService-Project 수정
FirstServiceController
@GetMapping("/check")
public String check() {
return "Hi, there. This is a message from First Service.";
}
- check 메소드는 HTTP GET 요청이
/check경로로 들어올 때,"Hi, there. This is a message from First Service."라는 메시지를 반환한다.
SecondServiceController
@GetMapping("/check")
public String check() {
return "Hi, there. This is a message from Second Service.";
}
- check 메소드는 HTTP GET 요청이
/check경로로 들어올 때,"Hi, there. This is a message from Second Service."라는 메시지를 반환한다.
결과
그림 1) Postman Test
- http://localhost:8080/first-service/check 경로를 호출하면 FirstServiceController의
check메소드가 호출되어"Hi, there. This is a message from First Service."라는 메시지가 응답된다. - http://localhost:8080/second-service/check 경로를 호출하면 SecondServiceController의
check메소드가 호출되어"Hi, there. This is a message from Second Service."라는 메시지가 응답된다.

Gateway-service log
Custom PRE filter: request id -> 37fc1887-1
Custom POST filter: response code -> 200 OK
- Spring Cloud Gateway는
first-service와second-service의 요청을 처리한다. - 각 서비스는
/check경로에서 특정 메시지를 반환한다. - 요청이 처리되는 동안
CustomFilter는 요청 ID와 응답 상태 코드를 로그로 남긴다.- PRE 필터: 요청 ID 출력
- POST 필터: 응답 코드 출력
Global Filter
- 공통적인 필터를 설정한다.
- application.yml의 preLogger, postLogger 값을 통해 baseMessage를 화면에 출력한다.
@ Gateway-service Project
application.yml
server:
port: 8080
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: gateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway GlobalFilter
preLogger: true
postLogger: true
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - AddRequestHeader=second-request, second-request-header2
# - AddResponseHeader=second-response, second-response-header2
- CustomFilter
default-filters: 공통 필터인GlobalFilter를 추가하여 모든 요청에 대해 기본적인 필터 작업을 수행하도록 설정한다. 이를 통해GlobalFilter는 모든 경로에서 공통적으로 동작하며,CustomFilter는 각 서비스별로 추가적으로 설정된다.
GlobalFilter 클래스
package com.example.gateway_service.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
public GlobalFilter() {super(Config.class);}
@Override
public GatewayFilter apply(Config config){
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Global Filter baseMessage: {}", config.getBaseMessage());
// Pre Filter
if(config.isPreLogger()) {
log.info("Global Filter Start: request id -> {}", request.getId());
}
return chain.filter(exchange).then(Mono.fromRunnable(()->{
// Post Filter
if(config.isPostLogger()){
log.info("Global Filter End: reponse code -> {}", response.getStatusCode());
}
}));
});
}
@Data
public static class Config {
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
}
GlobalFilter는Spring Cloud Gateway의 전역 필터로, 모든 요청에 대해 공통적으로 필터 작업을 수행한다.baseMessage,preLogger,postLogger와 같은 설정을 받아서 로그를 기록한다.preLogger와postLogger옵션을 통해 각각 요청 처리 전후에 로그를 남긴다.http://localhost:8080/first-service/check경로를 호출하면,CustomFilter와 함께GlobalFilter가 동작하고, 그에 따른 로그가 남는다.- GlobalFilter는 모든 요청에 대해 기본 메시지와 함께 Pre 로그 및 Post 로그를 기록한다.
- CustomFilter는 서비스별 필터로 요청 ID와 응답 상태 코드를 출력한다.
결과
- Postman에서 http://localhost:8080/first-service/check 를 호출할 때,
CustomFilter의 로그와 동일하게 동작한다.Gateway-service log
Global Filter baseMessage: Spring Cloud Gateway GlobalFilter
Global Filter Start: request id -> 87025ee0-1
Custom PRE filter: request id -> 87025ee0-1
Custom POST filter: response code -> 200 OK
Global Filter End: reponse code -> 200 OK
Logging Filter
@GateWayService-Project
application.yml
server:
port: 8080
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: gateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway GlobalFilter
preLogger: true
postLogger: true
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-request-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - AddRequestHeader=second-request, second-request-header2
# - AddResponseHeader=second-response, second-response-header2
- name: CustomFilter
- name: LoggingFilter
args:
baseMessage: Hi, there.
preLogger: true
postLogger: true
- Eureka 클라이언트 설정
- register-with-eureka: false로 설정하여 이 서비스가 Eureka 서버에 자신의 정보를 등록하지 않도록 한다.
- fetch-registry: false로 설정하여 이 서비스가 Eureka 서버로부터 다른 서비스의 레지스트리를 가져오지 않도록 한다.
- defaultZone:
http://localhost:8761/eureka로 설정하여 Eureka 서버의 기본 URL을 지정한다.
- Spring Cloud Gateway 설정
- default-filters: 모든 요청에 대해 전역 필터를 적용한다.
- GlobalFilter: 요청과 응답에 대해 로깅 기능을 제공한다.
- baseMessage: 로깅에 사용될 기본 메시지를 설정한다.
- preLogger: true로 설정하여 요청 전(pre)에 로그를 남긴다.
- postLogger: true로 설정하여 응답 후(post)에 로그를 남긴다.
- GlobalFilter: 요청과 응답에 대해 로깅 기능을 제공한다.
- Route 설정
- id:
first-service로 설정하여 라우트의 ID를 정의한다. - uri:
http://localhost:8081/로 설정하여/first-service/**경로의 요청을 해당 서비스로 전달한다. - predicates: Path 조건을
/first-service/**로 설정하여 이 경로에 들어오는 요청만 이 라우트를 선택하도록 한다. - filters: CustomFilter를 적용하여 이 라우트에 추가적인 필터를 설정한다.
- id:
second-service로 설정하여 두 번째 라우트의 ID를 정의한다. - uri:
http://localhost:8082/로 설정하여/second-service/**경로의 요청을 해당 서비스로 전달한다. - predicates: Path 조건을
/second-service/**로 설정하여 이 경로에 들어오는 요청만 처리하도록 한다. - filters:
- CustomFilter를 적용하여 사용자 정의 필터를 추가한다.
- LoggingFilter를 적용하여 로깅 필터를 추가한다.
- baseMessage: 로깅 메시지를
Hi, there.로 설정한다. - preLogger: true로 설정하여 요청 전(pre)에 로그를 기록한다.
- postLogger: true로 설정하여 응답 후(post)에 로그를 기록한다.
- baseMessage: 로깅 메시지를
- id:
- default-filters: 모든 요청에 대해 전역 필터를 적용한다.
LoggingFilter 클래스
package com.example.gateway_service.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {
public LoggingFilter() { super(Config.class); }
public GatewayFilter apply(Config config){
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Logging filter baseMessage: " + config.getBaseMessage());
if(config.isPreLogger()){
log.info("Logging PRE filter: request uri -> {}", request.getURI());
}
return chain.filter(exchange).then(Mono.fromRunnable(()->{
if(config.isPreLogger()){
log.info("Logging POST filter: response code -> {}", response.getStatusCode());
}
}));
};
}
@Data
public static class Config {
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
}
결과
- 로깅 필터를 secomd-service에 추가해서 http://localhost:8080/first-service/check 로 호출한다.
Gateway-Service 로그 흐름
http://localhost:8080/second-service/check로 요청을 보낼 때, 아래와 같은 로그 순서가 발생한다.
- Global Filter:
Spring Cloud Gateway GlobalFilter라는 기본 메시지를 로깅한다.- 요청 ID와 함께 요청이 시작된 시점에서 로그를 기록한다 (pre-로그).
- Custom Filter:
- 요청 ID를 기록하는 사용자 정의 필터가 요청 전(pre-filter)에서 로그를 남긴다.
- Logging Filter:
Hi, there라는 커스텀 메시지를 로그에 기록한다.- 요청 URI를 기록한다 (pre-로그).
- Proxied Service (second-service:
http://localhost:8082):- 요청이 두 번째 서비스로 전달되어 처리된다.
- Logging Filter (Post-filter):
- 응답 상태 코드를 로그에 기록한다 (post-로그).
- Custom Filter (Post-filter):
- 응답 상태 코드를 기록하는 사용자 정의 필터가 응답 후(post-filter)에서 로그를 남긴다.
- Global Filter (Post-filter):
- 응답 상태 코드와 함께 요청 처리가 완료된 시점을 로그에 기록한다 (post-로그).
Gateway-Service 로그
Global Filter baseMessage: Spring Cloud Gateway GlobalFilter
Global Filter Start: request id -> 4181e877-3
Custom PRE filter: request id -> 4181e877-3
Logging filter baseMessage: Hi, there.
Logging PRE filter: request uri -> http://localhost:8080/second-service/check
Logging POST filter: response code -> 200 OK
Custom POST filter: response code -> 200 OK
Global Filter End: reponse code -> 200 OK
- 요청 전 로깅:
GlobalFilter,CustomFilter,LoggingFilter가 순차적으로 요청 정보를 로깅한다.
- 요청 처리:
- 요청이 실제 서비스(
second-service로 설정된localhost:8082)로 전달되어 처리된다.
- 요청이 실제 서비스(
- 응답 후 로깅:
LoggingFilter,CustomFilter,GlobalFilter가 순차적으로 응답 정보를 로깅하며, 응답 상태 코드를 기록한다.
로드 밸런서란?
- 클라이언트와 서버 그룹 사이에 위치하여 서버에 가해지는 트래픽을 여러 대의 서버에 고르게 분배하는 역할을 한다.
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 로드 밸런서 기능을 구현하기 위해 각 서비스에 필요한 라이브러리를
pom.xml파일에 추가한다.
application.yml
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
- 게이트웨이 서비스와 각 프로젝트 별로 eureka 값을 추가한다
- 게이트웨이 서비스는 uri 값을 변경한다.
routes:
- id: first-service
uri: lb://MY-FIRST-SERVICE
predicates:
- Path=/first-service/**
filters:
- CustomFilter
- id: second-service
uri: lb://MY-SECOND-SERVICE
predicates:
- Path=/second-service/**
- 서비스의 라우팅 설정을 추가하여 Gateway가 로드 밸런서를 통해 트래픽을 분배하도록 한다.
기동순서
- Eureka Server:
localhost:8761에서 실행하여 서비스 레지스트리를 관리한다. - Gateway Server: Gateway 서버를 기동하여 클라이언트 요청을 처리하고, 로드 밸런서를 통해 서비스를 라우팅한다.
- First Service, Second Service: 각 서비스가 기동되고, Eureka에 등록된다.First Service와 Second Service를 2개씩 기동
1. Run Application
- VM Option -> -Dserver.port=9002
-Dserver.port=9002
2. Terminal
mvn spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9003'
mvn clean compile package
java -jar -Dserver.port=9004 ./target/user-service-0.0.1-SNAPSHOT.jar
Second Service인스턴스를9003포트로 기동한다. 이를 위해mvn spring-boot:run명령어를 사용하여 JVM 인자에 포트를 설정한다.- Maven 빌드를 수행하여 프로젝트를 컴파일하고 패키징한다
First Service의 또 다른 인스턴스를9004포트로 실행하려면,java -jar명령어를 사용하여 실행한다.- 이와 같은 방식으로 여러 인스턴스를 실행하고, 로드 밸런서를 통해 균등하게 트래픽을 분배한다.
반응형
'Cloud > MSA' 카테고리의 다른 글
| [MSA] MicroService 구현(회원가입 구현) (0) | 2024.11.26 |
|---|---|
| [MSA] MicroService 구현(사용자 서비스) (0) | 2024.06.29 |
| [MSA] Spring Cloud Gateway (0) | 2024.06.29 |
| [MSA] 스프링 클라우드란? (0) | 2023.07.24 |
| [MSA] SOA와 MSA (0) | 2023.07.24 |