블로그 | NGINX

NGINX 튜토리얼: 마이크로서비스 배포 및 구성 방법

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

이 게시물은 Microservices March 2023의 개념을 실제로 구현하는 데 도움이 되는 4가지 튜토리얼 중 하나입니다. 마이크로서비스 제공 시작하기 :

 

모든 앱은 구성이 필요하지만, 마이크로서비스를 구성할 때의 고려 사항은 모놀리식 앱 과 동일하지 않을 수 있습니다. 12가지 요소 앱요소 3 ( 환경에 구성 저장 )을 살펴보면 두 유형의 앱에 모두 적용할 수 있는 지침이 있지만, 해당 지침은 마이크로서비스 앱에 맞게 조정할 수 있습니다. 특히, 서비스 구성을 정의하는 방식을 조정하고, 서비스에 구성을 제공하고, 해당 서비스에 종속될 수 있는 다른 서비스에 대한 구성 값으로 서비스를 사용할 수 있도록 할 수 있습니다.

마이크로서비스에 대한 Factor 3를 적용하는 방법, 특히 구성 파일, 데이터베이스 및 서비스 검색에 대한 모범 사례에 대한 개념적 이해를 위해 블로그의 마이크로서비스 앱 구성을 위한 모범 사례를 읽어보세요. 이 글은 그 지식을 실천에 옮기는 좋은 방법입니다.

메모: 이 튜토리얼의 목적은 마이크로서비스를 프로덕션에 올바르게 배포하는 방법을 보여주는 것이 아니라 핵심 개념을 설명하는 것입니다. 실제 "마이크로서비스" 아키텍처를 사용하지만 몇 가지 중요한 단서가 있습니다.

  • 이 튜토리얼에서는 Kubernetes나 Nomad와 같은 컨테이너 오케스트레이션 프레임워크를 사용하지 않습니다. 이를 통해 특정 프레임워크의 세부 사항에 얽매이지 않고도 마이크로서비스 개념에 대해 배울 수 있습니다. 여기에 소개된 패턴은 이러한 프레임워크 중 하나를 실행하는 시스템으로 이식 가능합니다.
  • 이 서비스는 소프트웨어 엔지니어링의 엄격성보다는 이해하기 쉬운 방식으로 최적화되었습니다. 요점은 코드의 세부 사항이 아닌 시스템에서 서비스의 역할과 통신 패턴을 살펴보는 것입니다. 자세한 내용은 개별 서비스의 README 파일을 참조하세요.

튜토리얼 개요

이 튜토리얼에서는 Factor 3 개념이 마이크로서비스 앱에 어떻게 적용되는지 보여줍니다. 4가지 과제에서 여러분은 몇 가지 일반적인 마이크로서비스 구성 패턴을 살펴보고 해당 패턴을 사용하여 서비스를 배포하고 구성하게 됩니다.

  • 챌린지 1챌린지 2 에서는 마이크로서비스 앱의 구성을 어디에 두는지와 관련된 첫 번째 패턴을 살펴봅니다. 세 가지 일반적인 위치가 있습니다.

    • 응용 프로그램 코드
    • 애플리케이션에 대한 배포 스크립트
    • 배포 스크립트에서 액세스하는 외부 소스
  • 챌린지 3 에서는 두 가지 패턴을 더 설정합니다. NGINX를 역방향 프록시로 사용하여 앱을 외부에 노출하고 Consul을 사용하여 서비스 검색을 활성화합니다.
  • 4번째 과제 에서는 최종 패턴을 구현합니다. 마이크로서비스 인스턴스를 "작업 실행자"로 사용하여 일반적인 기능과 다른 일회성 작업을 수행합니다(이 경우 데이터베이스 마이그레이션을 에뮬레이션).

이 튜토리얼에서는 4가지 기술을 사용합니다.

  • messenger – 이 튜토리얼을 위해 만들어진 메시지 저장 기능이 있는 간단한 채팅 API
  • NGINX 오픈 소스메신저 서비스와 더 넓은 시스템에 대한 진입점
  • Consul – 동적 서비스 레지스트리 및 키-값 저장소
  • RabbitMQ – 서비스가 비동기적으로 통신할 수 있도록 하는 인기 있는 오픈 소스 메시지 브로커

컨테이너에서 NGINX 오픈 소스와 Consul 템플릿이 함께 실행되는 것을 보여주는 토폴로지 다이어그램입니다. 컨설팅 템플릿은 컨설팅 클라이언트와 소통합니다. NGINX 오픈 소스는 메신저 서비스를 위한 역방향 프록시로, messenger_db에 데이터를 저장하고 Rabbit MQ와 통신합니다.

튜토리얼의 개요를 알아보려면 이 영상을 시청하세요. 해당 단계가 이 게시물의 내용과 정확히 일치하지는 않지만, 개념을 이해하는 데 도움이 됩니다.

필수 조건 및 설정

필수 조건

자신의 환경에서 튜토리얼을 완료하려면 다음이 필요합니다.

  • Linux/Unix 호환 환경
  • Linux 명령줄, JavaScript 및 bash 에 대한 기본적인 지식(그러나 모든 코드와 명령이 제공되고 설명되므로 제한된 지식으로도 성공할 수 있음)
  • DockerDocker Compose
  • Node.js 19.x 이상

    • 우리는 버전 19.x를 테스트했지만, 최신 버전의 Node.js도 작동할 것으로 기대합니다.
    • Node.js 설치에 대한 자세한 내용은 메신저 서비스 저장소의 README를 참조하세요. 컨테이너에서 사용되는 것과 정확히 동일한 Node.js 버전을 얻으려면 asdf를 설치할 수도 있습니다.
  • curl (대부분 시스템에 이미 설치되어 있음)
  • 튜토리얼 개요 에 나열된 네 가지 기술: 메신저 (다음 섹션에서 다운로드합니다), NGINX 오픈 소스 , Consul , RabbitMQ .

설정

  1. 터미널 세션을 시작합니다(이후 지침에서는 이를 앱 터미널 이라고 합니다).
  2. 홈 디렉토리에서 microservices-march 디렉토리를 만들고 이 튜토리얼의 GitHub 저장소를 여기에 복제합니다. (다른 디렉토리 이름을 사용하고 이에 따라 지침을 조정할 수도 있습니다.)

    메모: 튜토리얼 전체에서 Linux 명령줄의 프롬프트는 생략되어 터미널에 명령을 복사하여 붙여 넣기가 더 쉬워졌습니다. 틸드( ~ )는 홈 디렉토리를 나타냅니다.

    mkdir ~/microservices-marchcd ~/microservices-march
    git 복제 https://github.com/microservices-march/platform.git --branch mm23-twelve-factor-start
    git 복제 https://github.com/microservices-march/messenger.git --branch mm23-twelve-factor-start
    
  3. 플랫폼 저장소로 변경하고 Docker Compose를 시작합니다.

    cd platformdocker 구성 -d --build
    

    이렇게 하면 RabbitMQ와 Consul이 모두 시작되며, 이는 이후의 챌린지에서 사용됩니다.

    • -d 플래그는 Docker Compose가 컨테이너가 시작되면 컨테이너에서 분리되도록 지시합니다(그렇지 않으면 컨테이너는 터미널에 연결된 상태로 유지됩니다).
    • --build 플래그는 Docker Compose가 시작 시 모든 이미지를 다시 빌드하도록 지시합니다. 이렇게 하면 파일에 잠재적인 변경 사항이 발생하더라도 실행 중인 이미지가 최신 상태로 유지됩니다.
  4. 메신저 저장소로 변경하고 Docker Compose를 시작합니다.

    cd ../messengerdocker 구성 -d --build
    

    이렇게 하면 메신저 서비스를 위한 PostgreSQL 데이터베이스가 시작됩니다. 이 튜토리얼의 나머지 부분에서는 이를 메신저 데이터베이스 라고 부르겠습니다.

