가볍게 마이크로서비스 구축해보기-3

Netflix Zuul를 이용해 API Gateway 패턴 구현하기

지난 ‘Netflix Eureka를 이용해 Service Discovery 패턴 구현하기’에 이어 이번 포스트에서는 API 게이트웨이(API Gateway) 패턴에 대한 내용을 다루고, Netflix Zuul를 이용해 구현해보도록 하겠습니다.
(게시일자 기준 Medium 블로그에서 GitHup Gist를 통해 삽입된 소스 코드가 보이지 않는 버그가 자주 발생하고 있습니다... 참고하시기 바랍니다.)

API 게이트웨이

많은 이들이 마이크로서비스 아키텍처(이하 MSA)와 비교되는 전통적인 SOA(Service Oriented Arichitecture)의 대표적인 실패 이유로, 지나치게 많은 기능이 집약되었던 ESB(Enterprise Service Bus) 패턴을 손꼽습니다.

API 게이트웨이 패턴은 앞서 ESB 패턴을 통해 경험했던 시행착오를 발판삼아, 서비스 라우팅 기능에 집중하고 고가용성을 확보하기 위해 보다 가볍게 설계된 디자인 패턴입니다.

이러한 API 게이트웨이는 클라이언트와 서비스 사이에 위치하여, 클라이언트로부터 받은 요청을 처리해 줄 적절한 서비스로 라우팅하는 역할을 수행 합니다. 결과적으로 클라이언트 입장에서는 MSA를 구성하는 여러 서비스들의 엔드 포인트가 API 게이트웨이에 의해 단일화 되기 때문에, 클라이언트로부터 시스템의 복잡도를 숨기는 효과 또한 기대할 수 있습니다.

Image for post
Image for post

위 그림 처럼, 일반적으로 MSA에서 API 게이트웨이는 서비스 레지스트리(Service Registry)와 함께 서버 사이드 디스커버리(Server-Side Discovery) 패턴을 구현합니다. 이와 같은 구조에서, API 게이트웨이는 클라이언트의 요청을 처리해줄 가용 상태의 서비스 네트워크 주소를 서비스 레지스트리에 질의 하여 해당 서비스를 호출하게 됩니다.
(서비스 디스커버리에 대해 좀 더 자세한 내용은 지난 포스트를 참조해 주시기 바랍니다.)

Netflix Zuul

Zuul은 Netflix OSS(Netflix Open Source Software)에 포함된 컴포넌트들 중 하나로써 API 게이트웨이 패턴을 구현할 수 있습니다. 이러한 Zuul은 단순한 라우팅 이외에도 요청과 응답을 동적으로 가로채 HTTP 메시지를 다룰 수 있는 필터링 기능을 함께 제공하고 있습니다.

필터는 ZuulFilter를 상속하여 만들 수 있으며, 아래와 같이 목적에 따라 크게 4가지로 분류됩니다.

  • 프리 필터(Pre Filter)
    : 라우팅 이전에 실행되며, 클라이언트 인증/인가 등에 활용할 수 있습니다.

그럼 지금부터 Sprig Cloud Netflix에서 제공하는 Zuul과 Eureka를 이용해 서버 사이드 디스커버리를 구현해보도록 하겠습니다.

구현 시나리오

지난 포스트에서 Eureka를 이용해 구현한 서비스 레지스트리와 서비스들(Service-A, Service-B)에 이어, API 게이트웨이를 추가하여 서버 사이드 디스커버리 패턴을 구현하도록 하겠습니다.

이로써 클라이언트는 서비스를 이용하기 위해 API 게이트웨이를 Edge 서비스로 마주하게됩니다.

또한, Zuul이 제공하는 라우팅 기능 이외에 필터 기능을 이용해 서비스에 접근할 수 있는 외부 시스템의 IP를 제한하도록 하겠습니다.

Zuul 라우터 설정

1. 프로젝트 템플릿 다운로드
Spring Initializr에서 ‘Zuul’과 ‘ Eureka Discovery Client’를 검색하여 의존성에 추가하고 프로젝트 템플릿을 다운 받아 IDE에 임포트합니다.

