마이크로서비스 NGINX로 배포 및 구성하기
해당 포스트의 목적은 몇 가지 핵심 개념을 설명하는 것이며, 프로덕션 환경에서 마이크로서비스 를 배포하는 올바른 방법을 보여주는 것은 아닙니다. 실제 “마이크로서비스” 아키텍처를 사용하지만 몇 가지 중요한 주의 사항이 있습니다.
해당 포스트에서는 Kubernetes나 Nomad와 같은 컨테이너 오케스트레이션 프레임워크를 사용하지 않습니다. 이는 특정 프레임워크의 세부 사항에 얽매이지 않고 마이크로서비스 개념에 대해 배울 수 있도록 하기 위한 것입니다. 여기에 소개된 패턴은 이러한 프레임워크 중 하나를 실행하는 시스템으로 이식할 수 있습니다.
서비스는 소프트웨어 엔지니어링의 엄격함보다는 이해하기 쉽도록 최적화되어 있습니다. 핵심은 코드의 세부 사항이 아니라 시스템에서 서비스의 역할과 통신 패턴을 살펴보는 것입니다.
목차
1. 개요
2. 전제조건 및 설정
2-1. 전제조건
2-2. 설정
3. 애플리케이션 수준 마이크로서비스 구성 정의
3-1. 예시 (1)
3-2. 예시 (2)
4. 마이크로서비스에 대한 배포 스크립트 생성
4-1. 초기 배포 스크립트 생성
4-2. 외부 소스에서 구성 값을 쿼리하도록 배포 스크립트 수정
4-2-1. Consul KV 저장소 사용
4-3. 마이크로서비스 배포 스크립트에 Secret 설정
4-4. 마이크로서비스 배포 스크립트 실행
5. 외부에 마이크로서비스 노출
5-1. 마이크로서비스 에 대한 Consul 구성
5-2. 마이크로서비스 에 대한 NGINX 설정
6. 마이크로서비스를 Job Runner로 사용하여 데이터베이스 마이그레이션
6-1. 메신저 데이터베이스 마이그레이션
6-2. 메신저 서비스 테스트
7. 마이크로서비스 정리
8. 결론
1. 개요
이 자습서에서는 마이크로서비스 앱에 Factor 3 개념을 어떻게 적용하는지 설명합니다. 네 가지 과제에서는 일반적인 마이크로서비스 구성 패턴을 살펴보고 이러한 패턴을 사용하여 서비스를 배포하고 구성하는 방법을 알려줍니다.
목차 3과 목차 4에서는 마이크로서비스 앱의 구성을 어디에 배치해야 하는지에 대한 첫 번째 패턴을 살펴봅니다. 일반적으로 세 가지 위치가 있습니다:
- 애플리케이션 코드
- 애플리케이션의 배포 스크립트
- 배포 스크립트에서 액세스하는 외부 소스
5-1 목차에서는 두 가지 패턴을 추가로 설정합니다. NGINX를 사용하여 앱을 리버스 프록시로 외부에 노출하고 Consul을 사용하여 서비스 검색을 활성화합니다.
목차 6에서는 마이크로서비스 인스턴스를 ‘작업 실행자’로 사용하여 일반적인 기능과 다른 일회성 작업(이 경우 데이터베이스 마이그레이션 에뮬레이션)을 수행하는 마지막 패턴을 구현합니다.
이 자습서에서는 다음 네 가지 기술을 사용합니다:
- 메신저 – 이 자습서 위해 만들어진 간단한 채팅 API로 메시지 저장 기능이 있습니다.
- NGINX 오픈소스 – 메신저 서비스 및 전체 시스템의 진입점으로 사용됩니다.
- Consul – 동적 서비스 레지스트리 및 키-값 저장소입니다.
- RabbitMQ – 서비스 간 비동기 통신을 가능하게 해주는 인기 있는 오픈소스 메시지 브로커입니다.
2. 전제조건 및 설정
2-1. 전제조건
이 자습서를 진행하기 위해 필요한 환경은 다음과 같습니다:
- Linux/Unix 호환 환경
- Linux Command Line, JavaScript, bash에 대한 기본적인 이해 (하지만 모든 코드와 명령어가 제공되고 설명되므로 지식이 제한적이더라도 성공할 수 있습니다)
- Docker 및 Docker Compose
- Node.js 19.x 이상
- Node.js 19.x 버전을 테스트했지만, 더 최신 버전의 Node.js도 작동할 것으로 예상됩니다.
- Node.js 설치에 대한 자세한 정보는 메신저 서비스 저장소의 README를 참조하세요. 또한, 동일한 Node.js 버전을 컨테이너에서 사용하기 위해 asdf를 설치할 수도 있습니다.
- curl (대부분의 시스템에서 이미 설치되어 있을 것임)
- 자습서 개요에 나열된 네 가지 기술: 메신저 (다음 섹션에서 다운로드), NGINX 오픈소스, Consul, RabbitMQ
2-2. 설정
1. 터미널 세션을 시작하세요.
2. 홈 디렉토리에서 microservices-march 디렉토리를 생성하고 이 자습서를 위한 GitHub 저장소를 해당 디렉토리에 복제하세요. (다른 디렉토리 이름을 사용하고 지침을 적절하게 수정할 수도 있습니다.)
Note: 자습서 전체에서 Linux Command Line의 프롬프트는 생략되어 있으므로 명령어를 터미널에 복사하여 붙여넣기하기 쉽도록 하였습니다. 물결표(~)는 홈 디렉토리를 나타냅니다.
mkdir ~/microservices-march
cd ~/microservices-march
git clone https://github.com/microservices-march/platform.git --branch mm23-twelve-factor-start
git clone https://github.com/microservices-march/messenger.git --branch mm23-twelve-factor-start
mkdir ~/microservices-march
cd ~/microservices-march
git clone https://github.com/microservices-march/platform.git --branch mm23-twelve-factor-start
git clone https://github.com/microservices-march/messenger.git --branch mm23-twelve-factor-start
3. 플랫폼 Repository를 변경하고 Docker Compose를 설치합니다.
cd platform
docker compose up -d --build
이 명령은 RabbitMQ와 Consul을 시작하며, 이후의 과제에서 사용됩니다.
- -d 플래그는 Docker Compose에게 컨테이너가 시작되면 분리되도록 지시합니다 (그렇지 않으면 컨테이너가 터미널에 계속 연결됩니다).
- –build 플래그는 Docker Compose에게 시작 시 모든 이미지를 다시 빌드하도록 지시합니다. 이렇게 하면 실행 중인 이미지가 파일의 잠재적인 변경을 통해 업데이트되는 것이 보장됩니다.
4. messenger 저장소로 이동한 다음 Docker Compose를 시작하세요.
cd ../messenger
docker compose up -d --build
이 명령은 메신저 서비스를 위한 PostgreSQL 데이터베이스를 시작합니다. 이후의 자습서에서는 이를 messenger-database라고 지칭할 것입니다.
3. 애플리케이션 수준 마이크로서비스 구성 정의
이 과제에서는 자습서에서 살펴볼 세 가지 위치 중 첫 번째인 애플리케이션 수준에서 구성을 설정합니다. (Challenge 2에서는 두 번째와 세 번째 위치인 배포 스크립트와 외부 소스를 설명합니다.)
twelve‑factor 앱은 특히 애플리케이션 수준의 구성을 제외합니다. 왜냐하면 이러한 구성은 서로 다른 배포 환경(twelve‑factor 앱에서는 배포라고 함) 간에 변경할 필요가 없기 때문입니다. 그럼에도 불구하고, 우리는 완성도를 위해 세 가지 유형을 모두 다루고 있습니다. 서비스를 개발, 빌드 및 배포하는 과정에서 각 범주를 다루는 방법은 다릅니다.
메신저 서비스는 Node.js로 작성되었으며, 메신저 저장소의 app/index.mjs에 진입점이 있습니다. 파일의 다음 줄은:
app.use(express.json());
애플리케이션 수준의 구성의 예입니다. 이는 Express 프레임워크를 구성하여 application/json 유형의 요청 본문을 JavaScript 객체로 역직렬화합니다.
이 로직은 애플리케이션 코드와 긴밀하게 결합되어 있으며, twelve‑factor 앱이 “구성”으로 간주하는 것과는 다릅니다. 그러나 소프트웨어에서는 모든 것이 상황에 따라 달라집니다, 그렇지 않나요?
다음 두 목차에서는 이 줄을 수정하여 애플리케이션 수준의 구성의 두 가지 예를 구현합니다.
3-1. 예시 (1)
이 예제에서는 메신저 서비스가 허용하는 요청 본문의 최대 크기를 설정합니다. 이 크기 제한은 express.json 함수의 limit 인자를 통해 설정됩니다. 이는 Express API 문서에서 설명된 대로 Express 프레임워크의 JSON 미들웨어 구성에 limit 인자를 추가하는 것입니다.
1. 텍스트 편집기에서 app/index.mjs를 열고 다음을 바꿉니다.
app.use(express.json())
->
app.use(express.json({ limit: "20b" }));
2. 터미널(설치 단계에서 사용한 터미널)에서 app 디렉토리로 이동한 다음 메신저 서비스를 시작하세요.
cd app
npm install
node index.mjs
messenger_service listening on port 4000
3. 두 번째로, 별도의 터미널 세션을 시작하세요 (이후의 지침에서는 이를 “클라이언트 터미널”이라고 지칭합니다) 그리고 메신저 서비스에 POST 요청을 보내세요. 오류 메시지는 요청 본문이 1단계에서 설정한 20바이트 제한 내에 있기 때문에 요청이 성공적으로 처리되었음을 나타내지만, JSON Payload의 내용이 잘못되었다는 것을 나타냅니다.
curl -d '{ "text": "hello" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations
...
{ "error": "Conversation must have 2 unique users" }
약간 더 긴 메시지 본문을 보내세요 (다시 클라이언트 터미널에서). 3단계보다 훨씬 더 많은 출력이 표시되며, 이번에는 요청 본문이 20바이트를 초과한다는 오류 메시지도 포함됩니다.
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations
...
\”PayloadTooLargeError: request entity too large"
3-2. 예시 (2)
이 예제에서는 단일 파일에서 전체 구성 “스키마”를 정의할 수 있는 convict라는 라이브러리를 사용합니다. 또한, 12요소 앱의 3번째 요소에서 제시된 두 가지 지침을 설명합니다:
- 환경 변수에 구성 저장 – 앱을 수정하여 최대 본문 크기를 앱 코드에 하드코딩하는 대신 환경 변수(JSON_BODY_LIMIT)를 사용하여 설정합니다.
- 서비스 구성을 명확하게 정의 – 이는 마이크로서비스를 위한 3번째 요소를 적용한 것입니다.
이 예제는 Challenge 2에서 활용할 “plumbing”도 설정합니다. 해당 과제에서 생성할 메신저 배포 스크립트는 여기에 삽입할 JSON_BODY_LIMIT 환경 변수를 설정하며, 이는 배포 스크립트에서 지정된 구성의 예시로서 앱 코드에 삽입됩니다.
1. convict 구성 파일인 app/config/config.mjs를 열어서 amqpport 키 뒤에 다음과 같이 jsonBodyLimit 키를 추가합니다.
jsonBodyLimit: {
doc: `The max size (with unit included) that will be parsed by the
JSON middleware. Unit parsing is done by the
https://www.npmjs.com/package/bytes library.
ex: "100kb"`,
format: String,
default: null,
env: "JSON_BODY_LIMIT",
},
convict 라이브러리는 JSON_BODY_LIMIT 환경 변수를 사용하여 명령줄에서 최대 본문 크기를 설정하는데 다음과 같은 3단계를 거칩니다.
- 올바른 환경 변수에서 값을 가져옵니다.
- 변수의 유형(문자열)을 확인합니다.
- 애플리케이션에서 jsonBodyLimit 키 아래에서 변수에 액세스할 수 있도록 설정합니다.
2. app/index.mjs에서 다음과 같이 대체합니다.
app.use(express.json({ limit: "20b" }));
->
app.use(express.json({ limit: config.get("jsonBodyLimit") }));
3. 터미널(예제 1의 2단계에서 메신저 서비스를 시작한 곳)에서 Ctrl+c를 눌러 서비스를 중지한 후, JSON_BODY_LIMIT 환경 변수를 사용하여 최대 본문 크기를 27bytes로 설정하고 다시 시작합니다.
^c
JSON_BODY_LIMIT=27b node index.mjs
이는 사용 사례에 따라 구성 방법을 수정하는 것이 합당한 경우의 예시입니다. 앱 코드에서 값을 하드코딩하는 대신 twelve-factor 앱에서 권장하는 대로 환경 변수로 설정하는 것으로 전환한 것입니다.
위에서 언급한 대로, 챌린지 2에서는 명령줄에서 환경 변수를 설정하는 대신 메신저 서비스의 배포 스크립트를 사용하여 환경 변수를 설정할 때 JSON_BODY_LIMIT 환경 변수를 사용하는 것이 두 번째 설정 위치의 예시가 될 것입니다.
4. 클라이언트 터미널에서 예제 1의 4단계에서 curl 명령을 반복합니다(요청 본문이 더 커짐). 이제 크기 제한을 27바이트로 늘렸으므로 요청 본문이 더 이상 제한을 초과하지 않으며 요청이 처리되었지만 JSON 페이로드의 콘텐츠가 잘못되었다는 오류 메시지가 표시됩니다.
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations
{ "error": "Conversation must have 2 unique users" }
원한다면 클라이언트 터미널을 닫아도 됩니다. 나머지 목차의 모든 명령은 터미널에서 실행합니다.
5. 터미널에서 Ctrl+c를 눌러 메신저 서비스를 중지합니다(위 3단계에서 이 터미널에서 서비스를 중지했다가 다시 시작했었죠).
^c
6. 메신저 데이터베이스를 중지합니다. 플랫폼 Repository에 정의된 인프라 요소에서 네트워크를 계속 사용 중이므로 표시되는 오류 메시지는 무시해도 됩니다. 메신저 Repository의 루트에서 이 명령을 실행합니다.
docker compose down
...failed to remove network mm_2023....
4. 마이크로서비스에 대한 배포 스크립트 생성
이 목차에서는 마이크로서비스 환경에서 흔히 사용되는 패턴을 구현하여 infrastructure-as-code 및 배포 manifests의 기능을 모방하는 배포 스크립트를 생성하고, 스크립트를 수정하여 외부 구성 소스를 사용하고, secret 값을 설정한 다음 스크립트를 실행하여 서비스와 infrastructure를 배포합니다.
이 배포 스크립트는 메신저 저장소의 새로 생성된 infrastructure 디렉토리에 생성됩니다. infrastructure라는 디렉토리(또는 그와 유사한 이름)는 현대적인 마이크로서비스 아키텍처에서 일반적으로 사용되며 다음과 같은 항목을 저장하는 데 사용됩니다:
- infrastructure-as-code (Terraform, AWS CloudFormation, Google Cloud Deployment Manager, Azure Resource Manager 등)
- 컨테이너 오케스트레이션 시스템의 구성 (예: Helm 차트 및 Kubernetes 매니페스트)
- 애플리케이션 배포와 관련된 기타 파일
이 패턴의 이점은 다음과 같습니다:
- 서비스 배포 및 서비스별 인프라스트럭처(예: 데이터베이스) 배포의 소유권을 서비스를 소유한 팀에 할당합니다.
- 팀은 이러한 요소 중 어떤 것이든 자체 개발 프로세스(코드 리뷰, CI 등)를 거치도록 할 수 있습니다.
- 팀은 외부 팀이 작업을 수행하는 것에 의존하지 않고 서비스 및 지원 인프라스트럭처의 배포 방식을 쉽게 변경할 수 있습니다.
앞서 언급했듯이, 해당 포스트에서는 실제 시스템을 설정하는 방법을 보여주는 것이 목적이 아니며, 이 목차에서 배포하는 스크립트는 실제 프로덕션 시스템과는 다릅니다. 대신, 이러한 마이크로서비스 관련 인프라스트럭처 배포와 관련된 tool 별 구성의 핵심 개념과 해결되는 문제를 보여주며, tool의 특정한 부분을 최소한으로 추상화합니다.
4-1. 초기 배포 스크립트 생성
1. 터미널에서 메신저 저장소의 루트 경로에 infrastructure 디렉토리를 생성하고, 메신저 서비스와 메신저 데이터베이스의 배포 스크립트를 담을 파일을 생성하세요. 환경에 따라 chmod 명령 앞에 sudo를 붙여야 할 수도 있습니다.
mkdir infrastructure
cd infrastructure
touch messenger-deploy.sh
chmod +x messenger-deploy.sh
touch messenger-db-deploy.sh
chmod +x messenger-db-deploy.sh
2. 선호하는 텍스트 편집기에서 메신저 배포 스크립트를 만들기 위해 messenger-deploy.sh 파일을 열고 다음 내용을 추가합니다.
#!/bin/bash
set -e
JSON_BODY_LIMIT=20b
docker run \
--rm \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
messenger
이 스크립트는 아직 완벽하지 않지만 몇 가지 개념을 보여줍니다.
- 환경 변수에 값을 할당하기 위해 배포 스크립트에 해당 구성을 직접 포함시킵니다.
- 이 스크립트는 실행 시 컨테이너에 환경 변수를 주입하기 위해 docker run 명령의 -e 옵션을 사용합니다.
환경 변수의 값을 설정하는 방식이 중복되어 보일 수 있지만, 배포 스크립트가 복잡해져도 스크립트의 맨 위를 빠르게 살펴보고 구성 데이터가 배포에 어떻게 제공되는지 이해할 수 있습니다.
또한 실제 배포 스크립트에서는 명시적으로 docker run 명령을 호출하지 않을 수도 있지만, 이 샘플 스크립트는 Kubernetes 매니페스트와 같은 것을 통해 해결되는 핵심 문제를 전달하기 위한 것입니다. Kubernetes와 같은 컨테이너 오케스트레이션 시스템을 사용하는 경우, 배포는 컨테이너를 시작하고 Kubernetes 구성 파일에서 파생된 애플리케이션 구성을 해당 컨테이너에서 사용할 수 있도록 합니다. 따라서 이 샘플 배포 파일은 배포 스크립트의 최소 버전으로 간주할 수 있으며, Kubernetes manifest와 같은 framework 별 배포 파일과 동일한 역할을 수행합니다.
실제 개발 환경에서는 이 파일을 소스 제어에서 확인하고 코드 검토를 거칠 수 있습니다. 이렇게 하면 다른 팀원들이 설정에 대해 의견을 제시할 수 있으므로 잘못된 설정 값으로 인해 예기치 않은 동작이 발생하는 사고를 방지하는 데 도움이 됩니다. 예를 들어, 이 스크린샷에서 한 팀원이 수신 JSON 요청 본문(JSON_BODY_LIMIT로 설정)에 대한 20bytes 제한이 너무 낮다고 지적하고 있습니다.