도전 1: 애플리케이션 수준 마이크로서비스 구성 정의

이번 과제에서는 튜토리얼에서 살펴볼 세 위치 중 첫 번째 위치인 애플리케이션 수준에서 구성을 설정합니다. ( 과제 2에서는 두 번째와 세 번째 위치, 배포 스크립트와 외부 소스를 설명합니다.)

12단계 앱은 애플리케이션 수준 구성을 특별히 제외합니다. 이러한 구성은 다양한 배포 환경(12단계 앱이 배포 라고 함) 간에 변경할 필요가 없기 때문입니다. 그럼에도 불구하고, 우리는 완전성을 위해 세 가지 유형을 모두 다룹니다. 서비스를 개발, 빌드, 배포할 때 각 범주를 처리하는 방법은 다릅니다.

메신저 서비스는 Node.js로 작성되었으며, 메신저 저장소의 app/index.mjs 에 진입점이 있습니다. 파일의 이 줄:

앱.express.json()을 사용합니다.

는 애플리케이션 수준 구성의 예입니다. 이는 application/json 유형의 요청 본문을 JavaScript 객체로 역직렬화하도록 Express 프레임워크를 구성합니다.

이 논리는 애플리케이션 코드와 긴밀하게 결합되어 있으며 12단계 앱에서 "구성"으로 간주되는 것이 아닙니다. 하지만 소프트웨어의 경우 모든 것은 상황에 따라 달라지지 않나요?

다음 두 섹션에서는 이 줄을 수정하여 애플리케이션 수준 구성의 두 가지 예를 구현합니다.

예제 1

이 예에서는 메신저 서비스에서 허용되는 요청 본문의 최대 크기를 설정합니다. 이 크기 제한은 Express API 설명서 에서 설명한 대로 express.json 함수의 limit 인수를 통해 설정됩니다. 여기서는 위에서 설명한 Express 프레임워크의 JSON 미들웨어 구성에 limit 인수를 추가합니다.

  1. 원하는 텍스트 편집기에서 app/index.mjs를 열고 다음을 바꾸세요.

    앱.사용(express.json())
    

    와 함께:

    앱.사용(express.json({ 제한: "20b" }));
    
  2. 앱 터미널( 설정 에서 사용한 터미널)에서 디렉토리로 변경하고 메신저 서비스를 시작합니다.

    cd app npm install node index.mjs messenger_service 포트 4000에서 수신 대기
    
  3. 두 번째 별도 터미널 세션을 시작하고(이후 지침에서는 클라이언트 터미널 이라고 함) 메신저 서비스에 POST 요청을 보냅니다. 오류 메시지는 요청 본문이 1단계에서 설정한 20바이트 제한보다 작았기 때문에 요청이 성공적으로 처리되었지만 JSON 페이로드의 내용이 올바르지 않음을 나타냅니다.

    curl -d '{ "text": "hello" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations ... { "error": "대화에는 2명의 고유한 사용자가 있어야 합니다"
    
  4. 조금 더 긴 메시지 본문을 보냅니다(역시 클라이언트 터미널에서). 3단계보다 출력이 훨씬 더 많으며 이번에는 요청 본문이 20바이트를 초과한다는 오류 메시지가 포함됩니다.

    curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations ... \”PayloadTooLargeError: 요청 엔터티가 너무 큽니다"
    

예제 2

이 예제에서는 단일 파일에서 전체 구성 "스키마"를 정의할 수 있는 라이브러리 인 convict를 사용합니다. 또한 12단계 앱의 요소 3에서 두 가지 지침을 설명합니다.

  • 환경 변수에 구성 저장 – 앱 코드에 하드코딩하는 대신 환경 변수( JSON_BODY_LIMIT )를 사용하여 최대 본문 크기를 설정하도록 앱을 수정합니다.
  • 서비스 구성을 명확하게 정의하세요 . 이는 마이크로서비스를 위한 Factor 3의 적용입니다. 이 개념에 익숙하지 않다면 블로그의 마이크로서비스 앱 구성을 위한 모범 사례 를 읽어보시기 바랍니다.

