Backend/MSA
API Gateway
chjysm
2024. 3. 26. 20:28
API Gateway 란?
사용자가 설정한 라우팅 설정에 따라서 각각의 엔드포인트에 요청을 대신 보내주고 응답을 대신 받아서 이를 전달하는 프록시 역할을 수행한다.
MSA 에 해당 구조가 사용되는 이유는
클라이언트에서 각각의 micro service 를 직접 호출하게 되면
micro service 에 수정사항이 생기면 각각의 클라이언트가 모두 수정되야 하므로
이를 방지하기 위해 사용한다.
API Gateway 의 기능
- 인증 및 권한 부여
- 서비스 검색 통합
- 응답 캐싱
- 정책, 회로 차단기(서비스 문제 생긴 경우) 및 Qos 다시 시도
- 속도 제한
- 부하 분산( Load Balancing )
- 로깅, 추적, 상관 관계
- 헤더, 쿼리 문자열 및 청구 변환
- IP 허용 목록에 추가
Netflix Rebbon (Deprecated)
- Client Side Load Balancer
- 서버가 아닌 클라이언트에서 사용한다.
- MSA 이름으로 호출 가능
- 비동기 처리가 불가능하므로 잘 사용하지 않는다.
Netflix zuul (Deprecated)
- Server Side Load Balancer
Spring Cloud Gateway
- 사용 권장
구현
1. Dependency 추가
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
2. yml 파일 설정
server:
port: 8000
# Service Discovery 에 게이트 웨이 등록
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
# Service Discovery 사용 안하는 경우
Spring:
application:
name: apigateway-service
cloud:
gateway:
# yml 에서 필터 및 라우팅 설정
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 = first-request, first-requests-header2
- AddResponseHeader = first-response, first-response-header2
# 커스텀 필터 적용
- CustomFilter
# 커스텀 필터 적용 (매개 변수 사용)
- name: loggingFilter
args:
baseMessage: Spring Cloud Gateway GlobalFilter
preLogger: true
postLogger: true
# 공통 필터 설정
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway GlobalFilter
preLogger: true
postLogger: true
# Service Discovery 사용 하는 경우
Spring:
cloud:
gateway:
routes:
- id: first-service
uri: lb://MY-FIRST-SERVICE # 로드 밸런서:서비스 이름 => 이러면 Service Discovery 에서 자동으로 로드 밸런서 동작을 한다.
predicates:
- Path=/first-service/**
filters:
3. java 코드를 이용한 필터 및 라우터 추가
/**
* 자바 코드를 이용한 필터 적용
* */
@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();
}
}
4. 커스텀 필터 구현
/**
* 커스텀 필터
* 각각의 라우터 정보에 맵핑 필요
* */
@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> {
public CustomFilter(Class<Config> configClass) {
super(configClass);
}
public static class Config{
// Config Info
}
@Override
public GatewayFilter apply(Config config) {
// 기본
return (exchange,chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Custom PRE filter: request ID -> {}", request.getId());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Custom POST filter: response code -> {}", response.getStatusCode());
}));
};
// 우선 순위 설정 가능
GatewayFilter filter = new OrderedGatewayFilter((exchange,chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info("Custom PRE filter: request ID -> {}", request.getId());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("Custom POST filter: response code -> {}", response.getStatusCode());
}));
}, Ordered.HIGHEST_PRECEDENCE);
return filter;
}
}
5. 글로벌 필터 구현
@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {
public GlobalFilter(Class<Config> configClass) {
super(configClass);
}
@Data
public class Config {
private String baseMessage;
private boolean preLogger;
private boolean postLogger;
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
log.info(config.getBaseMessage());
if(config.isPreLogger()){
log.info(request.getId());
}
return chain.filter(exchange).then(Mono.fromRunnable( () -> {
if(config.isPostLogger()){
log.info(response.getStatusCode() + "");
}
}));
};
}
}