Java 적응기 06 - Netflix Zuul

이번에는 Netflix Zuul 도입기에 대해서 이야기 해보고자 한다.

지난 MSA 그리고 API Gateway 글을 읽고 오면 더 이해가 쉽게 될 것이다.


내가 합류했던 팀은 MSA로 구성되어 있었다.

반면 API Gateway가 존재하지 않았다 보니, frontend에 바로 물려 있는 서버에서 proxy 처리를 해주고 있었고, 그 서버에 interceptor를 통한 인증 로직도 함께 포함되어 있었다.

zuul 도입 이전 아키텍쳐

우선 frontend에서 proxy를 처리하는 코드가 범용적이지 않다보니, 이를 우회하기 위해서 javascript단에서 서비스를 직접 호출하는 상황이 일부 존재했다. 직접 호출시의 규격에 제약도 없다보니, 어떤 사용자가 호출한 것인지 분류해내기 매우 까다로울 수 밖에 없었다.

서비스별로 로드밸런싱/endpoint 변경 처리/부하 제어/호출 권한 관리 어느 하나 쉽지 않았다. 각 서비스가 어디에 떠있는지 관리하기 어렵기 때문에 호출 권한 검사, 부하 체크도 어려웠다.

각 서비스별 endpoint를 들고 있었어야 되는데, 내가 아닌 다른 서비스가 재시작 될 때에도 내가 들고 있는 값을 갱신해 주기 위한 여러가지 처리가 필요했다. (이는 동적 설정 파일 혹은 db에 관리하지 않은 이슈도 있긴하지만 endpoint 정보의 중복이란 관점만 생각해도 문제가 있다)

또한 frontend와 동일한 백엔드 서버를 거치자니 사용자 요청과 서비스에서의 호출인지 구분도 어렵고 모든 예외처리, 인증, 인가, 부하 제어, 로드밸런싱 로직이 서비스 갯수만큼 필요하다. 이를 sdk를 제공함으로써 조금은 편해질 수 있어도 기능의 역할의 중복은 피해갈 수 없다.

마찬가지로 Open API 제공 하기에도 어려웠는데, API 사용자별 ratelimt, 권한 검사, proxy (with router)를 구현해주고 관리해주어야 했기에 때문이다. 외부 사용 권한 제어는 API Umbrella를 통해서 대행하고 있었으나, frontend, 서비스간 호출까지 포함해서 일관성 있는 아키텍쳐를 구성하고 관리하고 싶은 니즈가 있었기에, 요구사항에 맞는 기술 선정을 위한 검토와 개발을 진행하게 되었다.


API Gateway가 도입 되는 과정에서 여러가지 도입 검토 후보군이 존재했다.

  • ruby on rails : API Umbrella
  • node.js : express gateway, proxy를 통한 자체 구현
  • go : tyk
  • lua : kong
  • java : netflix zuul

최초에는 API Umbrella가 먼저 도입됐다. 다만 인증 인가 로직을 붙이기 위해서 rails 코드를 추가/관리해주어야 된다는 점과, 패키징 되서 배포되는 모델이다보니 코드 수정 및 연계가 불편한점으로 인해, 타 부서에 배포하는 용도로만 사용됐다.

이후 node.js로 proxy를 이용한 도입과 netflix zuul을 비교하게 되었는데, 이 중 zuul이 자바로 이루어진 점과 spring-cloud에 포함된 스프링 공식이되었고 spring 서버군에 적용될 공용 코드를 다수 이용할 수 있다는 점, 유틸리티 기능이 풍부하다는 점 등에서 선택하게됐다.


최근 여러 컨퍼런스나 블로그에서 언급된 내용이지만 나도 조금 설명을 하자면 zuul은 아래 4가지 핵심 기능으로 분류된다.

netflix zuul에 대해 간략히 살펴보자면 크게 4가지 기능으로 분류 된다.

  • Zuul : Router and Filter
  • Ribbon : Load Balancer
  • Eureka : Registry Service
  • Hystrix : Latency and fault tolerance

이렇게 네가지 기능을 통한 API Gateway 구현을 하게 된다.

