The Service Registry
(서비스 레지스트리)

서비스 레지스트리는 서비스 검색의 핵심적인 부분이다. 서비스 레지스트리는 서비스 인스턴스의 네트워크 상 위치를 포함하고 있는 데이터베이스이다. 서비스 레지스트리는 고가용성이어야 하고, 최신 상태가 되어 있어야 한다. 클라이언트는 서비스 레지스트리를 통해서 얻은 네트워크 위치를 캐시할 수 있다. 그러나 캐시한 정보는 점차적으로 오래된 데이터가 되고, 클라이언트는 서비스 인스턴스를 검색할 수 없게 된다. 결과적으로, 서비스 레지스트리는 일관성을 유지하기 위해서 복제 프로토콜을 사용하는 서버 클러스터로 이루어진다.

앞에서 언급했듯이, Netflix Eureka는 서비스 레지스트리의 좋은 예이다. 서비스 인스턴스를 등록하고 질의하기 위한 REST API를 제공한다. 서비스 인스턴스는 POST 요청을 사용하여 네트워크 상의 위치를 등록한다. 매 30초마다 PUT 요청을 사용하여 등록 정보를 새로 고쳐야 한다. 등록 정보는 HTTP DELETE 요청을 사용하여 삭제되거나, 등록 시간 초과에 의해 제거된다. 예상한 것처럼, 클라이언트는 HTTP GET 요청을 통해서 등록된 서비스 인스턴스를 조회할 수 있다.

Netflix는 각 Amazon EC2 가용성 존에서 하나 이상의 Eureka서버를 실행하여 고가용성을 실현한다. 각 Eureka 서버는 탄력적인 IP 주소를 갖는 EC2 인스턴스에서 실행된다. DNS TEXT 레코드는 Eureka Cluster 설정을 저장하는데 사용되고, Eureka Cluster 설정은 가용성 존에서부터 Eureka 서버의 네트워크 상 위치 목록까지의 맵이다. Eureka 서버가 시작되면, Eureka Cluster 설정을 얻기 위해서 DNS에 질의하고, 피어의 위치를 찾은 후 사용되지 않은 Elastic IP 주소를 할당한다.

Eureka 클라이언트 - 서비스와 서비스 클라이언트 - 는 Eureka 서버의 네트워크상 위치를 찾기 위해서 DNS에 질의한다. 클라이언트는 동일한 가용성 존에 있는 Eureka 서버를 사용하는 것을 선호한다. 그러나 아무것도 사용할 수 없는 경우에는 또다른 가용성 존에 있는 Eureka 서버를 사용한다.

서비스 레지스트리의 다른 예는 다음과 같다.

  - etcd : 설정의 공유 및 서비스 검색에 사용되는 고 가용적이고 분산되어 있는 일관성 있는 key-value 저장소이다. etcd를 사용하고 있는 주목할만한 2가지 프로젝트는 Kubernetes와 Cloud Foundry이다.
  - consul : 서비스 검색과 설정에 사용하는 도구. 클라이언트가 서비스를 등록하고 검색할 수 있도록 API를 제공한다. consul은 서비스 가용성을 확인하기 위하여 health check를 수행할 수 있다.
  - Apache Zookeeper : 분산 어플리케이션에서 폭넓게 사용되는 고 성능의 coordination 서비스. Apache Zookeeper는 원래 Hadoop의 하위 프로젝트였으나, 지금은 Top-Level 프로젝트이다.

또한, 이전에 언급했듯이, Kubernetes와 Marathon, AWS와 같은 몇몇 시스템에는 명시적인 서비스 레지스트리를 가지고 있지는 않다. 대신에 서비스 레지스트리를 인프라의 일부분으로 내장하고 있다.

지금까지 서비스 레지스트리의 개념에 대해 살펴보았다. 이제 서비스 인스턴스가 서비스 레지스트리에 어떻게 등록되는지를 살펴 보자.
The Server-Side Discovery Pattern
(서버 측면의 검색 패턴)

서비스를 검색하는 또다른 접근법은 서버 측면의 검색 패턴이다. 다음 다이어그램은 이 패턴의 구조를 보여준다.

사용자 삽입 이미지