이 예제는 또한 챌린지 2 에서 활용할 수 있는 몇 가지 "배관"을 설정합니다. 해당 챌린지에서 만들 메신저 배포 스크립트는 JSON_BODY_LIMIT 환경 변수를 설정하고, 이 변수를 여기 앱 코드에 삽입하여 배포 스크립트에 지정된 구성을 보여줍니다.

  1. convict 구성 파일 app/config/config.mjs 를 열고 amqpport 키 뒤에 다음을 새 키로 추가합니다.

    jsonBodyLimit: { doc: `JSON 미들웨어에서 구문 분석할 최대 크기(단위 포함)입니다. 단위 파싱은
    https://www.npmjs.com/package/bytes 라이브러리에 의해 수행됩니다.
    예: "100kb"`,
    형식: 문자열,
    기본값: null,
    env: "JSON_BODY_LIMIT",
    },
    

    3단계에서 명령줄에 최대 본문 크기를 설정하는 데 사용할 경우 convict 라이브러리는 JSON_BODY_LIMIT 환경 변수의 구문 분석을 처리합니다.

    • 올바른 환경 변수에서 값을 가져옵니다.
    • 변수의 유형( 문자열 )을 확인합니다.
    • jsonBodyLimit 키 아래에서 애플리케이션에서 이에 대한 액세스를 활성화합니다.
  2. app/index.mjs 에서 다음을 교체하세요.

    앱.사용(express.json({ 제한: "20b" }));
    

    ~와 함께

    앱의 express.json 파일에서 jsonBodyLimit 속성을 설정합니다.
    
  3. 앱 터미널( 예제 1 의 2단계에서 메신저 서비스를 시작한 곳)에서 Ctrl+c를 눌러 서비스를 중지합니다. 그런 다음 JSON_BODY_LIMIT 환경 변수를 사용하여 최대 본문 크기를 27바이트로 설정하여 다시 시작합니다.

    ^cJSON_BODY_LIMIT=27b 노드 인덱스.mjs
    

    이는 사용 사례에 적합할 때 구성 방법을 수정하는 예입니다. 즉, 앱 코드에서 값(이 경우 크기 제한)을 하드코딩하는 것에서 12단계 앱에서 권장하는 대로 환경 변수로 설정하는 것으로 전환한 것입니다.

    위에서 언급했듯이, 챌린지 2 에서 JSON_BODY_LIMIT 환경 변수를 사용하는 것은 구성을 위한 두 번째 위치의 예가 되며, 이는 명령줄에서 환경 변수를 설정하는 대신 메신저 서비스의 배포 스크립트를 사용하여 환경 변수를 설정할 때 사용됩니다.

  4. 클라이언트 터미널에서 예제 14단계curl 명령을 반복합니다(요청 본문이 더 큽니다). 이제 크기 제한을 27바이트로 늘렸기 때문에 요청 본문이 더 이상 제한을 초과하지 않고 요청이 처리되었지만 JSON 페이로드의 내용이 올바르지 않다는 오류 메시지가 표시됩니다.

    curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations { "오류": "대화에는 2명의 고유한 사용자가 있어야 합니다"
    

    원하시면 클라이언트 터미널을 닫으셔도 됩니다. 튜토리얼의 나머지 부분에서는 모든 명령을 앱 터미널에서 실행합니다.

  5. 앱 터미널에서 Ctrl+c를 눌러 메신저 서비스를 중지합니다(위의 3단계에서 이 터미널에서 서비스를 중지하고 다시 시작했습니다).

    ^씨
    
  6. 메신저-데이터베이스를 중지합니다. 플랫폼 저장소에 정의된 인프라 요소가 네트워크를 계속 사용 중이므로 표시된 오류 메시지는 무시해도 됩니다. 메신저 저장소 루트에서 이 명령을 실행하세요.

    docker compose down ...mm_2023 네트워크를 제거하는 데 실패했습니다....
    

도전 2: 서비스에 대한 배포 스크립트 만들기

구성은 코드와 엄격하게 분리되어야 합니다(그렇지 않으면 배포 간에 어떻게 달라질 수 있습니까?)
– 12가지 요소 앱의 요소 3에서

언뜻 보면 이것은 "소스 제어에 구성을 체크인하지 마세요"라는 의미로 해석할 수 있습니다. 이번 과제에서는 마이크로서비스 환경에 대한 일반적인 패턴을 구현합니다. 이는 규칙을 어기는 것처럼 보일 수 있지만 실제로는 규칙을 준수하는 동시에 마이크로서비스 환경에 필수적인 귀중한 프로세스 개선을 제공합니다.

이 과제에서는 인프라스트럭처 코드 의 기능을 모방하는 배포 스크립트와 마이크로서비스에 구성을 제공하는 배포 매니페스트를 만들고 , 외부 구성 소스를 사용하도록 스크립트를 수정하고 , 비밀을 설정한 다음, 스크립트를 실행하여 서비스와 해당 인프라를 배포합니다.

메신저 저장소의 새로 만든 인프라 디렉토리에 배포 스크립트를 만듭니다. 인프라 (또는 이와 비슷한 이름)라는 디렉토리는 현대 마이크로서비스 아키텍처에서 일반적인 패턴으로, 다음과 같은 항목을 저장하는 데 사용됩니다.

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

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

이전에 언급했듯이 이 튜토리얼의 목적은 실제 시스템을 설정하는 방법을 보여주는 것이 아니며, 이 챌린지에서 배포하는 스크립트는 실제 운영 시스템과 비슷하지 않습니다. 오히려 마이크로서비스 관련 인프라 구축을 다룰 때 도구별 구성을 통해 해결되는 핵심 개념과 문제를 설명하면서 동시에 가능한 최소한의 특정 도구로 스크립트를 추상화합니다.

초기 배포 스크립트 만들기

  1. 앱 터미널에서 메신저 리포 루트에 인프라 디렉토리를 만들고 메신저 서비스와 메신저 데이터베이스에 대한 배포 스크립트를 포함할 파일을 만듭니다. 환경에 따라 chmod 명령 앞에 sudo를 붙여야 할 수도 있습니다.

    mkdir 인프라 cd 인프라
    터치 메신저-배포.sh
    chmod +x 메신저-배포.sh
    터치 메신저-db-배포.sh
    chmod +x 메신저-db-배포.sh
    
  2. 원하는 텍스트 편집기에서 messenger-deploy.sh를 열고 다음을 추가하여 메신저 서비스에 대한 초기 배포 스크립트를 만듭니다.

    #!/bin/bashset -e JSON_BODY_LIMIT=20b 도커 실행 \ --rm \ -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT} " \ 메신저
    

이 스크립트는 아직 완성되지 않았지만 몇 가지 개념을 보여줍니다.

  • 배포 스크립트에 해당 구성을 직접 포함시켜 환경 변수에 값을 할당합니다.
  • docker run 명령에서 -e 플래그를 사용하여 런타임에 컨테이너에 환경 변수를 삽입합니다.

이런 방식으로 환경 변수의 값을 설정하는 것은 중복되는 것처럼 보일 수 있지만, 이를 통해 배포 스크립트가 아무리 복잡해지더라도 스크립트의 맨 위를 빠르게 살펴보면 배포에 구성 데이터가 어떻게 제공되는지 이해할 수 있습니다.

또한, 실제 배포 스크립트가 docker run 명령을 명시적으로 호출하지 않더라도, 이 샘플 스크립트는 Kubernetes 매니페스트와 같은 것으로 해결되는 핵심 문제를 전달하기 위한 것입니다. Kubernetes와 같은 컨테이너 오케스트레이션 시스템을 사용하는 경우 배포를 통해 컨테이너가 시작되고 Kubernetes 구성 파일에서 파생된 애플리케이션 구성이 해당 컨테이너에서 사용 가능하게 됩니다. 따라서 이 샘플 배포 파일은 Kubernetes 매니페스트와 같은 프레임워크별 배포 파일과 동일한 역할을 하는 배포 스크립트의 최소 버전으로 간주할 수 있습니다.

실제 개발 환경에서는 이 파일을 소스 제어에 체크인하고 코드 검토를 거칠 수 있습니다. 이렇게 하면 나머지 팀원도 설정에 대해 의견을 제시할 수 있으며, 잘못 구성된 값으로 인해 예상치 못한 동작이 발생하는 사고를 방지하는 데 도움이 됩니다. 예를 들어, 이 스크린샷에서 팀원은 수신 JSON 요청 본문에 대한 20바이트 제한( JSON_BODY_LIMIT 로 설정)이 너무 낮다는 점을 올바르게 지적하고 있습니다.

메시지 본문의 20바이트 제한이 너무 작다는 코드 검토 스크린샷

외부 소스에서 구성 값을 쿼리하기 위한 배포 스크립트 수정

이 과제의 이 부분에서는 마이크로서비스 구성을 위한 세 번째 위치, 즉 배포 시점에 쿼리되는 외부 소스를 설정합니다. 배포 시점에 동적으로 값을 등록하고 외부 소스에서 이를 가져오는 방식은 지속적으로 업데이트해야 하고 오류가 발생할 수 있는 값을 하드코딩하는 방식보다 훨씬 더 나은 방법입니다. 자세한 내용은 블로그의 마이크로서비스 앱 구성을 위한 모범 사례를 참조하세요.

이 시점에서 메신저 서비스에 필요한 보조 서비스를 제공하기 위해 두 개의 인프라 구성 요소가 백그라운드에서 실행 중입니다.

  1. 실제 배포에서 플랫폼 팀이 소유한 RabbitMQ( 설정 의 3단계에서 시작됨)
  2. 실제 배포에서 팀이 소유한 메신저 데이터베이스 ( 설정 의 4단계에서 시작)

app/config/config.mjs 에 있는 메신저 서비스의 convict 스키마는 이러한 외부 구성에 해당하는 필수 환경 변수를 정의합니다. 이 섹션에서는 이러한 두 구성 요소를 설정하여 일반적으로 접근 가능한 위치에 변수 값을 설정하여 구성을 제공합니다. 이렇게 하면 메신저 서비스가 배포될 때 해당 값을 쿼리할 수 있습니다.

RabbitMQ와 메신저 데이터베이스 에 필요한 연결 정보는 Consul 키/값(KV) 저장소 에 등록됩니다. 이 저장소는 배포되는 모든 서비스가 접근할 수 있는 공통 위치입니다. Consul KV 저장소는 이러한 유형의 데이터를 저장하는 표준적인 장소는 아니지만, 이 튜토리얼에서는 단순화를 위해 이를 사용합니다.

  1. 이전 섹션 의 2단계에서 만든 infrastructure/messenger-deploy.sh 의 내용을 다음으로 바꾸세요.

    #!/bin/bashset -e # 이 구성에는 변경을 위한 새 커밋이 필요합니다 NODE_ENV=production PORT=4000 JSON_BODY_LIMIT=100kb # 시스템 에서 정보를 가져와서 Postgres 데이터베이스 구성 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 구성 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 실행 \ --rm \ -e NODE_ENV="${NODE_ENV} " \ -e 포트="${PORT} " \ -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT} " \ -e PGUSER="${POSTGRES_USER} " \ -e PGPORT="${PGPORT} " \ -e PGHOST="${PGHOST} " \ -e AMQPPORT="${AMQPPORT} " \ -e AMQPHOST="${AMQPHOST} " \ 메신저
    

    이 스크립트는 두 가지 유형의 구성을 보여줍니다.

    • 배포 스크립트에 직접 지정된 구성 – 배포 환경( NODE_ENV )과 포트( PORT )를 설정하고 JSON_BODY_LIMIT를 20바이트보다 더 현실적인 값인 100KB로 변경합니다.
    • 외부 소스에서 쿼리된 구성 – Consul KV 스토어에서 POSTGRES_USER , PGPORT , PGHOST , AMQPHOSTAMQPPORT 환경 변수의 값을 가져옵니다. 다음 두 단계에서는 Consul KV 저장소의 환경 변수 값을 설정합니다.
  2. messenger-db-deploy.sh를 열고 다음을 추가하여 messenger-database에 대한 초기 배포 스크립트를 만듭니다.

    #!/bin/bashset -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_비밀번호="${POSTGRES_PASSWORD} " \ -e PGPORT="${PORT} " \ -e PGDATA=/var/lib/postgresql/data/pgdata \ --network mm_2023 \ postgres:15.1 # 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' # 이것은 위의 "--name" 플래그와 일치합니다 . # (호스트 이름) 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 네트워크의 데이터베이스 호스트 이름이 됩니다.
  3. 실제 배포에서는 일반적으로 플랫폼 팀(또는 유사한 팀)이 플랫폼 리포지토리의 RabbitMQ와 같은 서비스의 배포 및 유지 관리를 처리합니다. 이는 메신저 리포지토리의 메신저-데이터베이스 와 유사합니다. 그런 다음 플랫폼 팀은 해당 인프라의 위치를 이에 의존하는 서비스에서 검색할 수 있는지 확인합니다. 튜토리얼의 목적을 위해 RabbitMQ 값을 직접 설정하세요.

    curl -X PUT --silent --output /dev/null --show-error --fail \ -H "콘텐츠 유형: application/json" \
    -d "rabbitmq" \
    http://localhost:8500/v1/kv/amqp-host
    
    curl -X PUT --silent --output /dev/null --show-error --fail \
    -H "콘텐츠 유형: application/json" \
    -d "5672" \
    http://localhost:8500/v1/kv/amqp-port
    

    (RabbitMQ 변수를 정의하는 데 amqp를 사용하는 이유가 궁금할 것입니다. RabbitMQ가 AMQP 프로토콜을 사용하기 때문입니다.)

배포 스크립트에 비밀 설정

메신저 서비스의 배포 스크립트에서 중요한 데이터 중 하나만 누락되었는데, 바로 메신저 데이터베이스 의 비밀번호입니다!

메모: 이 튜토리얼에서는 비밀 관리에 중점을 두지 않으므로 단순화를 위해 비밀은 배포 파일에 정의됩니다. 실제 환경(개발, 테스트 또는 운영)에서는 이 작업을 절대 수행하지 마세요. 엄청난 보안 위험이 발생합니다 .

적절한 비밀 관리에 대해 알아보려면 2023년 3월 마이크로서비스 2단원, 마이크로서비스 비밀 관리 101을 확인하세요. (스포일러: 비밀 관리 도구는 비밀을 저장하는 유일하게 안전한 방법입니다.)

  1. Consul KV 저장소에 있는 messenger-database 의 비밀번호 비밀번호를 저장하려면 infrastructure/messenger-db-deploy.sh 의 내용을 다음으로 바꾸세요.

    #!/bin/bashset -e PORT=5432 POSTGRES_USER=postgres # 참고: 실제 배포에서는 이 작업을 절대로 수행하지 마십시오. 비밀번호암호화된 비밀 저장소에만 저장하세요.
    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_비밀번호="${POSTGRES_PASSWORD} " \ -e PGPORT="${PORT} " \ -e PGDATA=/var/lib/postgresql/data/pgdata \ --network mm_2023 \ postgres:15.1 echo "메신저-db-포트 키 등록\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \ -H "콘텐츠 유형: application/json" \ -d "${PORT} " echo "메신저-db-호스트 등록 키\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \ -H "콘텐츠 유형: application/json" \ -d 'messenger-db-primary' # 이것은 위의 "--name" 플래그와 일치합니다 . # 우리의 설정에서는 호스트 이름을 의미합니다. echo "메신저-db-application-user 등록 키\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \ -H "콘텐츠 유형: application/json" \ -d "${POSTGRES_USER} " echo "메신저-db-암호-절대-하지-않음-키 등록\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-절대-하지-않음-이것 \ -H "콘텐츠-유형: application/json" \ -d "${POSTGRES_PASSWORD} " printf "\nConsul에 postgres 세부 정보 등록 완료\n"
    
  2. Consul KV 스토어에서 messenger-database 비밀번호 비밀번호를 가져오려면 infrastructure/messenger-deploy.sh 의 내용을 다음으로 바꾸세요.

    #!/bin/bashset -e # 이 구성에는 변경을 위한 새 커밋이 필요합니다 NODE_ENV=production PORT=4000 JSON_BODY_LIMIT=100kb # 시스템 에서 정보를 가져와 Postgres 데이터베이스 구성 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) # 참고: 실제 배포에서는 이 작업을 절대로 수행하지 마십시오. 비밀번호암호화된 비밀 저장소에만 저장하세요.
    PGPASSWORD=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true) # 시스템에서 끌어와서 RabbitMQ 구성 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} " \ -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} " \ --네트워크 mm_2023 \ 메신저
    

