분산 컨테이너 환경에서의 디자인 패턴 (1)

구글 클라우드 팀이 Kubernetes와 같은 Container Orchestration 기술을 개발하면서 겪은 분산 컨테이너 환경에서의 디자인 패턴에 대해 정리한 내용입니다. 분산 어플리케이션을 컨테이너 환경으로 옮기려는 분들에게 많은 도움이 될 듯 합니다.

Modular Container Design

최근 많은 개발 환경이 Docker 기반의 컨테이너 환경으로 옮겨가고 있습니다. 그 중에서는 복잡한 의존성에서 벗어나 독립적으로 운영하고 싶어서 옮기는 경우가 많습니다.

하지만 분산 컨테이너 환경은 아주 복잡하게 연결되어 있어서 운영하기 힘듭니다. 복잡한 연결을 쉽게 이해하려면 이 문제가 어디에 해당하는지 경계를 잘 정의하는 것이 중요합니다. 이렇게 경계를 정의하고 분류하는 것을 모듈화라고 부릅니다.

우리는 과거부터 효율적인 코드를 작성하기 위해 절차지향 프로그래밍에서 객체지향 프로그래밍으로 변화해왔습니다. 객체지향 프로그래밍의 클래스는 경계를 정의한다는 측면에서 컨테이너 환경과 유사한 면이 있습니다.

modular

과거 모놀리틱 아키텍쳐에서는 왼쪽 그림과 같은 구조로 설계되어 왔습니다. 우리가 기여하거나 자주 수정하는 코드가 있는 반면, 코어 모듈이나 공통에 해당하는 부분은 잘 변하지 않습니다.

컨테이너 환경은 이와 조금 다릅니다. 우선 컨테이너 환경에서 컨테이너는 하나의 어플리케이션이 아니라는 점을 이해하고 있어야 합니다. 하나의 컨테이너는 객체지향 언어의 클래스 또는 함수와 유사합니다. 오른쪽 그림처럼 작은 모듈 조각을 모으고 조립해서 다음 어플리케이션을 설계하는 형태가 되어야 합니다.

Benefit

위와 같은 컨테이너 환경을 구성했을 때 가지는 장점은 아래와 같습니다.

  • 이미 만들어 놓은 컨테이너를 재사용할 수 있고 사용하기 쉬움
  • 컨테이너 경계에 따라 팀의 역할을 분리시킬 수 있음
  • 분리되어 있기 때문에 각 모듈에 대해 더 깊게 이해할 수 있음
  • 각 모듈에 대해 작은 수정사항을 빠르게 업데이트할 수 있음
  • 흔히 얘기하는 관심사의 분리를 만족시킬 수 있음 (Seperate concerns)

Requirements

위와 같은 컨테이너 환경을 구성하기 위해 필요한 요소는 아래와 같습니다.

  • 공통된 네임스페이스 (PID, IPC, 네트워크 등), 마치 localhost 처럼 사용할 수 있도록 하나의 컨테이너가 다른 컨테이너의 프로세스를 컨트롤 할 수 있어야 합니다.
  • 공유할 수 있는 파일 시스템, Kubernetes의 PV, PVC에 내한 내용을 떠올리면 이해하기 쉽습니다.
  • 어플리케이션을 구성할 때 노드의 분리도 고려되어야 합니다. 예시로 WordPress-MySQL 어플리케이션을 배포한다고 가정했을 때, WordPress가 배포된 노드와 MySQL 노드의 위치가 달라야 합니다. 그리고 WordPress, MySQL에 각각에 대한 Scale-out도 고려되어야 합니다.
  • 컨테이너가 런타임 시점에 파라메터로 설정 값을 받을 수 있도록 설계되어야 합니다. 그리고 각 이미지에 대한 문서화도 필요합니다. 특히 여러 컨테이너에서 공통으로 사용하는 라이브러리의 경우 필요

Sidecar Pattern