이 중 zuul은 router를 통한 endpoint 관리와 filter를 통한 인증/인가 처리, 접근 제어, 부하 제어등을 구현하게 된다.

https://cloud.spring.io/spring-cloud-netflix/multi/multi__router_and_filter_zuul.html

Zuul Filters

Filter의 종류와 각 Filter에서의 적절한 처리

  • Pre
    • Request Header 및 파라미터 검사. 일관성이 어긋난 요청을 하진 않는지. Ratelimit 처리. 요청자 정보를 얻어오기. 혹은 요청자 정보를 각 컴포넌트에 신뢰성 있게 전달. (헤더에 추가하는게 가장 좋다)
  • Post
    • Response Header 및 파라미터, Body 검사. Body 로깅도 처리하면 좋다. 처리 결과에 대한 로깅, Ratelimit을 위한 요청 기록 처리
  • Error
    • 오류 발생시 핸들링. 여기서 Exception을 catch해서 핸들링했다.
  • Custom
    • 무언가 부가 작업을 해주어야 할 때. (요청에 따라 다른 코드를 넣으면 좋다. 인증/인가라거나 기타 등등)
  • Routing
    • 어디로 라우팅할지를 결정 지어 줌. 이 시점에서 ribbon 사용시에는 ribbon에서 전달된 정보를 바탕으로 origin server를 결정짓는다.
    • 만약 서버 목록 중 특정 서버로 보내고 싶은 로직을 구현하고 싶을 경우 이 곳에서 처리하면 된다.

zuul을 통한 service 관리를 간략히 살펴보면 다음과 같다.

