마이크로서비스 아키텍처(MSA)의 서비스 검색
이 포스트는 마이크로서비스로 애플리케이션을 구축하는 것에 대한 시리즈의 네 번째 포스트입니다. 첫 번째 포스트에서는 마이크로서비스 아키텍처(MSA) 패턴을 소개하고 마이크로서비스 사용의 장점과 단점에 대해 논의했습니다. 시리즈의 두 번째 및 세 번째 포스트에서는 마이크로서비스 아키텍처 내 통신의 다양한 측면을 설명합니다. 이 블로그에서 우리는 밀접하게 관련된 서비스 발견 문제를 탐구합니다.
목차
1. 서비스 검색을 사용하는 이유
1-1. 클라이언트 측 검색 패턴 (The Client-Side Discovery Pattern)
1-2. 서버 측 검색 패턴 (The Server‑Side Discovery Pattern)
2. 서비스 레지스트리 (The Service Registry)
3. 서비스 등록 옵션 (Service Registration Options)
3-1. 자체 등록 패턴 (The Self‑Registration Pattern)
3-2. 타사 등록 패턴 (The Third‑Party Registration Pattern)
4. 요약
1. 서비스 검색을 사용하는 이유
REST API 또는 Thrift API가 있는 서비스를 호출하는 코드를 작성한다고 가정해 보겠습니다. 요청을 하려면 코드가 서비스 인스턴스의 네트워크 위치(IP 주소 및 포트)를 알아야 합니다. 물리적 하드웨어에서 실행되는 기존 애플리케이션에서 서비스 인스턴스의 네트워크 위치는 상대적으로 정적입니다. 예를 들어, 코드는 가끔 업데이트되는 구성 파일에서 네트워크 위치를 읽을 수 있습니다.
그러나 최신 클라우드 기반 마이크로서비스 애플리케이션에서는 다음 다이어그램과 같이 해결하기가 훨씬 더 어려운 문제입니다.

서비스 인스턴스에는 동적으로 할당된 네트워크 위치가 있습니다. 또한 서비스 인스턴스 집합은 자동 크기 조정, 실패 및 업그레이드로 인해 동적으로 변경됩니다. 결과적으로 클라이언트 코드는 보다 정교한 서비스 검색 메커니즘을 사용해야 합니다.
클라이언트 측 탐색(client-side discovery)과 서버 측 탐색(server-side discovery)의 두 가지 주요 서비스 탐색 패턴이 있습니다. 먼저 클라이언트 측 검색을 살펴보겠습니다.
1-1. 클라이언트 측 검색 패턴 (The Client-Side Discovery Pattern)
클라이언트 측 검색(client-side discovery)을 사용할 때 클라이언트는 사용 가능한 서비스 인스턴스의 네트워크 위치를 결정하고 요청을 로드 밸런싱해야 합니다. 클라이언트는 사용 가능한 서비스 인스턴스의 데이터베이스인 서비스 레지스트리를 쿼리합니다. 그런 다음 클라이언트는 로드 밸런싱 알고리즘을 사용하여 사용 가능한 서비스 인스턴스 중 하나를 선택하고 요청합니다.
다음 다이어그램은 이 패턴의 구조를 보여줍니다.

