NGINXチュートリアル:マイクロサービスのデプロイと構成方法

NGINX | 2023年3月07日

この記事は、Microservices March 2023:マイクロサービスの提供を始めるの概念を実践するための4つのチュートリアルのうちの1つです:

すべてのアプリには構成が必要ですが、マイクロサービスを構成する際の考慮事項は、モノリシックアプリの場合とは異なることがあります。Factor 3(設定を環境変数に格納する)というThe Twelve-Factor Appの原則は、どちらの種類のアプリにも適用できる指針ですが、マイクロサービスアプリ向けに調整することもできます。具体的には、サービス構成の定義方法、サービスへの構成の提供方法、およびそのサービスに依存する可能性がある他のサービスで、当該サービスを構成値として利用できるようにする方法を調整できます。

Factor 3をマイクロサービス向けに適応する方法、特に構成ファイル、データベース、サービスディスカバリに関するベストプラクティスを概念的に理解するには、F5のブログ記事「マイクロサービスアプリの構成に関するベストプラクティス」をご覧ください。この記事は、その知識を実践に生かすうえで大いに役立ちます。

注:このチュートリアルの目的は、いくつかの中核的な概念を説明することであり、本番環境にマイクロサービスをデプロイする正しい方法を示すことではありません。実際の「マイクロサービス」アーキテクチャを使用していますが、いくつかの重要な注意点があります。

  • このチュートリアルでは、KubernetesやNomadなどのコンテナオーケストレーションフレームワークは使用しません。これは、特定のフレームワークの詳細にとらわれずに、マイクロサービスの概念を学べるようにするためです。ここで紹介するパターンは、これらのフレームワークのいずれかを実行しているシステムにも適用できます。
  • これらのサービスは、ソフトウェアエンジニアリング上の厳密さよりも、理解しやすさを重視して最適化されています。ここで重要なのは、コードの詳細ではなく、システム内で各サービスが果たす役割と通信パターンを確認することです。詳しくは、各サービスの「README」ファイルを参照してください。

チュートリアルの概要

このチュートリアルでは、Factor 3の概念をマイクロサービスアプリに適用する方法を説明します。4つの課題を通じて、一般的なマイクロサービス構成パターンを確認し、それらのパターンを使用してサービスをデプロイおよび構成します。

  • In チャレンジ1とチャレンジ2では、マイクロサービスアプリの構成情報をどこに配置するかに関する最初のパターンを確認します。一般的な配置場所は次の3つです。
    • アプリケーションコード
    • アプリケーションのデプロイスクリプト
    • デプロイメントスクリプトがアクセスする外部ソース
  • In チャレンジ3では、さらに2つのパターンを設定しました。NGINXをリバースプロキシとして使用してアプリを外部に公開するパターンと、Consulを使用してサービスディスカバリを有効にするパターンです。
  • In チャレンジ4では、最後のパターンを実装します。マイクロサービスのインスタンスを「ジョブランナー」として使用し、通常の機能とは異なる1回限りの処理を実行します(この例では、データベース移行をエミュレートします)。

このチュートリアルでは、次の4つのテクノロジーを使用します。

  • messenger – このチュートリアル用に作成された、メッセージ保存機能を備えたシンプルなチャットAPI
  • NGINX Open Source – messengerサービスおよびシステム全体へのエントリーポイント
  • Consul – 動的なサービスレジストリおよびキーバリューストア
  • RabbitMQ – サービス間の非同期通信を可能にする、広く利用されているオープンソースのメッセージブローカー

コンテナ内でNGINX Open SourceとConsulテンプレートが連携して動作する構成図。ConsultテンプレートはConsultクライアントと通信します。NGINX Open Sourceはmessengerサービスのリバースプロキシとして機能し、messengerサービスはmessenger_dbにデータを保存してRabbit MQと通信します。

この動画では、チュートリアルの概要をご覧いただけます。手順はこの記事と完全には一致しませんが、概念を理解するうえで役立ちます。

 

前提条件とセットアップ

前提条件

ご自身の環境でこのチュートリアルを完了するには、以下が必要です。

  • Linux/Unix互換環境
  • Linuxコマンドライン、JavaScript、bashに関する基本的な知識(ただし、すべてのコードとコマンドが提示され、説明も付いているため、知識が限られていても問題なく進められます)
  • DockerとDocker Compose
  • Node.js 19.x以降
    • バージョン19.xでテスト済みですが、それより新しいバージョンのNode.jsでも動作すると考えられます。
    • Node.jsのインストールに関する詳細は、README(messengerサービスのリポジトリ内)を参照してください。また、asdfをインストールすると、コンテナで使用されているものとまったく同じバージョンのNode.jsを利用できます。
  • curl(ほとんどのシステムにインストール済み)
  • 以下の4つのテクノロジーがチュートリアルの概要に記載されています:messenger(次のセクションでダウンロードします)、NGINX Open Source、Consul、RabbitMQ。