이제부터 자주 사용되는 세 가지 디자인 패턴을 소개드리려고 합니다. 먼저 첫 번째는 사이드 카 패턴입니다. 사이드 카 패턴은 이전에 사용되던 컨테이너의 기능을 확장시키고 싶을 때 유용하게 사용됩니다. 여기서 이전에 사용되던 컨테이너란 잘 변하지 않으며 같은 작업을 반복하는 어플리케이션을 말합니다.

위 그림의 예시에서 이전에 사용되던 컨테이너는 왼쪽의 node.js 어플리케이션 입니다. node.js 어플리케이션은 단순히 파일 시스템에 접근하여 어떤 작업을 수행하는 일만 합니다. 만일 파일 시스템에 대해 git 동기화 기능을 추가하고 싶다면, 오른쪽 컨테이너처럼 확장시킬 수 있습니다. node.js 어플리케이션은 사이드 카 컨테이너가 어떤 작업을 수행하는지 고려할 필요가 없습니다. 사이드 카 컨테이너 역시 어떤 어플리케이션이 이 파일을 서빙하는지 고려할 필요가 없습니다. 앞서 말한 것처럼 관심사의 분리를 만족시키며, 컨테이너를 관리하는 팀을 분리시킬 수 있습니다. 또한 사이드 카 컨테이너를 다른 어플리케이션에서 재사용할 수 있고, 더 다양한 기능으로 확장시킬 수 있습니다.

Ambassador Pattern

다음은 엠베서더 패턴입니다. 엠베서더 패턴은 어플리케이션을 대신하여 외부의 네트워크 또는 요청을 처리해야할 때 유용하게 사용됩니다.

위 그림의 예시에서 어플리케이션은 PHP 앱이고 Memcache를 사용한 지속적인 해싱이 필요하다고 가정해보겠습니다. 그리고 Memcache 사용을 위해 twemproxy라는 라이브러리를 가져와야 합니다. 위 그림처럼 어플리케이션과 twemproxy 컨테이너를 분리시킨다면, twemproxy 컨테이너는 외부에 있는 Memcache 샤드를 관리하고 통신하는 역할을 수행할 수 있습니다. 기존에 있던 어플리케이션 컨테이너는 twemproxy 컨테이너와 같은 네임스페이스에 존재하지만, 외부의 통신에 대해서는 관여할 필요가 없습니다. 역시 마찬가지로 관심사의 분리를 만족시키며, 재사용될 수 있고, 다양한 기능으로 확장시킬 수 있습니다.

Adapter Pattern

다음은 어댑터 패턴입니다. 어댑터 패턴은 추상화된 레이어가 필요할 때 유용하게 사용됩니다.

대표적인 예시로 위 그림과 같은 모니터링 에이전트가 있습니다. 일반적으로 Redis, Memcache, MySQL 등 다양한 컴포넌트에 모니터링이 필요합니다. 모놀리틱 구조로 설계되었다면, 새로운 컴포넌트에 대한 지원이 필요할때마다 변경이 필요합니다. 하지만 분리되어 있는 공통 인터페이스가 존재한다면, 새로운 컴포넌트를 쉽게 추가할 수 있습니다. 특히 오픈소스 진영에서는 많은 사람들이 다양한 컴포넌트를 사용하기 때문에 많이 활용됩니다.

많이 사용하는 prometheus exporter도 이와 같은 패턴으로 설계되어 있습니다. exporter는 모니터링 시스템과 쉽게 결합할 수 있습니다. 그리고 exporter와 memcache, redis는 결합해서 하나의 컨테이너로 배포하는 형태입니다. redis-exporter에 코드 변경이 이루어지더라도 memcache-exporter는 변경될 필요가 없습니다.

Reference

이외에도 Replication, Micro service Load Balancer에 사용되는 다양한 패턴이 존재합니다. 분산 컨테이너 환경에서 어플리케이션을 개발하는 일은 레고 블럭을 조립하는 것과 비슷합니다. 더 자세한 내용이 궁금하신 분은 아래 링크를 확인하시면 됩니다.