[서버개발캠프] Spring Cloud Gateway 기본

Sieun Sim·2020년 1월 23일
5

서버개발캠프

목록 보기
10/21

*https://spring.io/guides/gs/gateway 따라가기

스프링부트 버전 2.1.0으로 설정

원래 2.2.7인가 더 높은거였는데 netty 에러가 계속 걸려서 찾아보니 2.1.0 버전으로 하래서 일단 설정을 바꿔서 진행한다.

RouteLocatorBuilder

RouteLocatorBuilder 는 predicates와 fileters들을 사용자의 routes에 추가해 request/response를 커스터마이징하는 등 조건에 따라 컨트롤할 수 있게 해준다.

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f -> f.addRequestHeader("Hello", "World"))
            .uri("http://httpbin.org:80"))
        .build();
}

Header에 "Hello" : "world" 잘 적용되는 것을 볼 수 있다.

{
  "args": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "ko,en-US;q=0.9,en;q=0.8", 
    "Cache-Control": "max-age=0", 
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"0:0:0:0:0:0:0:1%0:33090\"", 
    "Hello": "World", 
    "Host": "httpbin.org", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36", 
    "X-Forwarded-Host": "localhost:8080"
  }, 
  "origin": "0:0:0:0:0:0:0:1%0, 211.34.57.200, 211.34.57.200", 
  "url": "https://localhost:8080/get"
}

Netflix Hystrix

Hystrix는 외부자원을 많이 사용하는 마이크로 서비스 아키텍쳐 기반의 서버환경에서, 외부자원에 이상이 생겼을때에도 문제없이 서비스를 할수 있도록 도와주는 프레임웍이다.

외부 API 에러가 지속적으로 나는 경우 일정시간동안 에러에 대비한 리소스를 캐싱하고, 외부 API 호출을 차단하여 내/외부 자원의 고갈현상을 막는 방법이다.

사용자에게 잘 보여주고 싶은 Gateway 뒤의 서비스들에 문제가 생겼을 때 적절하게 처리해주는 라이브러리다. Hystrix는 request에 대한 filter로 구현되어 있다.

이 예시에서는 응답을 보내기 전에 몇 초간 기다려주는 HTTPBin의delay API를 사용한다. 이 API는 응답을 보내기까지 오래 걸릴수도 있으므로 이 API를 사용하는 route를 HystrixCommand로 감쌀 수 있다.

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f -> f.addRequestHeader("Hello", "World"))
            .uri("http://httpbin.org:80"))
        .route(p -> p
            .host("*.example.com")
            .filters(f -> f.hystrix(config -> config.setName("mycmd")))
            .uri("http://httpbin.org:80")).
        build();
}

앞의 예제가 path prediate였다면 추가된 코드는 host predicate를 사용한다. 호스트가 .host(HOST)의 HOST면 그 요청을 HTTPBin으로 라우트하고 HystrixCommand 로 감쌀 것이다. 이것은 라우트에 필터를 적용함으로써 실행된다. Hystrix 필터는 configuration 객체로 정의된다. 위 예제에서는 HystrixCommand 에 mycmd라고 이름지어준 것이다. 이 예제를 실행하기 위해서는

$ curl --dump-header - --header 'Host: www.example.com' http://localhost:8080/delay/3

--dump-header 옵션으로 Response의 헤더를 확인하고, - 옵션으로 stdout으로 프린트한다.

Header에 "Host: ~HOST" 라는 내용을 추가해줘야 정상적으로 확인할 수 있다. /delay/3 요청을 줌으로써 딜레이를 준다.

응답은 다음과 같이 나오는데

HTTP/1.1 504 Gateway Timeout
Content-Type: application/json;charset=UTF-8
Content-Length: 158

{"timestamp":"2020-01-23T08:46:43.445+0000","path":"/delay/3","status":504,"error":"Gateway Timeout","message":"Response took longer than configured timeout"}

HTTPBin의 응답을 기다리다가 time out 된 것이다. 이에 대해 Hystrix 옵션을 사용해 클라이언트가 504 응답보다 의미있는 응답을 받도록 fallback을 설정할 수 있다. 현업에서는 cache 되어있는 데이터를 줄 수 있겠지만 여기서는 간단히 구현한다.

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f -> f.addRequestHeader("Hello", "World"))
            .uri("http://httpbin.org:80"))
        .route(p -> p
            .host("*.example.com")
            .filters(f -> f.hystrix(config -> config
                .setName("mycmd")
                .setFallbackUri("forward:/fallback")))
            .uri("http://httpbin.org:80"))
        .build();
}

이제 Hystrix가 씌워진 라우트가 time out 되면 이 앱의 /fallback 경로로 부른다.

그래서 /fallback 엔드포인트를 컨트롤러에 추가해준다.

@RequestMapping("/fallback")
public Mono<String> fallback() {
  return Mono.just("fallback!");
}

왜 리액티브인가: https://javacan.tistory.com/entry/why-reactive-summary