서비스 인스턴스의 네트워크 위치는 시작될 때 서비스 레지스트리에 등록됩니다. 인스턴스가 종료되면 서비스 레지스트리에서 제거됩니다. 서비스 인스턴스의 등록은 일반적으로 하트비트 메커니즘을 사용하여 주기적으로 새로 고쳐집니다.
Netflix OSS는 클라이언트 측 검색 패턴의 좋은 예를 제공합니다. Netflix Eureka는 서비스 레지스트리입니다. 서비스 인스턴스 등록을 관리하고 사용 가능한 인스턴스를 쿼리하기 위한 REST API를 제공합니다. Netflix Ribbon은 Eureka와 함께 사용 가능한 서비스 인스턴스 간에 요청을 로드 밸런싱하는 IPC 클라이언트입니다. 이 포스트의 뒷부분에서 유레카에 대해 더 자세히 논의할 것입니다.
클라이언트 측 검색 패턴에는 다양한 장점과 단점이 있습니다. 이 패턴은 비교적 간단하며 서비스 레지스트리를 제외하고 다른 움직이는 부분이 없습니다. 또한 클라이언트는 사용 가능한 서비스 인스턴스에 대해 알고 있기 때문에 일관되게 해싱을 사용하는 것과 같은 지능적인 애플리케이션별 로드 밸런싱 결정을 내릴 수 있습니다. 이 패턴의 한 가지 중요한 단점은 클라이언트를 서비스 레지스트리와 연결한다는 것입니다. 서비스 클라이언트에서 사용하는 각 프로그래밍 언어 및 프레임워크에 대해 클라이언트 측 서비스 검색 논리를 구현해야 합니다.
클라이언트 측 검색을 살펴보았으므로 이제 서버 측 검색을 살펴보겠습니다.
1-2. 서버 측 검색 패턴 (The Server‑Side Discovery Pattern)
서비스 검색에 대한 다른 접근 방식은 서버 측 검색 패턴(server-side discovery pattern)입니다. 다음 다이어그램은 이 패턴의 구조를 보여줍니다.