4-2. 외부 소스에서 구성 값을 쿼리하도록 배포 스크립트 수정
이 목차에서는 마이크로서비스 구성을 위한 세 번째 위치, 즉 배포 시점에 쿼리되는 외부 소스를 설정합니다. 값은 동적으로 등록되고 배포 시점에 외부 소스에서 가져오는 것이 지속적으로 업데이트되어야 하며, 하드코딩된 값보다 훨씬 더 나은 방법입니다.
이 시점에서는 메신저 서비스에 필요한 보조 서비스를 제공하기 위해 두 개의 인프라 구성 요소가 백그라운드에서 실행되고 있습니다.
- 실제 배포에서 플랫폼 팀이 소유하는 RabbitMQ
- 실제 배포에서 팀이 소유한 메신저 데이터베이스
app/config/config.mjs의 메신저 서비스에 대한 convict 스키마는 이러한 외부 구성에 해당하는 필수 환경 변수를 정의합니다. 이 섹션에서는 이 두 구성 요소를 설정하여 일반적으로 액세스할 수 있는 위치에 변수 값을 설정하여 메신저 서비스가 배포될 때 해당 변수를 쿼리할 수 있도록 합니다.
RabbitMQ와 메신저 데이터베이스에 필요한 연결 정보는 모든 서비스가 배포될 때 액세스할 수 있는 공통 위치인 Consul 키/값(KV) 스토어에 등록됩니다. Consul KV 저장소는 이러한 유형의 데이터를 저장하는 표준 위치는 아니지만, 이 목차에서는 단순화를 위해 이 저장소를 사용합니다.
4-2-1. Consul KV 저장소 사용
1. 이전 섹션의 2단계에서 만든 infrastructure/messenger-deploy.sh의 내용을 다음과 같이 변경합니다.
#!/bin/bash
set -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)
docker run \
--rm \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
messenger
- 이 스크립트는 두 가지 유형의 구성을 보여줍니다:
- 배포 스크립트에서 직접 지정한 구성 – 배포 환경(NODE_ENV)과 포트(PORT)를 설정하고 JSON_BODY_LIMIT를 20바이트보다 더 현실적인 값인 100KB로 변경합니다.
- 외부 소스에서 쿼리한 구성 – Consul KV 스토어에서 POSTGRES_USER, PGPORT, PGHOST, AMQPHOST, AMQPPORT 환경 변수의 값을 가져옵니다. 다음 두 단계로 Consul KV 저장소에서 환경
- 변수 값을 설정합니다.
2. 메신저 데이터베이스에 대한 초기 배포 스크립트를 생성하기 위해 messenger-db-deploy.sh를 열고 다음을 추가합니다.
#!/bin/bash
set -e
PORT=5432
POSTGRES_USER=postgres
docker run \
-d \
--rm \
--name messenger-db \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
# Register details about the database with Consul
curl -X PUT http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
curl -X PUT http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db' # This matches the "--name" flag above
# (the hostname)
curl -X PUT http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"
이 스크립트는 배포 시 메신저 서비스에서 쿼리할 수 있는 구성을 정의하는 것 외에도 초기 배포 스크립트 만들기의 메신저 서비스 초기 스크립트와 동일한 두 가지 개념을 설명합니다.
- 배포 스크립트에서 직접 특정 구성을 지정합니다. 이 경우 PostgreSQL 데이터베이스에 실행할 포트와 기본 사용자의 사용자 이름을 알려줍니다.
- 런타임에 컨테이너에 환경 변수를 주입하기 위해 -e 플래그를 사용하여 Docker를 실행합니다. 또한 실행 중인 컨테이너의 이름을 messenger-db로 설정하는데, 이는 설정 2단계에서 플랫폼 서비스를 시작할 때 생성한 Docker 네트워크에서 데이터베이스의 hostname이 됩니다.
3. 실제 배포에서는 일반적으로 플랫폼 리포지토리에 있는 메신저 데이터베이스의 경우와 마찬가지로 플랫폼 리포지토리에 있는 RabbitMQ와 같은 서비스의 배포 및 유지 관리를 처리하는 것은 플랫폼팀입니다. 그런 다음 플랫폼 팀은 해당 인프라에 의존하는 서비스에서 해당 인프라의 위치를 검색할 수 있는지 확인합니다. 이 자습서의 목적에 따라 RabbitMQ 값을 직접 설정하세요.
curl -X PUT --silent --output /dev/null --show-error --fail \
-H "Content-Type: application/json" \
-d "rabbitmq" \
http://localhost:8500/v1/kv/amqp-host
curl -X PUT --silent --output /dev/null --show-error --fail \
-H "Content-Type: application/json" \
-d "5672" \
http://localhost:8500/v1/kv/amqp-port
4-3. 마이크로서비스 배포 스크립트에 Secret 설정
메신저 서비스의 배포 스크립트에는 하나의 중요한 데이터가 빠져 있습니다 바로 메신저 데이터베이스의 비밀번호입니다!
참고: 시크릿 관리는 이 자습서의 초점이 아니므로 간단함을 위해 시크릿을 배포 파일에 정의합니다. 실제 환경(개발, 테스트 또는 프로덕션)에서는 절대로 이렇게 하지 마세요. 이는 매우 큰 보안 위험을 초래합니다.
1. infrastructure/messenger-db-deploy.sh의 내용을 다음과 같이 변경하여 messenger-database의 비밀번호 시크릿을 Consul KV 저장소에 저장하세요.
#!/bin/bash
set -e
PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
POSTGRES_PASSWORD=postgres
docker run \
--rm \
--name messenger-db-primary \
-d \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db-primary' # This matches the "--name" flag above
# which for our setup means the hostname
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"
echo "Register key messenger-db-password-never-do-this\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_PASSWORD}"
printf "\nDone registering postgres details with Consul\n"
2. infrastructure/messenger-deploy.sh의 내용을 다음과 같이 변경하여 messenger-database의 비밀번호 시크릿을 Consul KV 저장소에서 가져오세요.
#!/bin/bash
set -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true)
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)
docker run \
--rm \
-d \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
--network mm_2023 \
messenger
4-4. 마이크로서비스 배포 스크립트 실행
1. 메신저 저장소의 app 디렉토리로 이동하여 메신저 서비스의 Docker 이미지를 빌드하세요.
cd ../app
docker build -t messenger .
2. 플랫폼 서비스에 속하는 컨테이너만 실행 중인지 확인하세요.
docker ps --format '{{.Names}}'
consul-server
consul-client
rabbitmq
3. 메신저 저장소의 루트로 이동하여 메신저 데이터베이스와 메신저 서비스를 배포하세요.
cd ..
./infrastructure/messenger-db-deploy.sh
./infrastructure/messenger-deploy.sh
messenger-db-deploy.sh 스크립트는 messenger-database를 시작하고 해당 정보를 시스템(이 경우 Consul KV 저장소)에 등록합니다.
그런 다음 messenger-deploy.sh 스크립트는 애플리케이션을 시작하고 messenger-db-deploy.sh에 의해 시스템에 등록된 구성을 가져옵니다(다시 말해, Consul KV 저장소).
힌트: 컨테이너가 시작되지 않는 경우 배포 스크립트의 docker run 명령에 두 번째 매개변수( -d \ 라인)를 제거하고 스크립트를 다시 실행하세요. 그러면 컨테이너가 전면에서 실행되므로 로그가 터미널에 표시되어 문제를 식별할 수 있습니다. 문제를 해결하면 실제 컨테이너가 백그라운드에서 실행되도록 -d \ 라인을 복원하세요.
4. 배포가 성공했는지 확인하기 위해 애플리케이션에 간단한 헬스 체크 요청을 보내세요.
curl localhost:4000/health
curl: (7) Failed to connect to localhost port 4000 after 11 ms: Connection refused
이런, 실패입니다! 실제로 하나의 중요한 구성 요소가 빠져 있고 메신저 서비스가 더 넓은 시스템에 노출되지 않았습니다. 메신저 서비스는 mm_2023 네트워크 내에서 행복하게 실행되고 있지만, 해당 네트워크는 Docker 내부에서만 접근할 수 있습니다.
5. 다음 목차에서 새 이미지를 생성하기 위해 실행 중인 컨테이너를 중지하세요.
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))
5. 외부에 마이크로서비스 노출
프로덕션 환경에서는 일반적으로 서비스를 직접 노출하지 않습니다. 대신 일반적인 마이크로서비스 패턴을 따라 메인 서비스 앞에 리버스 프록시 서비스를 배치합니다.
이 과제에서는 서비스 디스커버리를 설정하여 메신저 서비스를 외부에 노출합니다. 이는 새로운 서비스 정보의 등록 및 다른 서비스가 액세스할 때 해당 정보의 동적 업데이트를 의미합니다. 이를 위해 다음 기술을 사용합니다.
- 동적 서비스 레지스트리인 Consul 및 Consul 데이터를 기반으로 파일을 동적으로 업데이트하는 도구인 Consul 템플릿
- NGINX 오픈소스 – 컨테이너에서 실행되는 애플리케이션의 여러 개별 인스턴스로 구성되는 메신저 서비스 의 단일 진입점을 노출하는 리버스 프록시 및 로드 밸런서
5-1. 마이크로서비스 에 대한 Consul 구성
메신저 저장소의 app/consul/index.mjs 파일에는 메신저 서비스를 시작할 때 Consul에 등록하고 정상적인 종료 시에는 등록을 해제하기 위해 필요한 모든 코드가 포함되어 있습니다. 이 파일은 Consul의 서비스 레지스트리에 새로 배포된 서비스를 등록하는 register라는 함수를 노출합니다.
1. 선호하는 텍스트 편집기에서 app/index.mjs 파일을 열고 다음 스니펫을 다른 import 문 다음에 추가하여 app/consul/index.mjs에서 register 함수를 가져옵니다.
import { register as registerConsul } from "./consul/index.mjs";
그런 다음 스크립트의 끝에 있는 SERVER START 섹션을 아래와 같이 수정하여 애플리케이션이 시작된 후 registerConsul()을 호출하도록 변경하세요.
/* =================
SERVER START
================== */
app.listen(port, async () => {
console.log(`messenger_service listening on port ${port}`);
registerConsul();
});
export default app;
2. 3-2의 예시 1에서 추가한 jsonBodyLimit 키 다음에 app/config/config.mjs의 convict 스키마를 열고 다음 구성 값을 추가하세요.
consulServiceName: {
doc: "The name by which the service is registered in Consul. If not specified, the service is not registered",
format: "*",
default: null,
env: "CONSUL_SERVICE_NAME",
},
consulHost: {
doc: "The host where the Consul client runs",
format: String,
default: "consul-client",
env: "CONSUL_HOST",
},
consulPort: {
doc: "The port for the Consul client",
format: "port",
default: 8500,
env: "CONSUL_PORT",
},
이는 새로운 서비스가 등록되는 이름을 구성하고 Consul 클라이언트의 호스트 이름과 포트를 정의합니다. 다음 단계에서는 메신저 서비스의 배포 스크립트를 수정하여 이 새로운 Consul 연결 및 서비스 등록 정보를 포함시킵니다.
3. infrastructure/messenger-deploy.sh 파일을 열고 이전 단계에서 설정한 Consul 연결 및 서비스 등록 정보를 메신저 서비스 구성에 포함시키기 위해 다음 내용으로 내용을 바꿉니다.
#!/bin/bash
set -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
CONSUL_SERVICE_NAME="messenger"
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-application-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-host?raw=true")
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true")
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")
docker run \
--rm \
-d \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
-e CONSUL_HOST="${CONSUL_HOST}" \
-e CONSUL_PORT="${CONSUL_PORT}" \
-e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
--network mm_2023 \
messenger
주요 사항은 다음과 같습니다:
- CONSUL_SERVICE_NAME 환경 변수는 메신저 서비스 인스턴스가 자신을 Consul에 등록할 때 사용할 이름을 지정합니다.
- CONSUL_HOST 및 CONSUL_PORT 환경 변수는 배포 스크립트가 실행되는 위치에서 실행되는 Consul 클라이언트를 위한 것입니다.
참고: 실제 배포에서는 이러한 연결 정보 없이 서비스가 Consul을 쿼리할 수 없으므로 Consul을 담당하는 팀은 모든 환경에서 CONSUL_HOST 및 CONSUL_PORT 환경 변수를 제공해야 하는 것과 같이 팀 간에 합의된 구성 예제입니다.
4. 터미널에서 app 디렉토리로 이동하여 실행 중인 메신저 서비스 인스턴스를 중지하고 새로운 서비스 등록 코드를 포함시키기 위해 Docker 이미지를 다시 빌드하세요.
cd app
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))
docker build -t messenger .
5. 브라우저에서 http://localhost:8500으로 이동하여 Consul UI가 작동하는 것을 확인하세요.
6. 메신저 저장소의 루트에서 배포 스크립트를 실행하여 메신저 서비스의 인스턴스를 시작하세요.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
7. 브라우저의 Consul UI에서 헤더 바의 Services를 클릭하여 메신저 서비스가 단일로 실행되고 있는지 확인하세요.