セットアップ

  1. ターミナルセッションを開始します(以降の手順では、これを「アプリターミナル」と呼びます)。
  2. ホームディレクトリにmicroservices-marchディレクトリを作成し、このチュートリアルで使用するGitHubリポジトリをその中にクローンします。(別のディレクトリ名を使用し、それに応じて手順を読み替えることもできます。)注:このチュートリアルでは、コマンドをターミナルにコピー&ペーストしやすいように、Linuxコマンドラインのプロンプトを省略しています。チルダ(~)はホームディレクトリを表します。
mkdir ~/microservices-marchcd ~/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
  1. Change to the platformリポジトリに移動し、Docker Composeを起動します:
cd platformdocker compose up -d --build
  1. これによりRabbitMQとConsulの両方が起動し、以降の課題で使用されます。
    • The -dフラグを指定すると、コンテナの起動後にDocker Composeがコンテナからデタッチします(指定しない場合、コンテナはターミナルにアタッチされたままになります)。
    • The --buildフラグを指定すると、Docker Composeは起動時にすべてのイメージを再ビルドします。これにより、ファイルに変更が加えられた場合でも、実行するイメージを最新の状態に保つことができます。
  2. Change to the messengerリポジトリに移動し、Docker Composeを起動します:
cd ../messengerdocker compose up -d --build
  1. これにより、messengerサービス用のPostgreSQLデータベースが起動します。以降、このチュートリアルではこのデータベースを「messenger-database」と呼びます。

課題1:アプリケーションレベルのマイクロサービス構成を定義する

この課題では、チュートリアルで取り上げる3つの場所のうち、最初の場所であるアプリケーションレベルで構成を設定します。(課題2では、2番目と3番目の場所であるデプロイスクリプトと外部ソースについて説明します。)

Twelve-Factor Appでは、アプリケーションレベルの設定は明示的に除外されています。このような設定は、異なるデプロイ環境(Twelve-Factor Appでは「デプロイ」と呼びます)間で変更する必要がないためです。ただし、網羅性を期すため、ここでは3つのタイプをすべて取り上げます。サービスの開発、ビルド、デプロイにおいて、各カテゴリーへの対応方法はそれぞれ異なります。

The messengerサービスはNode.jsで記述されており、エントリーポイントはapp/index.mjs(messengerリポジトリ内)です。このファイルの次の行をご覧ください。

app.use(express.json());

は、アプリケーションレベルの設定例です。Express frameworkで、タイプが「application/json」のリクエストボディをデシリアライズしてJavaScriptオブジェクトに変換するよう設定します。

このロジックはアプリケーションコードと密接に結び付いており、The Twelve-Factor Appでいう「設定」には該当しません。ただし、ソフトウェアではすべてが状況次第です。

次の2つのセクションでは、この行を変更して、アプリケーションレベルの構成例を2つ実装します。

例1

この例では、messengerサービスが受け入れるリクエスト本文の最大サイズを設定します。このサイズ制限は、Express APIドキュメントで説明されているように、express.json関数のlimit引数で設定します。ここでは、上記で説明したExpressフレームワークのJSONミドルウェアの設定にlimit引数を追加します。

  1. 任意のテキストエディターでapp/index.mjsを開き、次の箇所を置き換えます:
app.use(express.json())
  1. を次の内容に置き換えます:
app.use(express.json({ limit: "20b" }));
  1. アプリのターミナル(「セットアップ」で使用したターミナル)で、appディレクトリに移動し、messengerサービスを起動します:
cd app npm install node index.mjs
messenger_service listening on port 4000
  1. 別の2つ目のターミナルセッション(以降の手順では「クライアントターミナル」と呼びます)を開始し、POSTリクエストをmessengerサービスに送信します。エラーメッセージは、リクエスト本文がステップ1で設定した20バイトの上限を下回っていたためリクエストは正常に処理されたものの、JSONペイロードの内容が正しくないことを示しています:
curl -d '{ "text": "hello" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
{ "error": "Conversation must have 2 unique users" }
  1. (再度クライアント端末で)少し長めのメッセージ本文を送信します。ステップ3よりもはるかに多くの出力が表示されます。今回はリクエスト本文が20バイトを超えていることを示すエラーメッセージも含まれます:
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
\”PayloadTooLargeError: request entity too large"

例2

この例では、convictを使用します。これは、構成全体の「スキーマ」を1つのファイルで定義できるライブラリです。また、The Twelve-Factor Appの第3の要素に含まれる2つのガイドラインも示しています。

  • 構成を環境変数に保存する – アプリを変更し、最大ボディサイズをアプリのコードにハードコードする代わりに、環境変数(JSON_BODY_LIMIT)を使用して設定します。
  • サービス構成を明確に定義する – これは、Factor 3をマイクロサービス向けに応用したものです。この概念をご存じでない場合は、F5のブログ記事「マイクロサービスアプリの構成に関するベストプラクティス」をご一読ください。

