🖥️ CS/Architecture

[마이크로서비스 아키텍처 구축] 5. MSA 마이크로서비스 통신 구현 (2)

한국의 메타몽 2024. 10. 28. 00:29

book

해당 글은 마이크로서비스 아키텍처 구축에서 학습한 내용을 다룹니다.



목차

  1. MSA에서 DRY와 코드 재사용의 위험
  2. 서비스 디스커버리
    1. DNS
    2. 동적 서비스 레지스트리


1. MSA에서 DRY와 코드 재사용의 위험


개발자라면 DRY(Don't Repeat Yourself), 즉, 반복하지 말라는 약어를 종종 듣게된다.
동일한 작업을 수행하는 코드 라인이 많으면 코드 베이스가 필요 이상으로 커져 파악하기 어려워지며,
동작을 변경하려고 할 때 해당 동작이 시스템의 많은 곳에 복제되어있다면 변경할때 누락되기 쉽다.


때문에 DRY는 재사용할 수 있는 코드를 만들도록 유도한다.
중복된 코드를 모아 추상화한 다음, 여러 곳에서 호출할 수 있으며, 아마도 어디에서나 사용할 수 있는 공유 라이브러리를 만드는 단계까지 갈 수도 있다.



라이브러리를 통한 코드 공유


MSA 서비스와 소비자를 지나치게 결합하는 것은 어떤 대가를 치르더라도 피하고 싶은 상황이다.
특히 공유 코드가 서비스 경계 외부, 즉, 소비자와 닿는 연결점이 존재한다면 말이다.


로깅 라이브러리와 같은 공통 코드를 사용하는 것은 외부 세계에 보이지 않는 내부 개념이므로 사용해도 괜찮다.
그러나 소비자와 관련이 있는 코드를 공유하게 될 경우, 아래 문제점을 잊어서는 안된다.


  • 라이브러리는 한 번에 업데이트 할 수 없음
    • 업데이트되는 라이브러리와 해당 라이브러리를 사용하는 MSA 서비스를 모두 다같이 동시에 배포하는 것은 골치아픈일

때문에 MSA 서비스 경계를 넘어 코드 재사용성을 위해 라이브러리를 사용하는 경우, 동일한 라이브러리의 다른 여러 버전이 동시에 존재할 수 있다는 사실을 받아들여야 한다. 물론 시간이 지남에 따라 마지막 버전으로 모두 업데이트 할 수도 있지만, 이 사실에 동의한다면 라이브러리를 통해 코드를 재사용해라.


넷플릭스의 경우엔 클라이언트 라이브러리(ex : Spring Cloud OpenFeign)를 강조하지만, 넷플릭스의 클라이언트 라이브러리는 서비스 디스커버리, 실패 모드, 로깅과 더불어 실제로 서비스 자체의 특성과는 무관한 다른 측면을 처리한다.



2. 서비스 디스커버리


MSA 서비스를 몇 개 이상 갖고 있다면 모든 것이 도대체 어디있는지 파악하는게 관심을 기울일 수 밖에 없다.
어쩌면 모니터링돼야 하는 것을 알기 위해 해당 환경에서 무엇이 실행되고 있는지 알고 싶을 수 있다.
대체로 이러한 사용 예는 모두 서비스 디스커버리 (Service Discovery) 라는 분야에 속한다.


2-1 DNS


하나의 노드만 있는 상황에서는 DNS가 호스트를 직접 참조하는 것이 좋다. 하지만 둘 이상의 호스트 인스턴스가 필요한 상황에서는 DNS 엔트리가 개별 호스트의 서비스 시작 및 중단을 적절히 처리할 수 있는 로드밸런서로 확인되도록 해야만 한다.



2-2. 동적 서비스 레지스트리


동적 환경에서 노드를 찾는 방법으로서 2-1에서 언급한 DNS는 여러모로 약점이 존재하기 마련이다. (로드 밸런서만 생각해도 그렇다.)
때문에 여러 대체 시스템이 등장했다.



주키퍼 (Zookeeper)


주키퍼는 하둡(Hadoop) 프로젝트의 일부분으로 개발되었으며, 구성(configuration) 관리, 서비스 간 데이터 동기화, 메세지 큐 등 여러 많은 사례에 사용되고 있다.


클러스터에서 여러 노드를 실행하며, 이 말은 즉슨 최소한 3개의 주키퍼 노드가 실행될 것을 예상해야 한다는 의미이다.
주키퍼의 주요 장점은 이러한 노드 간에 데이터가 안전하게 복제되고 노드가 실패할 때 일관성이 유지되도록 하는 데 있다.


근본적으로 주키퍼는 정보를 저장하기 위한 계층적 네임스페이스를 제공한다.
클라이언트는 이 계층에 새 노드를 삽입하거나 변경, 또는 쿼리를 한다.
또한 노드가 변경될 때 알려줄 감시(watch) 기능도 추가할 수 있다. 다시 말해 이 구조에서 서비스가 있는 위치에 대한 정보를 저장할 수 있고 서비스가 변경될 때 클라이언트에 알려주는 정보를 저장할 수 있다는 의미다.


주키퍼는 범용 구성 저장소로 사용하는 경우가 많으므로, 서비스별 구성 정보를 저장할 수도 있다.
이를 통해 로그 수준을 동적으로 변경하거나 시스템의 기능을 끄는 것과 같은 작업을 수행할 수 있다.


그러나 주키퍼 보다는 더 많은 기능들이 존재하므로, 필자는 서비스 디스커버리를 위해 주키퍼를 사용하진 않는다.



콘술 Consul


콘술은 서비스 디스커버리를 위한 HTTP 인터페이스를 노출한다.
콘술의 서비스 등록에서 키 / 값 저장소 쿼리나 상태 확인 (Health Check) 추가에 이르기까지 모든 작업에 REST 기반 HTTP를 사용한다.
이로 인해 다양한 기술 스택과의 통합이 매우 간단해진다.


콘술 항목을 기반으로 텍스트 파일을 업데이트하는 방법을 제공하는 콘술 템플릿이 존재하는데,
이를 통해 구성 정보를 읽는 어떤 프로그램도 콘술 자체에 대해 알 필요 없이 텍스트 파일을 동적으로 업데이트 할 수 있다.
이에 대한 좋은 사용 사례는 HAProxy와 같은 로드 밸런서를 사용해 로드 밸런서 풀에 노드를 동적으로 추가하거나 제거하는 것이다.



etcd와 쿠버네티스


쿠버네티스와 함께 제공되는 구성 관리 저장소인 etcd에서 서비스 디스커버리의 일부 기능이 제공된다.
etcd나 콘술이나 서로 유사한 기능을 갖고 있으며, 쿠버네티스는 이를 사용해 다양한 구성 정보를 관리한다.


쿠버네티스에서 서비스 디스커버리가 작동하는 방식은 포드에 컨테이너를 배포한 후, 포드와 관련된 메타데이터에 대한 패턴 매칭을 통해 어떤 포드가 서비스에 포함되는지를 동적으로 식별하는 것이다. 그런 다음 서비스에 대한 요청이 해당 서비스를 구성하는 포드 중 하나로 라우팅된다.


사실 쿠버네티스가 자체적으로 기본 기능을 제공하므로, 콘술을 둘러싼 광범위한 도구 생태계에 관심이 없다면 쿠버네티스만으로도 충분할 수 있다.