API gateway 역할을 해주는 spring cloud zuul 과 Ribbon 은 spring boot 2.4 까지는 maintenance.
- 인증 및 권한 부여
- 서비스 검색 통합
- 응답 캐싱
- 정책 , 회로 차단기 및 Qos(Quality of Service) 다시시도
- 속도 제한 , 부하 분산
- 로깅 , 추적 , 상관 관계
- 헤더 , 쿼리 문자열 및 청구 변환
- IP허용 목록에 추가
Spring cloud에서 msa간의 통신
1. RestTemplate
2. Feign Client
Zull 실습 spring boot 2.3 이하의 버전만 가능
server:
port: 8000
spring:
application:
name: my-zuul-service
zuul:
routes:
first-service:
path: /first-service/**
url: http://localhost:8081
second-service:
path: /first-service/**
url: http://localhost:8082
@EnableZuulProxy 를 작성해 준후에 application.yml 에 service instance들을 연결해주면 된다.
firstservice , secondservice 각각 어떤 url 로 갈지 적어준것이다.
다음으로는 Spring boot 최신 버젼도 호환이되는 Spring Cloud Gateway 를 사용해보자.
server:
port: 8000
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: apigateway-service
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
이떄 first service ,second service controller에서 주의를 해야한다.
왜냐하면 라우팅되는 실제 uri는 "http://localhost:8081/first-service/**" 로 포워딩되어서 날라가기 때문이다.
각 서비스의 Controller단에서 @RequestMapping에서 ("/first-service/")와 같은 작업을 해줘야 제대로 맵핑이 된다.
Filter 적용
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig
{
@Bean
public RouteLocator gatewayRoutes(RouteLocatorBuilder builder)
{
return builder.routes()
.route(r -> r.path("/first-service/**")
.filters(f -> f.addRequestHeader("first-request", "first-request-header")
.addResponseHeader("first-response", "first-response-header"))
.uri("http://localhost:8081"))
.route(r -> r.path("/second-service/**")
.filters(f -> f.addRequestHeader("second-request", "second-request-header")
.addResponseHeader("second-response", "second-response-header"))
.uri("http://localhost:8082"))
.build();
}
}
위의 application yaml 에서 spring cloud 옵션을 주석처리하고 따로 Configuration Bean을 만들어서 위와 동일한 역할 + header를 추가해주는 코드이다.
localhost:8000/first-service/message 쪽으로 request를 보내면
@RestController
@RequestMapping("/first-service")
@Slf4j
public class FirstServiceController
{
@GetMapping("/welcome")
public String welcome()
{
return "Welcome to the First service";
}
@GetMapping("/message")
public String message(@RequestHeader("first-request") String header)
{
log.info(header);
return "Hello World in First Service";
}
}
이 controller를 타고 gateway에서 입력된 Header 값을 log를 출력하고 string 을 return 한다.
server:
port: 8000
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: apigateway-service
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
- AddRequestHeader=first-request, first-requests-header2
- AddResponseHeader=first-response, first-response-header2
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
- AddRequestHeader=second-request, second-requests-header2
- AddResponseHeader=second-response, second-response-header2
동일하게 application yaml에서 filters property를 정의함으로 할 수있다.
Custom Filter
@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config>
{
public CustomFilter()
{
super(Config.class);
}
@Override
public GatewayFilter apply(Config config)
{
//Custom Pre Filter
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Custom PRE filter: request id -> {}",request.getId());
// Custom Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("Custom POST filter: response code -> {}",response.getStatusCode());
}));
});
}
public static class Config
{
// Put the configuration properties
}
}
server:
port: 8000
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: apigateway-service
cloud:
gateway:
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-requests-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - AddRequestHeader=second-request, second-requests-header2
# - AddResponseHeader=second-response, second-response-header2
- CustomFilter
Mono 인 Reactive stream을 반환값으로 줘야한다.
Custome Filter이기에 모든 라우팅 패스에 알아서 추가해줘야 작동한다.
위에 예시에 first-service, second-service 둘다 추가해서 동작한것이다.
Global Filter
@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config>
{
public GlobalFilter()
{
super(Config.class);
}
@Override
public GatewayFilter apply(Config config)
{
//Custom Pre Filter
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Global Filter baseMessage: {}",config.getBaseMessage());
if (config.isPreLogger()){
log.info("GLobal Filter Start: request id -> {}" , request.getId());
}
// Custom Post Filter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()){
log.info("Global Filter End : response code -> {}",response.getStatusCode());
}
}));
});
}
@Data
public static class Config
{
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
// Put the configuration properties
}
}
server:
port: 8000
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: apigateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gatway Global Filter
preLogger: true
postLogger: true
routes:
- id: first-service
uri: http://localhost:8081/
predicates:
- Path=/first-service/**
filters:
# - AddRequestHeader=first-request, first-requests-header2
# - AddResponseHeader=first-response, first-response-header2
- CustomFilter
- id: second-service
uri: http://localhost:8082/
predicates:
- Path=/second-service/**
filters:
# - AddRequestHeader=second-request, second-requests-header2
# - AddResponseHeader=second-response, second-response-header2
- CustomFilter
위에 패스에 일일이 추가해줘야 되는 CustomFilter와 다르게 모든 패스에 적용하고 싶을 때 쓰는것이 Global Filter이다.
2022-10-14 12:32:51.475 INFO 4295 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter baseMessage: Spring Cloud Gateway Global Filter
2022-10-14 12:32:51.476 INFO 4295 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter Start: request id -> c99822f7-1
2022-10-14 12:32:51.476 INFO 4295 --- [or-http-epoll-3] c.e.a.config.CustomFilter : Custom PreFilter: request id -> c99822f7-1
2022-10-14 12:32:51.548 INFO 4295 --- [or-http-epoll-3] c.e.a.config.CustomFilter : Custom POST filter: response -> 200 OK
2022-10-14 12:32:51.548 INFO 4295 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter End: response -> 200 OK
Global Filter는 가장 겉에 존재하게 된다. 가장 먼저 적용이되고 안의 CustomFilter가 따로 적용이 된다.
Logging Filter
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {
public LoggingFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
GatewayFilter filter = new OrderedGatewayFilter((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 id -> {}", request.getId());
}
// Custom PostFilter
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()) {
log.info("Logging Post Filter: response -> {}",response.getStatusCode());
}
}));
}, Ordered.LOWEST_PRECEDENCE);
return filter;
}
@Data
public static class Config {
// put the configuration Properties
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
}
2022-10-14 13:13:08.607 INFO 9297 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter baseMessage: Spring Cloud Gateway Global Filter
2022-10-14 13:13:08.607 INFO 9297 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter Start: request id -> f5da4e12-1
2022-10-14 13:13:08.608 INFO 9297 --- [or-http-epoll-3] c.e.a.config.CustomFilter : Custom PreFilter: request id -> f5da4e12-1
2022-10-14 13:13:08.676 INFO 9297 --- [or-http-epoll-3] c.e.a.config.LoggingFilter : Logging Filter baseMessage: hi, there.
2022-10-14 13:13:08.677 INFO 9297 --- [or-http-epoll-3] c.e.a.config.LoggingFilter : Logging Pre Filter: request id -> f5da4e12-1
2022-10-14 13:13:08.677 INFO 9297 --- [or-http-epoll-3] c.e.a.config.LoggingFilter : Logging Post Filter: response -> 200 OK
2022-10-14 13:13:08.677 INFO 9297 --- [or-http-epoll-3] c.e.a.config.CustomFilter : Custom POST filter: response -> 200 OK
2022-10-14 13:13:08.677 INFO 9297 --- [or-http-epoll-3] c.e.a.config.GlobalFilter : Global Filter End: response -> 200 OK
Lowest 순서로 Logging filter를 적용했으므로 가장 맨 뒷단에서 가장 나중에 적용된것을 확인 할 수 있다.
LoadBalance = Eureka Service (service discovery) + api gateway
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/**
filters:
Eureka 의 service discovery 덕분에 더 이상 api gateway 뒤에 있는 msa의 위치정보를 몰라도 된다.
더 이상 api gateway 에서 직접 msa를 호출하는것이 아닌 Eureka registry에 등록되고 등록된 msa를 호출하는 방식으로 변경된 것이다.
이런식으로 discover service에 여러개의 instance가 등록이 되어 LB가 가능해진다. 기본 전략은 round robin 이다.
'Cloud > SpringCloud로 개발하는 MSA' 카테고리의 다른 글
MS간의 data 동기화 (1. kafka 기본 이론) (0) | 2022.11.02 |
---|---|
MS간 통신 (0) | 2022.10.29 |
gateway 인증 + Cloud Config + Cloud Bus(rabbitMQ) + 암호화 (0) | 2022.10.28 |
Service Discovery (0) | 2021.06.29 |
Microservice 소개 (0) | 2021.06.22 |