この例では、チャレンジ2で利用するための「下準備」も行います。具体的には、このチャレンジで作成するmessengerデプロイスクリプトでJSON_BODY_LIMIT環境変数を設定し、ここでアプリコードに挿入します。これは、デプロイスクリプトで指定する構成の例です。

  1. Open the convict設定ファイル「app/config/config.mjs」を開き、amqpportキーの後に新しいキーとして以下を追加します。
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",
},
  1. 以下のステップ3でコマンドラインから最大ボディサイズを設定する際、convictライブラリがJSON_BODY_LIMIT環境変数を解析します。
    • 正しい環境変数から値を取得する
    • 変数の型(String)を確認します
    • アプリケーション内でjsonBodyLimitキーを使用してアクセスできるようにします
  2. In app/index.mjsで、次のコードを置き換えます:
app.use(express.json({ limit: "20b" }));
  1. を指定して
app.use(express.json({ limit: config.get("jsonBodyLimit") }));
  1. アプリのターミナル(messengerサービスを例1のステップ2で起動したターミナル)で、Ctrl+cを押してサービスを停止します。次に、JSON_BODY_LIMIT環境変数を使用して本文の最大サイズを27バイトに設定し、サービスを再起動します:
^cJSON_BODY_LIMIT=27b node index.mjs
  1. これは、ユースケースに適している場合に設定方法を変更する例です。アプリコードに値(この場合はサイズ上限)をハードコーディングする方法から、The Twelve-Factor Appで推奨されている環境変数による設定へと切り替えています。前述のとおり、チャレンジ2では、JSON_BODY_LIMIT環境変数の設定場所が2つ目の設定例となります。コマンドラインで設定する代わりに、messengerサービスのデプロイスクリプトを使用して環境変数を設定します。
  2. クライアントのターミナルで、curlコマンドを例1のステップ4(リクエストボディが大きい方)と同様に再度実行します。サイズ上限を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" }
  1. 必要に応じて、クライアントターミナルを閉じてもかまいません。以降のチュートリアルでは、すべてのコマンドをアプリターミナルで実行します。
  2. アプリのターミナルで、Ctrl+cを押してmessengerサービスを停止します(上記のステップ3で、このターミナルからサービスを停止して再起動しています)。
^c
  1. Stop the messenger-databaseを停止します。表示されるエラーメッセージは無視しても問題ありません。このネットワークは、platformリポジトリで定義されているインフラストラクチャ要素によって引き続き使用されています。このコマンドは、messengerリポジトリのルートで実行してください。
docker compose down
...failed to remove network mm_2023....

課題2:サービスのデプロイスクリプトを作成する

「設定はコードから厳密に分離する必要があります(そうでなければ、デプロイごとに設定を変えることができるでしょうか?)」— Twelve-Factor Appの第3の要素より

一見すると、これは「構成情報をソース管理にチェックインしてはならない」という意味に受け取れるかもしれません。この課題では、一見このルールに反するように見えても、実際にはルールを守りながら、マイクロサービス環境に不可欠なプロセス改善をもたらす一般的なパターンを実装します。

このチャレンジでは、デプロイメントスクリプトを作成し、マイクロサービスに構成を提供するInfrastructure as Codeとデプロイメントマニフェストの機能を再現します。次に、スクリプトを変更して外部の構成ソースを使用するようにし、シークレットを設定し、その後、スクリプトを実行してサービスとそのインフラストラクチャをデプロイします。

デプロイメントスクリプトは、infrastructureリポジトリ内に新しく作成したmessengerディレクトリに作成します。infrastructure(またはその名前を変えたもの)というディレクトリは、最新のマイクロサービスアーキテクチャで一般的に使用され、次のようなものを格納します。

このパターンには、次のようなメリットがあります。

  • サービスのデプロイと、データベースなどのサービス固有のインフラストラクチャのデプロイに関する責任を、そのサービスを所有するチームに割り当てます。
  • チームは、これらの要素に対する変更が、コードレビューやCIなどの開発プロセスを経るようにできます。
  • チームは、外部チームに作業を依頼することなく、サービスとそれを支えるインフラストラクチャのデプロイ方法を容易に変更できます。

前述のとおり、このチュートリアルは実際のシステムの構築方法を示すことを目的としておらず、この課題でデプロイするスクリプトも実際の本番システムを模したものではありません。むしろ、特定のツールへの依存を可能な限り抑えるようスクリプトを抽象化しつつ、マイクロサービス関連のインフラストラクチャをデプロイする際の中核的な概念と、ツール固有の設定によって解決される問題の一部を示しています。

