The guidelines known as the 12요소 앱으로 알려진 지침은 10여 년 전에 처음 발표되었습니다. 그 이후 이 지침에서 요구하는 거의 모든 관행은 웹 앱을 작성하고 배포하는 사실상의 표준으로 자리 잡았습니다. 앱을 구성하고 배포하는 방식이 변화하는 가운데서도 이러한 관행은 여전히 유효하지만, 경우에 따라 앱 개발 및 배포를 위한 마이크로서비스 패턴에 이러한 관행을 어떻게 적용해야 하는지 이해하려면 좀 더 세밀한 해석이 필요합니다.
이 블로그에서는 3번째 원칙인 환경에 설정 저장을 중점적으로 다룹니다. 이 원칙의 내용은 다음과 같습니다:
- 구성이란 배포 환경에 따라 달라지는 모든 것을 의미합니다(12요소 앱에서는 이를 배포라고 부릅니다).
- 설정은 앱의 코드와 엄격하게 분리되어야 합니다. 그렇지 않으면 배포 환경마다 어떻게 다르게 구성할 수 있겠습니까?
- 구성 데이터는 환경 변수에 저장됩니다.
마이크로서비스로 전환하더라도 이러한 지침을 계속 준수할 수 있지만, 12요소 앱을 문자 그대로 해석한 내용과 항상 정확히 일치하는 방식으로 적용되는 것은 아닙니다. 구성 데이터를 환경 변수로 제공하는 것과 같은 일부 지침은 그대로 적용하기 좋습니다. 그 밖의 일반적인 마이크로서비스 관행은 12요소 앱의 핵심 원칙을 따르면서도 이를 확장한 것에 더 가깝습니다. 이 글에서는 요소 3의 관점에서 마이크로서비스 구성 관리의 세 가지 핵심 개념을 살펴보겠습니다:
마이크로서비스의 주요 용어 및 개념
마이크로서비스에 맞게 팩터 3을 조정하는 방법을 논의하기에 앞서, 몇 가지 핵심 용어와 개념을 이해해 두는 것이 좋습니다.
- 모놀리식 앱 아키텍처 – 앱 기능을 구성 요소 모듈로 분리하지만 모든 모듈을 하나의 코드베이스에 포함하는 전통적인 아키텍처 모델입니다.
- 마이크로서비스 앱 아키텍처 – 인증, 알림 또는 결제 처리와 같이 명확하게 정의된 일련의 작업을 각각 수행하는 여러 개의 소규모 구성 요소로 크고 복잡한 앱을 구축하는 아키텍처 모델입니다. 이러한 소규모 구성 요소 자체를 ‘마이크로서비스’라고도 합니다. 실제로 일부 ‘마이크로서비스’는 상당히 클 수도 있습니다.
- 서비스 – 시스템 내의 단일 애플리케이션 또는 마이크로서비스를 지칭하는 일반적인 용어입니다.
- 시스템 – 이 블로그에서 조직이 제공하는 모든 기능을 구현하기 위해 함께 구성된 전체 마이크로서비스 및 지원 인프라를 의미합니다.
- 아티팩트 – 테스트 및 빌드 파이프라인에서 생성되는 객체입니다. 애플리케이션 코드가 포함된 Docker 이미지 등 다양한 형태로 존재할 수 있습니다.
- 배포 – 스테이징, 통합 또는 프로덕션과 같은 환경에서 실행되는 아티팩트의 실행 중인 ‘인스턴스’입니다.
마이크로서비스와 모놀리식 아키텍처 비교
모놀리식 애플리케이션에서는 조직 내 모든 팀이 동일한 애플리케이션과 관련 인프라를 함께 다룹니다. 일반적으로 모놀리식 앱은 표면적으로 마이크로서비스보다 단순해 보이지만, 조직이 마이크로서비스로 전환하기로 결정하는 데에는 몇 가지 공통적인 이유가 있습니다:
- 팀 자율성 – 모놀리식 애플리케이션에서는 기능과 하위 시스템의 소유권을 명확히 정의하기가 까다로울 수 있습니다. 조직이 성장하고 성숙해짐에 따라 애플리케이션 기능에 대한 책임이 점점 더 많은 팀에 분산되는 경우가 많습니다. 이로 인해 특정 기능을 담당하는 팀이 모놀리스 내의 모든 관련 하위 시스템까지 소유하지는 않으므로 팀 간에 의존성이 생깁니다.
- “영향 범위” 축소 – 대규모 애플리케이션을 하나의 단위로 개발하고 배포하면, 한 하위 시스템의 오류로 인해 전체 앱의 기능이 저하될 수 있습니다.
- 기능을 독립적으로 확장 – 모놀리식 앱에서 단 하나의 모듈에만 부하가 집중되더라도, 시스템 장애나 성능 저하를 방지하려면 조직은 전체 앱의 인스턴스를 여러 개 배포해야 합니다.
물론 마이크로서비스에는 복잡성 증가, 관찰 가능성 저하, 새로운 보안 모델의 필요성 등 고유한 과제가 따릅니다. 하지만 많은 조직, 특히 규모가 크거나 빠르게 성장하는 조직은 고객에게 제공하는 경험을 뒷받침할 안정적이고 신뢰할 수 있는 기반을 구축하는 과정에서 팀에 더 많은 자율성과 유연성을 부여하기 위해 이러한 과제를 감수할 가치가 있다고 판단합니다.
마이크로서비스 아키텍처에 필요한 변화
모놀리식 앱을 마이크로서비스로 리팩터링할 때 각 서비스는 다음 요건을 충족해야 합니다:
- 예측 가능한 방식으로 구성 변경 사항을 수용해야 합니다
- 예측 가능한 방식으로 더 광범위한 시스템에 자신의 존재를 알림
- 문서화가 잘 되어 있어야 합니다
모놀리식 앱에서는 프로세스상의 사소한 불일치나 공통된 가정에 대한 의존이 큰 문제가 되지 않습니다. 하지만 수많은 마이크로서비스가 각각 분리되어 있는 환경에서는 이러한 불일치와 가정이 상당한 어려움과 혼란을 초래할 수 있습니다. 마이크로서비스를 도입하면서 필요한 변화 중 상당수는 기술적으로 불가피하지만, 의외로 많은 변화가 팀 내부의 업무 방식과 다른 팀과의 협업 방식에 관한 것입니다.
마이크로서비스 아키텍처를 도입할 때 주목할 만한 조직 변화는 다음과 같습니다:
- 동일한 코드베이스에서 함께 작업하는 대신, 팀은 완전히 분리되며 각 팀이 하나 이상의 서비스를 전적으로 책임집니다. 가장 일반적인 마이크로서비스 구현 방식에서는 팀을 ‘교차 기능형’으로 재편하기도 합니다. 즉, 다른 팀에 대한 의존도를 최소화하면서 팀의 목표를 달성하는 데 필요한 모든 역량을 갖춘 구성원들로 팀을 구성합니다.
- 플랫폼 팀(시스템의 전반적인 상태를 책임지는 팀)은 이제 단일 애플리케이션을 관리하는 대신, 서로 다른 팀이 소유한 여러 서비스를 조율해야 합니다.
- 도구 지원 팀은 시스템의 안정성을 유지하면서도 각 서비스 소유 팀이 목표를 신속하게 달성할 수 있도록 도구와 지침을 지속적으로 제공할 수 있어야 합니다.