배포 스크립트 실행

  1. 메신저 리포지토리의 디렉토리로 변경하고 메신저 서비스에 대한 Docker 이미지를 빌드합니다.

    cd ../appdocker build -t messenger .
    
  2. 플랫폼 서비스에 속한 컨테이너만 실행 중인지 확인합니다.

    docker ps --format '{{.Names}}' consul-server consul-client rabbitmq
    
  3. 메신저 저장소의 루트로 변경하고 메신저 데이터베이스메신저 서비스를 배포합니다.

    cd .../인프라/메신저-db-deploy.sh
    ./인프라/메신저-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 로컬호스트:4000/health curl: (7) 11ms 후에 로컬호스트 포트 4000에 연결에 실패했습니다: 연결이 거부되었습니다
    

    아, 실패! 결론적으로, 여전히 중요한 구성 요소 하나가 누락되어 있으며 메신저 서비스가 광범위한 시스템에 노출되지 않았습니다. mm_2023 네트워크 내부에서 잘 실행되고 있지만, 해당 네트워크는 Docker 내에서만 접근할 수 있습니다.

  5. 다음 과제에서 새 이미지를 만들기 위해 실행 중인 컨테이너를 중지하세요.

    도커 rm $(도커 정지 $(도커 ps -a -q --필터 조상=메신저 --format="{{.ID}}"))
    