初期デプロイスクリプトの作成

  1. アプリのターミナルで、infrastructureディレクトリをmessengerリポジトリのルートに作成し、messengerサービスとmessenger-databaseのデプロイスクリプトを格納するファイルを作成します。環境によっては、chmodコマンドの先頭にsudoを付ける必要があります:
mkdir infrastructurecd infrastructure touch messenger-deploy.sh chmod +x messenger-deploy.sh touch messenger-db-deploy.sh chmod +x messenger-db-deploy.sh
  1. 任意のテキストエディターでmessenger-deploy.shを開き、以下の内容を追加して、messengerサービスの初期デプロイスクリプトを作成します:
#!/bin/bashset -e

JSON_BODY_LIMIT=20b

docker run \
  --rm \
  -e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
  messenger

現時点ではこのスクリプトは未完成ですが、次の2つの概念を示しています。

  • その設定をデプロイスクリプトに直接含めることで、環境変数に値を割り当てます。
  • 実行時に環境変数をコンテナへ注入するため、-eフラグをdocker runコマンドで使用します。

この方法で環境変数の値を設定するのは冗長に思えるかもしれません。しかし、こうしておけば、デプロイスクリプトがどれほど複雑になっても、スクリプトの冒頭を見るだけで、デプロイに構成データがどのように渡されているかを把握できます。

また、実際のデプロイスクリプトでは、明示的にdocker runコマンドを呼び出さない場合もありますが、このサンプルスクリプトは、Kubernetesマニフェストのようなものが解決する中核的な課題を示すことを目的としています。Kubernetesのようなコンテナオーケストレーションシステムを使用する場合、デプロイによってコンテナが起動し、Kubernetes構成ファイルから生成されたアプリケーション構成がそのコンテナで利用できるようになります。したがって、このサンプルデプロイファイルは、Kubernetesマニフェストのようなフレームワーク固有のデプロイファイルと同じ役割を果たす、最小限のデプロイスクリプトと見なすことができます。

実際の開発環境では、このファイルをソース管理にチェックインし、コードレビューにかけることが考えられます。これにより、チームの他のメンバーが設定についてコメントできるため、誤った設定値によって予期しない動作が発生する事態を回避しやすくなります。たとえば、このスクリーンショットでは、受信するJSONリクエスト本文の上限が20バイト(JSON_BODY_LIMITで設定)では低すぎると、チームメンバーが適切に指摘しています。

メッセージ本文の上限が20バイトでは小さすぎるとのコードレビューの指摘を示すスクリーンショット

外部ソースから構成値を取得するようにデプロイスクリプトを変更

この課題では、マイクロサービスの構成情報を格納する3つ目の場所として、デプロイ時に照会する外部ソースを設定します。値をハードコーディングすると、継続的な更新が必要になり、障害の原因となる可能性があります。そのため、値を動的に登録し、デプロイ時に外部ソースから取得する方がはるかに優れた方法です。詳しくは、F5ブログの「マイクロサービスアプリの構成に関するベストプラクティス」をご覧ください。

この時点で、messengerサービスに必要な補助サービスを提供するため、2つのインフラストラクチャコンポーネントがバックグラウンドで実行されています。

  1. 実際のデプロイではプラットフォームチームが所有するRabbitMQ(「セットアップ」のステップ3で起動)
  2. 実際のデプロイではチームが所有する「messenger-database」(「セットアップ」のステップ4で起動)

The convictスキーマは、messengerサービスのapp/config/config.mjsで、これらの外部設定に対応する必須の環境変数を定義します。このセクションでは、共通の場所からアクセスできるように変数の値を設定し、デプロイ時にmessengerサービスが参照できる設定を、この2つのコンポーネントから提供できるようにします。

RabbitMQとmessenger-databaseに必要な接続情報は、サービスのデプロイ時にすべてのサービスからアクセスできる共通の場所であるConsul Key/Value (KV)ストアに登録されます。Consul KVストアは、この種のデータを保存する標準的な場所ではありませんが、このチュートリアルでは簡略化のために使用します。

  1. 次の内容でinfrastructure/messenger-deploy.sh(前のセクションのステップ2で作成)の内容を置き換えます:
#!/bin/bashset -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
  1. このスクリプトは、次の2種類の構成を例示しています。
    • デプロイメントスクリプト内で直接指定する構成 – デプロイメント環境(NODE_ENV)とポート(PORT)を設定し、JSON_BODY_LIMITを20バイトよりも現実的な値である100 KBに変更します。
    • 外部ソースから取得する構成 – Consul KVストアから環境変数POSTGRES_USER、PGPORT、PGHOST、AMQPHOST、AMQPPORTの値を取得します。次の2つの手順で、Consul KVストアに環境変数の値を設定します。
  2. Open 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_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}"
  1. このスクリプトでは、デプロイ時にmessengerサービスが照会できる構成を定義するだけでなく、messengerサービス用の初期スクリプトを作成した「初期デプロイスクリプトの作成」と同じ2つの概念について説明します。
    • 一部の設定はデプロイスクリプト内で直接指定します。この例では、PostgreSQLデータベースが使用するポートと、デフォルトユーザーのユーザー名を指定します。
    • Dockerを-eフラグ付きで実行し、実行時に環境変数をコンテナへ注入します。また、実行中のコンテナ名を「messenger-db」に設定します。この名前は、「platform」サービスを「セットアップ」のステップ2で起動した際に作成したDockerネットワーク内で、データベースのホスト名になります。
  2. 実際のデプロイでは通常、RabbitMQのようなサービスのデプロイと保守は、platformリポジトリ内でプラットフォームチーム(または同様のチーム)が担当します。これは、messengerリポジトリ内のmessenger-databaseに対して行う作業と同様です。その後、プラットフォームチームは、そのインフラストラクチャに依存するサービスがその場所を検出できるようにします。このチュートリアルでは、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
  1. (RabbitMQの変数の定義にamqpが使用されていることを不思議に思われるかもしれません。これは、AMQPがRabbitMQで使用されるプロトコルだからです。)

デプロイメントスクリプトでシークレットを設定

デプロイメントスクリプトに不足している(重要な)データは、messengerサービスのmessenger-databaseのパスワードだけです。

注:このチュートリアルではシークレット管理を主題としていないため、簡略化のためにシークレットをデプロイメントファイル内で定義しています。開発、テスト、本番のいずれであっても、実際の環境では絶対にこのような設定を行わないでください。重大なセキュリティリスクが生じます。

適切なシークレット管理については、Microservices March 2023の「ユニット2:マイクロサービスのシークレット管理入門」をご覧ください。(先に答えを言うと、シークレットを真に安全に保存する唯一の方法は、シークレット管理ツールを使用することです)。

  1. Replace the contents of infrastructure/messenger-db-deploy.shの内容を以下に置き換え、messenger-databaseのパスワードシークレットをConsul KVストアに保存します:
#!/bin/bashset -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"
  1. Replace the contents of infrastructure/messenger-deploy.shの内容を以下のように置き換え、Consul KVストアからmessenger-databaseのパスワードシークレットを取得します。
#!/bin/bashset -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

デプロイスクリプトの実行

  1. Change to the appディレクトリに移動し、messengerリポジトリでmessengerサービス用のDockerイメージをビルドします:
cd ../appdocker build -t messenger .
  1. 次のコマンドを実行し、platformサービスに属するコンテナのみが実行中であることを確認します:
docker ps --format '{{.Names}}'consul-server
consul-client
rabbitmq
  1. Change to the root of the messengerリポジトリのルートに移動し、messenger-databaseサービスとmessengerサービスをデプロイします:
cd .../infrastructure/messenger-db-deploy.sh ./infrastructure/messenger-deploy.sh
  1. The messenger-db-deploy.shスクリプトは、messenger-databaseを起動し、適切な情報をシステム(この場合はConsul KVストア)に登録します。続いて、messenger-deploy.shスクリプトがアプリケーションを起動し、messenger-db-deploy.shによって登録された設定をシステム(ここでもConsul KVストア)から取得します。ヒント:コンテナを起動できない場合は、デプロイスクリプト内のdocker runコマンドから2番目のパラメーター(-d \の行)を削除し、スクリプトを再度実行してください。これにより、コンテナがフォアグラウンドで起動し、ログがターミナルに表示されるため、問題の特定に役立つ可能性があります。問題を解決したら、実際のコンテナがバックグラウンドで実行されるように、-d \の行を元に戻してください。
  2. デプロイが成功したことを確認するため、アプリケーションに簡単なヘルスチェックリクエストを送信します:
curl localhost:4000/healthcurl: (7) Failed to connect to localhost port 4000 after 11 ms: Connection refused
  1. エラーが発生しました。重要な設定がまだ1つ不足しているため、messengerサービスはシステム全体に公開されていません。このサービスはmm_2023ネットワーク内では正常に稼働していますが、このネットワークにはDocker内からしかアクセスできません。
  2. 次のチャレンジで新しいイメージを作成する準備として、実行中のコンテナを停止します:
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))

チャレンジ3:サービスを外部に公開する

本番環境では、通常、サービスを直接公開しません。代わりに、一般的なマイクロサービスのパターンに従い、メインサービスの前段にリバースプロキシサービスを配置します。

この課題では、サービスディスカバリを設定して、messengerサービスを外部に公開します。サービスディスカバリでは、新しいサービス情報を登録し、他のサービスからアクセスされた際にその情報を動的に更新します。そのために、次のテクノロジーを使用します。

  • Consul(動的なサービスレジストリ)と、Consul template(Consulデータに基づいてファイルを動的に更新するツール)
  • NGINX Open Sourceは、リバースプロキシおよびロードバランサーとして、コンテナで実行される複数のアプリケーションインスタンスで構成されるmessengerサービスに単一のエントリポイントを提供します