서비스 구성을 명확하게 정의하기
마이크로서비스 아키텍처에서 팩터 3을 확장해야 하는 부분 중 하나는 구성을 비롯해 서비스에 관한 몇 가지 핵심 정보를 명확히 정의하고, 다른 서비스와 공유하는 컨텍스트를 최소화해야 한다는 점입니다. 팩터 3에서는 이를 직접 다루지 않지만, 수많은 개별 마이크로서비스가 애플리케이션 기능 구현에 기여하는 경우에는 특히 중요합니다.
마이크로서비스 아키텍처에서 서비스 소유자인 여러분의 팀은 전체 시스템에서 특정 역할을 수행하는 서비스를 소유합니다. 여러분의 서비스와 상호 작용하는 서비스를 담당하는 다른 팀은 코드와 문서를 읽고 기여하기 위해 여러분의 서비스 저장소에 접근할 수 있어야 합니다.
또한 소프트웨어 개발 분야에서는 개발자가 입사하거나 퇴사하는 경우뿐만 아니라 내부 조직 개편으로 인해 팀 구성이 자주 바뀌는 것이 안타까운 현실입니다. 특정 서비스에 대한 책임 역시 팀 간에 이관되는 경우가 많습니다.
이러한 현실을 고려할 때 코드베이스와 문서는 매우 명확하고 일관되어야 하며, 이를 위해서는 다음과 같은 조치가 필요합니다:
- 각 구성 옵션의 목적을 명확하게 정의하기
- 구성 값의 예상 형식을 명확하게 정의
- 애플리케이션에서 구성 값을 어떤 방식으로 제공해야 하는지 명확하게 정의
- 이 정보를 제한된 수의 파일에 기록
많은 애플리케이션 프레임워크에서는 필수 구성을 정의하는 방법을 제공합니다. 예를 들어, convict NPM 패키지는 Node.js 애플리케이션에 필요한 전체 구성 ‘스키마’를 하나의 파일에 저장합니다. 이 파일은 Node.js 앱을 실행하는 데 필요한 모든 구성의 단일 기준 정보원 역할을 합니다.
견고하고 쉽게 찾을 수 있는 스키마를 제공하면 팀 구성원과 외부 사용자 모두가 안심하고 서비스를 이용할 수 있습니다.
서비스에 구성을 제공하는 방법
애플리케이션에 필요한 구성 값을 명확히 정의했다면, 배포된 마이크로서비스 애플리케이션이 구성을 가져오는 두 가지 주요 소스 간의 중요한 차이도 고려해야 합니다:
- 구성 설정을 명시적으로 정의하며 애플리케이션 소스 코드와 함께 제공되는 배포 스크립트
- 배포 시 조회되는 외부 소스
배포 스크립트는 마이크로서비스 아키텍처에서 흔히 사용되는 코드 구성 패턴입니다. 이는 12요소 앱이 처음 발표된 이후 새롭게 등장한 개념이므로, 필연적으로 기존 원칙을 확장한 것이라 할 수 있습니다.
패턴: 애플리케이션과 함께 배치하는 배포 및 인프라 구성
최근에는 애플리케이션 코드와 동일한 저장소에 infrastructure라는 이름의 폴더(또는 이와 유사한 이름의 폴더)를 두는 것이 일반적입니다. 이 폴더에는 대개 다음 항목이 포함됩니다:
- 코드형 인프라(Terraform가 대표적인 예)로, 데이터베이스와 같이 서비스가 의존하는 인프라를 정의합니다
- 다음과 같은 컨테이너 오케스트레이션 시스템 구성: Helm 차트 및 Kubernetes 매니페스트
- 애플리케이션 배포와 관련된 기타 모든 파일
언뜻 보기에는 구성을 코드와 엄격하게 분리해야 한다는 요소 3의 원칙을 위반하는 것처럼 보일 수 있습니다.
실제로 인프라 폴더를 애플리케이션 옆에 배치하면 이 규칙을 준수하면서도 마이크로서비스 환경에서 작업하는 팀에 필수적인 유용한 프로세스 개선을 실현할 수 있습니다.
이 패턴의 이점은 다음과 같습니다:
- 서비스를 소유한 팀은 해당 서비스의 배포와 서비스별 인프라(예: 데이터베이스)의 배포도 담당합니다.
- 소유 팀은 이러한 요소에 대한 모든 변경 사항이 자체 개발 프로세스(코드 검토, CI)를 거치도록 할 수 있습니다.
- 팀은 외부 팀에 작업을 의존하지 않고도 서비스와 이를 지원하는 인프라의 배포 방식을 쉽게 변경할 수 있습니다.
이 패턴이 제공하는 이점은 개별 팀의 자율성을 강화하는 동시에 배포 및 구성 프로세스를 더욱 엄격하게 관리하도록 보장한다는 점에 주목하세요.
어떤 유형의 구성을 어디에 배치해야 할까요?
실제로는 인프라 폴더에 저장된 배포 스크립트를 사용하여, 스크립트 자체에 명시적으로 정의된 구성과 배포 시 외부 소스에서 구성을 가져오는 작업을 모두 관리합니다. 이를 위해 서비스의 배포 스크립트에서 다음을 수행합니다:
- 특정 구성 값을 직접 정의
- 배포 스크립트를 실행하는 프로세스가 외부 소스에서 원하는 구성 값을 찾을 위치를 정의합니다
서비스의 특정 배포에만 적용되고 팀에서 완전히 제어할 수 있는 구성 값은 infrastructure 폴더의 파일에 직접 지정할 수 있습니다. 예를 들어 앱에서 시작한 데이터베이스 쿼리의 최대 실행 시간을 제한하는 값이 이에 해당할 수 있습니다. 배포 파일을 수정하고 애플리케이션을 다시 배포하면 이 값을 변경할 수 있습니다.
이 방식의 장점 중 하나는 이러한 구성 변경이 반드시 코드 검토와 자동화된 테스트를 거치므로 잘못 구성된 값으로 인해 서비스 중단이 발생할 가능성이 줄어든다는 것입니다. 코드 검토를 거친 값의 변경 내역과 특정 시점의 구성 키 값은 소스 제어 도구의 기록에서 확인할 수 있습니다.
애플리케이션 실행에 필요하지만 팀에서 제어할 수 없는 값은 애플리케이션이 배포되는 환경에서 제공해야 합니다. 예를 들어, 서비스가 의존하는 다른 마이크로서비스에 연결할 때 사용하는 호스트 이름과 포트가 이에 해당합니다.
해당 서비스는 여러분의 팀이 소유한 것이 아니므로 포트 번호와 같은 값을 임의로 가정해서는 안 됩니다. 이러한 값은 언제든지 변경될 수 있으며, 수동으로 변경하든 자동화된 프로세스를 통해 변경하든 변경 시 중앙 구성 저장소에 등록해야 합니다. 그러면 해당 값을 필요로 하는 애플리케이션에서 이를 조회할 수 있습니다.
이러한 지침은 마이크로서비스 구성을 위한 두 가지 모범 사례로 요약할 수 있습니다.
마이크로서비스 구성 시 하지 말아야 할 것: 하드코딩된 값이나 상호 합의된 값에 의존하지 않기
배포 스크립트에 특정 값을 하드코딩하는 것이 가장 간단해 보일 수 있습니다. 예를 들어 서비스가 연동하는 다른 서비스의 위치를 하드코딩할 수 있습니다. 하지만 실제로 이러한 유형의 구성을 하드코딩하는 것은 위험합니다. 특히 서비스 위치가 자주 변경되는 현대적인 환경에서는 더욱 그렇습니다. 연동 대상 서비스를 직접 소유하고 있지 않다면 특히 더 위험합니다.
스크립트에서 서비스 위치를 최신 상태로 유지하는 데 자신의 꼼꼼함을 믿어도 된다고 생각할 수 있습니다. 더 나쁘게는, 위치가 변경될 때 해당 서비스를 담당하는 팀이 알려 줄 것이라고 믿을 수도 있습니다. 하지만 스트레스가 심한 상황에서는 꼼꼼함을 유지하기 어려운 경우가 많으며, 사람의 철저함에 의존하면 시스템이 아무런 경고 없이 장애를 일으킬 위험이 있습니다.
마이크로서비스 구성 시 권장 사항: 서비스가 “내 데이터베이스는 어디에 있지?”라고 묻게 하세요
위치 정보의 하드코딩 여부와 관계없이 애플리케이션이 핵심 인프라가 특정 위치에 있다는 전제에 의존해서는 안 됩니다. 대신 새로 배포된 서비스는 시스템 내의 공통 정보원에 “내 데이터베이스는 어디에 있는가?”와 같은 질문을 하고 해당 외부 리소스의 현재 위치에 대한 정확한 답을 받아야 합니다. 각 서비스가 배포될 때 시스템에 스스로 등록하도록 하면 훨씬 간단해집니다.
서비스를 구성으로 제공하기
시스템이 “내 데이터베이스는 어디에 있는가?”와 “내가 의존하는 ‘서비스 X’는 어디에 있는가?”라는 질문에 답할 수 있어야 하는 것처럼, 서비스 역시 다른 서비스가 배포 방식에 대해 전혀 알지 못하더라도 쉽게 찾아 통신할 수 있도록 시스템에 노출되어야 합니다.
마이크로서비스 아키텍처의 핵심 구성 방식 중 하나는 서비스 디스커버리입니다. 이는 새로운 서비스 정보를 등록하고, 다른 서비스가 해당 정보에 액세스할 때 이를 동적으로 업데이트하는 것을 의미합니다. 마이크로서비스에 서비스 디스커버리가 필요한 이유를 설명한 후, NGINX Open Source와 Consul을 사용하여 이를 구현하는 방법을 예제를 통해 살펴보겠습니다.
일반적으로 하나의 서비스에 대해 여러 인스턴스(배포)를 동시에 실행합니다. 이렇게 하면 추가 트래픽을 처리할 수 있을 뿐만 아니라, 새 배포를 시작하여 다운타임 없이 서비스를 업데이트할 수도 있습니다. NGINX와 같은 도구는 리버스 프록시 및 로드 밸런서 역할을 하여 수신 트래픽을 처리하고 가장 적합한 인스턴스로 라우팅합니다. 이는 서비스에 의존하는 다른 서비스가 NGINX에만 요청을 보내면 되고 배포에 대해서는 전혀 알 필요가 없다는 점에서 유용한 패턴입니다.
예를 들어 messenger라는 서비스의 인스턴스 하나가 리버스 프록시 역할을 하는 NGINX 뒤에서 실행 중이라고 가정해 보겠습니다.