2. application.yml 설정

  • zuul.routes.application-name.path
    : Eureka 서버에 application-name으로 등록된 서비스로 라우팅할 URI 패턴 정의

3. @EbableZuulProxy, @EnableDiscoveryClient 활성화
리버스 프락시(Reverse Proxy) 기능을 활성화시키고, Eureka Client로써 동작하도록 @EbableZuulProxy와 @EnableDiscoveryClient를 메인 애플리케이션 클래스에 추가합니다.

Zuul 필터 설정

1. ZuulFilter 상속
필터를 만들기 위해 ZuulFilter를 상속하고 아래와 같이 4가지의 메서드를 오버라이딩합니다.

- String FilterType() : 필터의 타입(Pre, route,  post, error) 정의
- int filterOrder() : 필터의 실행 순서 정의
- boolean shouldFilter() : filter의 실행 여부 정의
- object run() : 필터가 수행할 로직 정의
* FilterType이나 filterOrder의 경우, Zuul 의존성에 포함되어져 있는FilterConstants.Java에 정의된 상수 값들을 이용하시는 것을 추천드립니다.
  • 라우팅 이전에 실행될 필터
    : 클라이언트의 IP를 체크하고, 허용된 대역(로컬호스트로 제한)이 아닐 경우 403 상태코드를 반환합니다.
  • 라우팅 이후 실행될 필터
    : UUID 정보를 담은 HTTP 헤더를 추가하여 클라이언트에게 응답합니다.

3. 메인 애플리케이션에 필터 빈(Bean) 등록
메인 애플리케이션에 앞서 정의한 두 개의 필터들을 빈으로 등록합니다.

5. Eureka 대시보드 확인
이제 Zuul 프로젝트를 실행시키면, 지난 포스트에서 생성한 서비스와 함께 Eureka 서버의 대시보드에 등록된 것을 확인할 수 있습니다.

Image for post
Image for post

시나리오 검증

로컬 호스트의 HTTP 클라이언트를 이용해 Service-B의 /api/healthcheck를 API 게이트웨이에게 요청해보겠습니다.

Image for post
Image for post

API 게이트웨이로부터 Trace-ID가 추가된 헤더와 Service-B의 응답이 담겨진 바디 메시지를 확인할 수 있습니다. 또한, Service-B의 인스턴스들을 라운드 로빈 기반으로 호출하고 있는 것을 확인할 수 있습니다.
(Zuul 역시 Eureka와 마찬가지로 로드밸러서인 Ribbon이 내장되어 있어 동일한 서비스가 다수의 인스턴스로 운영될 경우, 라운드 로빈을 기반으로 서비스 인스턴스를 호출하게 됩니다.)

Image for post
Image for post

반면, 외부 호스트에서 Service-B의 /api/healthcheck를 API 게이트웨이에게 요청해 볼 경우, 프리 필터에 의해 아래와 같이 403 상태코드를 응답 받게 되는 것을 확인할 수 있습니다.

Image for post
Image for post

지금까지 살펴 본 바와 같이 MSA에서 API 게이트웨이는 다양한 역할을 수행할 수 있는 매력적인 포지션에 위치해 있습니다. 하지만, 과거 ESB 패턴의 실패를 교훈 삼아 과한 기능들을 API 게이트웨이에 구현하여 가용성을 저하시키는 일이 발생하지 않도록 주의 할 필요가 있습니다.

특히, 서비스 오케스트레이션(Service Orchestration)은 API 게이트웨이의 가용성을 저하시키는 대표적인 사례로 손꼽히기 때문에 필요할 경우 별도의 모듈로 분리하여 구현하는 것을 추천드립니다.

다음 포스트에서는 Netflix Hystrix을 이용해 Circuit Breaker 패턴을 구현해보도록 하겠습니다.

피드백은 언제나 환영입니다!

참고자료

Written by

기술을 공유하고 함께 성장해가는 개발 문화에 조금이나마 보탬이 되어 보고자 기술 블로그를 시작하게 되었습니다.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store