サービスディスカバリの詳細については、F5のブログの「サービスを構成情報として利用可能にする」(『マイクロサービスアプリの構成に関するベストプラクティス』)をご覧ください。

Consulのセットアップ

The app/consul/index.mjs file in the messengerリポジトリ内のファイルには、起動時にmessengerサービスをConsulに登録し、正常終了時に登録を解除するために必要なコードがすべて含まれています。このファイルは、registerという1つの関数を公開しており、新たにデプロイされたサービスをConsulのサービスレジストリに登録します。

  1. 任意のテキストエディターで、app/index.mjsを開き、他のimport文の後に次のスニペットを追加して、register関数をapp/consul/index.mjsからインポートします:
import { register as registerConsul } from "./consul/index.mjs";
  1. 次に、スクリプト末尾の「SERVER START」セクションを次のように変更し、アプリケーションの起動後に「registerConsul()」を呼び出すようにします。
/* =================  SERVER START
================== */
app.listen(port, async () => {
  console.log(`messenger_service listening on port ${port}`);
  registerConsul();
});

export default app;
  1. Open the convictスキーマ(app/config/config.mjs)を開き、Example 2の手ップ1で追加したjsonBodyLimitキーの後に、以下の設定値を追加します。
  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",
  },
  1. これにより、新しいサービスを登録する際の名前と、Consulクライアントのホスト名およびポートを設定します。次の手順では、messengerサービスのデプロイスクリプトを変更し、この新しいConsul接続情報とサービス登録情報を追加します。
  2. Open infrastructure/messenger-deploy.shを開き、内容を以下に置き換えます。これにより、messengerサービスの構成に、前の手順で設定したConsulの接続情報とサービス登録情報が追加されます。
#!/bin/bashset -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
  1. 主な注意点は次のとおりです。注:実際の環境にデプロイする場合、このような構成についてはチーム間で合意する必要があります。サービスはこの接続情報がなければConsulにクエリを実行できないため、Consulを担当するチームは、すべての環境でCONSUL_HOSTおよびCONSUL_PORT環境変数を提供する必要があります。
    • The CONSUL_SERVICE_NAME環境変数は、messengerサービスインスタンスがConsulへの登録時に使用する名前を指定します。
    • デプロイスクリプトが実行される場所で動作するConsulクライアントには、環境変数CONSUL_HOSTとCONSUL_PORTを使用します。
  2. アプリのターミナルで、appディレクトリに移動し、実行中のmessengerサービスのインスタンスをすべて停止してから、新しいサービス登録コードを組み込むためにDockerイメージを再ビルドします:
cd appdocker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}")) docker build -t messenger .
  1. ブラウザーでhttp://localhost:8500にアクセスすると、動作中のConsul UIを確認できます(ただし、現時点では特に目立った動作はありません)。
  2. At the root of the messengerリポジトリのルートでデプロイスクリプトを実行し、messengerサービスのインスタンスを起動します:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
  1. ブラウザーでConsul UIを開き、ヘッダーバーの[Services]をクリックして、1つのmessengerサービスが実行中であることを確認します。
    Consul UIの[Services]タブ。consulサービスとmessengerサービスのインスタンスがそれぞれ1つ表示されています
  2. デプロイメントスクリプトをさらに数回実行して、messengerサービスのインスタンスを追加で起動します。Consul UIで、それらが実行中であることを確認します。
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh
  1. サービスの5つのインスタンスが表示されたConsul UIの[messenger]タブ

NGINXのセットアップ

次に、NGINX Open Sourceをリバースプロキシおよびロードバランサーとして追加し、受信トラフィックを実行中のすべての「messenger」インスタンスにルーティングします。

  1. アプリのターミナルで、messengerリポジトリのルートに移動し、load-balancerというディレクトリと、次の3つのファイルを作成します。
mkdir load-balancercd load-balancer touch nginx.ctmpl touch consul-template-config.hcl touch Dockerfile
  1. The Dockerfileは、NGINXとConsul templateを実行するコンテナを定義します。Consul templateは、messengerサービスのサービスレジストリで変更(サービスインスタンスの起動または停止)が発生した際に、他の2つのファイルを使用してNGINXのアップストリームを動的に更新します。
  2. 手順1で作成した「nginx.ctmpl」ファイルを開き、Consul templateがNGINXアップストリームグループを動的に更新するために使用する、次の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;
    }
}
  1. このスニペットは、Consulに登録されている各messengerサービスインスタンスのIPアドレスとポート番号を、NGINXのmessenger_serviceアップストリームグループに追加します。NGINXは、動的に定義されたアップストリームサービスインスタンス群に受信リクエストをプロキシします。
  2. ステップ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"
}
  1. このConsul templateの設定では、sourceテンプレート(前の手順で作成したNGINX設定スニペット)を再レンダリングして、指定したdestinationに配置し、最後に指定したcommand(NGINXに設定を再読み込みさせるコマンド)を実行します。実際には、Consulでサービスインスタンスが登録、更新、または登録解除されるたびに、新しいdefault.confファイルが作成されます。その後、NGINXはダウンタイムなしで設定を再読み込みし、トラフィックの送信先となる最新かつ正常なサーバー(messengerサービスインスタンス)のセットを維持します。
  2. ステップ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"]
  1. Dockerイメージをビルドします:
docker build -t messenger-lb .
  1. Change to the root of the messengerディレクトリのルートに移動し、NGINXサービスのデプロイファイルとしてmessenger-load-balancer-deploy.shという名前のファイルを作成します(このチュートリアルでこれまでにデプロイした他のサービスと同様です)。環境によっては、chmodコマンドの先頭にsudoを付ける必要があります:
cd .. touch infrastructure/messenger-load-balancer-deploy.sh chmod +x infrastructure/messenger-load-balancer-deploy.sh
  1. Open messenger-load-balancer-deploy.shを開き、次の内容を追加します:
#!/bin/bashset -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
  1. 必要な準備がすべて整ったら、NGINXサービスをデプロイします:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-load-balancer-deploy.sh
  1. 外部からmessengerサービスにアクセスできるか確認します:
curl -X GET http://localhost:8085/health
OK
  1. 正常に動作しています。NGINXは、作成されたmessengerサービスのすべてのインスタンスに負荷を分散しています。これは、X-Forwarded-Forヘッダーに、前のセクションのmessengerサービスについてConsul UIに表示されたIPアドレスと同じものが、ステップ 8で表示されていることから確認できます。

課題4:サービスをジョブランナーとして使用してデータベースを移行する

大規模なアプリケーションでは、データの変更などの単発タスクを実行できる小規模なワーカープロセスを備えた「ジョブランナー」がよく使用されます(例:Sidekiq、Celery)。こうしたツールでは、多くの場合、RedisやRabbitMQなどの追加のサポートインフラストラクチャが必要です。ここでは、messengerサービス自体を「ジョブランナー」として使用し、単発タスクを実行します。この方法が適しているのは、このサービスがもともと非常に小規模であり、データベースや依存するその他のインフラストラクチャと十分に連携できるうえ、トラフィックを処理するアプリケーションとは完全に分離して実行されているためです。

これには3つのメリットがあります。

  1. ジョブランナー(実行するスクリプトを含む)には、本番サービスとまったく同じチェックおよびレビュープロセスが適用されます。
  2. データベースユーザーなどの設定値は簡単に変更でき、本番環境のデプロイをよりセキュアにできます。たとえば、本番サービスは、既存のテーブルへの書き込みとクエリのみが可能な「低権限」ユーザーで実行できます。また、別のサービスインスタンスを設定し、テーブルの作成と削除が可能な高権限ユーザーとしてデータベース構造を変更できます。
  3. サービスの本番トラフィックも処理しているインスタンスでジョブを実行するチームもあります。ジョブの問題が、コンテナ内のアプリケーションが実行しているほかの機能に影響を及ぼす可能性があるため、これは危険です。そもそも、このような事態を避けるためにマイクロサービスを採用しているのではないでしょうか。

このチャレンジでは、データベースの設定値をいくつか変更し、アーティファクトを新しい役割に対応させる方法を確認します。さらに、新しい値を使用するようにmessengerデータベースを移行し、そのパフォーマンスをテストします。

messengerデータベースの移行

実際の本番環境にデプロイする場合は、権限が異なる「アプリケーションユーザー」と「移行ユーザー」という2つのユーザーを作成することが考えられます。簡略化のため、この例ではデフォルトユーザーをアプリケーションユーザーとして使用し、スーパーユーザー権限を持つ移行ユーザーを作成します。実際の環境では、各ユーザーの役割に応じて必要となる具体的な最小権限を十分に検討することをお勧めします。

  1. アプリのターミナルで、スーパーユーザー権限を持つ新しいPostgreSQLユーザーを作成します:
echo "CREATE USER messenger_migrator WITH SUPERUSER PASSWORD 'migrator_password';" | docker exec -i messenger-db-primary psql -U postgres
  1. データベースデプロイスクリプト(infrastructure/messenger-db-deploy.sh)を開き、新しいユーザーの認証情報を追加するために内容を置き換えます。注:ここで改めて強調します。実環境にデプロイする場合、絶対にデータベースの認証情報などのシークレットを、デプロイスクリプトやシークレット管理ツール以外の場所に保存しないでください。詳しくは、Microservices March 2023の「ユニット2:マイクロサービスのシークレット管理入門」を参照してください。