클라이언트는 로드 밸런서를 통해 서비스에 요청합니다. 로드 밸런서는 서비스 레지스트리를 쿼리하고 각 요청을 사용 가능한 서비스 인스턴스로 라우팅합니다. 클라이언트 측 검색과 마찬가지로 서비스 인스턴스는 서비스 레지스트리에 등록 및 등록 취소됩니다.
AWS Elastic Load Balancer(ELB)는 서버 측 검색 라우터의 예입니다. ELB는 일반적으로 인터넷에서 외부 트래픽을 로드 밸런싱하는 데 사용됩니다. 그러나 ELB를 사용하여 가상 사설 클라우드(VPC) 내부에 있는 트래픽을 로드 밸런싱할 수도 있습니다. 클라이언트는 DNS 이름을 사용하여 ELB를 통해 요청(HTTP 또는 TCP)을 만듭니다. ELB는 등록된 EC2(Elastic Compute Cloud) 인스턴스 또는 ECS(EC2 Container Service) 컨테이너 세트 간에 트래픽 로드 밸런싱을 수행합니다. 별도의 서비스 레지스트리가 없습니다. 대신 EC2 인스턴스와 ECS 컨테이너가 ELB 자체에 등록됩니다.
HTTP 서버 및 NGINX Plus 및 NGINX와 같은 로드 밸런서는 서버 측 검색 로드 밸런서로도 사용할 수 있습니다. 예를 들어 이 블로그 포스트는 Consul 템플릿을 사용하여 NGINX 리버스 프록시를 동적으로 재구성하는 방법을 설명합니다. Consul 템플릿은 Consul 서비스 레지스트리에 저장된 구성 데이터에서 임의의 구성 파일을 주기적으로 재생성하는 도구입니다. 파일이 변경될 때마다 임의의 쉘 명령을 실행합니다. 블로그 포스트에 설명된 예에서 Consul Template은 리버스 프록시를 구성하는 nginx.conf 파일을 생성한 다음 NGINX에 구성을 다시 로드하도록 지시하는 명령을 실행합니다. 보다 정교한 구현은 HTTP API 또는 DNS를 사용하여 NGINX Plus를 동적으로 재구성할 수 있습니다.
Kubernetes 및 Marathon과 같은 일부 배포 환경은 클러스터의 각 호스트에서 프록시를 실행합니다. 프록시는 서버 측 검색 로드 밸런서의 역할을 합니다. 서비스에 요청하기 위해 클라이언트는 호스트의 IP 주소와 서비스의 할당된 포트를 사용하여 프록시를 통해 요청을 라우팅합니다. 그런 다음 프록시는 클러스터의 어딘가에서 실행 중인 사용 가능한 서비스 인스턴스에 요청을 투명하게 전달합니다.
서버 측 검색 패턴에는 몇 가지 장점과 단점이 있습니다. 이 패턴의 한 가지 큰 이점은 발견의 세부 사항이 클라이언트에서 추상화된다는 것입니다. 클라이언트는 로드 밸런서에 요청하기만 하면 됩니다. 따라서 서비스 클라이언트에서 사용하는 각 프로그래밍 언어 및 프레임워크에 대한 검색 논리를 구현할 필요가 없습니다. 또한 위에서 언급한 것처럼 일부 배포 환경에서는 이 기능을 무료로 제공합니다. 그러나 이 패턴에도 몇 가지 단점이 있습니다. 배포 환경에서 로드 밸런서를 제공하지 않는 한 설정하고 관리해야 하는 또 다른 고가용성 시스템 구성 요소입니다.
2. 서비스 레지스트리 (The Service Registry)
서비스 레지스트리는 서비스 검색의 핵심 부분입니다. 서비스 인스턴스의 네트워크 위치를 포함하는 데이터베이스입니다. 서비스 레지스트리는 가용성이 높고 최신 상태여야 합니다. 클라이언트는 서비스 레지스트리에서 얻은 네트워크 위치를 캐시할 수 있습니다. 그러나 해당 정보는 결국 오래되고 클라이언트는 서비스 인스턴스를 검색할 수 없게 됩니다. 결과적으로 서비스 레지스트리는 일관성을 유지하기 위해 복제 프로토콜을 사용하는 서버 클러스터로 구성됩니다.
앞서 언급했듯이 Netflix Eureka는 서비스 레지스트리의 좋은 예입니다. 서비스 인스턴스를 등록하고 쿼리하기 위한 REST API를 제공합니다. 서비스 인스턴스는 POST 요청을 사용하여 네트워크 위치를 등록합니다. 30초마다 PUT 요청을 사용하여 등록을 새로 고쳐야 합니다. HTTP DELETE 요청을 사용하거나 인스턴스 등록 시간이 초과되면 등록이 제거됩니다. 예상대로 클라이언트는 HTTP GET 요청을 사용하여 등록된 서비스 인스턴스를 검색할 수 있습니다.
Netflix는 각 Amazon EC2 가용 영역에서 하나 이상의 Eureka 서버를 실행하여 고가용성을 달성합니다. 각 Eureka 서버는 탄력적 IP 주소가 있는 EC2 인스턴스에서 실행됩니다. DNS TEXT 레코드는 Eureka 클러스터 구성을 저장하는 데 사용됩니다. Eureka 클러스터 구성은 가용성 영역에서 Eureka 서버의 네트워크 위치 목록에 대한 맵입니다. Eureka 서버가 시작되면 DNS를 쿼리하여 Eureka 클러스터 구성을 검색하고 해당 피어를 찾고 사용되지 않은 탄력적 IP 주소를 자체 할당합니다.
Eureka 클라이언트(서비스 및 서비스 클라이언트)는 DNS를 쿼리하여 Eureka 서버의 네트워크 위치를 검색합니다. 클라이언트는 동일한 가용성 영역에서 Eureka 서버를 사용하는 것을 선호합니다. 그러나 사용할 수 없는 경우 클라이언트는 다른 가용 영역에 있는 Eureka 서버를 사용합니다.
서비스 레지스트리의 다른 예는 다음과 같습니다.
- etcd – 공유 구성 및 서비스 검색에 사용되는 고가용성, 분산, 일관된 key-value 저장소(store)입니다. etcd를 사용하는 두 가지 주목할만한 프로젝트는 Kubernetes와 Cloud Foundry입니다.
- consul – 서비스 검색 및 구성을 위한 도구입니다. 클라이언트가 서비스를 등록하고 검색할 수 있는 API를 제공합니다. Consul는 서비스 가용성을 결정하기 위해 상태 확인을 수행할 수 있습니다.
- Apache Zookeeper – 분산 애플리케이션에 널리 사용되는 고성능 조정 서비스입니다. Apache Zookeeper는 원래 Hadoop의 하위 프로젝트였으나 현재는 최상위 프로젝트입니다.
또한 이전에 언급했듯이 Kubernetes, Marathon 및 AWS와 같은 일부 시스템에는 명시적 서비스 레지스트리가 없습니다. 대신 서비스 레지스트리는 인프라의 기본 제공 부분일 뿐입니다.
이제 서비스 레지스트리의 개념을 살펴보았으므로 서비스 인스턴스가 서비스 레지스트리에 등록되는 방법을 살펴보겠습니다.
3. 서비스 등록 옵션 (Service Registration Options)
앞서 언급했듯이 서비스 인스턴스는 서비스 레지스트리에 등록 및 등록 취소되어야 합니다. 등록 및 등록 취소를 처리하는 몇 가지 다른 방법이 있습니다. 한 가지 옵션은 서비스 인스턴스가 자체 등록 패턴(self‑registration pattern)인 자체 등록을 하는 것입니다. 다른 옵션은 타사 등록 패턴(third‑party registration pattern)인 서비스 인스턴스의 등록을 관리하는 일부 다른 시스템 구성 요소를 위한 것입니다. 먼저 자체 등록 패턴을 살펴보겠습니다.
3-1. 자체 등록 패턴 (The Self‑Registration Pattern)
자체 등록 패턴(Self‑Registration Patter)을 사용할 때 서비스 인스턴스는 서비스 레지스트리에 자체 등록 및 등록 취소를 담당합니다. 또한 필요한 경우 서비스 인스턴스는 등록 만료를 방지하기 위해 하트비트(heartbeat) 요청을 보냅니다. 다음 다이어그램은 이 패턴의 구조를 보여줍니다.