이제 앱이 인기를 끌게 되면 어떻게 될까요? 물론 좋은 소식이지만, 트래픽이 증가하면서 messenger 인스턴스가 많은 CPU를 사용하고 요청 처리 시간도 길어지는 반면, 데이터베이스는 별문제 없이 작동하고 있다는 사실을 알게 됩니다. 이는 messenger 서비스의 인스턴스를 하나 더 배포하여 문제를 해결할 수 있음을 의미합니다.

두 번째 messenger 서비스 인스턴스를 배포하면 NGINX는 해당 인스턴스가 실행 중임을 어떻게 감지하고 트래픽을 보내기 시작할까요? NGINX 구성에 새 인스턴스를 수동으로 추가하는 방법도 있지만, 확장 또는 축소되는 서비스가 늘어나면 금세 관리하기 어려워집니다.
일반적인 해결 방법은 Consul과 같은 고가용성 서비스 레지스트리를 사용하여 시스템의 서비스를 추적하는 것입니다. 새 서비스 인스턴스는 배포될 때 Consul에 등록됩니다. Consul은 주기적으로 상태 확인 요청을 보내 인스턴스의 상태를 모니터링합니다. 상태 확인에 실패한 인스턴스는 사용 가능한 서비스 목록에서 제거됩니다.