도전 3: 서비스를 외부 세계에 공개

프로덕션 환경에서는 일반적으로 서비스를 직접 노출하지 않습니다. 대신 일반적인 마이크로서비스 패턴을 따르고 기본 서비스 앞에 역방향 프록시 서비스를 배치합니다.

이 과제에서는 서비스 검색을 설정하여 메신저 서비스를 외부에 노출합니다. 서비스 검색은 새로운 서비스 정보를 등록하고 다른 서비스에서 액세스할 때 해당 정보를 동적으로 업데이트하는 기능입니다. 이를 위해 다음 기술을 사용합니다.

서비스 검색에 대해 자세히 알아보려면 블로그의 '마이크로서비스 앱 구성을 위한 모범 사례' 에서 서비스를 구성으로 사용 가능하게 만들기를 참조하세요.

Consul 설정

메신저 저장소에 있는 app/consul/index.mjs 파일에는 시작 시 메신저 서비스를 Consul에 등록하고 정상적으로 종료될 때 등록을 해제하는 데 필요한 모든 코드가 포함되어 있습니다. 새로 배포된 서비스를 Consul의 서비스 레지스트리에 등록하는 register 라는 하나의 함수를 제공합니다.

  1. 원하는 텍스트 편집기에서 app/index.mjs를 열고 다른 import 문 뒤에 다음 스니펫을 추가하여 app/consul/index.mjs 에서 register 함수를 가져옵니다.

    "./consul/index.mjs"에서 { registerConsul로 등록 }을 가져옵니다.
    

    그런 다음 스크립트의 끝에 있는 SERVER START 섹션을 수정하여 애플리케이션이 시작된 후 registerConsul()을 호출하도록 합니다.

    /* =================== 서버 시작 ================== */ app.listen(port, async () => { console.log(`messenger_service가 포트에서 수신 대기 중입니다.${port} `); registerConsul(); }); 기본 앱 내보내기;
    
  2. app/config/config.mjs 에서 convict 스키마를 열고 예제 2 의 1단계에서 추가한 jsonBodyLimit 키 뒤에 다음 구성 값을 추가합니다.

      consulServiceName: { 문서: "Consul에 등록된 서비스 이름입니다. 지정하지 않으면 서비스가 등록되지 않습니다",
    형식: "*",
    기본값: null,
    env: "CONSUL_SERVICE_NAME",
    },
    consulHost: {
    문서: "Consul 클라이언트가 실행되는 호스트",
    형식: 문자열,
    기본값: "consul-client",
    env: "CONSUL_HOST",
    },
    consulPort: {
    문서: "Consul 클라이언트의 포트",
    형식: "port",
    기본값: 8500,
    환경: "CONSUL_PORT",
    },
    

    이는 새로운 서비스가 등록되는 이름을 구성하고 Consul 클라이언트의 호스트 이름과 포트를 정의합니다. 다음 단계에서는 메신저 서비스의 배포 스크립트를 수정하여 새로운 Consul 연결 및 서비스 등록 정보를 포함합니다.

  3. infrastructure/messenger-deploy.sh를 열고 그 내용을 다음 내용으로 바꿔서 이전 단계에서 설정한 Consul 연결 및 서비스 등록 정보를 메신저 서비스 구성에 포함시킵니다.

    #!/bin/bashset -e # 이 구성에는 NODE_ENV=production PORT=4000 JSON_BODY_LIMIT=100kb CONSUL_SERVICE_NAME="messenger" # Consul 호스트와 포트는 각 호스트에 포함되어 있으므로 Consul을 쿼리할 수 없습니다. CONSUL_HOST="${CONSUL_HOST} " 영사관 포트 = "${CONSUL_PORT} " # 시스템 에서 정보를 가져와서 Postgres 데이터베이스 구성 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") # 참고: 실제 배포에서는 이 작업을 절대로 수행하지 마십시오. 비밀번호암호화된 비밀 저장소에만 저장하세요.
    PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true") # 시스템에서 끌어와서 RabbitMQ 구성 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} " \ -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} " \ --네트워크 mm_2023 \ 메신저
    

    주목해야 할 주요 사항은 다음과 같습니다.

    • CONSUL_SERVICE_NAME 환경 변수는 메신저 서비스 인스턴스가 Consul에 등록할 때 사용할 이름을 알려줍니다.
    • CONSUL_HOSTCONSUL_PORT 환경 변수는 배포 스크립트가 실행되는 위치에서 실행되는 Consul 클라이언트를 위한 것입니다.

    메모: 실제 배포에서 이는 팀 간에 합의되어야 하는 구성의 예입니다. Consul을 담당하는 팀은 모든 환경에서 CONSUL_HOSTCONSUL_PORT 환경 변수를 제공해야 합니다. 서비스는 이 연결 정보 없이는 Consul을 쿼리할 수 없기 때문입니다.

  4. 앱 터미널에서 디렉토리로 변경하고 메신저 서비스의 실행 중인 인스턴스를 중지한 다음 Docker 이미지를 다시 빌드하여 새 서비스 등록 코드를 넣습니다.

    cd appdocker 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에서 헤더 바에 있는 서비스를 클릭하여 단일 메신저 서비스가 실행 중인지 확인합니다.

    Consul 및 Messenger 서비스 각각 하나의 인스턴스가 있는 Consul UI 서비스 탭

  8. 메신저 서비스의 인스턴스를 더 시작하려면 배포 스크립트를 몇 번 더 실행합니다. Consul UI에서 실행 중인지 확인하세요.

    CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
    

    서비스의 5개 인스턴스가 있는 Consul UI '메신저' 탭