이 접근 방식의 좋은 예는 Netflix OSS Eureka 클라이언트입니다. Eureka 클라이언트는 서비스 인스턴스 등록 및 등록 취소의 모든 측면을 처리합니다. 서비스 디스커버리를 비롯한 다양한 패턴을 구현하는 Spring Cloud 프로젝트는 서비스 인스턴스를 Eureka에 자동으로 등록하는 것을 용이하게 합니다. @EnableEurekaClient 주석으로 Java 구성 클래스에 주석을 추가하기만 하면 됩니다.
자체 등록 패턴에는 다양한 장점과 단점이 있습니다. 한 가지 이점은 비교적 간단하고 다른 시스템 구성 요소가 필요하지 않다는 것입니다. 그러나 주요 단점은 서비스 인스턴스를 서비스 레지스트리에 연결한다는 것입니다. 서비스에서 사용하는 각 프로그래밍 언어 및 프레임워크에서 등록 코드를 구현해야 합니다.
서비스 레지스트리에서 서비스를 분리하는 대체 접근 방식은 타사 등록 패턴입니다.
3-2. 타사 등록 패턴 (The Third‑Party Registration Pattern)
타사 등록 패턴(Third‑Party Registration Pattern)을 사용할 때 서비스 인스턴스는 서비스 레지스트리에 자신을 등록할 책임이 없습니다. 대신 서비스 등록 대행자로 알려진 다른 시스템 구성 요소가 등록을 처리합니다. 서비스 등록자는 배포 환경을 폴링하거나 이벤트를 구독하여 실행 중인 인스턴스 집합에 대한 변경 사항을 추적합니다. 새로 사용 가능한 서비스 인스턴스를 발견하면 서비스 레지스트리에 인스턴스를 등록합니다. 서비스 등록 기관은 종료된 서비스 인스턴스도 등록 취소합니다. 다음 다이어그램은 이 패턴의 구조를 보여줍니다.

