この投稿は、 2023 年 3 月の Microservices の概念を実践するのに役立つ 4 つのチュートリアルの 1 つです。 マイクロサービスの提供を開始する:
マイクロサービス アーキテクチャには、チームの自律性の向上、スケーリングと展開の柔軟性の向上など、多くの利点があります。 欠点としては、システム内のサービスが増えるほど (マイクロサービス アプリでは数十、数百のサービスが存在する場合があります)、システム全体の動作を明確に把握することが難しくなります。 複雑なソフトウェア システムの作成者および保守者として、私たちは明確なイメージを持つことの重要性を認識しています。 可観測性ツールを使用すると、多数のサービスとサポートインフラストラクチャ全体にわたってその画像を構築できるようになります。
このチュートリアルでは、マイクロサービス アプリの非常に重要な可観測性の 1 つであるトレースについて説明します。 始める前に、可観測性について議論するときによく使用される用語をいくつか定義しましょう。
これらすべての概念を使用して、マイクロサービスのパフォーマンスに関する洞察を得ることができます。 トレースは、リクエストが行われたときに、多くの場合は疎結合された複数のコンポーネント間で何が起こっているかの「全体像」を提供するため、可観測性戦略の特に便利な部分です。 これは、パフォーマンスのボトルネックを特定するのに特に効果的な方法でもあります。
このチュートリアルでは、急速に人気が高まっているテレメトリの収集、処理、エクスポートのためのオープン ソースのベンダー中立標準であるOpenTelemetry (OTel) のトレース ツール キットを使用します。 OTel のコンセプトでは、トレースは複数のサービスに関係する可能性のあるデータ フローを、時系列に並べられた一連の「チャンク」に分割し、次の内容を簡単に理解できるようにします。
OTel に詳しくない場合は、 「OpenTelemetry とは?」を参照して、標準の詳細な概要と実装に関する考慮事項を確認してください。
このチュートリアルでは、OTel を使用してマイクロサービス アプリの操作をトレースすることに重点を置いています。 このチュートリアルの 4 つの課題では、システムを通じてリクエストを追跡し、マイクロサービスに関する質問に答える方法を学習します。
これらの課題は、トレースを初めて設定するときに推奨されるプロセスを示しています。 手順は次のとおりです。
注記: このチュートリアルの目的は、テレメトリに関するいくつかのコア概念を説明することであり、マイクロサービスを本番環境にデプロイする正しい方法を示すことではありません。 実際の「マイクロサービス」アーキテクチャを使用していますが、いくつか重要な注意点があります。
この図は、チュートリアルで使用されるマイクロサービスとその他の要素間の全体的なアーキテクチャとデータ フローを示しています。
2 つのマイクロサービスは次のとおりです。
サポート インフラストラクチャは次の 3 つです。
OTel をしばらく図から外して、追跡している一連のイベント、つまり、ユーザーが新しいチャット メッセージを送信し、受信者にその通知が届いたときに何が起こるかに集中することができます。
フローは次のようになります。
同時に:
テレメトリ計測を設定するときは、「すべてを送信して洞察を期待する」よりも明確な計測の目標から始めるのが最適です。 このチュートリアルには、3 つの主要なテレメトリ目標があります。
これらの目標は、システムの技術的な操作とユーザー エクスペリエンスの両方に関連していることに注意してください。
独自の環境でチュートリアルを完了するには、次のものが必要です。
Linux/Unix互換環境
注記: このチュートリアルの NGINX トレースを伴うアクティビティは、NGINX 用の OpenTelemetry モジュールに互換性がないため、ARM ベースのプロセッサでは動作しません。 (これには、Linux aarch64 アーキテクチャと、M1 または M2 チップを搭載した Apple マシンが含まれます。) メッセンジャーおよび通知サービスに関連するアクティビティは、すべてのアーキテクチャで動作します。
bash
の基本的な知識 (ただし、すべてのコードとコマンドが提供され、説明されているため、限られた知識でも成功できます)Node.js 19.x 以降
curl
(ほとんどのシステムに既にインストールされています)注記: メッセンジャーおよび通知サービスは Node.js で記述されているため、このチュートリアルでは JavaScript SDK を使用します。 また、OTel自動インストルメンテーション機能 (自動インストルメンテーションとも呼ばれます) を設定して、OTel から入手できる情報の種類を把握できるようにします。 このチュートリアルでは、OTel Node.js SDK について知っておく必要のあるすべてのことが説明されていますが、詳細については、 OTel のドキュメントを参照してください。
ホーム ディレクトリにmicroservices-marchディレクトリを作成し、このチュートリアルの GitHub リポジトリをそこにクローンします。 (別のディレクトリ名を使用して、それに応じて手順を変更することもできます。)
注記: チュートリアル全体を通して、コマンドをターミナルにコピーして貼り付けやすくするために、Linux コマンドラインのプロンプトは省略されています。 チルダ ( ~
) はホームディレクトリを表します。
mkdir ~/microservices-marchcd ~/microservices-march
git clone https://github.com/microservices-march/messenger --branch mm23-metrics-start
git clone https://github.com/microservices-march/notifier --branch mm23-metrics-start
git clone https://github.com/microservices-march/platform --branch mm23-metrics-start
このチャレンジでは、メッセンジャーサービスを開始し、 OTel 自動インストルメンテーションを構成してテレメトリをコンソールに送信します。
プラットフォームリポジトリに変更し、Docker Compose を起動します。
cd ~/microservices-march/platformdocker を作成 -d --build
これにより、後続のチャレンジで使用される RabbitMQ と Jaeger が起動します。
‑d
フラグは、コンテナが起動したら Docker Compose にコンテナからデタッチするように指示します (そうでない場合、コンテナはターミナルに接続されたままになります)。--build
フラグは、起動時にすべてのイメージを再構築するように Docker Compose に指示します。 これにより、実行中のイメージは、ファイルへの潜在的な変更を通じて最新の状態に保たれます。メッセンジャーリポジトリのアプリディレクトリに変更し、Node.js をインストールします (必要に応じて別の方法に置き換えることもできます)。
cd ~/microservices-march/messenger/appasdf をインストールします
依存関係をインストールします:
npmインストール
メッセンジャーサービス用の PostgreSQL データベースを起動します。
ドッカー構成アップ -d
データベース スキーマとテーブルを作成し、シード データを挿入します。
npm 実行 refresh-db
OTel 自動インストルメンテーションを使用すると、トレースを設定するためにメッセンジャーのコードベースを変更する必要はありません。 すべてのトレース構成は、アプリケーション コード自体にインポートされるのではなく、実行時に Node.js プロセスにインポートされるスクリプトで定義されます。
ここでは、トレースの最も基本的な出力先であるコンソールを使用して、メッセンジャーサービスの自動インストルメンテーションを構成します。 チャレンジ 2では、外部コレクターとして Jaeger にトレースを送信するように設定を変更します。
引き続きメッセンジャーリポジトリのアプリディレクトリで作業し、コア OTel Node.js パッケージをインストールします。
npm インストール @opentelemetry/sdk-node@0.36.0 \
@opentelemetry/auto-instrumentations-node@0.36.4
これらのライブラリは次の機能を提供します。
@opentelemetry/sdk-node
– OTelデータの生成とエクスポート@opentelemetry/auto-instrumentations-node
– 最も一般的な Node.js インストルメンテーションのデフォルト構成による自動セットアップ注記: OTel の奇妙な点は、JavaScript SDK が非常に小さな部分に分割されていることです。 したがって、このチュートリアルの基本的な例のために、さらにいくつかのパッケージをインストールします。 このチュートリアルで説明されているもの以外のインストルメンテーション タスクを実行するために必要なパッケージを理解するには、(非常に優れた) OTel入門ガイドを熟読し、OTel GitHub リポジトリを確認してください。
OTel トレースのセットアップおよび構成コードを格納するために、 tracing.mjsという新しいファイルを作成します。
タッチトレーシング.mjs
好みのテキスト エディターでtracing.mjsを開き、次のコードを追加します。
//1
「@opentelemetry/sdk-node」から opentelemetry をインポートします。
「@opentelemetry/auto-instrumentations-node」から { getNodeAutoInstrumentations } をインポートします。
//2
const sdk = new opentelemetry.NodeSDK({
traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
instruments: [getNodeAutoInstrumentations()],
});
//3
sdk.start();
コードは次のことを行います。
NodeSDK の新しいインスタンスを作成し、次のように構成します。
ConsoleSpanExporter
)。自動インストゥルメンターを計測の基本セットとして使用します。 このインストルメンテーションは、最も一般的な自動インストルメンテーション ライブラリをすべて読み込みます。 チュートリアルで関連するものは次のとおりです。
pg
) の@opentelemetry/instrumentation-pg
@opentelemetry/instrumentation-express
amqplib
) の@opentelemetry/instrumentation-amqplib
手順 3 で作成した自動インストルメンテーション スクリプトをインポートして、メッセンジャーサービスを開始します。
ノード --import ./tracing.mjsindex.mjs
しばらくすると、トレースに関連する多くの出力がコンソール (ターミナル) に表示され始めます。
...
{
トレースID: '9c1801593a9d3b773e5cbd314a8ea89c'、
親 ID: 未定義、
トレース状態: 未定義、
名前: 'fs statSync'、
ID: '2ddf082c1d609fbe'、
種類: 0,
タイムスタンプ: 1676076410782000、
期間: 3,
属性: {},
ステータス: { コード: 0 },
イベント: [],
リンク: []
}
...
注記: チャレンジ 2 で再利用できるように、ターミナル セッションを開いたままにしておきます。
トレースを表示および分析するために使用できるツールは多数ありますが、このチュートリアルではJaegerを使用します。 Jaeger は、スパンやその他のトレース データを表示するための Web ベースのユーザー インターフェイスが組み込まれた、シンプルなオープン ソースのエンドツーエンドの分散トレース フレームワークです。 プラットフォームリポジトリで提供されるインフラストラクチャには Jaeger (チャレンジ 1 のステップ 1 で開始) が含まれているため、複雑なツールを扱う代わりにデータの分析に集中できます。
Jaeger はブラウザのhttp://localhost:16686エンドポイントでアクセスできますが、今すぐエンドポイントにアクセスすると、システムに関する情報は何も表示されません。 これは、現在収集しているトレースがコンソールに送信されているためです。 Jaeger でトレース データを表示するには、 OpenTelemetry プロトコル(OTLP) 形式を使用してトレースをエクスポートする必要があります。
このチャレンジでは、以下のインストルメンテーションを構成することで、コア ユーザー フローをインストルメント化します。
念のため、OTel 自動インストルメンテーションを使用するということは、トレースを設定するためにメッセンジャーのコードベースを変更する必要がないことを意味します。 代わりに、すべてのトレース構成は、実行時に Node.js プロセスにインポートされるスクリプト内にあります。 ここでは、メッセンジャーサービスによって生成されたトレースの送信先をコンソールから外部コレクター (このチュートリアルでは Jaeger) に変更します。
チャレンジ 1 と同じターミナルで作業し、メッセンジャーリポジトリのappディレクトリに、OTLP エクスポーター Node.js パッケージをインストールします。
インストールは、@opentelemetry/exporter-trace-otlp-http@0.36.0 です。
@opentelemetry/exporter-trace-otlp-http
ライブラリは、HTTP 経由で OTLP 形式でトレース情報をエクスポートします。 これは、OTel 外部コレクターにテレメトリを送信するときに使用されます。
tracing.mjs (チャレンジ 1 で作成および編集したもの) を開き、次の変更を加えます。
ファイルの先頭にあるインポート
ステートメントのセットに次の行を追加します。
"@opentelemetry/exporter-trace-otlp-http" から { OTLPTraceExporter } をインポートします。
OTel SDK に提供する「エクスポーター」を、チャレンジ 1 で使用したコンソール エクスポーターから、OTLP データを HTTP 経由で OTLP 互換コレクターに送信できるエクスポーターに変更します。 交換する:
traceExporter:新しい opentelemetry.tracing.ConsoleSpanExporter()、
と:
トレース エクスポーター: 新しい OTLPTraceExporter({ ヘッダー: {} })、
注記: 簡潔にするために、このチュートリアルでは、コレクターがデフォルトの場所http://localhost:4318/v1/tracesにあると想定しています。 実際のシステムでは、場所を明示的に設定することをお勧めします。
コンソールに送信される OTel 自動インストルメンテーションの構成の手順 4 でこの端末で開始したメッセンジャーサービスを停止するには、 Ctrl+c
を押します。 次に、手順 2 で設定した新しいエクスポーターを使用するために再起動します。
^cnode --import ./tracing.mjs index.mjs
2 番目の別のターミナル セッションを開始します。 (以降の手順では、これをクライアント端末と呼び、手順 1 と 3 で使用した元の端末をメッセンジャー端末と呼びます。) 約 10 秒待ってから、メッセンジャーサービスにヘルスチェック要求を送信します (複数のトレースを表示する場合は、これを数回実行できます)。
curl -X GET http://localhost:4000/health
リクエストを送信する前に 10 秒待つと、サービスの開始時に自動インストルメンテーションによって生成される多数のトレースの後にリクエストが送信されるため、トレースを見つけやすくなります。
ブラウザで、 http://localhost:16686の Jaeger UI にアクセスし、OTLP エクスポーターが期待どおりに動作していることを確認します。 タイトル バーの[検索]をクリックし、 [サービス]フィールドのドロップダウン メニューから、名前がunknown_serviceで始まるサービスを選択します。 [トレースの検索]ボタンをクリックします。
ウィンドウの右側にあるトレースをクリックすると、その中のスパンのリストが表示されます。 各スパンは、トレースの一部として実行された操作(複数のサービスが関係する場合もあります)を説明します。 スクリーンショットのjsonParserスパンは、メッセンジャーサービスのリクエスト処理コードのjsonParser
部分の実行にかかった時間を示しています。
ステップ 5 で説明したように、OTel SDK によってエクスポートされたサービスの名前 ( unknown_service ) には意味がありません。 これを修正するには、メッセンジャー ターミナルでCtrl+c を
押してメッセンジャーサービスを停止します。 次に、さらにいくつかの Node.js パッケージをインストールします。
^c
npm インストール @opentelemetry/semantic-conventions@1.10.0 \
@opentelemetry/resources@1.10.0
これら 2 つのライブラリは次の機能を提供します。
@opentelemetry/semantic-conventions
– OTel 仕様で定義されているトレースの標準属性を定義します。@opentelemetry/resources
– OTel データを生成するソース (このチュートリアルではメッセンジャーサービス) を表すオブジェクト (リソース) を定義します。テキスト エディターでtracing.mjsを開き、次の変更を加えます。
ファイルの先頭にあるインポート
ステートメントのセットに次の行を追加します。
「@opentelemetry/resources」から Resource をインポートします。「@opentelemetry/semantic-conventions」から SemanticResourceAttributes をインポートします。
最後のインポート
ステートメントの後に次の行を追加して、OTel 仕様の正しいキーの下にmessenger
というリソース
を作成します。
const リソース = 新しいリソース ({ [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});
オレンジ色で強調表示された行を黒色の行の間に追加して、リソース
オブジェクトを NodeSDK コンストラクターに渡します。
const sdk = new opentelemetry.NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ headers: {} }), instruments: [getNodeAutoInstrumentations()], });
メッセンジャーサービスを再起動します。
ノード --import ./tracing.mjsindex.mjs
約 10 秒待ってから、クライアント ターミナル (手順 4 で開いたもの) で、サーバーに別のヘルスチェック要求を送信します (複数のトレースを表示する場合は、コマンドを数回実行できます)。
curl -X GET http://localhost:4000/health
注記: クライアント端末は次のセクションで再利用できるように開いたままにし、メッセンジャー端末はチャレンジ 3 で再利用できるように開いたままにしておきます。
ブラウザの Jaeger UI にmessengerという新しいサービスが表示されていることを確認します (これには数秒かかる場合があり、Jaeger UI を更新する必要がある場合があります)。
[サービス] ドロップダウン メニューから[メッセンジャー]を選択し、 [トレースの検索]ボタンをクリックすると、メッセンジャーサービスから発生した最近のトレースがすべて表示されます (スクリーンショットには、20 個のうち最新の 2 個が表示されています)。
トレースをクリックすると、その中のスパンが表示されます。 各スパンは、メッセンジャーサービスから発信されたものとして適切にタグ付けされます。
次に、基本的にメッセンジャーサービスの場合の前の 2 つのセクションと同じコマンドを実行して、通知サービスの自動インストルメンテーションを起動して構成します。
新しいターミナル セッション (以降の手順では通知ターミナルと呼ばれます) を開きます。 通知リポジトリのアプリディレクトリに変更し、Node.js をインストールします (必要に応じて別の方法に置き換えることもできます)。
cd ~/microservices-march/notifier/appasdf install
依存関係をインストールします:
npmインストール
通知サービス用の PostgreSQL データベースを起動します。
ドッカー構成アップ -d
データベース スキーマとテーブルを作成し、シード データを挿入します。
npm 実行 refresh-db
OTel Node.js パッケージをインストールします (パッケージの機能の説明については、 「コンソールに送信される OTel 自動インストルメンテーションの構成」の手順 1 と 3 を参照してください)。
npm をインストールします @opentelemetry/auto-instrumentations-node@0.36.4 \
@opentelemetry/exporter-trace-otlp-http@0.36.0 \
@opentelemetry/resources@1.10.0 \
@opentelemetry/sdk-node@0.36.0 \
@opentelemetry/semantic-conventions@1.10.0
tracing.mjsという新しいファイルを作成します。
タッチトレーシング.mjs
好みのテキスト エディターでtracing.mjsを開き、次のスクリプトを追加して OTel SDK を起動して実行します。
opentelemetry を "@opentelemetry/sdk-node" からインポートします。
{ getNodeAutoInstrumentations } を "@opentelemetry/auto-instrumentations-node" からインポートします。
{ OTLPTraceExporter } を "@opentelemetry/exporter-trace-otlp-http" からインポートします。
{ Resource } を "@opentelemetry/resources" からインポートします。
{ SemanticResourceAttributes } を "@opentelemetry/semantic-conventions" からインポートします。
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: "notifier",
});
const sdk = new opentelemetry.NodeSDK({
resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instruments: [getNodeAutoInstrumentations()],
});
sdk.start();
注記: このスクリプトは、 SemanticResourceAttributes.SERVICE_NAME
フィールドの値がnotifier
である点を除いて、メッセンジャーサービスのスクリプトとまったく同じです。
OTel 自動計測機能を使用して通知サービスを開始します。
ノード --import ./tracing.mjsindex.mjs
約 10 秒待ってから、クライアント端末で通知サービスにヘルスチェック要求を送信します。 このサービスは、ポート 4000 でリッスンしているメッセンジャーサービスとの競合を防ぐために、ポート 5000 でリッスンしています。
カール http://localhost:5000/health
注記: チャレンジ 3 で再利用できるように、クライアント端末と通知端末を開いたままにしておきます。
ブラウザの Jaeger UI にnotifierという新しいサービスが表示されていることを確認します。
NGINX の場合、OTel 自動インストルメンテーション メソッドを使用する代わりに、手動でトレースを設定します。 現在、OTel を使用して NGINX をインストルメントする最も一般的な方法は、 C で記述されたモジュールを使用することです。サードパーティのモジュールは NGINX エコシステムの重要な部分ですが、セットアップには多少の作業が必要です。 このチュートリアルではセットアップの手順を説明します。 背景情報については、ブログの「NGINX および NGINX Plus 用のサードパーティ製動的モジュールのコンパイル」を参照してください。
新しいターミナル セッション ( NGINX ターミナル) を開始し、ディレクトリをメッセンジャーリポジトリのルートに変更して、 load-balancerという新しいディレクトリと、 Dockerfile 、 nginx.conf 、 opentelemetry_module.confという新しいファイルを作成します。
cd ~/microservices-march/messenger/mkdir load-balancer
cd load-balancer
touch Dockerfile
touch nginx.conf
touch opentelemetry_module.conf
好みのテキスト エディターでDockerfileを開き、次のコードを追加します (コメントで各行の機能について説明していますが、すべてを理解していなくても Docker コンテナーをビルドして実行できます)。
FROM --platform=amd64 nginx:1.23.1 # nginx.conf ファイルを独自のものに置き換えますCOPY nginx.conf /etc/nginx/nginx.conf # NGINX OTel モジュールのバージョンを定義しますARG OPENTELEMETRY_CPP_VERSION=1.0.3 # NGINX のコンパイルおよび実行時に使用される共有ライブラリの検索パスを定義しますENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib # 1. Consul テンプレートの最新バージョンと OTel C++ Web サーバー モジュール otel-webserver-module ADD をダウンロードしますhttps://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv${OPENTELEMETRY_CPP_VERSION} /opentelemetry-webserver-sdk-x64-linux.tgz /tmp RUN apt-get update \ && apt-get install -y --no-install-recommends dumb-init unzip \ # 2. モジュール ファイルを抽出します&& tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \ && rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \ # 3. メインの NGINX 構成ファイルの先頭に 'load_module' ディレクティブをインストールして追加します&& /opt/opentelemetry-webserver-sdk/install.sh \ && echo "load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.23.1/ngx_http_opentelemetry_module.so;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf # 4. NGINX OTelモジュールの設定ファイルにコピーします。COPY opentelemetry_module.conf /etc/nginx/conf.d/opentelemetry_module.conf EXPOSE 8085 STOPSIGNAL SIGQUIT
nginx.confを開き、以下を追加します。
events {}
http {
include /etc/nginx/conf.d/opentelemetry_module.conf;
アップストリーム メッセンジャー {
server localhost:4000;
}
server {
listen 8085;
location / {
proxy_pass http://messenger;
}
}
}
この非常に基本的なNGINX 構成ファイルは、 NGINX に次のことを指示します。
注記: これは、実稼働環境でのリバース プロキシおよびロード バランサーとしての NGINX の実際の構成にかなり近いものです。 唯一の大きな違いは、 upstream
ブロックのserver
ディレクティブへの引数が、通常はlocalhost
ではなくドメイン名または IP アドレスであることです。
opentelemetry_module.confを開き、以下を追加します。
NginxModuleEnabled オン;NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends オン;
NginxModuleTraceAsError オン;
NGINX と NGINX OTel モジュールを含む Docker イメージをビルドします。
ビルドを実行します。
NGINX リバース プロキシとロード バランサの Docker コンテナを起動します。
docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
クライアント端末で、NGINX リバース プロキシとロード バランサーを介してメッセンジャーサービスにヘルス チェック要求を送信します (この要求を送信する前に待つ必要はありません)。
カール http://localhost:8085/health
注記: チャレンジ 3 で再利用できるように、NGINX とクライアント ターミナルを開いたままにしておきます。
ブラウザで、以前に開始したサービスとともに、新しいmessenger-lbサービスが Jaeger UI にリストされていることを確認します。 ブラウザで Jaeger UI を再読み込みする必要がある場合があります。
「アーキテクチャとユーザーフロー」では、ユーザーフローの段階について概説しましたが、要約すると次のようになります。
テレメトリを実装する目的は次のとおりです。
このチャレンジでは、OTel 計測によって生成されたトレースが前述の目標を満たしているかどうかを評価する方法を学習します。 まず、システムを試してみて、いくつかのトレースを作成します。 次に、 NGINX 、メッセンジャーサービス、通知サービスによって生成されたメッセージ フローとそのセクションのトレースを調べます。
クライアント端末で会話を設定し、2 人のユーザー間でいくつかのメッセージを送信します。
curl -X POST \
-H "コンテンツタイプ: application/json" \
-d '{"participant_ids": [1, 2]}' \
'http://localhost:8085/conversations'
curl -X POST \
-H "ユーザー ID: 1" \
-H "コンテンツタイプ: application/json" \
-d '{"content": "これは最初のメッセージです"}' \
'http://localhost:8085/conversations/1/messages'
curl -X POST \
-H "ユーザー ID: 2" \
-H "コンテンツタイプ: application/json" \
-d '{"content": 「これは 2 番目のメッセージです」}' \
'http://localhost:8085/conversations/1/messages'
次のような出力が通知サービスによって生成され、通知端末に表示されます。
新しいメッセージを受信しました: {"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}新しいメッセージの通知を SMS で 12027621401 に送信しています
新しいメッセージを受信しました: {"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}
新しいメッセージの通知を電子メールで the_hotstepper@kamo.ze に送信しています
新しいメッセージの通知を SMS で 19147379938 に送信しています
ブラウザで Jaeger UI を開き、 [サービス]ドロップダウン メニューからmessenger-lb を選択して、 [トレースの検索]ボタンをクリックします。 フローの最初から始まるトレースのリストが表示されます。 任意のトレースをクリックすると、次のスクリーンショットのようにその詳細が表示されます。
クリックして少し探検してみましょう。 次に進む前に、トレース内の情報が、チャレンジ 3 の概要に記載されている計測の目標をどのようにサポートするかを検討します。 関連する質問は次のとおりです。
親スパン内に 11 個の子スパンがある NGINX スパンから始めます。 現在の NGINX 構成は非常に単純なので、子スパンはあまり興味深いものではなく、NGINX リクエスト処理ライフサイクルの各ステップにかかる時間を示すだけです。 ただし、親スパン (一番最初のスパン) には、いくつか興味深い洞察が含まれています。
タグの下に次の属性が表示されます。
http.method
フィールド – POST
( REST用語では作成を意味します)http.status_code
フィールド –201
(作成が成功したことを示します)http.target
フィールド – conversations/1/messages
(メッセージ エンドポイント)これら 3 つの情報を総合すると、次のことがわかります。 「 POST
リクエストが/conversations/1/messages
に送信され、応答が201
(正常に作成されました)”。 これは、「アーキテクチャとユーザーフロー」のステップ 1 と 4a に対応します。
webengine.name
フィールドに、これがリクエストの NGINX 部分であることが示されます。さらに、 messengerとnotifierのスパンはmessenger-lb conversations/1
スパン内にネストされているため ( 「トレースの読み取り準備」のスクリーンショットに示されているように)、NGINX リバース プロキシ経由でメッセンジャーサービスに送信されたリクエストが、フロー内のすべての予想されるコンポーネントにヒットしたことがわかります。
この情報は、NGINX リバース プロキシがフローの一部であったことがわかるため、目的を満たしています。
messenger-lbというラベルの付いたスパンのリストで、最新のスパン (リストの一番下にあります) を見て、リクエストの NGINX 部分にかかった時間を確認します。 スクリーンショットでは、スパンは 589 マイクロ秒 (µs) で始まり、24 µs 続きました。つまり、リバース プロキシ操作全体はわずか 613 µs、つまり約 0.6 ミリ秒 (ms) しかかかりませんでした。 (もちろん、チュートリアルを自分で実行した場合、正確な値は異なります。)
このような設定では、ほとんどの場合、値は他の測定値と比較してのみ有用であり、システムによって異なります。 ただし、この場合、この操作の長さが 5 秒に近づく危険性は明らかにありません。
この情報は、NGINX 操作に 5 秒近くかかっていないことがわかるため、目的を満たしています。 フロー内に非常に遅い操作がある場合、それは後で実行されるはずです。
NGINX リバース プロキシ レイヤーにはこれに関する情報が含まれていないため、メッセンジャースパンの手順に進むことができます。
トレースのメッセンジャーサービス セクションには、さらに 11 個のスパンが含まれています。 繰り返しになりますが、子スパンのほとんどは、Express フレームワークがリクエストを処理するときに使用する基本的な手順に関するものであり、あまり興味深いものではありません。 ただし、親スパン (一番最初のスパン) にも、興味深い洞察が含まれています。
タグの下に次の属性が表示されます。
http.method
フィールド – POST
(REST 用語では、これは作成を意味します)http.route
フィールド – /conversations/:conversationId/messages
(メッセージ ルート)http.target
フィールド – /conversations/1/messages
(メッセージ エンドポイント)この情報は、メッセンジャーサービスがフローの一部であり、エンドポイント ヒットが新しいメッセージ エンドポイントであったことを示しているため、目標を満たしています。
次のスクリーンショットに示されているように、トレースのメッセンジャー部分は 1.28 ミリ秒で始まり、36.28 ミリ秒で終了し、全体の時間は 35 ミリ秒でした。 その時間のほとんどは、JSON の解析 (ミドルウェア
-
jsonParser
) と、さらに長い時間、データベースへの接続 ( pg-pool.connect
およびtcp.connect
) に費やされました。
メッセージの書き込みプロセス中に複数の SQL クエリも実行されることを考えると、これは理にかなっています。 つまり、これらのクエリのタイミングをキャプチャするために、自動インストルメンテーション構成を拡張する必要があることを意味します。 (チュートリアルではこの追加のインストルメンテーションは示されていませんが、チャレンジ 4 では、データベース クエリをラップするために使用できるスパンを手動で作成します。)
この情報は、メッセンジャーの操作に 5 秒近くかからないことを示しているため、目標を満たしています。 フロー内に非常に遅い操作がある場合、それは後で実行されるはずです。
NGINX スパンの様に、メッセンジャースパンにはこの情報は含まれていないので、通知スパンに移動することができます。
トレースの通知セクションには、次の 2 つのスパンのみが含まれます。
chat_queue
プロセス
スパン –通知サービスがchat_queueメッセージ キューからのイベントを処理したことを確認します。pg-pool.connect
スパン – イベント処理後に通知サービスがデータベースに何らかの接続を行ったことを示します。これらのスパンから得られる情報は、すべてのステップを理解するという目標を部分的にしか達成しません。 通知サービスがキューからイベントを消費する段階に到達したことはわかりますが、次の点についてはわかりません。
これは、通知サービス フローを完全に理解するには、次の操作を実行する必要があることを示しています。
通知サービス スパンの全体的なタイミングを見ると、リクエストがフローの通知セクションで 30.77 ミリ秒を費やしたことがわかります。 ただし、フロー全体 (受信者への通知の送信) の「終了」を通知するスパンがないため、フローのこのセクションの合計タイミングや操作の全体的な完了時間を特定することはできません。
ただし、通知サービスのchat_queue
プロセス
スパンは、メッセンジャーサービスのchat_queue
送信
スパンが 4.12 ミリ秒で開始されてから 2 ミリ秒後に、6.12 ミリ秒で開始されたことがわかります。
この目標は、通知機能がメッセンジャーサービスによって送信されてから 2 ミリ秒後にイベントを消費したことがわかっているため達成されます。 目標 2 とは異なり、この目標を達成するには、イベントが完全に処理されたかどうか、またはそれにかかった時間を知る必要はありません。
現在の OTel 自動計測によって生成されたトレースの分析に基づくと、次のことが明らかです。
これらのスパンの多くは、現在の形式では役に立ちません。
リクエスト
ハンドラー
、およびすべてのデータベース操作のスパンが目標に関連しているようです。 一部のミドルウェア スパーン ( expressInit
やcorsMiddleware
など) は関連性がないと思われるため、削除できます。次の重要なスパンが欠落しています:
これは、基本的な計測が最後の目標を達成することを意味します。
しかし、最初の 2 つの目標を達成するには情報が不十分です。
このチャレンジでは、チャレンジ 3 で行ったトレース分析に基づいて OTel 計測を最適化します。 これには、不要なスパンの削除、新しいカスタム スパンの作成、通知サービスによって消費されるイベントがメッセンジャー サービスによって生成されたイベントであることを確認することが含まれます。
好みのテキスト エディターで、メッセンジャーリポジトリのappディレクトリにあるtracing.mjsファイルを開き、上部のインポート ステートメントのリストの最後に次のコードを追加します。
const IGNORED_EXPRESS_SPANS = new Set([ "ミドルウェア - expressInit",
"ミドルウェア - corsMiddleware",
]);
これは、次の Jaeger UI のスクリーンショットに示されているスパンのリストから派生したスパン名のセットを定義します。このスパン名のセットは、このフローに有用な情報を提供しないため、トレースから省略されます。 スクリーンショットにリストされている他のスパンも不要であると判断し、それらをIGNORED_EXPRESS_SPANS
のリストに追加することもできます。
オレンジ色で強調表示された行を変更して、自動インストルメンテーション構成にフィルターを追加し、不要なスパンを省略します。
const sdk = new opentelemetry.NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ headers: {} }), instruments: [getNodeAutoInstrumentations()], });
これに:
const sdk = new opentelemetry.NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ headers: {} }), instruments: [ getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-express": { ignoreLayers: [ (name) => { return IGNORED_EXPRESS_SPANS.has(name); }, ], }, }), ], });
getNodeAutoInstrumentations
関数は、ステップ 1 で定義されたスパンのセットを参照して、 @opentelemetry/instrumentation-express
によって生成されたトレースからそれらをフィルター処理します。 つまり、 return
ステートメントはIGNORED_EXPRESS_SPANS
に属するスパンに対してtrue
に解決され、 ignoreLayers
ステートメントはそのスパンをトレースから削除します。
メッセンジャー ターミナルで、 Ctrl + c
を押してメッセンジャーサービスを停止します。 次に再起動します。
^cnode --import ./tracing.mjs index.mjs
約 10 秒待ってから、クライアント端末で新しいメッセージを送信します。
curl -X POST \ -H "ユーザーID: 2" \
-H "コンテンツタイプ: application/json" \
-d '{"content": 「これは 2 番目のメッセージです」}' \
'http://localhost:8085/conversations/1/messages'
Jaeger UI でメッセンジャースパンを再確認します。 expressInit
とcorsMiddleware の
2 つのミドルウェア
スパンは表示されなくなりました (チャレンジ 3 のトレースのメッセンジャー セクションを調べるの目標 2のスクリーンショットと比較できます)。
このセクションでは、初めてアプリケーション コードに触れます。 自動インストルメンテーションでは、アプリケーションを変更することなく大量の情報が生成されますが、特定のビジネス ロジックをインストルメント化することによってのみ、一部の洞察が得られます。
インストルメント化している新しいメッセージ フローの場合、この例としては、メッセージの受信者への通知の送信のトレースが挙げられます。
通知リポジトリのappディレクトリにあるindex.mjs を開きます。 このファイルには、サービスのビジネス ロジックがすべて含まれています。 ファイルの先頭にあるインポート
ステートメントのリストの最後に次の行を追加します。
"@opentelemetry/api" から { trace } をインポートします。
このコードを置き換えます(ファイルの約91行目)。
for (let pref of preference) { console.log( `新しいメッセージの通知を送信しています${pref.address_type}に${pref.address}`); }
と:
const tracer = trace.getTracer("notifier"); // 1tracer.startActiveSpan( // 2 "notification.send_all", { attribute: { user_id: msg.user_id, }, }, (parentSpan) => { for (let pref of preference) { tracer.startActiveSpan( // 3 "notification.send", { attribute: { // 4 notification_type: pref.address_type, user_id: pref.user_id, }, }, (span) => { console.log( `新しいメッセージの通知を${pref.address_type}に${pref.address}` ); span.end(); // 5 } ); } parentSpan.end(); // 6 } );
新しいコードは次のことを行います。
トレーサー
を取得します。notification.send_all
という新しい親スパンを開始し、メッセージの送信者を識別するためにuser_id
属性を設定します。notification.send_all
の下にnotification.send
という新しい子スパンが作成されます。 通知ごとに新しいスパンが生成されます。子スパンにさらに属性を設定します:
notification_type
– SMS
またはメール
のいずれかuser_id
– 通知を受信するユーザーのIDnotification.send
スパンを順番に閉じます。notification.send_all
スパンを閉じます。親スパンを設定すると、ユーザーの通知設定が検出されない場合でも、各「通知の送信」操作が報告されることが保証されます。
通知ターミナルで、 Ctrl + c
を押して通知サービスを停止します。 次に再起動します。
^cnode --import ./tracing.mjs index.mjs
約 10 秒待ってから、クライアント端末で新しいメッセージを送信します。
curl -X POST \ -H "ユーザーID: 2" \
-H "コンテンツタイプ: application/json" \
-d '{"content": 「これは 2 番目のメッセージです」}' \
'http://localhost:8085/conversations/1/messages'
Jaeger UI で通知スパンを再確認します。親スパンと 2 つの子スパンが表示され、それぞれに「通知の送信」操作があります。
新しいメッセージ フロー中にリクエストが実行するすべてのステップを確認できるため、最初の目標と 2 番目の目標を完全に達成できるようになりました。 各スパンのタイミングにより、これらのステップ間の遅延が明らかになります。
フローを完全に把握するために必要なものがもう 1 つあります。 通知サービスによって処理されているイベントは、実際にはメッセンジャーサービスによって送信されたイベントですか?
2 つのトレースを接続するために明示的な変更を加える必要はありませんが、自動インストルメンテーションの魔法を盲目的に信頼することも望ましくありません。
これを念頭に置いて、NGINX サービスで開始されるトレースが通知サービスによって使用されるトレースと実際に同じである (同じトレース ID を持つ) ことを確認するための簡単なデバッグ コードを追加します。
メッセンジャーリポジトリのappディレクトリにあるindex.mjsファイルを開き、次の変更を加えます。
上部のインポート ステートメントのリストの最後に次の行を追加します。
"@opentelemetry/api" から { trace } をインポートします。
既存の黒色の行の下に、オレンジ色で強調表示された行を追加します。
非同期関数 createMessageInConversation(req, res) { const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);
新しい行は、新しいメッセージの作成を処理するメッセンジャーの関数内からTRACE_ID を
出力します。
通知リポジトリのappディレクトリにあるindex.mjsファイルを開き、オレンジ色で強調表示された行を黒色で既存の行の下に追加します。
非同期関数 handleMessageConsume(channel, msg, handlers) をエクスポートします { console.log("RABBIT_MQ_MESSAGE: ", msg);
新しい行には、通知サービスによって受信された AMQP イベントの完全な内容が出力されます。
メッセンジャー ターミナルと通知ターミナルの両方で次のコマンドを実行して、メッセンジャー サービスと通知サービスを停止して再起動します。
^cnode --import ./tracing.mjs index.mjs
約 10 秒待ってから、クライアント端末でメッセージを再度送信します。
curl -X POST \ -H "ユーザーID: 2" \
-H "コンテンツタイプ: application/json" \
-d '{"content": 「これは 2 番目のメッセージです」}' \
'http://localhost:8085/conversations/1/messages'
メッセンジャーおよび通知サービスのログを確認します。 メッセンジャーサービスのログには、メッセージのトレース ID を報告する次のような行が含まれます (チュートリアルを実行すると、実際の ID は異なります)。
トレースID: 29377a9b546c50be629c8e64409bbfb5
同様に、通知サービス ログは次のような出力でトレース ID を報告します。
_spanContext: {
トレースID: '29377a9b546c50be629c8e64409bbfb5'、spanId: 'a94e9462a39e6dbf'、トレースフラグ: 1,
トレース状態: 未定義
},
コンソールではトレース ID が一致していますが、最後のステップとして、Jaeger UI のトレース ID と比較することができます。関連するトレース ID エンドポイント (実際のエンドポイントは異なりますが、この例ではhttp://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5です) で UI を開き、トレース全体を表示します。 イェーガーの追跡により次のことが確認されました:
注記: 実際の運用システムでは、フローが期待どおりに動作していることを確認したら、このセクションで追加したコードを削除します。
このチュートリアルでは、いくつかのコンテナーとイメージを作成しました。 削除するには、次の手順に従ってください。
実行中の Docker コンテナを削除するには:
docker rm $(docker stop メッセンジャー-lb)
プラットフォームサービスとメッセンジャーおよび通知データベース サービスを削除するには:
cd ~/microservices-march/platform && docker compose down
cd ~/microservices-march/notifier && docker compose down
cd ~/microservices-march/messenger && docker compose down
おめでとうございます。チュートリアルは完了です。
しかし、理想的なトレース構成がどのようなものかは、まだほんの表面をなぞった程度にしか理解できていません。 実稼働環境では、各データベース クエリのカスタム スパンや、各サービスのコンテナー ID などの実行時の詳細を説明するすべてのスパンの追加メタデータなどを追加する必要がある場合があります。また、システムの健全性の全体像を把握するために、他の 2 種類の OTel データ (メトリックとログ) を実装することもできます。
マイクロサービスに関する学習を継続するには、「Microservices March 2023」をご覧ください。 ユニット4: 「可観測性によるマイクロサービスの混乱と複雑さの管理」では、可観測性データの 3 つの主要なクラス、インフラストラクチャとアプリの調整の重要性、および詳細なデータの分析を開始する方法について学習します。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"