ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 7. [MSA 구현 퀵스타트] API G/W Zuul 초간단 구현
    Spring Cloud 2021. 4. 11. 02:38

    마이크로 서비스 아키텍처의 단일 진입점을 담당하는 API Gateway를 구현해 보도록 하겠습니다.

    이론적인 부분(1~3장)구현할 전체 프로젝트 구조(4장)를 보지 읽지 못하신 분들은 먼저 읽어보시기를 권장 드립니다.

     

    API Gateway를 구현하는 많은 라이브러리가 존재하지만, 그중에서 가장 많이 쓰는 Zuul과 Spring Cloud에서 개발한 Spring Cloud Gateway(SCG)을 알아보겠습니다.

     

    Zuul(version 1)은 초창기에 Netflix에서 개발한 뒤 현재까지도 현업에서 꾸준히 사용되고 있는 라이브러리 입니다. 그만큼 구현 및 운영을 위한 자료가 많이 축척되어 있는 장점이 있습니다.

     

    Spring Cloud Gateway는 이름 그대로 Spring의 Pivotal에서 만들어진 라이브러리로 동기식 통신만을 지원하는 Zuul 1과는 달리 비동기 기반의 Gateway 입니다.

    하여 Reactive Streams, Jetty 기반으로 구현된 Spring Cloud Gateway를 완전히 이해하고 자유롭게 기능을 구현하기 위해서는 Project Reactor, Web Flux, Flow Api 등의 비동기 기술을 다룰수 있어야 합니다.

    하지만 다행히도 자바 코드 없이 프로퍼티 환경설정 만으로도 상당히 많은 Gateway의 기능을 제어할 수 있도록 설계되어 있습니다.

     

    우리는 우선 가장 오래되었지만 그만큼 현업에서 많이 사용하고 기본이 될만한 우선 Zuul1을 구현하고, 차후 Spring Cloud Gateway를 이용하여 구현해 보겠습니다.

     

    Zuul의 이론은 3장에서 다루었기에 바로 Zuul의 기본 구조를 알아보고 구현하겠습니다.

     

     

    Zuul의 기본 구성

    Zuul은 요청이 들어 오면 Pre Filter > Route Filter > Post Filter의 순으로 동작 하며 각 필터들의 역할은 다음과 같습니다.

     

    Pre Filter

    대상 서비스로 요청이 Routing 되기 전에 실행되는 필터입니다.

    대상 서비스에 요청하기 전에 유효한 요청인지 인증(Authentication)이나 인가(Authorization)를 하거나, 요청 로깅, 요청의 유효성 검사, 공통 초기화 설정 등의 기능을 처리하기에 좋은 위치 입니다.

     

    Post Filter

    대상 서비스를 호출하고 최종 응답을 클라이언트에 반환한 뒤에 호출되는 메서드 입니다.

    응답 로깅을 하거나, 완료 후 처리되는 공통 로직 등의 기능을 넣기에 적절한 위치 입니다.

     

    Route Filter

    따로 구현하지 않아도 Zuul 내부적으로 기본 Route Filter(RibbonRoutingFilter)가 작동하여 대상 서비스에 라우팅을 수행합니다.

    Custom Route Filter를 구현하면 기본 라우팅이 수행되기 전에 호출을 가로채어 동적으로 원하는 Routing 기능을 구현 할 수 있습니다.

    예를 들어 원본 대상 라우팅전에 반드시 거쳐야 하는 특정 서비스를 호출해야 한다거나, 카나리아 테스트를 위해 라우팅 대상 서비스를 동적으로 변환하여 라우팅하는 등의 기능을 구현할 수 있습니다.

     

    * 카나리아 테스트

    특정 서비스의 새로운 버전이 개발되었으면 안정적인 Release 버전으로 배포하기 전에 우선 베타 버전의 안전성 검증이 이루어져야 합니다. 이때 전체 사용자 중 일부 사용자에게 베타 버전을 시험적으로 노출시킬 수 있습니다. 만약 베타 버전에 심각한 장애가 발견 되더라도 전체 사용자에게 영향을 미치지 않았기에 위험성을 최소화 할 수 있습니다.

     

    구현할 내용

    예제 소스

    이번장에 진행될 소스는 아래 Github에서 다운로드 받으시고, "3.Api-Gateway" branch를 체크아웃해주세요.

     

    서비스1에 "/ribbon/{id}"라는 API가 있습니다.

    클라이언트가 API Gateway에게 "localhost:5555/api/service1/ribbon/{id}" URL로  요청을 하면,

    API Gateway는 service1을 보고 서비스1의 "localhost:8011/ribbon/{id}" URL로 Routing 합니다.

     

    또한 각각의 Pre Filter / Post Filter / Route 필터에 간단한 로깅을 남겨 동작되는 것을 확인해 보겠습니다.

    소스 구조

     

    API Gateway (Zuul 1) 구현

    Library Dependency (build.gradle)

    1
    2
    3
    4
    5
    6
    7
    dependencies {
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
        implementation 'org.springframework.boot:spring-boot-starter-actuator'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        ..... 중략 .....
    }
    cs

    spring-cloud-starter-netflix-zuul을 설정합니다. 또한 zuul 역시 Eureka 서버에 등록되어야 하기에 spring-cloud-starter-netflix-eureka-client의 라이브러리가 필요합니다.

     

    application.yml 설정

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    server:
      port: 5555
     
    spring:
      application:
        name: api-gateway
     
    eureka:
      instance:
        prefer-ip-address: true
      client:
        register-with-eureka: true
        fetch-registry: true
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
     
    zuul:
      prefix:  /api
      routes:
        # 수동 경로 매핑  http://localhost:5555/srve2/v1/~~
        service2: /srve2/**
    cs

    앞 장에서 설명한 eureka에 대한 설정은 생략하겠습니다.

    나머지 zuul의 설정은 거의 없습니다. 사실 Zuul은 설정을 거의 하지 않아도 기본 동작을 충실히 수행합니다.

    위의 prefix와 routes 설정도 필수가 아닙니다. (하지만 너무 설정을 안하면 허전하니 퀵스타트에서는 위 2가지만 해보도록 하겠습니다.)

     

    zuul.prefix

    앞에서 서비스1에 "/ribbon/{id}"API를 요청하기 위해 API Gateway로 "localhost:5555/api/service1/ribbon/{id}" URL을 호출한다고 하였습니다. 이 URL의 "/api" 부분이 prefix 설정에 의해 URI 맨 앞에 붙은 것입니다.

    만약 해당 설정을 하지 않는다면 URL은 "/api"가 제외된 "localhost:5555/service1/ribbon/{id}"가 되겠지요.

     

    zuul.routes

    수동으로 경로 매핑을 하는 설정입니다. 위와 같이 설정을 하면 기본 URI인 "localhost:5555/api/service2/name/{id}" 이 아닌 "localhost:5555/api/srve2/name/{id}"로도 요청을 할수가 있습니다.

     

    코드 구현

    필터를 구현해 보겠습니다. 사실 로깅 외에는 별다른 기능을 구현하지 않아 단순합니다. (차후 포스팅에서 여러가지 기능을 추가해 보도록 하겠습니다.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Slf4j
    @Component
    public class PreFilter extends ZuulFilter {
     
        @Override
        public String filterType() {
            return "pre";
        }
     
        @Override
        public int filterOrder() {
            return 1;
        }
     
        @Override
        public boolean shouldFilter() {
            return true;
        }
     
        @Override
        public Object run() {
            log.info("===== START Pre Filter. =====");
            return null;
        }
    }
    cs

    각 필터들은 ZullFilter를 기본적으로 상속 받아서 추가 기능을 구현해야합니다.

    우선 filterType() 메소드를 오버라이드 하여 "pre" / "route" / "post" 문자열을 지정하여 해당 필터가 어떤 필터인지 결정합니다.

     

    Pre1Filter, Pre2Filter 등과 같이 각각 필터들을 여러개 구현 할 수도 있습니다. 이때 각 필터들의 실행 순서를 정해주는 것이 filterOrder() 메소드 입니다. 숫자가 작은 필터가 먼저 수행됩니다.

     

    shouldFilter() 메소드는 해당 필터가 작동할지 여부를 결정합니다. 만약 false를 리턴하면 런타임 시에 해당 필터는 동작하지 않습니다.

    (해당 값을 프로퍼티에 등록해서 나중에 알아볼 Config Server에 관리하게 하면, 실시간으로 필터를 On/Off 할 수도 있습니다.)

     

    run() 메소드는 요청 응답 라우팅 전/후에 특정 처리를 해줄 로직을 구현하는 장소입니다. 퀵스타트이기 단순히 간단한 로깅만 하였지만, 앞서 설명한 막강한 기능들을 추가할 수 있습니다.

     

    동작 확인

    구현이 완료되었으니 실행을 해보도록 하겠습니다.

    앞서 구현한 유레카 서버, 서비스1, 서비스2와 API Gateway를 모드 실행합니다.

     

    유레카 대시 보드(localhost:8761)를 보면 API-GATEWAY가 추가로 등록된 것을 확인 할 수 있습니다. 다음은 첨부된 Postman으로 API를 호출하여 동작을 확인해 보겠습니다.

     

    http://localhost:5555/actuator/routes

    API Gateway가 라우팅 가능한 서비스 목록을 보여줍니다. (actuator 등록이 되어야 확인 가능한 API 입니다.)

    아래와 같이 3가지로 라우팅이 가능합니다. "/api/srve2/**"는 위 설정에서 service2를 srve2로 동적 라우팅 설정을 하였기에 추가된 URI입니다.

    http://localhost:5555/api/service1/ribbon/1을 호출해 보겠습니다.

    예상대로의 결과를 잘 받아오는 것을 확인할 수 있습니다.

     

     

    마무리

    모든 API의 진입 관문인 API Gateway를 구현해 보았습니다. 다음 장에서는 API G/W 필수적인 기능을 확장하여 추가 구현을 하겠습니다.

     

     

    참고문헌

    - 스프링 마이크로서비스 코딩 공작소 정성권 옮김 길벗

    - https://github.com/klimtever

    댓글

Designed by Tistory.