NGINX 설정

다음 단계는 NGINX 오픈 소스를 역방향 프록시 및 부하 분산 장치로 추가하여 들어오는 트래픽을 모든 실행 중인 메신저 인스턴스로 라우팅하는 것입니다.

  1. 앱 터미널에서 messenger repo의 루트로 디렉토리를 변경하고 load-balancer 라는 디렉토리와 세 개의 파일을 만듭니다.

    mkdir 로드 밸런서rcd 로드 밸런서
    터치 nginx.ctmpl
    터치 consul-template-config.hcl
    터치 Dockerfile
    

    Dockerfile 은 NGINX와 Consul 템플릿이 실행되는 컨테이너를 정의합니다. Consul 템플릿은 메신저 서비스가 서비스 레지스트리에서 변경될 때(서비스 인스턴스가 시작되거나 중단될 때) 다른 두 파일을 사용하여 NGINX 업스트림을 동적으로 업데이트합니다.

  2. 1단계에서 만든 nginx.ctmpl 파일을 열고 Consul 템플릿이 NGINX 업스트림 그룹을 동적으로 업데이트하는 데 사용하는 다음 NGINX 구성 스니펫을 추가합니다.

    업스트림 메신저 서비스 { {{- 범위 서비스 "메신저" }}
    서버 {{ .주소 }}:{{ .포트 }};
    {{- 끝 }}
    }
    
    서버 {
    수신 8085;
    서버 이름 로컬 호스트;
    
    위치 / {
    프록시 패스 http://메신저 서비스;
    add_header 업스트림 호스트 $upstream_addr;
    }
    }
    

    이 스니펫은 Consul에 등록된 각 메신저 서비스 인스턴스의 IP 주소와 포트 번호를 NGINX messenger_service 업스트림 그룹에 추가합니다. NGINX는 들어오는 요청을 동적으로 정의된 업스트림 서비스 인스턴스 세트로 프록시합니다.

  3. 1단계에서 만든 consul-template-config.hcl 파일을 열고 다음 구성을 추가합니다.

    consul { 주소 = "consul-client:8500"
    
    재시도 {
    활성화 = 참
    시도 = 12
    백오프 = "250ms"
    }
    }
    템플릿 {
    소스 = "/usr/templates/nginx.ctmpl"
    대상 = "/etc/nginx/conf.d/default.conf"
    권한 = 0600
    명령 = "if [ -e /var/run/nginx.pid ]; then nginx -s reload; else nginx; fi"
    }
    

    Consul 템플릿에 대한 이 구성은 소스 템플릿(이전 단계에서 만든 NGINX 구성 스니펫)을 다시 렌더링하고 지정된 대상 에 배치한 다음, 마지막으로 지정된 명령 (NGINX에 구성을 다시 로드하도록 지시)을 실행하도록 지시합니다.

    실제로 이는 Consul에서 서비스 인스턴스가 등록, 업데이트 또는 등록 해제될 때마다 새로운 default.conf 파일이 생성된다는 것을 의미합니다. 그러면 NGINX가 가동 중지 없이 구성을 다시 로드하여 NGINX가 트래픽을 전송할 수 있는 최신의 건강한 서버 세트( 메신저 서비스 인스턴스)를 확보할 수 있도록 합니다.

  4. 1단계에서 만든 Dockerfile 파일을 열고 다음 내용을 추가하면 NGINX 서비스를 빌드합니다. (이 튜토리얼의 목적을 위해 Dockerfile을 이해할 필요는 없지만, 편의상 코드가 인라인으로 문서화되어 있습니다.)

    FROM nginx:1.23.1 ARG CONSUL_TEMPLATE_VERSION=0.30.0 # Consul 클러스터의 위치에 대한 환경 변수를 설정합니다 . 기본적으로 consul-client:8500으로 확인하려고 시도합니다.
    # Consul이 컨테이너로 실행되는 경우의 동작은 다음과 같습니다. 
    # 동일한 호스트이며 이 NGINX 컨테이너에 연결되어 있습니다(별칭 사용) 
    # (물론 영사죠). 하지만 이 환경 변수는 또한 다음과 같을 수 있습니다.
    # 컨테이너가 시작될 때 재정의하여 해결하려는 경우
    # 다른 주소.
    
    ENV CONSUL_URL consul-client:8500 # 지정된 버전의 Consul 템플릿 다운로드 ADD https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION} /영사-템플릿_${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 복사 consul-template-config.hcl ./consul-template-config.hcl 복사 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. 메신저 디렉토리의 루트로 변경하고 NGINX 서비스의 배포 파일로 messenger-load-balancer-deploy.sh 라는 파일을 만듭니다(튜토리얼 전체에서 배포한 다른 서비스와 마찬가지로). 환경에 따라 chmod 명령 앞에 sudo를 붙여야 할 수도 있습니다.

    cd ..
    touch 인프라/메신저-로드-밸런서-배포.sh
    chmod +x 인프라/메신저-로드-밸런서-배포.sh
    
  7. messenger-load-balancer-deploy.sh를 열고 다음 내용을 추가합니다.

    #!/bin/bashset -e # Consul 호스트와 포트는 각 호스트에 포함됩니다. Consul 호스트와 포트를 알 때까지는 Consul을 쿼리할 수 없습니다. CONSUL_HOST="${CONSUL_HOST} " 영사관 포트 = "${CONSUL_PORT} " docker run \ --rm \ -d \ --name messenger-lb \ -e CONSUL_URL="${CONSUL_HOST} :${CONSUL_PORT} " \ -p 8085:8085 \ --network mm_2023 \ 메신저-lb
    
  8. 이제 모든 것이 준비되었으니 NGINX 서비스를 배포하세요.

    CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-load-balancer-deploy.sh
    
  9. 외부에서 메신저 서비스에 액세스할 수 있는지 확인하세요.

    curl -X GET http://localhost:8085/health 확인
    

    효과가 있어요! NGINX는 이제 생성된 모든 메신저 서비스 인스턴스에서 로드 밸런싱을 수행합니다. 이전 섹션의 8단계 에서 Consul UI에 표시된 것과 동일한 메신저 서비스 IP 주소가 X-Forwarded-For 헤더에 표시되어 있기 때문에 이를 알 수 있습니다.

도전 4: 서비스를 작업 실행자로 사용하여 데이터베이스 마이그레이션