8. 배포 스크립트를 몇 번 더 실행하여 메신저 서비스의 여러 인스턴스를 시작하세요. Consul UI에서 실행 중인지 확인하세요.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh

5-2. 마이크로서비스 에 대한 NGINX 설정
다음 단계는 NGINX Open Source를 역방향 프록시 및 로드 밸런서로 추가하여 들어오는 트래픽을 실행 중인 모든 메신저 인스턴스로 라우팅하는 것입니다.
1. app 터미널에서 메신저 저장소의 루트 디렉토리로 이동하고 load-balancer라는 디렉토리를 만들고 세 개의 파일을 생성하세요.
mkdir load-balancer
cd load-balancer
touch nginx.ctmpl
touch consul-template-config.hcl
touch Dockerfile
Dockerfile은 NGINX와 Consul 템플릿이 실행되는 컨테이너를 정의합니다. Consul 템플릿은 메신저 서비스가 변경될 때(서비스 인스턴스가 등록되거나 종료될 때) NGINX upstream을 동적으로 업데이트하기 위해 다른 두 파일을 사용합니다.
2. 바로 위 Step 1에서 생성된 nginx.ctmpl 파일을 열고 Consul 템플릿이 NGINX upstream 그룹을 동적으로 업데이트하는 데 사용하는 다음 NGINX 구성 스니펫을 추가하세요.
upstream messenger_service {
{{- range service "messenger" }}
server {{ .Address }}:{{ .Port }};
{{- end }}
}
server {
listen 8085;
server_name localhost;
location / {
proxy_pass http://messenger_service;
add_header Upstream-Host $upstream_addr;
}
}
이 스니펫은 각 메신저 서비스 인스턴스의 IP 주소와 포트 번호를 Consul에 등록된대로 NGINX messenger_service upstream 그룹에 추가합니다. NGINX는 동적으로 정의된 업스트림 서비스 인스턴스 집합으로 들어오는 요청을 프록시합니다.
3. Step 1에서 생성된 consul-template-config.hcl 파일을 열고 다음 구성을 추가하세요.
consul {
address = "consul-client:8500"
retry {
enabled = true
attempts = 12
backoff = "250ms"
}
}
template {
source = "/usr/templates/nginx.ctmpl"
destination = "/etc/nginx/conf.d/default.conf"
perms = 0600
command = "if [ -e /var/run/nginx.pid ]; then nginx -s reload; else nginx; fi"
}
Consul 템플릿의 이 구성은 소스 템플릿(이전 단계에서 생성된 NGINX 구성 스니펫)을 다시 렌더링하여 지정된 대상에 배치하고 지정된 명령을 실행하도록 지시합니다(이는 NGINX에 구성을 다시로드하도록 알려줍니다).
실제로는 Consul에서 서비스 인스턴스가 등록, 업데이트 또는 등록 해제될 때마다 새로운 default.conf 파일이 생성됩니다. 그런 다음 NGINX는 구성을 다시로드하여 다운타임 없이 최신 및 정상적인 서버(메신저 서비스 인스턴스) 집합을 유지하고 트래픽을 보낼 수 있도록 합니다.
4. Step 1에서 생성된 Dockerfile 파일을 열고 다음 내용을 추가하세요. 이는 NGINX 서비스를 빌드합니다. (이 자습서의 목적을 위해 Dockerfile을 이해할 필요는 없지만 코드는 편의를 위해 인라인으로 문서화되어 있습니다.)
FROM nginx:1.23.1
ARG CONSUL_TEMPLATE_VERSION=0.30.0
# Set an environment variable for the location of the Consul
# cluster. By default, it tries to resolve to consul-client:8500
# which is the behavior if Consul is running as a container in the
# same host and linked to this NGINX container (with the alias
# consul, of course). But this environment variable can also be
# overridden as the container starts if we want to resolve to
# another address.
ENV CONSUL_URL consul-client:8500
# Download the specified version of Consul template
ADD https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION}/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip /tmp
RUN apt-get update \
&& apt-get install -y --no-install-recommends dumb-init unzip \
&& unzip /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip -d /usr/local/bin \
&& rm -rf /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip
COPY consul-template-config.hcl ./consul-template-config.hcl
COPY nginx.ctmpl /usr/templates/nginx.ctmpl
EXPOSE 8085
STOPSIGNAL SIGQUIT
CMD ["dumb-init", "consul-template", "-config=consul-template-config.hcl"
5. Docker 이미지를 빌드하세요.
docker build -t messenger-lb .
6. 메신저 디렉토리의 루트로 이동하여 messenger-load-balancer-deploy.sh라는 파일을 생성하세요. 이 파일은 NGINX 서비스의 배포 파일로 사용됩니다(튜토리얼 전체에서 배포한 다른 서비스와 마찬가지로). 환경에 따라 chmod 명령 앞에 sudo 접두사를 추가해야 할 수 있습니다.
cd ..
touch infrastructure/messenger-load-balancer-deploy.sh
chmod +x infrastructure/messenger-load-balancer-deploy.sh
7. messenger-load-balancer-deploy.sh 파일을 열고 다음 내용을 추가하세요.
#!/bin/bash
set -e
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
docker run \
--rm \
-d \
--name messenger-lb \
-e CONSUL_URL="${CONSUL_HOST}:${CONSUL_PORT}" \
-p 8085:8085 \
--network mm_2023 \
messenger-lb
8. 이제 모든 것이 준비되었으므로 NGINX 서비스를 배포합니다.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-load-balancer-deploy.sh
9. 외부에서 메신저 서비스에 액세스할 수 있는지 확인해보세요.
curl -X GET http://localhost:8085/health
OK
작동합니다! NGINX는 생성된 모든 메신저 서비스 인스턴스 간에 로드 밸런싱을 수행합니다. 이를 확인할 수 있는 이유는 이전 섹션의 8단계에서 Consul UI에 표시된 것과 동일한 메신저 서비스 IP 주소가 X-Forwarded-For 헤더에 표시되기 때문입니다.
6. 마이크로서비스를 Job Runner로 사용하여 데이터베이스 마이그레이션
대규모 애플리케이션은 종종 Sidekiq나 Celery와 같은 작은 워커 프로세스를 사용하는 “작업 실행기”를 활용하여 데이터 수정과 같은 일회성 작업을 수행합니다. 이러한 도구들은 종종 Redis나 RabbitMQ와 같은 추가 지원 인프라를 필요로 합니다. 이 경우, 메신저 서비스 자체를 “작업 실행기”로 사용하여 일회성 작업을 실행합니다. 이는 이미 충분히 작고, 데이터베이스 및 종속되는 다른 인프라 요소와 완전히 상호 작용할 수 있으며, 트래픽을 처리하는 애플리케이션과 완전히 별개로 실행되기 때문에 이해할 수 있습니다.
이를 수행하는 데 세 가지 이점이 있습니다.
- 작업 실행기(실행하는 스크립트 포함)는 제품 서비스와 정확히 동일한 검사 및 검토 과정을 거칩니다.
- 데이터베이스 사용자와 같은 구성 값은 쉽게 변경하여 제품 배포를 보다 안전하게 만들 수 있습니다. 예를 들어, 기존 테이블에서만 쓰기 및 쿼리할 수 있는 “낮은 권한” 사용자로 제품 서비스를 실행할 수 있습니다. 다른 서비스 인스턴스를 구성하여 테이블을 생성하고 제거할 수 있는 “고급 권한” 사용자로 데이터베이스 구조를 변경할 수 있습니다.
- 일부 팀은 작업을 처리하는 동시에 서비스 제품 트래픽을 처리하는 인스턴스에서 작업을 실행합니다. 이는 작업에 문제가 발생할 경우 컨테이너의 애플리케이션이 수행하는 다른 기능에 영향을 줄 수 있기 때문에 위험합니다. 이러한 문제를 피하기 위해 우리는 처음부터 마이크로서비스를 구축하는 것이 아닌가요?
이 목차에서는 데이터베이스 구성 값을 변경하고 메신저 데이터베이스를 새 값으로 마이그레이션하여 성능을 테스트하여 새로운 역할을 수행하는 아티팩트를 수정하는 방법을 탐색합니다.
6-1. 메신저 데이터베이스 마이그레이션
실제 프로덕션 배포에서는 “애플리케이션 사용자”와 “마이그레이션 사용자”라는 서로 다른 권한을 가진 두 개의 사용자를 생성할 수 있습니다. 간단하게 예를 들면, 이 예제에서는 기본 사용자를 애플리케이션 사용자로 사용하고, 슈퍼유저 권한을 가진 마이그레이션 사용자를 생성합니다. 실제 상황에서는 각 사용자의 역할에 따라 필요한 구체적인 최소 권한을 결정하는 데 더 많은 시간을 투자하는 것이 좋습니다.
1. 터미널에서 슈퍼유저 권한을 가진 새로운 PostgreSQL 사용자를 생성하세요.
echo "CREATE USER messenger_migrator WITH SUPERUSER PASSWORD 'migrator_password';" | docker exec -i messenger-db-primary psql -U postgres
2. 데이터베이스 배포 스크립트 (infrastructure/messenger-db-deploy.sh)를 열고 새로운 사용자의 자격 증명을 추가하기 위해 내용을 변경하세요.
Note: 실제 프로덕션 배포에서는 데이터베이스 자격 증명과 같은 비밀을 배포 스크립트나 비밀 관리 도구 이외의 다른 곳에 절대로 노출하지 마세요. 이를 다시 강조하고자 합니다.
#!/bin/bash
set -e
PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
# Because we’re focusing on other concepts in this tutorial, we
# set the password this way here for convenience.
POSTGRES_PASSWORD=postgres
# Migration user
POSTGRES_MIGRATOR_USER=messenger_migrator
# NOTE: As above, never do this in a real deployment.
POSTGRES_MIGRATOR_PASSWORD=migrator_password
docker run \
--rm \
--name messenger-db-primary \
-d \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db-primary' # This matches the "--name" flag above
# which for our setup means the hostname
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_PASSWORD}"
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_MIGRATOR_USER}"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_MIGRATOR_PASSWORD}"
printf "\nDone registering postgres details with Consul\n"
이 변경은 데이터베이스 배포 후 Consul에 설정되는 사용자 집합에 마이그레이션 사용자를 추가합니다.
3. infrastructure 디렉토리에 messenger-db-migrator-deploy.sh라는 새 파일을 생성하세요. (다시 말하지만, chmod 명령 앞에 sudo 접두사를 추가해야 할 수 있습니다.)
touch infrastructure/messenger-db-migrator-deploy.sh
chmod +x infrastructure/messenger-db-migrator-deploy.sh
4. messenger-db-migrator-deploy.sh 파일을 열고 다음 내용을 추가하세요.
#!/bin/bash
set -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
CONSUL_SERVICE_NAME="messenger-migrator"
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
# Get the migrator user name and password
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this?raw=true")
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")
docker run \--rm \
-d \
--name messenger-migrator \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
-e CONSUL_HOST="${CONSUL_HOST}" \
-e CONSUL_PORT="${CONSUL_PORT}" \
-e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
--network mm_2023 \
messenger
이 스크립트는 Set Up Consul의 3단계에서 생성한 infrastructure/messenger-deploy.sh 스크립트와 매우 유사한 형태입니다. 주요한 차이점은 CONSUL_SERVICE_NAME이 messenger 대신 messenger-migrator로 설정되고, PGUSER가 위의 1단계에서 생성한 “migrator” 슈퍼유저와 일치한다는 것입니다.
CONSUL_SERVICE_NAME이 messenger-migrator로 설정되어야 합니다. 만약 messenger로 설정된다면, NGINX는 이 서비스를 자동으로 회전시켜 API 호출을 받게 되는데, 이는 어떤 트래픽도 처리하지 않도록 의도되어 있지 않습니다.
이 스크립트는 migrator 역할로 짧은 수명의 인스턴스를 배포합니다. 이렇게 함으로써 마이그레이션에 문제가 발생해도 주요 메신저 서비스 인스턴스가 트래픽을 처리하는 데 영향을 주지 않습니다.
5. PostgreSQL 데이터베이스를 다시 배포하세요. 이 튜토리얼에서는 bash 스크립트를 사용하고 있으므로 데이터베이스 서비스를 중지하고 다시 시작해야 합니다. 실제 프로덕션 애플리케이션에서는 변경된 요소만 추가하는 infrastructure-as-code 스크립트를 실행하는 것이 일반적입니다.
docker stop messenger-db-primary
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-deploy.sh
6. PostgreSQL 데이터베이스 마이그레이션 서비스를 배포하세요.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-migrator-deploy.sh
7. 인스턴스가 예상대로 실행되는지 확인하세요.
docker ps --format "{{.Names}}"
...
messenger-migrator
또한 Consul UI에서 데이터베이스 마이그레이션 서비스가 messenger-migrator로 올바르게 자신을 Consul에 등록했는지 확인할 수 있습니다 (다시 말하지만, 트래픽을 처리하지 않기 때문에 messenger 이름으로 등록되지 않습니다).