클라이언트는 로드 밸런서를 통해 서비스에 요청을 보낸다. 로드 밸런서는 서비스 레지스트리에 질의하고, 이용 가능한 서비스 인스턴스로 각 요청을 라우팅한다. 클라이언트 측면의 검색처럼 서비스 인스턴스들은 서비스 레지스트리에 등록하거나 등록을 취소한다.

AWS(아마존 웹 서비스)의 Elastic Load Balancer(ELB)는 서버 측면의 검색 라우터에 대한 예제이다. ELB는 일반적으로 인터넷의 외부 트래픽 부하를 분산하는데 사용된다. 그러나, 가상 사설 클라우드(VPC) 내부의 트래픽 부하를 분산하는데 ELB를 사용할 수도 있다. 클라이언트는 DNS Name을 사용하여 ELB를 통해 요청(HTTP나 TCP) 한다. ELB는 등록된 Elastic Compute Cloud(EC2) 인스턴스나 EC2 Container Service(ECS) 컨테이너 모음 사이에서 트래픽 부하를 분산시킨다. 별도의 서비스 레지스트리는 없다. 대신, EC2 인스턴스와 ECS 컨테이너들은 ELB 자체에 등록되어 있다.

NGINX Plus와 NGINX와 같은 HTTP 서버와 로드 밸런서들은 서버 측면의 검색 로드 밸런서로 사용될 수도 있다. 예를 들면, 이 블로그 포스트는 Consul Template를 사용하여 NGINX의 Reverse Proxying를 동적으로 재설정하는 부분에 대해 설명하고 있다. Consul Template는 Consul 서비스 레지스트리에 저장되어 있는 설정 데이터를 이용하여 임의의 설정 파일들을 주기적으로 재생성하는 도구이다. 설정 파일들이 변경될 때마다 임의의 쉘 명령어를 실행한다. 블로그 포스트에서 설명된 예제에서 Consul Template는 Reverse Proxying을 설정하는 nginx.conf 파일을 생성한다. 그런 후, NGINX가 설정을 다시 로드하도록 명령어를 실행한다. 보다 더 정교한 구현은 HTTP API나 DNS를 사용하여 NGINX Plus를 동적으로 재구성할 수 있다.

Kubernetes와 Marathon과 같은 일부 배포 환경에서는 클러스터 내의 각 호스트에서 Proxy를 실행한다. Proxy는 서버 측면의 검색 로드 밸런서 역할을 수행한다. 서비스에 요청하기 위하여, 클라이언트는 호스트의 IP 주소와 서비스에 할당된 포트(port)를 사용하여 Proxy를 통해 요청을 라우팅한다. 그런 다음, Proxy는 클러스터 내 어딘가에서 실행 중인 사용 가능한 서비스 인스턴스에 요청을 투명하게 전달한다.

서버 측면의 검색 패턴은 여러 가지 장단점을 가지고 있다. 이 패턴의 한가지 커다른 장점은 검색의 세부사항이 클라이언트로부터 추상화되어 있다는 것이다. 클라이언트는 단순하게 로드 밸런서에게 요청한다. 서비스 클라이언트에서 사용한 프로그래밍 언어와 프레임워크에 대한 검색 로직을 구현할 필요가 없다. 또한, 위에서 언급한 것처럼, 어떤 배포 환경에서는 이 기능을 무료로 제공한다. 그러나 이 패턴은 몇 가지 단점이 있다. 로드 밸런서가 배포 환경에 의해 제공되지 않는 한, 로드 밸런서는 설치하고 관리해야만 하는 또다른 고가용성 시스템 구성 요소이다.
The Client-Side Discovery Pattern
(클라이언트 측면의 검색 패턴)

클라이언트 측 검색을 사용할 때, 클라이언트는  사용 가능한 서비스 인스턴스의 네트워크 위치를 결정하고, 인스턴스들 사이의 요청에 대한 부하 분산을 담당한다. 클라이언트는 이용 가능한 서비스 인스턴스들의 데이터베이스인 서비스 레지스트리(서비스 등록소)를 조회한다. 그런 다음 클라이언트는 이용 가능한 서비스 인스턴스 중에 로드밸런싱 알고리즘을 사용하여 하나를 선택하고, 요청한다.

다음 다이어그램은 이 패턴의 구조를 보여준다.

사용자 삽입 이미지

