API Gateway란?
** 이미지 출처: https://m.blog.naver.com/dktmrorl/222129517689
- API Gateway는 서비스 앞단에서 모든 API 요청을 단일화해주는 역할을 한다.
-
즉, 클라이언트 측에서는 마이크로서비스에 IP 변경등에 영향을 받지 않고 오로지 API Gateway에게 요청을 보내며 해당 요청을 API Gateway가 서비스에 보내주는 역할을 한다.
- API Gateway 기능
- 인증 및 권한 부여
- 서비스 검색 통합
- 응답 캐싱
- 정책, 회로 차단기 및 QoS 다시 시도
- 속도 제한
- 부하 분산
- 로깅, 추적, 상관관계
- 헤더, 쿼리 문자열 및 청구 변환
- IP 허용 목록에 추가(방화벽)
- Spring Cloud에서의 MSA 간 통신
- RestTemplate
RestTemplate restTemplate = new RestTemplate(); restTemplate.getForObject("http://localhost:8080/", User.class, 200);
- Feign Client
@FeignClient("stores") public interface StoreClient { @GetMapping(value="/stores") List<Store> getStores(); }
- RestTemplate
- Ribbon: Client side LoadBalancer
- 서비스 이름으로 호출 (MSA Service Name)
- Health Check
- Reactive(RxJava)와 호환이 안되어 최근에는 많이 사용하지 않는다.
- Spring Cloud Ribbon은 Spring Boot 2.4에서 maintenance 상태
- Netflix Zuul
- Routing, API Gateway 역할을 한다.
- client는 service를 직접 호출하는 것이 아닌 Zuul에 요청을 하면 Zuul이 service에게 요청을 보낸다.
- Spring Cloud Zuul은 Spring Boot 2.4에서 mainenance 상태
Netflix Zuul 구현
- Spring Boot 2.3.8을 사용해야 한다 (2.4는 지원하지 않는다.
- First Service와 Second Service 구현
- dependency: lombok, web, eureka client
- RestController: GET /welcome 요청 처리
@RestController public void FirstController { @GetMapping("/welcome") public String welcome() { return "Welcome First Service"; } }
- application.yml
server: port: 8081 spring: application: name: first-service eureka: client: register-with-eureka: false fetchRegistry: false
server: port: 8082 spring: application: name: second-service eureka: client: register-with-eureka: false fetchRegistry: false
- Zuul Service 구현
- Spring Boot: 2.3.8
- dependency: lombok, web, zuul
- 메인 함수
@SpringBootApplication @EnableZuulProxy public class ZuulServiceApplication { public static void main(String[] args) { SpringApplication.run(ZuulServiceApplication.class, args); } }
- application.yml 설정
server: port: 8000 spring: application: name: zuul-service zuul: routes: first-service: path: /first-service/** url: http://localhost:8081 second-service: path: /second-service/** url: http://localhost:8082
- 테스트
- 요청1: http://localhost:8080/first-service/welcome
- 요청2: http://localhost:8080/second-service/welcome
-
ZuulFilter
- ZuulFilter를 상속받아 Filter를 구현할 수 있다.
- filterType(): 요청 사전, 사후 필터를 작성할 수 있으며, filterType()이 pre면 사전, post는 사후이다.
- filterOrder(): 필터의 순서를 지정할 수 있다.
- shouldFilter(): true/false : 필터 사용여부
- run(): 필터 실행 메소드
@Slf4j @Component public class ZuulLoggingFilter extends ZuulFilter { @Override public Object run() throws ZuulException { // TODO Auto-generated method stub log.info("====print log"); HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); log.info("request: " + request.getRequestURI()); return null; } @Override public boolean shouldFilter() { // TODO Auto-generated method stub return true; } @Override public String filterType() { // TODO Auto-generated method stub return "pre"; } @Override public int filterOrder() { // TODO Auto-generated method stub // filter 순서 return 0; } }
- 로그를 확인할 수 있다.
2021-07-06 23:42:05.017 INFO 10252 --- [nio-8000-exec-2] c.example.demo.filter.ZuulLoggingFilter : ====print log 2021-07-06 23:42:05.018 INFO 10252 --- [nio-8000-exec-2] c.example.demo.filter.ZuulLoggingFilter : request: /second-service/welcome 2021-07-06 23:42:15.155 INFO 10252 --- [nio-8000-exec-3] c.example.demo.filter.ZuulLoggingFilter : ====print log 2021-07-06 23:42:15.156 INFO 10252 --- [nio-8000-exec-3] c.example.demo.filter.ZuulLoggingFilter : request: /first-service/welcome
Spring Cloud Gateway
- Spring Boot 2.4 버전 이상에서 사용할 수 있는 API Gateway
-
비동기 처리(WebFlux)가 가능하다
- 프로젝트 생성
- spring boot version: 2.4.9
- dependency: lombok, gateway, eureka client
- application.yml로 라우팅하기
server: port: 8000 eureka: client: register-with-eureka: false fetch-registry: false service-url: default-zone: 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:8081/ predicates: - Path= /second-service/**
- Path로 지정한 uri로 api gateway의 URI를 구분할 수 있다.
- 다만 Path가 그대로 service에 전달하게 된다.
- 즉, localhost:8000/first-service/welcome -> localhost:8081/first-service/welcome
- 그래서 first-service, second-service의 url을 변경해주어야 한다.
- java code를 활용하여 filter 추가하여 라우팅하기
@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")) .uri("http://localhost:8082")) .build(); } }
- application.yml 파일에 작성해두었던 spring.cloud.gateway.routes 밑에 부분은 주석처리한다.
- filters 메소드를 통해, request, response header를 설정할 수 있다.
-
Filter
** 이미지 출처: https://www.baeldung.com/spring-cloud-gateway-webfilter-factories
- property를 통한 filter 추가
spring: application: name: apigateway-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 - id: second-service uri: http://localhost:8081/ predicates: - Path= /second-service/** filters: - AddRequestHeader=second-request, second-request-header2 - AddResponseHeader=second-response, second-response-header2
- custom filter
@Component @Slf4j public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config>{ public static class Config { // put the configuration properties } public CustomFilter() { super(Config.class); } @Override public GatewayFilter apply(Config config) { // TODO Auto-generated method stub return (exchange, chain) -> { // Custom Pre Filter ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); log.info("Custom Pre filter: {}", request.getId()); // chaining한 후 Post Filter return chain.filter(exchange).then(Mono.fromRunnable(()-> { log.info("Custom Post filter.response status code: {}", response.getStatusCode()); })); }; } }
- java code를 활용한 custom filter
- AbstractGatewayFilterFactory 를 상속받아 사용한다.
- ServletHttpRequest, ServletHttpResponse 가 아닌 ServerHttpRequest, ServerHttpResponse를 사용
spring: application: name: apigateway-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:8081/ predicates: - Path= /second-service/** filters: #- AddRequestHeader=second-request, second-request-header2 #- AddResponseHeader=second-response, second-response-header2 - CustomFilter
- application.yml 에 등록
2021-07-08 10:44:59.837 INFO 7088 --- [ctor-http-nio-2] com.example.demo.CustomFilter : Custom Pre filter: 288bb018-1 2021-07-08 10:45:00.283 INFO 7088 --- [ctor-http-nio-4] com.example.demo.CustomFilter : Custom Post filter.response status code: 200 OK
- 요청 시 확인
- Global Filter
- Global Filter도 만드는 방법은 이전과 비슷하다.
- Custom Filter는 Custom Filter에 경우 application.yml에 route 정보에 따른 filter를 등록했다.
- Global Filter는 모든 route 정보에 적용된다.
@Component @Slf4j public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config>{ @Data public static class Config { private String baseMessage; private boolean pre; private boolean post; } public GlobalFilter() { super(Config.class); } @Override public GatewayFilter apply(Config config) { // TODO Auto-generated method stub return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); if(config.isPre()) { // Config에 있는 getter 사용 log.info("Global Pre Filter -> Request ID: {}", request.getId()); log.info(config.getBaseMessage()); } return chain.filter(exchange).then(Mono.fromRunnable(()->{ if(config.isPost()) { log.info("Global Post Filter -> Response Status Code: {}", response.getStatusCode()); } })); }; } }
- java code 작성
- CustomFilter와 작성 방식은 같다.
- CustomFilter 또한 GlobalFilter.Config 에 데이터를 놓고 getter, setter로 접근 가능하며 application.yml에 있는 값을 가져온다.
- AbstractGatewayFilterFactory를 상속받는다.
GatewayFilter filter = new OrderedGatewayFilter((exchange, chain)-> { // pre 정의 return chain.filter(exchange).then(Mono.fromRunnable(()-> { // post 정의 })); }, Ordered.HIGHEST_PRECEDENCE); return filter;
- public GatewayFilter apply 를 구현해주어야 한다.
- 리턴 객체는 GatewayFilter는 OrderedGatewayFilter 구현체를 사용하며 filter 메소드를 재정의해야 한다.
- OrderedGatewayFilter 구현을 해주었기 때문에 Filter 순위를 적용할 수 있다.
spring: application: name: apigateway-service cloud: gateway: default-filters: - name: GlobalFilter args: baseMessage: Spring Cloud Gateway Log Filter pre: True post: True routes: - id: first-service uri: http://localhost:8081/ predicates: - Path= /first-service/** filters: - CustomFilter - id: second-service uri: http://localhost:8081/ predicates: - Path= /second-service/** filters: - CustomFilter
- application.yml에 default_filtes로 GlobalFilter를 등록할 수 있다.
- args로 Config 값을 설정하였다.
2021-07-08 11:09:41.275 INFO 21260 --- [ctor-http-nio-3] com.example.demo.GlobalFilter : Global Pre Filter -> Request ID: 54a9517e-1 2021-07-08 11:09:41.276 INFO 21260 --- [ctor-http-nio-3] com.example.demo.CustomFilter : Custom Pre filter: 54a9517e-1 2021-07-08 11:09:41.695 INFO 21260 --- [ctor-http-nio-1] com.example.demo.CustomFilter : Custom Post filter.response status code: 200 OK 2021-07-08 11:09:41.695 INFO 21260 --- [ctor-http-nio-1] com.example.demo.GlobalFilter : Global Post Filter -> Response Status Code: 200 OK
- 로그를 확인할 수 있다.
- property를 통한 filter 추가
- Spring Cloud Gateway 와 Eureka 연동
- client가 localhost:8000/first-service/welcome을 요청
- Spring Cloud Gateway
- API Gateway는 Eureka에 요청정보를 전달하여 Service의 위치를 전달 받는다.
- Eureka Server: Service Discovery, Registration
- Eureka Server는 API Gateway에 요청된 정보를 토대로 First Service 또는 Second Service의 위치 정보를 전달한다.
- First Service, Second Service
- API Gateway에게 응답을 전달한다.
- localhost:8081/first-service/welcome
- localhost:8082/second-service/welcome
-
API Gateway는 Service에게 전달받은 응답을 Client에게 전달한다.
- 개발 순서
- Step1) Eureka Client 추가 - pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- Step2) Eureka Client 설정 - application.yml
- Spring Cloud Gateway
- 기존 사용했던 URI 정보를 lb://eureka-등록-service-name 으로 등록이 가능하다.
eureka: client: register-with-eureka: true fetch-registry: true service-url: default-zone: http://localhost:8761/eureka spring: application: name: apigateway-service cloud: gateway: default-filters: - name: GlobalFilter args: baseMessage: Spring Cloud Gateway Log Filter pre: true post: true routes: - id: first-service uri: lb://FIRST-SERVICE predicates: - Path= /first-service/** filters: # - AddRequestHeader=first-request, first-request-header2 # - AddResponseHeader=first-response, first-response-header2 - CustomFilter - id: second-service uri: lb://SECOND-SERVICE predicates: - Path= /second-service/** filters: #- AddRequestHeader=second-request, second-request-header2 #- AddResponseHeader=second-response, second-response-header2 - CustomFilter
- 기존 사용했던 URI 정보를 lb://eureka-등록-service-name 으로 등록이 가능하다.
- First Service, Second Service에 등록
eureka: client: register-with-eureka: true fetchRegistry: true service-url: defaultZone: http://localhost:871/eureka
- Spring Cloud Gateway
- Step3) Eureka Server에 등록 확인
- localhost:8761
- Step1) Eureka Client 추가 - pom.xml
출처
- Sprint Cloud로 개발하는 마이크로서비스 애플리케이션, 인프런 강의