서비스 레지스트라의 한 예는 오픈소스 레지스트라 프로젝트입니다. Docker 컨테이너로 배포된 서비스 인스턴스를 자동으로 등록 및 등록 취소합니다. 등록자는 etcd 및 Consul을 포함한 여러 서비스 레지스트리를 지원합니다.
서비스 레지스트라의 또 다른 예는 NetflixOSS Prana입니다. 비JVM 언어로 작성된 서비스를 위해 주로 고안되었으며 서비스 인스턴스와 나란히 실행되는 사이드카 애플리케이션입니다. Prana는 Netflix Eureka에 서비스 인스턴스를 등록 및 등록 취소합니다.
서비스 등록자는 배포 환경의 기본 제공 구성 요소입니다. Autoscaling Group에서 생성한 EC2 인스턴스는 자동으로 ELB에 등록할 수 있습니다. Kubernetes 서비스는 자동으로 등록되고 검색에 사용할 수 있습니다.
타사 등록 패턴에는 다양한 장점과 단점이 있습니다. 주요 이점은 서비스가 서비스 레지스트리에서 분리된다는 것입니다. 개발자가 사용하는 각 프로그래밍 언어 및 프레임워크에 대해 서비스 등록 논리를 구현할 필요가 없습니다. 대신 서비스 인스턴스 등록은 전용 서비스 내에서 중앙 집중식으로 처리됩니다.
이 패턴의 한 가지 단점은 배포 환경에 구축되지 않는 한 설정하고 관리해야 하는 또 다른 고가용성 시스템 구성 요소라는 것입니다.
4. 요약
마이크로서비스 애플리케이션에서 실행 중인 서비스 인스턴스 세트는 동적으로 변경됩니다. 인스턴스에는 동적으로 할당된 네트워크 위치가 있습니다. 결과적으로 클라이언트가 서비스를 요청하려면 서비스 검색 메커니즘을 사용해야 합니다.
서비스 검색의 핵심 부분은 서비스 레지스트리입니다. 서비스 레지스트리는 사용 가능한 서비스 인스턴스의 데이터베이스입니다. 서비스 레지스트리는 관리 API와 쿼리 API를 제공합니다. 서비스 인스턴스는 관리 API를 사용하여 서비스 레지스트리에 등록 및 등록 취소됩니다. 쿼리 API는 시스템 구성 요소에서 사용 가능한 서비스 인스턴스를 검색하는 데 사용됩니다.
두 가지 주요 서비스 검색 패턴이 있습니다. 클라이언트 쪽 검색과 서비스 쪽 검색입니다. 클라이언트 측 서비스 검색을 사용하는 시스템에서 클라이언트는 서비스 레지스트리를 쿼리하고 사용 가능한 인스턴스를 선택하고 요청합니다. 서버 측 검색을 사용하는 시스템에서 클라이언트는 서비스 레지스트리를 쿼리하고 사용 가능한 인스턴스에 요청을 전달하는 라우터를 통해 요청합니다.
서비스 인스턴스가 서비스 레지스트리에 등록 및 등록 취소되는 두 가지 주요 방법이 있습니다. 한 가지 옵션은 서비스 인스턴스가 자체 등록 패턴인 서비스 레지스트리에 자신을 등록하는 것입니다. 다른 옵션은 타사 등록 패턴인 서비스를 대신하여 일부 다른 시스템 구성 요소가 등록 및 등록 취소를 처리하는 것입니다.
일부 배포 환경에서는 Netflix Eureka 등의 서비스 레지스트리 또는 Apache Zookeeper를 사용하여 자체 서비스 검색 인프라를 설정해야 합니다. 다른 배포 환경에서는 서비스 검색이 기본 제공됩니다. 예를 들어 Kubernetes 및 Marathon은 서비스 인스턴스 등록 및 등록 취소를 처리합니다. 또한 서버 측 검색 라우터 역할을 하는 각 클러스터 호스트에서 프록시를 실행합니다.
HTTP 리버스 프록시 및 NGINX와 같은 로드 밸런서는 서버 측 검색 로드 밸런서로도 사용할 수 있습니다. 서비스 레지스트리는 라우팅 정보를 NGINX로 푸시하고 정상적인 구성 업데이트를 호출할 수 있습니다. 예를 들어 Consul 템플릿을 사용할 수 있습니다. NGINX Plus는 추가 동적 재구성 메커니즘을 지원합니다. DNS를 사용하여 레지스트리에서 서비스 인스턴스에 대한 정보를 가져올 수 있으며 원격 재구성을 위한 API를 제공합니다.
향후 블로그 포스트에서 우리는 계속해서 마이크로서비스의 다른 측면에 대해 알아볼 것입니다.
댓글을 달려면 로그인해야 합니다.