zuul routes 예제 (application.yml)

 zuul:
  routes:
    user_service: # serviceId
      path: /users/** # users path 이하의 요청은 모두 아래 url로 route한다.
      url: http://127.0.0.100
    registry_service: # serviceId
	path: /registry/** # registry path 이하의 요청은 모두 아래 url로 route한다.
	url: http://127.0.0.101

해당 설정으로 구동 시킨 서버가 http://127.0.0.1이었다면,

  1. http://127.0.0.1/users/ 이하로 들어온 모든 request
    • http://127.0.0.100 로 전달
    • serviceId는 user_service
  2. http://127.0.0.1/registry/ 이하로 들어온 모든 request
    • http://127.0.0.101
    • serviceId는 registry_service

serviceId는 ribbon, eureka, hystrix등에서 설정 때 쓰이는 핵심 키 값이므로 유념하자.

위 예제는 zuul만 사용했기 때문에 정해진 경로로만 보낸다. 만약 load balancing을 하고 싶다면 아래와 같이 구성하면 된다.

https://spring.io/guides/gs/client-side-load-balancing/

users: # serviceId
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:9092,localhost:9999
    ServerListRefreshInterval: 15000

serviceId로 들어온 요청을 listOfServers중 하나로 보낸다.

이 예시는 eureka를 사용하지 않는 케이스고 로드밸런싱 기능만 수행한다.

eureka 사용시 동작 구조는 다음과 같다.

Zuul Eureka

출처: https://exampledriven.wordpress.com/2016/07/06/spring-cloud-zuul-example/

아래 링크에도 잘 설명되어있으니 참고바란다. https://supawer0728.github.io/2018/03/11/Spring-Cloud-Ribbon%EA%B3%BC-Eureka/


hystrix의 경우 서비스 장애의 전파를 방지한다.

장애가 발생한 노드를 서비스 노드에서 제거하고, 요청에 대한 예외를 격리하여, 지연이 발생한 노드의 성능 저하가 다른 노드로 전파되지 않게끔 처리해준다.

Zuul Hystrix

출처: https://thepracticaldeveloper.com/2017/06/27/hystrix-fallback-with-zuul-and-spring-boot/

fallback 처리를 서비스 구조에 맞게 처리해줌으로써, 장애 대응을 한다. 이에 대한 다양한 팁이 공유 되는데, 서비스 자체가 아니라 특정 node만 제거하게끔 연동한다거나, 미리 캐싱해둔 html을 내려줌으로써 최대한 유사하게 처리한다거나 하는 처리를 대비해두고, fallback 처리를 통해 번 시간을 실제 장애 대응에 맞게 대응하도록 가이딩한다.

자세한 내용은 아래 링크에서 자세히 설명해주셨다. https://supawer0728.github.io/2018/03/11/Spring-Cloud-Hystrix/


다시 돌아와 팀에서 구현한 내용은 zuul 위주로 사용하게 됐다. router를 통한 endpoint 집중 관리, filter를 통한 인증/인가/접근 제어/부하 제어가 가장 중요한 이슈였기 때문이었다.

이 과정에서 router를 서비스 별로 구분하려 했는데, 애초에 서비스를 기능 단위로 쪼개 놓은 상태여서 이부분은 endpoint 취합 및 API Gateway를 사용하게끔 가이딩 하는걸로 충분했다.

다만 API 사용자별로 과도한 API 호출이 이뤄지지 않게끔 제어하는 기능이 필요했는데, 이는 https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit 를 사용자 단위로 처리할 수 있게 커스터마이징 해서 사용했다. 해당 모듈이 serviceId별로 ratelimit만을 제공해 커스터마이징이 불가피했다.

이어서 filter를 이용한 각종 처리를 검토했는데, 사용자를 3부류로 분류했다.

  1. Frontend
    • 말 그대로 Web Frontend 호출.
    • Frontend 사용자의 추가 인증을 처리한다.
  2. Service
    • 기능별로 쪼개진 각 서비스.
    • 다른 서비스를 호출하기 위해 분류한다.
  3. External (Open API Requester))
    • 온전히 Service를 가져다가 사용하기만 하는 외부 사용자.
    • 부하제어/인증/인가가 Service보다 조금 더 복잡해야 해서 따로 분류했다.

세가지 분류의 사용자는 각기 서버에서 미리 발급해둔 API-KEY를 사용하게 된다. API-KEY 자체는 JWT로 발급된 값으로써, 어떠한 사용자이며, 어떤 인증을 거쳐야하며, 어떤 ServiceId를 이용할 수 있는지를 담고 있다.

JWT를 이용한 이유는 JWT는 자체에 정보 값을 부여할 수 있어서 이를 이용하면 DB를 사용하지 않고도 각종 정보를 담을 수 있어서 였다. 단점은 기존에 발급된 토큰을 invalidate 시키기 어려운 점이다. 좀 더 자세한 내용은 다음 글에서 좀 더 자세히 다룰 예정이다.

이를 바탕으로 사용자 타입에 따른 인증 로직을 수행한다.

접근 제어의 경우에도 발급된 API-KEY에 따라 갈리는데, API-KEY별로 Ratelimit을 걸어서 일정 수준이상의 웹 콜을 요청 하지 못하도록 처리했다.

zuul 도입 이후 아키텍쳐

도입 이후 아키텍쳐는 다음과 같다.


웹 호출을 API Gateway로 모두 취합하고 나니 API 통계를 남길 수 있었다. 이전에는 통계를 남기기 위해 모든 API 서버마다 기록해야 했으며, 일부 에러는 취합되지 못하곤 했다. (서버가 꺼져서 응답이 없는 상황이나, interceptor 자체의 오동작 상황 등등)

이를 한 곳에 모을 수 있게 되자, 통계가 좀 더 확실해졌고 이 통계를 바탕으로 API 품질 개선을 이뤄 낼 수 있었다.

남기게 된 API 로그

  • Request
    • RequestPrefix
    • RequestFullURI
    • RequestParameters
    • ipAddress
  • Response
    • ResponseActionLog
    • ResponseStatusCode
    • rtCode
  • 식별 값
    • tenant 단위 정보, 요청자 정보
  • Error 발생시
    • Error cause & message
  • 기본 정보
    • 시간

로그 서버 저장 및 통계는 Mongodb로 했는데, 이에 대한 이야기는 추후에 좀 더 자세히 다뤄보겠다.


결과적으로 netflix zuul이 대다수 작업을 해주었기 때문에, 일부 개선을 위한 동작에 대한 작업만으로 목표를 달성 할 수 있었다.

이와 동시에 Netflix zuul에 대해서 공부하면서, Filter, Interceptor, AOP의 차이도 알 수 있었다.

바퀴를 재발명 많이 해본 입장에서 (어쩔 수 없는 재발명도 있었다고 본다. 오픈소스로 잘 공개되지 않는 인하우스 라이브러리 기반 개발이 주였던 C++ 게임 개발의 전례 때문이기도 했으니 말이다.) 자바의 가져다 쓰는 환경. 그리고 잘 가져다 쓰는 것이 중요한 환경이란 것을 깨닳았기에 그 적절한 사례를 또 하나 찾고 적용한 것 같아 기뻤다.

물론 잘 만들어 공개해준 netflix에 매우 큰 감사를 표한다.

MSA 그리고 API Gateway

내가 웹 서버 개발에 어렵지 않게 적응 할 수 있었던 데에는 웹과 게임 서버간에 의외로 비슷한 요소가 많았던 것이 크게 작용했다.

클라이언트와 서버의 연동이 어떻게 이루어지는가, 사용자에게 개방되는 요소가 무엇 인가, 이벤트를 어떻게 주고 받을 것인가 등등 사실상 이질감이 느껴지는 요소가 아주 많지는 않았다.

또 한가지 비슷한 요소가 있었는데, 백엔드 서비스간의 연동이다. 그 연동 관계를 서비스 아키텍쳐라고 부른다.

네트워크 모델과 유사한 면이 있는데, 이는 피어와 피어간의 통신 구조에 따라서 관리 이슈, 통신 코스트, 예외 상황, 장애 대응 등의 다양한 면에서 비슷한 접근이 가능하다.

이런 과정에서, MSA(Micro Service Architecture)가 대두되었는데, SOA (Service Oriented Architecture)의 발전형이라고 봐도 무방하다.

기본적으로 일체형 아키텍쳐(Monolithic Architecture)와 상반되는 개념으로써, 기능을 한 수레에 담지 않는 모델의 장점을 기반으로 설득력을 얻어가는 구조다.


일체형 아키텍쳐의 장점은 구현이 쉽다. 모든 데이터를 내가 보유하고 있으므로, 애초에 모든 일을 할 수 있다.

반면 성능 이슈 발생시 해결이 어렵다. 어떠한 기능으로 인한 성능 문제가 발생했는지 측정하기가 상대적으로 어려울 뿐더러, 특정 기능에 한정해 성능 개선이 필요할 때 유연하게 대비하기 어렵게 구현되기 쉽다. 또한 배포와 변경, 기동 시간에 단점이 있기 때문에 확장이 상대적으로 어려운 편에 속한다.

뿐만 아니라 로직의 결합도가 생기기 쉽고, 이는 스팟 포인트 증가, 블러킹 포인트 증가로 인한 성능 저하로 이루어지기 쉽고, 자연스레 버그 발생 확률이 증가하는 단점이 있다.

일체형 아키텍쳐와 MSA 비교

출처: martinfowler.com

이를 개선하기 위해 MSA는 서비스 단위로 서버를 구성하고, 수평 확장에 유연하므로 서비스 단위별로 스케일 인/아웃을 유연하게 구성하며, 결합도가 낮으므로 버그 발생 확률이 줄어들며, 테스트 단위도 작아질 수 있다.

성능 측정, 개선도 기능단위로 이루어지므로 명확하며, 필요한 만큼만 성능 확장을 할 수 있고 아키텍쳐 구축 과정에서 이뤄진 동적 스케일링 대비로 인한 안정적 서비스는 덤이다.

다만 여러 개 서비스의 데이터를 조합해서 사용해야 될 때의 문제, endpoint 관리의 문제, 적정한 서비스 단위를 규정하는 문제, 인증/인가 로직을 서비스마다 구현해야 되는 문제 등이 존재한다.

이를 해결하기 위해서 API Gateway를 사용하게 되는데, endpoint 관리 문제, 진입점 일치로 인한 인증/인가 처리의 유연함, 통계/로그 기록이 편해지는 장점, 과도한 트래픽 방지를 위한 Ratelimit 처리 등으로 인한 쪼개진 각 서비스의 이슈를 해결 하는 데에 집중한다.

API Gateway

단점으론 대다수의 서비스가 API Gateway를 통해 통신하게 되므로, API Gateway 자체에 장애가 발생하면 서비스 전체에 지장을 준다. 그래서 이를 대비하기 위한 여러가지 대안이 마련되고 있고, 그 중 최근 화두가 되고 있고, 가장 유명한 것은 Circuit Breaker 패턴을 기반으로 구현된 Netflix Hystrix라고 할 수 있다.


NetflixHystrix에 대해서 간단히 설명하고 넘어가자면, API Gateway의 구현 자체는 HTTP Request 응답을 대행하는 Proxy 역할을 담당하게 되므로 Request Proxy처리하는 동안 Request 요청을 잡고 있어야 한다.

대신해서 호출한 대행 요청이 오래 걸리게 된다면, 요청에 대한 처리속도가 저하되게 된다. 이렇게 밀리는 현상이 지속 되다 보면 전체적으로 응답 시간 저하가 이뤄지고 서비스에 지장을 주는 문제가 발생한다.

그래서 Timeout을 작게 잡아서, 특정 서버의 처리 시간 지연이 다른 요청에 영향을 주지 않게 처리해야한다.

이를 위해서 Timeout이 일정 수준 이상 발생한 서버는 요청 대상에서 격리 시킨다거나, Timeout 자체를 작게 설정해야 해서 영향을 덜 받게 만든다거나 하는 작업을 통해 API Gateway 자체의 Throughput 저하를 막아야 한다.

이 역할을 Hystrix가 담당해준다고 생각하면 된다. API Gateway에는 치명적인 단점인 장애의 전파지점이 SPOF (=Single Point Of Failure)가 되는 문제가 있으나, 이를 극복하기 위한 Netflix의 보완책이라고 볼 수 있다.


이와 별개로, API Gateway를 운용해보니 여러가지 생각이 들었다. 별개의 글에서 좀 더 자세히 설명하게 되겠지만, API Gateway에서 중요한 부분은 정확한 계측을 통해 안정적인 환경을 각 서비스에 전달하는 역할이다.

이를 위해서는 Public API Gateway는 사용자 요청을 수행하고 (Frontend 단에서 호출되게 처리. 인증/인가를 담당), Private API Gateway (내부 서버간 통신에서만 사용. ACL등의 최소화한 보안 처리. 통계의 분리)를 구성하는 것도 하나의 방법이라는 생각이 든다.


API Gateway가 해주면 좋은 역할

  • 인증
  • 인가
  • Ratelimit (사용자별, 서비스간 별개로 관리)
  • Endpoint 관리 (Routing, LoadBalancer, Dynamic Endpoint Management)
  • API 통계
  • API 로깅
  • API Request, Response 형식 검사 및 일관성 유지

이외에도 공통적으로 처리되어야 할 작업들을 API Gateway가 처리해주면 좋다.

다른 글에서 좀 더 자세히 다루겠지만, Netflix API Gateway 4종 세트 (Zuul, Ribbon, Eureka, Hystrix)는 API Gateway 모델의 장점 극대화, 단점 최소화를 이뤄냈다는 점에서 어떻게 구성했는지 배워볼 필요가 있다.

Netflix API Gateway의 핵심 기능

  • Zuul : Router and Filter
  • Ribbon : Load Balancer
  • Eureka : Registry Service
  • Hystrix : Latency and fault tolerance

Netflix Zuul

이 모듈들은 특별한 편집 없이 Annotation과 설정 파일만으로도 구축할 수 있다는 점이 더 큰 장점이다. 위에 언급한 대다수의 역할은 Zuul을 연동하고 동작 시킬 API Gateway 서버의 Filter에서 처리할 수 있다.

Zuul Filters


MSA에서 자주 겪는 문제는 모든 서비스에 같은 기능이 필요할 때의 중복 문제가 크게 발생한다. 이에 대한 버전별 서비스 이슈도 있고, endpoint 변경에 대해 유연한 대처도 어렵다.

이를 해결하기 위한 대안이 바로 API Gateway였고, 다양한 시도와 경험을 바탕으로 장단점이 밝혀졌다. 그리고, 그 것들을 극복하기 위한 대안들이 마련되며 MSA의 마스터 피스가 되었고, 현재로썬 Netflix Zuul이 안정적인 MSA 서비스 모델의 이상향으로 여겨지고 있다.

특히 Netflix의 어마어마한 사용자를 MSA로써 소화하는 모델에서 이용된 핵심 컴포넌트이기에, 그 노하우를 배우고 개선할 만 하다고 여겨진다.

모르긴 몰라도 많은 회사들이 내부적으로 자체적인 API Gateway를 개발하고, 운용할 것이다. 실제로 언어별로 다양하게 API Gateway 모듈이나 서비스가 존재하는 걸 보면, MSA에서 가장 중요한 역할은 API Gateway를 잘 구성하고 운용하는 데에 있다고 보는 듯 하다.

SOA에서 이어진 MSA의 확산, API Gateway 등장, API Gateway의 단점 개선으로 계속 진화하고 있는 MSA가 앞으로 어떻게 진화할지 궁금해진다.

윈도우 10 앱 추천

RSS

Note

To do list

Pocket

Database

  • HeidiSQL
  • NoSQLBooster (Mongodb)
    • https://nosqlbooster.com/
      • 사용해본 MongoDB 툴 중 가장 좋았음.
      • 리스트 관련해서 잔버그가 좀 있고, 페이징 처리시마다 쿼리를 다시 날리므로 무거운 쿼리를 수행하기에 적합하지 않음.
      • 소량 데이터 익스포트에선 편하나, 대량 데이터 익스포트는 유료 버전을 사용해야하는데, 익스포트쪽 잔버그가 꽤 보여서 불안 요소라 아직 안사봤음. 가격이 싸지도 않고…
  • Redis

Office

Sketch

CommandLine

  • Cmder
    • http://cmder.net/
      • cmd 창을 파워풀하게 만들어주는 프로그램.
      • ll, ls -al을 비롯한 각종 리눅스 like alias를 설정 가능하므로 여러모로 생산성을 높일 수 있다.

Capture

  • LightShot
    • https://app.prntscr.com/en/index.html
      • 캡쳐 프로그램 중 가장 간결함. 필요한 기능은 다 있다.
      • 캡쳐 후 간단한 메모 기능도 지원하며, 클라우드 저장 기능도 제공해 더할 나위 없이 편리한 캡쳐 툴.
  • PicPick
    • https://picpick.app/ko/
      • 좀 더 다양한 기능을 원할 경우에 선택지
      • 편집 기능도 좀 더 방대해서, 캡쳐 후 편집 기능을 쓰고 싶거나, 다양한 캡쳐 모드가 필요할 시 선택하면 좋다.

Web Dev

  • Postman
    • https://www.getpostman.com/
      • API 테스트에 이만한 툴이 없음.
      • 자동 동기화도 되고, 옵션도 디테일하고, 메뉴도 직관적인 편.
      • 환경 변수 기능, 파라미터, 바디, 쿠키 편집 등 안되는 기능이 없음.
      • 콜렉션 & 폴더 기능으로 테스트 셋 관리에도 유연함.
      • 다들 잘 모르는 기능이지만 환경 변수 기능 까지 더하면, 관리 이슈를 매우 낮출 수 있다.

Terminal

  • WinSSHTerm
    • https://winsshterm.blogspot.com/
      • windows 기반의 무료 SSH 툴 중 최고봉
      • 은근히 SSH 툴들이 비싸서…조금 싸면 직접 사서 쓰려고 했는데, 무료 툴 중에서 고르게 됨.
      • putty, pageant, VcXsrv, WinSCP 등의 툴을 별도로 다운받아야 하는게 번거로움.
      • ssh 설정만 해두면 같은 설정으로 scp와 연동이 되어있고, 클러스터 모드로 동일한 작업을 쉽게 처리할 수 있는 장점이 있다.
  • SmartTTY
    • https://sysprogs.com/SmarTTY/
      • Auto Completion 기능이 있어 아주 편하게 사용 가능.
      • SCP 파일 업로드/다운로드도 지원하고, 현재 폴더의 파일 목록도 편하게 보여주는 장점이 있다.

크롬 확장 프로그램 추천

테마

생산성

웹 개발

안드로이드 앱 추천

멀티미디어

생산성

정보