NGINX는 다양한 방법을 사용하여 Consul과 같은 레지스트리를 조회하고 그에 따라 라우팅을 조정할 수 있습니다. NGINX가 리버스 프록시 또는 로드 밸런서로 작동할 때 트래픽을 “업스트림” 서버로 라우팅한다는 점을 기억하세요. 다음과 같은 간단한 구성을 살펴보겠습니다.
# 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가 트래픽을 라우팅하려면 각 messenger 인스턴스의 정확한 IP 주소와 포트를 알아야 합니다. 이 경우 172.18.0.7과 172.18.0.8 모두 포트 4000을 사용합니다.
이때 Consul과 Consul 템플릿이 사용됩니다. Consul 템플릿은 NGINX와 동일한 컨테이너에서 실행되며 서비스 레지스트리를 유지 관리하는 Consul 클라이언트와 통신합니다.
레지스트리 정보가 변경되면 Consul template은 올바른 IP 주소와 포트가 반영된 새 버전의 NGINX 구성 파일을 생성하여 NGINX 구성 디렉터리에 저장한 다음, NGINX에 구성을 다시 로드하도록 지시합니다. NGINX가 구성을 다시 로드하는 동안에는 다운타임이 발생하지 않으며, 다시 로드가 완료되는 즉시 새 인스턴스가 트래픽을 수신하기 시작합니다.
이러한 상황에서 NGINX와 같은 리버스 프록시를 사용하면 다른 서비스가 액세스할 위치로 시스템에 등록할 단일 접점이 생깁니다. 따라서 팀은 다른 서비스가 해당 서비스 전체에 액세스하지 못하게 될까 걱정할 필요 없이 개별 서비스 인스턴스를 유연하게 관리할 수 있습니다.
NGINX와 마이크로서비스를 직접 체험해 보는 3월
마이크로서비스는 서비스의 기술적 측면뿐만 아니라 다른 팀과의 관계라는 조직적 측면에서도 복잡성을 높이는 것이 사실입니다. 마이크로서비스 아키텍처의 이점을 누리려면 모놀리식 아키텍처를 위해 설계된 관행을 비판적으로 재검토하여, 매우 다른 환경에 적용했을 때도 동일한 이점을 제공하는지 확인하는 것이 중요합니다. 이 블로그에서는 12요소 앱의 요소 3이 마이크로서비스 환경에서도 여전히 가치를 제공하지만, 구체적인 적용 방식을 조금만 변경하면 더 큰 이점을 얻을 수 있다는 점을 살펴보았습니다.
마이크로서비스 아키텍처에 12요소 앱을 적용하는 방법을 자세히 알아보려면 Microservices March 2023의 유닛 1을 확인하세요(블로그에 곧 게시 예정). 무료로 등록하여 이 주제에 관한 웨비나와 실습 랩을 이용해 보세요.

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.