Microservices 애플리케이션 구성을 위한 모범 사례

Twelve‑Factor App으로 알려진 지침은 10년 전에 처음 발표되었습니다. 그 이후로 거의 모든 필수 관행이 웹 애플리케이션을 작성하고 배포하는 사실상의 표준 방법이 되었습니다. 그리고 애플리케이션이 구성되고 배포되는 방식이 변경되어도 여전히 적용 가능하지만 경우에 따라 애플리케이션 개발 및 배포를 위한 Microservices 패턴에 사례가 어떻게 적용되는지 이해하기 위해 추가적인 뉘앙스가 필요합니다.

이 포스트는 다음과 같은 Factor 3, 환경에 구성 저장에 초점을 맞춥니다.

  • 구성은 배포 환경(Twelve‑Factor 요소로 구성된 애플리케이션이 배포를 호출합니다)에 따라 달라집니다.
  • 구성은 애플리케이션의 코드와 엄격하게 분리되어야 합니다. 그렇지 않으면 배포 환경에 따라 어떻게 달라질 수 있습니까?
  • 구성 데이터는 환경 변수에 저장됩니다.

Microservices로 이동할 때에도 이러한 지침을 준수할 수 있지만 항상 Twelve-Factor 앱의 문자 그대로 해석하는 방식으로 매핑되는 것은 아닙니다. 구성 데이터를 환경 변수로 제공하는 것과 같은 일부 지침은 훌륭하게 이어집니다. 다른 일반적인 Microservices 방식은 Twelve-Factor App의 핵심 원칙을 존중하지만 확장에 가깝습니다. 이 포스트에서는 Factor 3의 관점을 통해 Microservices에 대한 구성 관리의 세 가지 핵심 개념에 대해 살펴보겠습니다.

목차

1. 주요 Microservices 용어 및 개념
1-1. Microservices vs Monoliths
1-2. Microservices 아키텍처에 필요한 변경 사항
2. 서비스 구성을 명확하게 정의
3. 서비스에 구성이 제공되는 방법
3-1. 패턴: 애플리케이션 옆의 배포 및 인프라 구성
3-2. 어떤 유형의 구성을 사용할 수 있습니까?
4. 서비스를 구성으로 사용 가능하게 만들기
5. NGINX 및 Microservices 체험하기

1. 주요 Microservices 용어 및 개념

Microservices에 대한 Factor 3 적용에 대한 논의를 시작하기 전에 몇 가지 주요 용어와 개념을 이해하는 것이 도움이 됩니다.

  • Monolithic 애플리케이션 아키텍처 – 애플리케이션 기능을 구성 요소 모듈로 분리하지만 단일 Codebase에 모든 모듈을 포함하는 기존 아키텍처 모델입니다.
  • Microservices 애플리케이션 아키텍처 – 여러 개의 작은 구성 요소에서 크고 복잡한 애플리케이션을 구축하는 아키텍처 모델로, 각 구성 요소는 인증, 알림 또는 결제 처리와 같은 광범위한 작업을 수행합니다. “Microservices”는 작은 구성 요소 자체의 이름이기도 합니다. 실제로 일부 “Microservices”는 실제로 상당히 클 수 있습니다.
  • Service – 시스템의 단일 애플리케이션 또는 Microservices에 대한 일반적인 용어입니다.
  • System – 이 포스트의 맥락에서, 조직에서 제공하는 완전한 기능을 생성하기 위해 함께 제공되는 Microservices 및 지원 인프라의 전체 집합입니다.
  • Artifact – 테스트 및 빌드 Pipeline에 의해 생성된 개체입니다. 애플리케이션 코드가 포함된 Docker 이미지와 같은 다양한 형태를 취할 수 있습니다.
  • Deployment – 스테이징(Staging), 통합 또는 Production과 같은 환경에서 실행되는 Artifact의 실행 중인 “인스턴스”입니다.

1-1. Microservices vs Monoliths

Monolithic 애플리케이션을 사용하면 조직의 모든 팀이 동일한 애플리케이션과 주변 인프라에서 작업합니다. Monolithic 애플리케이션은 일반적으로 서류상으로는 Microservices보다 단순해 보이지만 조직이 Microservices로 전환하기로 결정하는 몇 가지 일반적인 이유는 다음과 같습니다.

팀 자율성 – Monolith에서 기능 및 하위 시스템의 소유권을 정의하는 것은 까다로울 수 있습니다. 조직이 성장하고 성숙해짐에 따라 애플리케이션 기능에 대한 책임은 점점 더 많은 팀에 분산되는 경우가 많습니다. 기능을 소유한 팀이 Monolith의 모든 관련 하위 시스템을 소유하지 않기 때문에 팀 간에 종속성이 생성됩니다.

“Blast Radius” 감소 – 대규모 애플리케이션을 하나의 단일 단위로 개발하고 배포할 때, 하나의 하위 시스템에서 오류가 발생하면 전체 애플리케이션의 기능이 저하될 수 있습니다.

기능을 독립적으로 확장 – Monolith 애플리케이션의 단일 모듈만 과부하 상태에 있더라도 조직은 시스템 오류 또는 성능 저하를 방지하기 위해 전체 애플리케이션에 많은 인스턴스를 배포해야 합니다.

물론 Microservices에는 복잡성 증가, 관찰 가능성 감소, 새로운 보안 모델의 필요성 등 고유한 문제가 있지만 많은 조직, 특히 규모가 크거나 빠르게 성장하는 조직은 팀에 더 많은 자율성과 고객에게 제공하는 경험을 위한 신뢰할 수 있고 안정적인 기반을 만드는 유연성을 제공할 가치가 있다고 판단합니다.

1-2. Microservices 아키텍처에 필요한 변경 사항

Monolithic 애플리케이션을 Microservices로 전환할 때 서비스는 다음을 충족해야 합니다.

  • 예측 가능한 방식으로 구성 변경 수락합니다.
  • 예측 가능한 방식으로 더 넓은 시스템에 자신을 알립니다.
  • 문서화가 잘 되어 있어야 합니다.

Monolith 애플리케이션의 경우 프로세스의 약간의 불일치와 공유된 가정에 대한 의존성은 중요하지 않습니다. 그러나 별도의 Microservices가 많은 경우 이러한 불일치와 가정으로 인해 많은 고통과 혼돈이 발생할 수 있습니다. Microservices를 사용하여 수행해야 하는 많은 변경 사항은 기술적인 필수 사항이지만 그보다 더 놀라운 숫자의 문제는 팀이 내부적으로 작업하고 다른 팀과 상호 작용하는 방식에 관한 것입니다.

Microservices 아키텍처의 주요한 조직적 변화는 다음과 같습니다.

  • 동일한 Codebase에서 함께 작업하는 대신 팀이 완전히 분리되어 각 팀이 하나 이상의 서비스를 전적으로 책임집니다. Microservices의 가장 일반적인 구현에서 팀은 “크로스-기능(Cross-Functional)”으로 재구성됩니다. 즉, 다른 팀에 대한 종속성을 최소화하면서 팀의 목표를 완료하는 데 필요한 모든 역량을 갖춘 구성원이 있음을 의미합니다.
  • 플랫폼(Platform)팀(시스템의 전반적인 상태를 담당)은 이제 단일 애플리케이션을 처리하는 대신 서로 다른 팀이 소유한 여러 서비스를 조정해야 합니다.
  • 툴링(Tooling)팀은 시스템을 안정적으로 유지하면서 목표를 빠르게 달성할 수 있도록 다양한 서비스 소유자 팀에 툴링 및 지침을 제공할 수 있어야 합니다.
Diagram comparing the organization of developer teams for monolithic and microservices apps

2. 서비스 구성을 명확하게 정의

Factor 3을 확장해야 하는 Microservices 아키텍처의 한 영역은 해당 구성을 포함하여 서비스에 대한 특정 필수 정보를 명확하게 정의하고 다른 서비스와 최소한의 공유 컨텍스트를 가정해야 할 필요성과 관련됩니다. Factor 3은 이를 직접적으로 다루지는 않지만 애플리케이션 기능에 기여하는 수많은 개별 Microservices에서 특히 중요합니다.

Microservices 아키텍처의 서비스 소유자로서 귀하의 팀은 시스템 전체에서 특정 역할을 수행하는 서비스를 소유합니다. 귀하의 서비스와 상호 작용하는 다른 팀은 귀하의 서비스 저장소에 액세스하여 코드와 문서를 읽고 기여해야 합니다.

또한 개발자의 입사 및 퇴사뿐만 아니라 내부 개편으로 인해 팀 구성원이 자주 변경되는 것이 소프트웨어 개발 분야의 안타까운 현실입니다. 또한 주어진 서비스에 대한 책임도 팀 간에 이전되는 경우가 많습니다.

이러한 현실을 고려할 때 Codebase와 문서는 다음을 통해 매우 명확하고 일관적이어야 합니다.

  • 각 구성 옵션의 목적을 명확히 정의합니다.
  • 구성 값의 예상 형식을 명확하게 정의합니다.
  • 애플리케이션이 구성 값을 제공할 것으로 예상하는 방법을 명확히 정의합니다.
  • 정보를 제한된 수의 파일에 기록합니다.

많은 애플리케이션 프레임워크는 필요한 구성을 정의하는 수단을 제공합니다. 예를 들어, Node.js 애플리케이션을 위한 Benet NPM 패키지는 단일 파일에 저장된 전체 구성 “Schema”를 사용합니다. Node.js 애플리케이션을 실행하는 데 필요한 모든 구성에 대한 Source 역할을 합니다.

강력하고 쉽게 검색할 수 있는 Schema를 사용하면 팀 구성원과 다른 구성원 모두가 서비스와 자신 있게 상호 작용할 수 있습니다.

3. 서비스에 구성이 제공되는 방법

애플리케이션에 필요한 구성 값을 명확하게 정의했으면 배포된 Microservices 애플리케이션이 해당 구성을 가져오는 두 가지 기본 Source 사이의 중요한 차이점도 존중해야 합니다.

  • 구성 설정을 명시적으로 정의하고 애플리케이션 Source 코드와 함께 제공되는 배포 스크립트
  • 배포 시 외부 Source가 Query됩니다.

배포 스크립트는 Microservices 아키텍처의 일반적인 코드 구성 패턴입니다. Twelve-Factor 애플리케이션이 처음 발행된 이후로 새로운 것이기 때문에 필연적으로 그것의 확장을 나타냅니다.

3-1. 패턴: 애플리케이션 옆의 배포 및 인프라 구성

최근 몇 년 동안 애플리케이션 코드와 동일한 Repository에 인프라라는 폴더가 있는 것이 일반화되었습니다. 일반적으로 다음을 포함합니다.

  • 서비스가 의존하는 인프라(예: 데이터베이스)를 설명하는 코드형 인프라(Terraform)
  • Helm ChartsKubernetes Manifests와 같은 컨테이너 오케스트레이션 시스템 구성
  • 애플리케이션 배포와 관련된 기타 모든 파일

언뜻 보기에 이것은 구성이 코드와 엄격하게 분리된다는 Factor 3의 규정을 위반하는 것처럼 보일 수 있습니다.

실제로 애플리케이션 옆에 배치된다는 것은 인프라 폴더가 실제로 규칙을 준수하면서 Microservices 환경에서 작업하는 팀에 중요한 귀중한 프로세스 개선을 가능하게 함을 의미합니다.

이 패턴의 이점은 다음과 같습니다.

  • 서비스를 소유한 팀은 서비스 배포 및 서비스별 인프라(예: 데이터베이스)의 배포도 소유합니다.
  • 소유 팀은 이러한 요소에 대한 변경 사항이 개발 프로세스(Code Review, CI)를 통과하는지 확인할 수 있습니다.
  • 팀은 외부 팀에 의존하지 않고 서비스 및 지원 인프라 배포 방식을 쉽게 변경할 수 있습니다.

이 패턴이 제공하는 이점은 개별 팀의 자율성을 강화하는 동시에 배포 및 구성 프로세스에 추가적인 엄격함을 적용한다는 점에 유의하십시오.

3-2. 어떤 유형의 구성을 사용할 수 있습니까?

실제로 인프라 폴더에 저장된 배포 스크립트를 사용하여 서비스에 대한 배포 스크립트를 사용하여 스크립트 자체에 명시적으로 정의된 구성과 배포 시 외부 Source에서 구성 검색을 모두 관리합니다.

서비스에 대한 배포 스크립트를 사용하면 다음과 같은 이점이 있습니다.

  • 특정 구성 값을 직접 정의할 수 있습니다.
  • 배포 스크립트를 실행하는 프로세스가 외부 Source에서 원하는 구성 값을 찾을 수 있는 위치 정의할 수 있습니다.

특정 서비스 배포와 관련된 구성 값과 팀이 완전히 제어하는 구성 값을 인프라 폴더의 파일에서 직접 지정할 수 있습니다. 예를 들어 애플리케이션에서 시작한 데이터베이스 Query가 실행될 수 있는 시간제한과 같은 것일 수 있습니다. 이 값은 배포 파일을 수정하고 애플리케이션을 다시 배포하여 변경할 수 있습니다.

이 체계의 한 가지 이점은 이러한 구성에 대한 변경 사항이 반드시 Code Review 및 자동화된 테스트를 거쳐야 하므로 잘못 구성된 값으로 인해 중단이 발생할 가능성이 줄어듭니다. Code Review를 거치는 값의 변경 사항과 특정 시점의 구성 키값은 Source 제어 도구의 기록에서 검색할 수 있습니다.