대규모 애플리케이션은 종종 데이터 수정과 같은 일회성 작업을 수행하는 데 사용할 수 있는 작은 작업자 프로세스가 있는 "작업 실행기"를 사용합니다(예: SidekiqCelery ). 이러한 도구에는 RedisRabbitMQ 와 같은 추가적인 지원 인프라가 필요한 경우가 많습니다. 이 경우 메신저 서비스 자체를 "작업 실행자"로 사용하여 일회성 작업을 실행합니다. 이는 이미 규모가 매우 작으면서도 데이터베이스 및 이에 의존하는 다른 인프라와 완벽하게 상호작용할 수 있으며 트래픽을 제공하는 애플리케이션과 완전히 별도로 실행되기 때문에 합리적입니다.

이렇게 하면 세 가지 이점이 있습니다.

  1. 작업 실행자(실행하는 스크립트 포함)는 프로덕션 서비스와 정확히 동일한 검사 및 검토 프로세스를 거칩니다.
  2. 데이터베이스 사용자와 같은 구성 값을 쉽게 변경하여 프로덕션 배포를 보다 안전하게 만들 수 있습니다. 예를 들어, 기존 테이블에서만 쓰기 및 쿼리를 수행할 수 있는 "낮은 권한" 사용자로 프로덕션 서비스를 실행할 수 있습니다. 더 높은 권한이 있는 사용자로서 데이터베이스 구조를 변경하여 테이블을 만들고 제거할 수 있도록 다른 서비스 인스턴스를 구성할 수 있습니다.
  3. 일부 팀은 서비스 프로덕션 트래픽을 처리하는 인스턴스에서도 작업을 실행합니다. 이것은 위험합니다. 작업의 문제로 인해 컨테이너 내의 애플리케이션이 수행하는 다른 기능에 영향을 미칠 수 있기 때문입니다. 그런 일을 피하는 것이 바로 우리가 처음부터 마이크로서비스를 하는 이유가 아닐까요?

이 챌린지에서는 일부 데이터베이스 구성 값을 변경하고 메신저 데이터베이스를 마이그레이션하여 새 값을 사용하고 성능을 테스트하여 새로운 역할을 수행하도록 아티팩트를 수정하는 방법을 살펴봅니다.

메신저 데이터베이스 마이그레이션

실제 프로덕션 배포의 경우 서로 다른 권한이 있는 두 명의 별도 사용자, 즉 "애플리케이션 사용자"와 "마이그레이터 사용자"를 만들 수 있습니다. 단순화를 위해 이 예에서는 기본 사용자를 애플리케이션 사용자로 사용하고 슈퍼유저 권한이 있는 마이그레이터 사용자를 생성합니다. 실제 상황에서는 각 사용자의 역할에 따라 필요한 특정 최소 권한이 무엇인지 결정하는 데 더 많은 시간을 할애하는 것이 좋습니다.

  1. 앱 터미널에서 슈퍼유저 권한이 있는 새로운 PostgreSQL 사용자를 만듭니다.

    echo "슈퍼유저 비밀번호 'migrator_password'로 사용자 messenger_migrator 만들기;" | docker exec -i messenger-db-primary psql -U postgres
    
  2. 데이터베이스 배포 스크립트( infrastructure/messenger-db-deploy.sh )를 열고 내용을 바꿔 새 사용자의 자격 증명을 추가합니다.

    메모: 실제 배포의 경우 데이터베이스 자격 증명과 같은 비밀을 배포 스크립트나 비밀 관리 도구 외의 다른 곳에 절대 넣지 마십시오. 자세한 내용은 단원 2를 참조하세요. 마이크로서비스의 비밀, 마이크로서비스 경영 101, 2023년 3월.

    #!/bin/bash set -e PORT=5432 POSTGRES_USER=postgres # 참고: 실제 배포에서는 이 작업을 절대로 수행하지 마십시오. 비밀번호는 암호화된 비밀 저장소에만 저장합니다. 이 튜토리얼에서는 다른 개념에 초점을 맞추고 있으므로 편의를 위해 여기서는 이런 방식으로 비밀번호를 설정합니다.
    POSTGRES_PASSWORD=postgres # 마이그레이션 사용자 POSTGRES_MIGRATOR_USER=messenger_migrator # 참고: 위에서 언급했듯이 실제 배포에서는 이 작업을 수행하지 마세요.
    POSTGRES_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_비밀번호="${POSTGRES_PASSWORD} " \ -e PGPORT="${PORT} " \ -e PGDATA=/var/lib/postgresql/data/pgdata \ --network mm_2023 \ postgres:15.1 echo "메신저-db-포트 키 등록\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \ -H "콘텐츠 유형: application/json" \ -d "${PORT} " echo "메신저-db-호스트 등록 키\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \ -H "콘텐츠 유형: application/json" \ -d 'messenger-db-primary' # 이것은 위의 "--name" 플래그와 일치합니다 . # 우리의 설정에서는 호스트 이름을 의미합니다. echo "메신저-db-application-user 등록 키\n" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \ -H "콘텐츠 유형: 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 "콘텐츠 유형: application/json" \ -d "${POSTGRES_PASSWORD} " echo "메신저-db-application-user\n 키 등록" curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-user \ -H "콘텐츠 유형: 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 "콘텐츠 유형: application/json" \ -d "${POSTGRES_MIGRATOR_PASSWORD} " printf "\nConsul에 postgres 세부 정보 등록 완료\n"
    

    이 변경 사항은 데이터베이스가 배포된 후 Consul에 설정된 사용자 집합에 마이그레이터 사용자를 추가할 뿐입니다.

  3. infrastructure 디렉토리에 messenger-db-migrator-deploy.sh 라는 새 파일을 만듭니다(다시 말하지만, chmod 명령 앞에 sudo를 붙여야 할 수도 있음):

    인프라/메신저-db-마이그레이터-배포.shchmod +x 인프라/메신저-db-마이그레이터-배포.sh를 터치합니다.
    
  4. messenger-db-migrator-deploy.sh를 열고 다음을 추가합니다.

    #!/bin/bashset -e # 이 구성에는 변경을 위한 새 커밋이 필요합니다 .NODE_ENV=production PORT=4000 JSON_BODY_LIMIT=100kb CONSUL_SERVICE_NAME="messenger-migrator" # Consul 호스트와 포트는 각 호스트에 포함되어 있으므로 Consul을 쿼리할 수 없습니다 .CONSUL_HOST="${CONSUL_HOST} " 영사관 포트 = "${CONSUL_PORT} " # 마이그레이터 사용자 이름과 비밀번호를 가져옵니다 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) # 참고: 실제 배포에서는 이 작업을 절대로 수행하지 마십시오. 비밀번호암호화된 비밀 저장소에만 저장하세요.
    PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this?raw=true") # 시스템에서 끌어와서 RabbitMQ 구성 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} " \ -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} " \ --네트워크 mm_2023 \ 메신저
    

    이 스크립트는 Consul 설정3단계 에서 만든 최종 형태의 infrastructure/messenger-deploy.sh 스크립트와 매우 유사합니다. 주요 차이점은 CONSUL_SERVICE_NAMEmessenger 대신 messenger-migrator 이고 PGUSER가 위의 1단계에서 만든 "migrator" 슈퍼유저에 해당한다는 것입니다.

    CONSUL_SERVICE_NAMEmessenger-migrator 인 것이 중요합니다. 이 서비스를 messenger 로 설정하면 NGINX가 자동으로 이 서비스를 순환시켜 API 호출을 수신하게 되며, 이 서비스는 트래픽을 처리할 목적으로 만들어지지 않았습니다.

    스크립트는 마이그레이터 역할로 단기 인스턴스를 배포합니다. 이를 통해 마이그레이션으로 인한 문제가 주요 메신저 서비스 인스턴스의 트래픽 처리에 영향을 미치는 것을 방지할 수 있습니다.

  5. PostgreSQL 데이터베이스를 다시 배포합니다. 이 튜토리얼에서는 bash 스크립트를 사용하고 있으므로 데이터베이스 서비스를 중지했다가 다시 시작해야 합니다. 프로덕션 애플리케이션에서는 일반적으로 인프라 코드 스크립트를 실행하여 변경된 요소만 추가합니다.

    docker stop messenger-db-primaryCONSUL_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}}" ... 메신저 마이그레이터
    

    Consul UI에서 데이터베이스 마이그레이터 서비스가 Messenger-migrator 로 Consul에 올바르게 등록되었는지 확인할 수도 있습니다(다시 말하지만, 트래픽을 처리하지 않기 때문에 메신저 이름으로 등록되지 않습니다).

    Consul 및 messenger-migrator 서비스 각각 1개 인스턴스와 messenger 서비스 인스턴스 4개가 있는 Consul UI 서비스 탭

  8. 이제 마지막 단계인 데이터베이스 마이그레이션 스크립트를 실행해 보겠습니다! 이러한 스크립트는 실제 데이터베이스 마이그레이션 스크립트와 유사하지는 않지만, messenger-migrator 서비스를 사용하여 데이터베이스별 스크립트를 실행합니다. 데이터베이스가 마이그레이션되면 messenger-migrator 서비스를 중지합니다.

    docker exec -i -e PGDATABASE=postgres -e CREATE_DB_NAME=messenger messenger-migrator 노드 스크립트/create-db.mjsdocker exec -i messenger-migrator 노드 스크립트/create-schema.mjs
    docker exec -i messenger-migrator 노드 스크립트/create-seed-data.mjs
    docker stop messenger-migrator
    