#!/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"
  1. この変更では、データベースのデプロイ後にConsulに設定されるユーザーの一覧に、マイグレーター用ユーザーを追加するだけです。
  2. Create a new file in the infrastructureディレクトリに、messenger-db-migrator-deploy.shという名前の新しいファイルを作成します(ここでも、chmodコマンドの先頭にsudoを付ける必要がある場合があります):
touch infrastructure/messenger-db-migrator-deploy.shchmod +x infrastructure/messenger-db-migrator-deploy.sh
  1. Open messenger-db-migrator-deploy.shを開き、以下を追加します。
#!/bin/bashset -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
  1. このスクリプトは、infrastructure/messenger-deploy.sh スクリプトの最終形とよく似ています。このスクリプトは、ステップ3の「Consulのセットアップ」で作成したものです。主な違いは、CONSUL_SERVICE_NAMEがmessengerではなくmessenger-migratorであることと、PGUSERが上記のステップ1で作成した「migrator」スーパーユーザーに対応していることです。CONSUL_SERVICE_NAMEをmessenger-migratorに設定することが重要です。これをmessengerに設定すると、NGINXはこのサービスをAPI呼び出しの受信対象として自動的にローテーションに組み込みますが、このサービスはトラフィックを処理するためのものではありません。このスクリプトは、migratorの役割を担う短時間稼働のインスタンスをデプロイします。これにより、移行に関する問題がメインのmessengerサービスインスタンスによるトラフィック処理に影響するのを防ぎます。
  2. PostgreSQLデータベースを再デプロイします。このチュートリアルではbashスクリプトを使用しているため、データベースサービスを停止して再起動する必要があります。本番アプリケーションでは通常、代わりにInfrastructure as Codeスクリプトを実行し、変更された要素のみを追加します。
docker stop messenger-db-primaryCONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-deploy.sh
  1. PostgreSQLデータベース移行サービスをデプロイします:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-migrator-deploy.sh
  1. インスタンスが想定どおりに実行されていることを確認します:
docker ps --format "{{.Names}}"
...
messenger-migrator
  1. Consul UIでは、データベース移行サービスがConsulにmessenger-migratorとして正しく登録されていることも確認できます(このサービスはトラフィックを処理しないため、ここでもmessengerという名前では登録されません):
    Consul UIの[Services]タブ。consulサービスとmessenger-migratorサービスが各1インスタンス、messengerサービスが4インスタンス表示されています
  2. 最後の手順として、データベース移行スクリプトを実行します。これらのスクリプトは実際のデータベース移行スクリプトとは異なりますが、messenger-migratorサービスを使用して、データベース固有のスクリプトを実行します。データベースの移行が完了したら、messenger-migratorサービスを停止します:
docker exec -i -e PGDATABASE=postgres -e CREATE_DB_NAME=messenger messenger-migrator node scripts/create-db.mjsdocker exec -i messenger-migrator node scripts/create-schema.mjs docker exec -i messenger-migrator node scripts/create-seed-data.mjs docker stop messenger-migrator

messengerサービスの動作をテストする

Now that you have migrated the messengerデータベースの最終形式への移行が完了したため、messengerサービスの動作を確認する準備が整いました。確認するには、NGINXサービスに対して基本的なcurlクエリをいくつか実行します(Set Up NGINXで、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" }
}
  1. 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"
  }
}
  1. 別のユーザー(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"
  }
}
  1. メッセージを取得します:
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"
    }
  ]
}

クリーンアップ

このチュートリアルでは、多数のコンテナとイメージを作成しました。保持する必要のない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)
  • To remove the platformサービスを削除するには:
# From the platform repositorydocker compose down
  • チュートリアル全体で使用したすべてのDockerイメージを削除するには、次のコマンドを実行します。
docker rmi messengerdocker rmi messenger-lb docker rmi postgres:15.1 docker rmi hashicorp/consul:1.14.4 docker rmi rabbitmq:3.11.4-management-alpine

次のステップ

「単純なものをセットアップするだけなのに、ずいぶん手間がかかる」と思われるかもしれません。実際、そのとおりです。マイクロサービスを中心としたアーキテクチャへ移行するには、サービスの構成や設定を細部まで慎重に行う必要があります。こうした複雑さはあるものの、ここまで着実に前進できました。

  • 他のチームにも理解しやすい、マイクロサービスに重点を置いた構成を設定しました。
  • マイクロサービスシステムを構築し、スケーリングと関連する各種サービスの利用の両面で、ある程度の柔軟性を確保します。

マイクロサービスについてさらに学ぶには、Microservices March 2023をご覧ください。Unit 2, Microservices Secrets Management 101では、マイクロサービス環境におけるシークレット管理について、詳しくわかりやすく解説しています。


共有

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります