블로그 | NGINX

마이크로서비스 앱 구성을 위한 모범 사례

NGINX-F5-수평-검정-유형-RGB의 일부
하비에르 에반스 썸네일
하비에르 에반스
2023년 3월 2일 게시

12단계 앱 으로 알려진 가이드라인은 10년 전에 처음 발표되었습니다. 그 이후로 거의 모든 의무적 관행이 웹앱을 작성하고 배포하는 사실상의 표준 방식이 되었습니다. 앱이 구성되고 배포되는 방식이 변경된 상황에서도 이러한 원칙은 여전히 적용 가능하지만, 어떤 경우에는 앱을 개발하고 배포하기 위한 마이크로서비스 패턴에 이러한 원칙이 어떻게 적용되는지 이해하기 위해 추가적인 이해가 필요합니다.

이 블로그는 환경에서 구성 저장의 요소 3에 초점을 맞추고 있으며 다음과 같이 설명합니다.

  • 구성은 배포 환경(12단계 앱이 배포 라고 부르는) 간에 달라지는 모든 것을 말합니다.
  • 구성은 앱 코드와 엄격하게 분리되어야 합니다. 그렇지 않으면 배포마다 어떻게 달라질 수 있겠습니까?
  • 구성 데이터는 환경 변수에 저장됩니다.

마이크로서비스로 이동하면서 여전히 이러한 지침을 준수할 수 있지만, 항상 12팩터 앱의 문자 그대로의 해석에 정확히 매핑되는 방식은 아닙니다. 환경 변수로 구성 데이터를 제공하는 것과 같은 일부 지침은 훌륭하게 이어집니다. 12단계 앱의 핵심 원칙을 존중하는 반면, 다른 일반적인 마이크로서비스 관행은 이를 확장한 형태에 더 가깝습니다. 이 게시물에서는 Factor 3의 관점에서 마이크로서비스를 위한 구성 관리의 세 가지 핵심 개념을 살펴보겠습니다.

주요 마이크로서비스 용어 및 개념

마이크로서비스에 맞게 Factor 3를 적용하는 것에 대한 논의에 들어가기 전에 몇 가지 핵심 용어와 개념을 이해하는 것이 도움이 됩니다.

  • 모놀리식 앱 아키텍처 – 앱 기능을 구성 요소 모듈로 분리하지만 모든 모듈을 단일 코드베이스에 포함하는 전통적인 아키텍처 모델입니다.
  • 마이크로서비스 앱 아키텍처는 각각 잘 정의된 범위의 작업(예: 인증, 알림 또는 결제 처리)을 수행하는 여러 개의 작은 구성 요소로 대규모 복잡한 앱을 구축하는 아키텍처 모델입니다. "마이크로서비스"는 작은 구성 요소 자체를 지칭하는 이름이기도 합니다. 실제로 일부 "마이크로서비스"는 실제로 매우 클 수 있습니다.
  • 서비스 – 시스템 내의 단일 애플리케이션이나 마이크로서비스를 지칭하는 일반적인 용어입니다.
  • 시스템 – 이 블로그의 맥락에서 조직이 제공하는 완전한 기능을 생성하기 위해 함께 모이는 마이크로서비스와 지원 인프라의 전체 세트입니다.
  • 아티팩트 – 테스트 및 빌드 파이프라인에서 생성된 객체입니다. 이는 앱의 코드가 포함된 Docker 이미지 등 여러 형태를 취할 수 있습니다.
  • 배포 - 스테이징, 통합 또는 프로덕션과 같은 환경에서 실행되는 아티팩트의 실행 중인 "인스턴스"입니다.

마이크로서비스 대 모놀리스

모노리식 애플리케이션을 사용하면 조직의 모든 팀이 동일한 애플리케이션과 관련 인프라에서 작업합니다. 모놀리식 앱은 일반적으로 문서상으로는 마이크로서비스보다 간단해 보이지만 조직이 마이크로서비스로 전환하기로 결정하는 데에는 몇 가지 일반적인 이유가 있습니다.

  • 팀 자율성 – 모놀리스에서 기능과 하위 시스템의 소유권을 정의하는 것은 까다로울 수 있습니다. 조직이 성장하고 성숙해짐에 따라 앱 기능에 대한 책임이 점점 더 많은 팀으로 분산되는 경우가 많습니다. 모놀리스의 모든 관련 하위 시스템을 소유하지 않은 기능을 소유한 팀으로 인해 팀 간에 종속성이 발생합니다.
  • "폭발 반경" 감소 - 대규모 애플리케이션을 하나의 단일 단위로 개발 및 배포하는 경우 하위 시스템 하나의 오류가 전체 앱의 기능을 저하시킬 수 있습니다.
  • 기능을 독립적으로 확장 – 모놀리식 앱의 단일 모듈만 부하가 많이 걸리더라도 조직은 시스템 장애나 성능 저하를 방지하기 위해 전체 앱의 여러 인스턴스를 배포해야 합니다.

물론 마이크로서비스에는 복잡성 증가, 관찰성 감소, 새로운 보안 모델 필요성 등 고유한 과제가 따릅니다. 하지만 많은 조직, 특히 대규모 또는 빠르게 성장하는 조직은 고객에게 제공하는 경험에 대한 안정적이고 신뢰할 수 있는 기반을 구축하기 위해 팀에 더 많은 자율성과 유연성을 제공하기 위해 이러한 과제를 감수할 가치가 있다고 결정합니다.

마이크로서비스 아키텍처에 필요한 변경 사항

모놀리식 앱을 마이크로서비스로 리팩토링하는 경우 서비스는 다음을 수행해야 합니다.

  • 예측 가능한 방식으로 구성 변경 사항 수락
  • 예측 가능한 방식으로 더 넓은 시스템에 자신을 알리십시오.
  • 잘 문서화하세요

단일 앱의 경우 프로세스 상의 불일치나 공유된 가정에 대한 종속성은 그다지 중요하지 않습니다. 그러나 개별 마이크로서비스가 많으면 이러한 불일치와 가정으로 인해 많은 어려움과 혼란이 생길 수 있습니다. 마이크로서비스를 변경하는 데 필요한 변경 사항 중 많은 부분은 기술적으로 필요하지만, 놀랍게도 그 중 상당수는 팀이 내부적으로 작업하고 다른 팀과 상호 작용하는 방식과 관련이 있습니다.

마이크로서비스 아키텍처를 적용한 주요 조직 변화는 다음과 같습니다.

  • 동일한 코드베이스에서 함께 작업하는 대신, 팀은 완전히 분리되어 각 팀이 하나 이상의 서비스에 대해 전적으로 책임을 지게 됩니다. 마이크로서비스를 가장 일반적으로 구현할 때 팀은 또한 "교차 기능적"으로 재구성됩니다. 즉, 다른 팀에 대한 종속성을 최소화하면서 팀의 목표를 완료하는 데 필요한 모든 역량을 갖춘 멤버가 있다는 의미입니다.
  • 플랫폼 팀(시스템 전반의 상태를 담당)은 이제 단일 애플리케이션을 다루는 대신, 여러 팀이 소유한 여러 서비스를 조정해야 합니다.
  • 툴링 팀은 다양한 서비스 소유자 팀에 툴링과 지침을 제공하여 시스템을 안정적으로 유지하는 동시에 목표를 빠르게 달성할 수 있도록 지원해야 합니다.

모놀리식 및 마이크로서비스 앱의 개발자 팀 구성을 비교하는 다이어그램

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

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

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

또한 소프트웨어 개발 분야에서 팀 구성원은 개발자가 회사에 합류하거나 떠나는 경우뿐만 아니라 내부 조직 개편으로 인해 자주 바뀐다는 것은 불행한 현실입니다. 또한, 주어진 서비스에 대한 책임도 종종 팀 간에 이전됩니다.

이러한 현실에 비추어 볼 때, 귀하의 코드베이스와 문서는 매우 명확하고 일관성이 있어야 하며, 이는 다음을 통해 달성됩니다.

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

많은 애플리케이션 프레임워크는 필요한 구성을 정의하는 수단을 제공합니다. 예를 들어, Node.js 애플리케이션용 convict NPM 패키지는 단일 파일에 저장된 완전한 구성 "스키마"를 사용합니다. 이는 Node.js 앱을 실행하는 데 필요한 모든 구성에 대한 진실의 원천 역할을 합니다.

강력하고 쉽게 발견할 수 있는 스키마를 통해 팀원과 다른 사람들이 서비스를 자신 있게 사용할 수 있습니다.

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

애플리케이션에 필요한 구성 값을 명확하게 정의했다면 배포된 마이크로서비스 애플리케이션이 구성을 가져오는 두 가지 기본 소스 간의 중요한 구별도 존중해야 합니다.

  • 구성 설정을 명시적으로 정의하고 애플리케이션 소스 코드와 함께 제공되는 배포 스크립트
  • 배포 시점에 외부 소스가 쿼리됨

배포 스크립트는 마이크로서비스 아키텍처에서 일반적인 코드 구성 패턴입니다. 이러한 기능은 12단계 앱이 처음 출시된 이후 새롭게 출시되었기 때문에 필연적으로 12단계 앱의 확장 버전입니다.

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

최근 몇 년 사이에 애플리케이션 코드와 동일한 저장소에 infrastructure (또는 이와 비슷한 이름의 폴더)라는 이름의 폴더를 갖는 것이 일반적이 되었습니다. 일반적으로 다음이 포함됩니다.

  • 서비스가 의존하는 인프라를 설명하는 코드로서의 인프라( Terraform 이 일반적인 예)(예: 데이터베이스)
  • Helm 차트Kubernetes 매니페스트 와 같은 컨테이너 오케스트레이션 시스템에 대한 구성
  • 애플리케이션 배포와 관련된 기타 파일

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

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

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

  • 서비스를 소유한 팀은 서비스 배포와 서비스별 인프라(예: 데이터베이스)의 배포도 소유합니다.
  • 소유 팀은 이러한 요소에 대한 변경 사항이 개발 프로세스(코드 검토, CI)를 거치도록 할 수 있습니다.
  • 팀은 외부 팀에 작업을 맡기지 않고도 서비스와 지원 인프라의 배포 방식을 쉽게 변경할 수 있습니다.

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

어떤 유형의 구성이 어디에 적용됩니까?

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

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

서비스의 특정 배포에 특화되어 있고 팀에서 완전히 제어할 수 있는 구성 값은 infrastructure 폴더의 파일에 직접 지정할 수 있습니다. 예를 들어 앱에서 시작한 데이터베이스 쿼리를 실행할 수 있는 시간을 제한하는 것과 같은 것이 있습니다. 이 값은 배포 파일을 수정하고 애플리케이션을 다시 배포하면 변경될 수 있습니다.

이 방식의 장점 중 하나는 구성을 변경하면 반드시 코드 검토와 자동 테스트를 거쳐야 하므로 잘못 구성된 값으로 인해 중단이 발생할 가능성이 줄어든다는 것입니다. 코드 검토를 거친 값의 변경 사항과 특정 시점의 구성 키 값은 소스 제어 도구의 기록에서 확인할 수 있습니다.

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

해당 서비스는 귀하의 팀이 소유하지 않으므로 포트 번호와 같은 값에 대해 가정할 수 없습니다. 이러한 값은 언제든지 변경될 수 있으며 변경 시 중앙 구성 저장소에 등록되어야 합니다. 해당 변경이 수동으로 이루어지든 자동 프로세스를 통해 이루어지든 상관없습니다. 그런 다음 이를 사용하는 애플리케이션에서 쿼리를 실행할 수 있습니다.

이러한 지침은 마이크로서비스 구성을 위한 두 가지 모범 사례로 요약할 수 있습니다.

마이크로서비스 구성에서는 다음을 하지 마십시오. 하드코딩된 값이나 상호 합의된 값에 의존

배포 스크립트에 특정 값(예: 서비스가 상호 작용하는 서비스의 위치)을 하드코딩하는 것이 가장 간단한 것처럼 보일 수 있습니다. 실제로 이런 유형의 구성을 하드코딩하는 것은 위험합니다. 특히 서비스 위치가 자주 변경되는 현대 환경에서는 더욱 그렇습니다. 특히 두 번째 서비스를 소유하지 않은 경우 더욱 위험합니다.

스크립트에서 서비스 위치를 업데이트하기 위해 자신의 부지런함에 의지할 수 있다고 생각할 수도 있고, 더 나쁜 경우 위치가 변경될 때 소유 팀에 의지하여 알려줄 수 있다고 생각할 수도 있습니다. 스트레스가 많은 시기에는 근면함이 흐트러지는 경우가 많고, 인간의 엄격함에 의존하다 보면 시스템이 아무런 경고 없이 고장날 위험이 있습니다.

마이크로서비스 구성은 다음을 수행합니다. 서비스에 "내 데이터베이스는 어디에 있습니까?"라고 물어보세요.

위치 정보가 하드코딩되어 있든 없든, 애플리케이션은 중요 인프라가 특정 위치에 있어야 한다는 전제 하에 동작해서는 안 됩니다. 대신, 새로 배포된 서비스는 "내 데이터베이스는 어디에 있나요?"와 같은 시스템 내의 일반적인 소스에 질문을 하고 해당 외부 리소스의 현재 위치에 대한 정확한 답변을 받아야 합니다. 각 서비스가 배포되는 대로 시스템에 등록되면 작업이 훨씬 간단해집니다.

서비스를 구성으로 사용 가능하게 만들기

시스템이 "내 데이터베이스는 어디에 있는가?"와 "내가 의존하고 있는 '서비스 X'는 어디에 있는가?"라는 질문에 대한 답변을 제공해야 하는 것처럼, 서비스는 배포 방법에 대해 아무것도 알지 못해도 다른 서비스가 쉽게 찾아서 통신할 수 있는 방식으로 시스템에 노출되어야 합니다.

마이크로서비스 아키텍처의 주요 구성 관행은 서비스 검색입니다. 즉, 새로운 서비스 정보를 등록하고 다른 서비스에서 액세스할 때 해당 정보를 동적으로 업데이트하는 것입니다. 마이크로서비스에 서비스 검색이 필요한 이유를 설명한 후 NGINX 오픈 소스와 Consul을 사용하여 이를 달성하는 방법의 예를 살펴보겠습니다.

한 번에 여러 서비스 인스턴스(배포)를 실행하는 것은 일반적인 관행입니다. 이를 통해 추가 트래픽을 처리할 수 있을 뿐만 아니라 새로운 배포를 실행하여 다운타임 없이 서비스를 업데이트할 수도 있습니다. NGINX와 같은 도구는 역방향 프록시 및 부하 분산 장치 역할을 하며, 들어오는 트래픽을 처리하고 가장 적합한 인스턴스로 라우팅합니다. 이것은 좋은 패턴입니다. 왜냐하면 귀하의 서비스에 의존하는 서비스는 NGINX에만 요청을 보내고 귀하의 배포에 대해 아무것도 알 필요가 없기 때문입니다.

예를 들어, NGINX 뒤에서 역방향 프록시 역할을 하는 messenger 라는 서비스의 단일 인스턴스가 있다고 가정해 보겠습니다.

NGINX에 의해 역방향 프록시되는 '메신저' 마이크로서비스의 단일 인스턴스 다이어그램

이제 여러분의 앱이 인기를 얻는다면 어떨까요? 이는 좋은 소식으로 여겨지지만, 트래픽이 증가했기 때문에 메신저 인스턴스가 많은 CPU를 사용하고 요청을 처리하는 데 시간이 더 오래 걸리는 반면 데이터베이스는 아무 문제없이 작동하는 것처럼 보입니다. 이는 메신저 서비스의 다른 인스턴스를 배포하면 문제를 해결할 수 있음을 나타냅니다.

메신저 서비스의 두 번째 인스턴스를 배포할 때 NGINX는 해당 인스턴스가 라이브 상태인지 어떻게 알고 해당 인스턴스로 트래픽을 전송합니까? NGINX 구성에 수동으로 새로운 인스턴스를 추가하는 것도 한 가지 방법이지만, 더 많은 서비스가 확장되거나 축소됨에 따라 빠르게 관리하기 어려워집니다.

일반적인 솔루션은 Consul 과 같은 가용성이 높은 서비스 레지스트리를 갖춘 시스템에서 서비스를 추적하는 것입니다. 새로운 서비스 인스턴스는 배포됨에 따라 Consul에 등록됩니다. Consul은 주기적으로 인스턴스에 상태 검사를 보내 인스턴스의 상태를 모니터링합니다. 인스턴스가 상태 검사에 실패하면 사용 가능한 서비스 목록에서 제거됩니다.

NGINX에서 역방향 프록시를 사용하고 서비스 검색을 위해 Consul을 사용하는 '메신저' 마이크로서비스의 두 인스턴스 다이어그램

NGINX는 다양한 방법을 사용하여 Consul과 같은 레지스트리에 쿼리를 보내고 이에 따라 라우팅을 조정할 수 있습니다. 역방향 프록시 또는 로드 밸런서 역할을 할 때 NGINX는 트래픽을 "업스트림" 서버로 라우팅한다는 점을 기억하세요. 다음과 같은 간단한 구성을 고려해 보세요.


# "messenger_service"라는 업스트림 그룹을 정의합니다.
upstream messenger_service {
server 172.18.0.7:4000;
server 172.18.0.8:4000;
}

server {
listen 80;

location /api {
# '/api'로 시작하는 경로로 HTTP 트래픽을 프록시하여
# 위의 'upstream' 블록으로 보냅니다. 기본 로드 밸런싱 알고리즘인 
# 라운드 로빈은 블록에서 두 서버 간의 요청을 번갈아 가며 수행합니다. 
# 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와 동일한 컨테이너에서 실행되며 서비스 레지스트리를 유지 관리하는 Consul 클라이언트와 통신합니다.

레지스트리 정보가 변경되면 Consul 템플릿은 올바른 IP 주소와 포트를 포함하는 NGINX 구성 파일의 새 버전을 생성하고, 이를 NGINX 구성 디렉토리에 쓰고, NGINX에 구성을 다시 로드하라고 지시합니다. NGINX가 구성을 다시 로드할 때는 다운타임이 발생하지 않으며, 다시 로드가 완료되자마자 새 인스턴스가 트래픽을 수신하기 시작합니다.

이런 상황에서 NGINX와 같은 역방향 프록시를 사용하면 다른 서비스가 액세스할 수 있는 장소로 시스템에 등록할 수 있는 단일 터치 포인트가 있습니다. 귀하의 팀은 다른 서비스가 서비스 전체에 대한 액세스를 잃을까봐 걱정하지 않고 개별 서비스 인스턴스를 관리할 수 있는 유연성을 확보할 수 있습니다.

NGINX 및 마이크로서비스에 대한 실습 3월

마이크로서비스는 서비스의 기술적 측면과 다른 팀과의 관계에 대한 조직적 측면에서 모두 복잡성을 증가시킨다는 점을 인정합니다. 마이크로서비스 아키텍처의 이점을 누리려면 모노리스를 위해 설계된 관행을 비판적으로 재검토하여 매우 다른 환경에 적용했을 때에도 여전히 동일한 이점을 제공하는지 확인하는 것이 중요합니다. 이 블로그에서는 12가지 요소 앱 중 요소 3이 마이크로서비스 컨텍스트에서 여전히 가치를 제공하지만 구체적으로 적용되는 방식을 약간 변경하면 이점을 얻을 수 있는지 살펴보았습니다.

12팩터 앱을 마이크로서비스 아키텍처에 적용하는 방법에 대해 자세히 알아보려면 2023년 3월 마이크로서비스 단원 1을 확인하세요( 곧 블로그에 게재 ). 무료로 등록하세요 이 주제에 대한 웨비나와 실습 랩에 참여하세요.


"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."