애플리케이션을 실행하는 데 필요하지만 팀에서 제어할 수 없는 값은 애플리케이션이 배포된 환경에서 제공되어야 합니다. 서비스가 의존하는 다른 Microservices에 연결하는 호스트명과 포트를 예로 들 수 있습니다.

해당 서비스는 팀에서 소유하지 않기 때문에 포트 번호와 같은 값에 대해 가정할 수 없습니다. 이러한 값은 언제든지 변경될 수 있으며 변경이 수동으로 수행되든 일부 자동 프로세스에 의해 수행되든 변경될 때 일부 중앙 구성 저장소에 등록해야 합니다. 그런 다음 종속된 애플리케이션에서 Query 할 수 있습니다.

Microservices 구성에 대한 두 가지 모범 사례로 이러한 지침을 요약할 수 있습니다.

Microservices 구성 금지: 하드코딩되거나 상호 합의된 값에 의존

예를 들어 서비스가 상호 작용하는 서비스의 위치와 같이 배포 스크립트에서 특정 값을 하드코딩하는 것이 가장 간단해 보일 수 있습니다. 실제로 이러한 유형의 구성을 하드코딩하는 것은 특히 서비스 위치가 자주 변경되는 현대적인 환경에서 위험합니다. 두 번째 서비스를 소유하지 않은 경우 특히 위험합니다.

스크립트에서 서비스 위치를 최신 상태로 유지하기 위해 자신의 노력에 의존할 수 있다고 생각할 수도 있고, 위치가 변경될 때 알려주는 소유 팀에 의존할 수도 있습니다. 부지런함은 종종 스트레스를 받을 때 미끄러지고 인간의 엄격함에 따라 경고 없이 시스템이 실패할 위험에 처하게 됩니다.

Microservices 구성 작업: 서비스에서 “내 데이터베이스가 어디에 있습니까?”라고 묻습니다

위치 정보가 하드코딩되었는지에 관계없이 애플리케이션은 특정 위치에 있는 중요한 인프라에 의존해서는 안 됩니다. 대신 새로 배포된 서비스는 시스템 내에서 “내 데이터베이스는 어디에 있습니까?”와 같은 공통 Source를 질문해야 합니다. 해당 외부 리소스의 현재 위치에 대한 정확한 답변을 받습니다. 배포할 때 모든 서비스를 시스템에 등록하면 작업이 훨씬 간단해집니다.

위치 정보가 하드 코딩되어 있는지에 관계없이 애플리케이션은 특정 위치에 있는 중요한 인프라에 의존해서는 안 됩니다. 대신 새로 구축된 서비스는 “내 데이터베이스가 어디에 있습니까?”와 같은 시스템 내의 몇 가지 공통 Source에 질문하고 해당 외부 리소스의 현재 위치에 대한 정확한 답변을 받아야 합니다. 모든 서비스가 배포될 때 시스템에 자체 등록되도록 하면 작업이 훨씬 간단해집니다.

4. 서비스를 구성으로 사용 가능하게 설정

시스템이 “내 데이터베이스는 어디에 있습니까?”라는 질문에 대한 답을 제공해야 하는 것과 같습니다. 그리고 “내가 의존하고 있는 ‘서비스 X’는 어디에 있습니까?”와 같이 서비스는 배포 방법에 대해 전혀 알지 못하더라도 다른 서비스가 쉽게 찾고 대화할 수 있는 방식으로 시스템에 노출되어야 합니다.

Microservices 아키텍처의 주요 구성 사례는 서비스 검색(Service Discovery)입니다. 새로운 서비스 정보 등록 및 다른 서비스에서 액세스할 때 해당 정보의 동적 업데이트입니다. Microservices에 서비스 검색이 필요한 이유를 설명한 후 NGINX Open Source 및 Consul을 사용하여 이를 수행하는 방법의 예를 살펴보겠습니다.

서비스의 여러 인스턴스(배포)를 한 번에 실행하는 것이 일반적입니다. 이를 통해 추가 트래픽을 처리할 수 있을 뿐만 아니라 새 배포를 시작하여 다운타임(Downtime) 없이 서비스를 업데이트할 수 있습니다. Reverse Proxy 및 Load Balanncer 역할을 하는 NGINX와 같은 도구는 들어오는 트래픽을 처리하고 가장 적절한 인스턴스로 라우팅합니다. 서비스에 의존하는 서비스는 NGINX에만 요청을 보내고 배포에 대해 알 필요가 없기 때문에 이것은 좋은 패턴입니다.

예를 들어, Reverse Proxy 역할을 하는 NGINX 뒤에서 실행되는 메신저라는 서비스의 단일 인스턴스가 있다고 가정해 보겠습니다.