8. 이제 마지막 단계입니다. 데이터베이스 마이그레이션 스크립트를 실행하세요! 이 스크립트는 실제 데이터베이스 마이그레이션 스크립트와는 다르지만, messenger-migrator 서비스를 사용하여 데이터베이스별 스크립트를 실행합니다. 데이터베이스가 마이그레이션된 후에는 messenger-migrator 서비스를 중지하세요.
docker exec -i -e PGDATABASE=postgres -e CREATE_DB_NAME=messenger messenger-migrator node scripts/create-db.mjs
docker exec -i messenger-migrator node scripts/create-schema.mjs
docker exec -i messenger-migrator node scripts/create-seed-data.mjs
docker stop messenger-migrator
6-2. 메신저 서비스 테스트
이제 messenger 데이터베이스를 최종 형식으로 마이그레이션했으므로 messenger 서비스가 실제로 작동하는 것을 확인할 수 있습니다! 이를 위해 NGINX 서비스에 대해 몇 가지 기본적인 curl 쿼리를 실행합니다 (NGINX를 시스템의 진입점으로 설정했습니다).
다음 명령 중 일부는 JSON 출력을 형식화하기 위해 jq 라이브러리를 사용합니다. 필요한 경우 설치하거나 원하는 경우 명령 줄에서 생략할 수 있습니다.
1. 대화를 생성하세요.
curl -d '{"participant_ids": [1, 2]}' -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations'
{
"conversation": { "id": "1", "inserted_at": "YYYY-MM-DDT06:41:59.000Z" }
}
2. ID가 1인 사용자로부터 대화에 메시지를 보내세요.
curl -d '{"content": "This is the first message"}' -H "User-Id: 1" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq
{
"message": {
"id": "1",
"content": "This is the first message",
"index": 1,
"user_id": 1,
"username": "James Blanderphone",
"conversation_id": 1,
"inserted_at": "YYYY-MM-DDT06:42:15.000Z"
}
}
3. 다른 사용자 (ID가 2인 사용자)로부터 메시지로 회신하세요.
curl -d '{"content": "This is the second message"}' -H "User-Id: 2" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq
{
"message": {
"id": "2",
"content": "This is the second message",
"index": 2,
"user_id": 2,
"username": "Normalavian Ropetoter",
"conversation_id": 1,
"inserted_at": "YYYY-MM-DDT06:42:25.000Z"
}
}
4. 메세지를 가져오세요.
curl -X GET 'http://localhost:8085/conversations/1/messages' | jq
{
"messages": [
{
"id": "1",
"content": "This is the first message",
"user_id": "1",
"channel_id": "1",
"index": "1",
"inserted_at": "YYYY-MM-DDT06:42:15.000Z",
"username": "James Blanderphone"
},
{
"id": "2",
"content": "This is the second message",
"user_id": "2",
"channel_id": "1",
"index": "2",
"inserted_at": "YYYY-MM-DDT06:42:25.000Z",
"username": "Normalavian Ropetoter"
}
]
}
7. 마이크로서비스 정리
자습서를 진행하는 동안 상당한 수의 컨테이너와 이미지를 생성했습니다! 원하지 않는 Docker 컨테이너와 이미지를 제거하기 위해 다음 명령을 사용하세요.
- 다음 명령을 사용하여 실행 중인 Docker 컨테이너를 제거하세요.
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))
docker rm $(docker stop messenger-db-primary)
docker rm $(docker stop messenger-lb)
- 플랫폼 서비스를 제거하세요.
# From the platform repository
docker compose down
- 튜토리얼 전체에서 사용된 모든 Docker 이미지를 제거하세요.
docker rmi messenger
docker rmi messenger-lb
docker rmi postgres:15.1
docker rmi hashicorp/consul:1.14.4
docker rmi rabbitmq:3.11.4-management-alpine
8. 결론
“이렇게 간단한 것을 설정하는 데 많은 작업이 필요한 것 같다”고 생각하실 수도 있습니다 – 그리고 맞습니다! 마이크로서비스 중심의 아키텍처로 전환하려면 서비스의 구조와 구성에 대해 꼼꼼함이 필요합니다. 모든 복잡성에도 불구하고, 몇 가지 확실한 진전을 이루었습니다.
NGINX를 통해 마이크로서비스를 구축하는 자세한 방법은 NGINX STORE에 문의하여 상담 받아보세요.
아래 뉴스레터를 구독하고 NGINX와 NGINX STORE의 최신 정보들을 빠르게 전달 받아보세요.
댓글을 달려면 로그인해야 합니다.