리액티브 스트림즈(http://www.reactive-streams.org/)는 비동기 스트림 처리를 위한 표준이다. 스프링 리액터는 이를 구현한 라이브러리이며 자바9의 Flow API도 리액티브 스트림 API를 따르고 있다.

Spring Cloud Gateway와 webflux, reactive

Spring Cloud Gateway 는 WebFlux와 Reactor 프로젝트를 기반으로 비동기적으로 만들어졌다. 그리고 Servlet 대신 비동기적 네트워크 처리에 최적화된 netty 서버를 이용한다고 한다. 그동안 사용하던 Spring MVC에서의 servlet 기반 프로젝트와는 아예 다른 개념인 것이다. 마이크로서비스를 위한 API Gateway는 늘어난 http통신을 빨리빨리 처리하기위해 nonblocking & aysnchronus 하게 돌아갈 필요가 있다. Fully Reactive한 프로그램을 만드려면 다른 마이크로 서비스들도 WebFlux로 짜야 하는 듯하다.

Reactor의 Mono와 Flux

우선 예제에 들어가기 앞서 Mono와 Flux의 차이점을 알 필요가 있습니다. Mono는 0-1개의 결과만을 처리하기 위한 Reactor의 객체이고, Flux는 0-N개인 여러 개의 결과를 처리하는 객체입니다. Reactor를 사용해 일련의 스트림을 코드로 작성하다 보면 보통 여러 스트림을 하나의 결과를 모아줄 때 Mono를 쓰고, 각각의 Mono를 합쳐서 여러 개의 값을 여러 개의 값을 처리하는 Flux로 표현할 수도 있습니다.

Spring Cloud Gateway에서 Reactor 프로젝트를 사용했다고 했는데, Reactor 프로젝트는 스프링 차원의 프레임워크는 아니고 비동기적으로 처리하기위한 별개의 프로젝트이다. Mono로 래핑한다, mono를 이용해 시그널을 보낸다..라고 하는데 이 개념이 아직 너무 생소하다. 결과를 Reactive하게 처리해주는 신호 객체 라고 생각하면 될 것 같다.

HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

fallback!

Writing Tests

좋은 개발자로서 내 Gateway가 기대한대로 잘 되고 있는지 test를 써야만 한다. 대부분은 외부의 리소스에 대한 의존성을 줄이고 싶으므로(특히 unit test에서는) HTTPBin에 의존하면 안된다. 그래서 URI를 변수로 configurable하게 만들어서 원할때 쉽게 바꿀 수 있도록 한다. 설명이 참 친절하구나..

@ConfigurationProperties
class UriConfiguration {

  private String httpbin = "http://httpbin.org:80";

  public String getHttpbin() {
    return httpbin;
  }

  public void setHttpbin(String httpbin) {
    this.httpbin = httpbin;
  }
}

그리고 이 설정을 사용하기 위해 어플리케이션 파일에 @EnableConfigurationProperties(UriConfiguration.class) 어노테이션을 추가해준다.
WireMock from Spring Cloud Contract로 HTTPBin의 API들을 mock할 수 있다.
@AutoConfigureWireMock(port = 0) annotation은 WireMock을 랜덤 포트로 쓸 수 있게 해준다.
@SpringBootTest annotation을 사용해 로컬로 도는 WireMock server에 적용
WebTestClient는 게이트웨이에 실제 요청을 보내는 역할이다.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.?)

WebEnvironment의 속성 4가지
MOCK: WebApplicationContext를 로드하고 서블릿 컨테이너 환경을 mocking 한다. 내장된 서블릿 컨테이너는 전혀 시작되지 않는다.
RANDOM_PORT: EmbeddedWebApplicationContext를 로드하고 내장된 서블릿 컨테이너가 시작되는데 요청을 받아들이는 port를 랜덤하게 바꾸고 시작한다.
DEFINED_PORT: EmbeddedWebApplicationContext를 로드하는데, 지정한 포트를 가지고 요청을 받아들인다. (default는 8080)
NONE: ApplicationContext를 로드하기는 하지만 서블릿 컨테이너 환경을 제공하지 않는다.

Properties

Spring Boot는 기본적으로 클래스 경로상의 application.properties(또는 application.yml)를 통해 애플리케이션 설정을 수행합니다. 하지만 테스트 중에는 설정이 기존과 달라질 필요가 있는 경우가 많은데 이때를 위한 기능을 SpringBootTest에서 제공하고 있습니다. SpringBootTest는 properties라는 속성이 존재합니다. 이 속성을 이용해서 별도의 테스트를 위한 application.properties(또는 application.yml)을 지정할 수 있습니다.

참고자료

사용하면서 알게 된 Reactor, 예제 코드로 살펴보기
스프링 리액터 시작하기 1 - 리액티브 스트림 Flux Mono Subscriber
https://javacan.tistory.com/entry/why-reactive-summary
Building a Gateway
[삽질로그] SpringApplicationConfiguration 과 SpringBootTest의 사용
Spring Boot Test : TOAST Meetup

0개의 댓글