서비스 인스턴스의 네트워크 상 위치는 서비스가 시작될 때, 서비스 레지스트리에 등록되고, 서비스 인스턴스가 종료될 때, 서비스 레지스트리에서 삭제된다. 일반적으로 서비스 인스턴스의 등록은 Heart-Beat 메커니즘을 사용하여 주기적으로 갱신된다.

Netflix OSS는 클라이언트 측면에서의 검색 패턴의 좋은 예제이다. Netflix Eureka는 서비스 레지스트리이다. 서비스 인스턴스 등록을 관리하고 이용 가능한 서비스 인스턴스를 조회할 수 있도록 REST API를 제공한다.  Netflix Ribbon은 이용 가능한 서비스 인스턴스들 사이에 요청을 분산하기 위해 Eureka와 함께 사용하는 IPC 클라이언트이다. 이 기사(article)의 뒷부분에 Eureka에 대해서 더 깊이 다룰 것이다.

클라이언트 측면의 검색 패턴은 다양한 장점과 단점이 있다. 이 패턴은 상대적으로 간단하고, 서비스 등록을 제외하면 다른 움직이는 부분은 없다. 또한 클라이언트는 이용 가능한 서비스 인스턴스에 대해 알고 있기 때문에, 일관되게 해싱을 사용하는 것처럼 지능적이고 어플리케이션에 특화된 부하분산 결정을 할 수 있다. 이 패턴의 한가지 중요한 단점은 클라이언트가 서비스 레지스트리와 결합되어 있다는 것이다. 서비스 클라이언트에서 사용하는 각 프로그래밍 언어와 프레임워크에 대해 클라이언트 측면의 서비스 검색 로직을 구현해야만 한다.

지금까지 클라이언트 측면의 검색에 대해서 살펴보았다. 이제 서버 측면의 검색에 대해서 살펴보자.
이것은 Microservice를 사용하여 어플리케이션을 작성하는 것에 대한 시리즈 중에서 4번째 article이다. 첫번째 article에서는 Microservice Architecture 패턴을 소개하고, microservice를 사용할 때의 이점과 단점에 대해서 논의했다. 시리즈 중에서 두번째와 세번째 article에서는 microservice architecture에서 통신의 다양한 측면에 관하여 설명했다. 이번 article에서는, 서비스 검색과 밀접하게 연관된 문제에 대해서 탐험할 것이다.

Why Use Service Discovery?
(왜 서비스 검색을 사용하는가?)

REST API나 Thrift API를 가진 서비스를 호출하는 어떤 코드를 작성한다고 상상해 보자. 요청을 하기 위해서는, 서비스 인스턴스의 네트워크 상의 위치(IP 주소와 port)를 알아야 한다. 물리적인 하드웨어에서 실행되는 전통적인 어플리케이션에서는, 서비스 인스턴스의 네트워크상 위치는 상대적으로 정적이다. 예를 들면, 때때로 업데이트되는 설정 파일에서 네트워크 위치를 읽을 수 있다.

하지만, 현대의 클라우드 기반 microservice 어플리케이션에서는 다음 다이어그램에서 보여지는 것처럼 해결할 훨씬 더 어려운 문제가 있다.

사용자 삽입 이미지

서비스 인스턴스에는 네트워크 위치가 동적으로 할당된다. 더우기, 서비스 인스턴스의 모음은 auto-scaling, 장애, 업그레이드 때문에 동적으로 변경된다. 따라서, 클라이언트 코드에는 더 정교한 서비스 검색 메커니즘이 사용되어야 한다.

서비스 검색 패턴에는 클라이언트 측면의 검색과 서버 측면의 검색, 2가지 유형이 있다. 먼저 클라이언트 측면의 검색을 살펴보자.



Summary
(요약)

Microservice는 프로세스간 통신 메커니즘을 사용해야만 한다. 서비스가 어떻게 통신할 것인지 설계할 때, 서비스들이 어떻게 상호작용하는지, 각 서비스의 API는 어떻게 정의할 것인지, API를 어떻게 진화시킬 것인지, 부분적인 오류는 어떻게 처리할 것인지에 대한 다양한 이슈를 고려해야만 한다. Microservice가 사용할 수 있는 2가지 종류의 IPC 메커니즘이 있다. 비동기식 메시지, 동기식 Request/Response 메커니즘이 그것이다. 이 시리즈의 다음 article에서는 microservice architecture에서 서비스 검색에 대한 문제를 살펴볼 것이다.