Diagram of a single instance of the 'messenger' microservice being reverse proxied by NGINX

이제 애플리케이션이 인기를 얻으면 어떻게 될까요? 이는 좋은 소식으로 간주되지만 트래픽 증가로 인해 메신저 인스턴스가 많은 CPU를 소비하고 요청을 처리하는 데 시간이 오래 걸리는 반면 데이터베이스는 제대로 작동하는 것처럼 보입니다. 이는 메신저 서비스의 다른 인스턴스를 배포하여 문제를 해결할 수 있음을 나타냅니다.

Diagram of a two instances of the 'messenger'; microservice being reverse proxied by NGINX

메신저 서비스의 두 번째 인스턴스를 배포할 때 NGINX는 어떻게 그것이 활성화되었음을 알고 트래픽을 전송하기 시작합니까? NGINX 구성에 새 인스턴스를 수동으로 추가하는 것도 한 가지 방법이지만, 더 많은 서비스가 스케일 업(Scale Up) 및 스케일 다운됨(Scale Down)에 따라 빠르게 관리할 수 없게 됩니다.

일반적인 솔루션은 Consul과 같은 고가용성 서비스 Registry를 사용하여 시스템의 서비스를 추적하는 것입니다. 새 서비스 인스턴스는 배포 시 Consul에 등록됩니다. Consul은 주기적으로 상태 확인을 전송하여 인스턴스의 상태를 모니터링합니다. 인스턴스가 상태 확인에 실패하면 사용 가능한 서비스 목록에서 제거됩니다.

Diagram of a two instances of the 'messenger' microservice being reverse proxied by NGINX, with Consul for service discovery

NGINX는 다양한 방법을 사용하여 Consul과 같은 Registry를 Query 하고 그에 따라 라우팅을 조정할 수 있습니다. Reverse Proxy 또는 Load Balancer 역할을 할 때 NGINX는 트래픽을 “upstream” 서버로 라우팅합니다. 다음과 같은 간단한 구성을 고려하십시오.

# Define an upstream group called "messenger_service"
upstream messenger_service {
    server 172.18.0.7:4000;
    server 172.18.0.8:4000;
}

server {
    listen 80;

    location /api {
        # Proxy HTTP traffic with paths starting with '/api' to the
        # 'upstream' block above. The default load-balancing algorithm, 
        # Round-Robin, alternates requests between the two servers 
        # in the block.
        proxy_pass http://messenger_service;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

기본적으로 NGINX는 트래픽을 라우팅하기 위해 각 메신저 인스턴스의 정확한 IP 주소와 포트를 알아야 합니다. 이 경우 172.18.0.7 및 172.18.0.8 모두에서 포트 4000입니다.

여기서 Consul과 Consul 템플릿이 등장합니다. Consul 템플릿은 NGINX와 동일한 컨테이너에서 실행되며 서비스 Registry를 유지 관리하는 Consul 클라이언트와 통신합니다.

레지스트리 정보가 변경되면 Consul 템플릿은 올바른 IP 주소 및 포트를 사용하여 NGINX 구성 파일의 새 버전을 생성하고 NGINX 구성 디렉터리에 기록하며 구성을 Reload 하도록 NGINX에 알립니다. NGINX가 구성을 Reload 할 때 다운타임이 없으며 Reload가 완료되는 즉시 새 인스턴스가 트래픽 수신을 시작합니다.

이러한 상황에서 NGINX와 같은 Reverse Proxy를 사용하면 다른 서비스가 액세스할 수 있는 장소로 시스템에 등록할 수 있는 Single Touch Point가 있습니다. 귀사의 팀은 다른 서비스가 전체 서비스에 액세스할 수 없게 될 것을 걱정하지 않고 개별 서비스 인스턴스를 유연하게 관리할 수 있습니다.

5. NGINX 및 Microservices 체험하기

Microservices는 서비스에 대한 기술적 용어와 다른 팀과의 관계에 대한 조직적 용어 모두에서 복잡성을 증가시킵니다. Microservices 아키텍처의 이점을 누리려면 Monolith 용으로 설계된 방식을 비판적으로 재검토하여 매우 다른 환경에 적용했을 때 여전히 동일한 이점을 제공하는지 확인하는 것이 중요합니다. 이 포스트에서는 Twelve-Factor App의 Factor 3이 어떻게 Microservices 환경에서 가치를 제공하는지와 그것이 구체적으로 적용되는 방식에 대한 것과 작은 변화로부터 어떤 이점을 얻을 수 있는지에 대해 살펴보았습니다.

NGINX Plus를 직접 사용해 Microservices를 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.