메신저 서비스 실행 테스트

이제 메신저 데이터베이스를 최종 포맷으로 마이그레이션했으니, 메신저 서비스가 실제로 작동하는 모습을 직접 보실 수 있습니다! 이렇게 하려면 NGINX 서비스에 대해 기본적인 curl 쿼리를 실행합니다( NGINX 설정 에서 NGINX를 시스템의 진입점으로 구성했습니다).

다음 명령 중 일부는 jq 라이브러리를 사용하여 JSON 출력을 포맷합니다. 필요에 따라 설치할 수도 있고, 원하면 명령줄에서 생략할 수도 있습니다.

  1. 대화 만들기:

    curl -d '{"참가자_ID": [1, 2]}' -H "콘텐츠 유형: application/json" -X POST 'http://localhost:8085/conversations' { "대화": { "ID": "1", "삽입_시간": " YYYY - MM - DD T06:41:59.000Z" } }
    
  2. ID 1을 가진 사용자로부터 대화로 메시지를 보내세요:

    curl -d '{"콘텐츠": "이것은 첫 번째 메시지입니다"}' -H "사용자 ID: 1" -H "콘텐츠 유형: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq { "메시지": { "ID": "1", "내용": "이것은 첫 번째 메시지입니다", "인덱스": 1, "사용자_ID": 1, "사용자 이름": "제임스 블랜더폰", "conversation_id": 1, "삽입_시간": " YYYY - MM - DD T06:42:15.000Z" } }
    
  3. 다른 사용자(ID 2)의 메시지로 답장:

    curl -d '{"콘텐츠": "이것은 두 번째 메시지입니다"}' -H "사용자 ID: 2" -H "콘텐츠 유형: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq { "메시지": { "ID": "2", "내용": "이것은 두 번째 메시지입니다", "인덱스": 2, "사용자_ID": 2, "사용자 이름": "노르말라비아 로프토터", "conversation_id": 1, "삽입_시간": " YYYY - MM - DD T06:42:25.000Z" } }
    
  4. 메시지를 가져옵니다:

    curl -X GET 'http://localhost:8085/conversations/1/messages' | jq { "메시지": [ { "id": "1", "내용": "이것은 첫 번째 메시지입니다", "user_id": "1", "채널_ID": "1", "인덱스": "1", "삽입_시간": " YYYY - MM - DD T06:42:15.000Z", "사용자 이름": "제임스 블랜더폰" }, { "id": "2", "내용": "이것은 두 번째 메시지입니다", "user_id": "2", "채널_ID": "1", "인덱스": "2", "삽입_시간": " YYYY - MM - DD T06:42:25.000Z", "사용자 이름": "노르말라비아 로페토터" } ] }
    

청소하다

이 튜토리얼을 진행하는 동안 상당수의 컨테이너와 이미지를 생성했습니다! 다음 명령을 사용하여 보관하지 않으려는 Docker 컨테이너와 이미지를 제거하세요.

  • 실행 중인 Docker 컨테이너를 제거하려면:

    도커 rm $(도커 stop $(도커 ps -a -q --filter ancestor=messenger --format="{{.ID}}"))도커 rm $(도커 stop messenger-db-primary)
    도커 rm $(도커 stop messenger-lb)
    
  • 플랫폼 서비스를 제거하려면:

    # 플랫폼 저장소에서 docker compose down
    
  • 튜토리얼 전체에서 사용된 모든 Docker 이미지를 제거하려면:

    도커 rmi 메신저도커 rmi 메신저-lb
    도커 rmi postgres:15.1
    도커 rmi hashicorp/consul:1.14.4
    도커 rmi rabbitmq:3.11.4-management-alpine
    

다음 단계

"간단한 것을 설정하는 데 이렇게 많은 작업이 필요한가"라고 생각하실 수도 있습니다. 맞는 말입니다! 마이크로서비스 중심 아키텍처로 전환하려면 서비스를 구조화하고 구성하는 방법에 대한 세심한 주의가 필요합니다. 모든 복잡성에도 불구하고, 당신은 탄탄한 진전을 이루었습니다.

  • 다른 팀이 쉽게 이해할 수 있는 마이크로서비스 중심 구성을 설정합니다.
  • 다양한 서비스의 확장성과 사용 측면에서 어느 정도 유연하게 마이크로서비스 시스템을 설정합니다.

마이크로서비스 교육을 계속하려면 2023년 3월 마이크로서비스를 확인하세요. 2단원, 마이크로서비스 비밀 관리 101 에서는 마이크로서비스 환경에서의 비밀 관리에 대한 심층적이면서도 사용자 친화적인 개요를 제공합니다.


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