ブログ | NGINX

NGINX チュートリアル: OpenTelemetry トレースを使用してマイクロサービスを理解する方法

NGINX-F5 水平黒タイプ RGB の一部
ハビエル・エヴァンス サムネイル
ハビエル・エヴァンス
2023年3月28日公開

この投稿は、 2023 年 3 月の Microservices の概念を実践するのに役立つ 4 つのチュートリアルの 1 つです。 マイクロサービスの提供を開始する:

 

マイクロサービス アーキテクチャには、チームの自律性の向上、スケーリングと展開の柔軟性の向上など、多くの利点があります。 欠点としては、システム内のサービスが増えるほど (マイクロサービス アプリでは数十、数百のサービスが存在する場合があります)、システム全体の動作を明確に把握することが難しくなります。 複雑なソフトウェア システムの作成者および保守者として、私たちは明確なイメージを持つことの重要性を認識しています。 可観測性ツールを使用すると、多数のサービスとサポートインフラストラクチャ全体にわたってその画像を構築できるようになります。

このチュートリアルでは、マイクロサービス アプリの非常に重要な可観測性の 1 つであるトレースについて説明します。 始める前に、可観測性について議論するときによく使用される用語をいくつか定義しましょう。

  • 可観測性– 外部出力 (トレース、ログ、メトリックなど) に関する知識のみに基づいて、複雑なシステム (マイクロサービス アプリなど) の内部状態または状況を理解する能力。
  • 監視– 一定期間にわたってオブジェクトの進行状況やステータスを観察および確認する機能。 たとえば、ピーク時にアプリに流入するトラフィックを監視し、その情報を使用してそれに応じて適切にスケーリングすることができます。
  • テレメトリ– メトリック、トレース、ログを収集し、それらを発生元から別のシステムに転送して保存および分析する行為。 また、データ自体も。
  • トレーシング/トレース– 分散システムのすべてのノードを通過するリクエストまたはアクションの経路の記録。
  • スパン– 操作のトレースとそれに関連するメタデータ内のレコード。 トレースは多数のネストされたスパンで構成されます。
  • イベント ログ/ログ– メタデータを含むタイムスタンプ付きのテキスト レコード。
  • メトリック– 実行時にキャプチャされた測定値。 たとえば、特定の時点でアプリケーションによって使用されているメモリの量などです。

これらすべての概念を使用して、マイクロサービスのパフォーマンスに関する洞察を得ることができます。 トレースは、リクエストが行われたときに、多くの場合は疎結合された複数のコンポーネント間で何が起こっているかの「全体像」を提供するため、可観測性戦略の特に便利な部分です。 これは、パフォーマンスのボトルネックを特定するのに特に効果的な方法でもあります。

このチュートリアルでは、急速に人気が高まっているテレメトリの収集、処理、エクスポートのためのオープン ソースのベンダー中立標準であるOpenTelemetry (OTel) のトレース ツール キットを使用します。 OTel のコンセプトでは、トレースは複数のサービスに関係する可能性のあるデータ フローを、時系列に並べられた一連の「チャンク」に分割し、次の内容を簡単に理解できるようにします。

  • 「チャンク」で発生したすべてのステップ
  • これらのステップにかかった時間は
  • 各ステップに関するメタデータ

OTel に詳しくない場合は、 「OpenTelemetry とは?」を参照して、標準の詳細な概要と実装に関する考慮事項を確認してください。

チュートリアルの概要

このチュートリアルでは、OTel を使用してマイクロサービス アプリの操作をトレースすることに重点を置いています。 このチュートリアルの 4 つの課題では、システムを通じてリクエストを追跡し、マイクロサービスに関する質問に答える方法を学習します。

これらの課題は、トレースを初めて設定するときに推奨されるプロセスを示しています。 手順は次のとおりです。

  1. システムと、計測する特定の操作を理解します。
  2. 実行中のシステムから何を知る必要があるかを決定します。
  3. システムを「単純に」インストルメント化します。つまり、不要な情報を除去したり、カスタム データ ポイントを収集したりせずに、デフォルト構成を使用します。そして、インストルメンテーションが質問の回答に役立つかどうかを評価します。
  4. 報告される情報を調整して、質問に迅速に回答できるようにします。

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

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

チュートリアルのアーキテクチャとテレメトリの目標

アーキテクチャとユーザーフロー

この図は、チュートリアルで使用されるマイクロサービスとその他の要素間の全体的なアーキテクチャとデータ フローを示しています。

チュートリアルで使用されるトポロジを示す図。2 つのマイクロサービス、NGINX、および RabbitMQ を含むメッセージング システムの OpenTelemetry トレースを示します。

2 つのマイクロサービスは次のとおりです。

サポート インフラストラクチャは次の 3 つです。

  • NGINX オープンソースメッセンジャーサービスとシステム全体へのエントリポイント
  • RabbitMQ – サービスが非同期通信できるようにする人気のオープンソースメッセージブローカー
  • Jaeger – システムのコンポーネントからテレメトリを収集して視覚化するオープンソースのエンドツーエンドの分散トレースシステム

OTel をしばらく図から外して、追跡している一連のイベント、つまり、ユーザーが新しいチャット メッセージを送信し、受信者にその通知が届いたときに何が起こるかに集中することができます。

チュートリアルで使用されるメッセージング システムの情報の流れを示す図

フローは次のようになります。

  1. ユーザーがメッセンジャーサービスにメッセージを送信します。 NGINX リバース プロキシはメッセージを傍受し、メッセンジャーサービスの多数のインスタンスの 1 つに転送します。
  2. メッセンジャーサービスは新しいメッセージをデータベースに書き込みます。
  3. メッセンジャーサービスは、メッセージが送信されたことを示すために、 chat_queueと呼ばれる RabbitMQ メッセージ キューにイベントを生成します。 このイベントは一般的なものであり、特定のターゲットはありません。
  4. 同時に:

    • 4a. メッセンジャーサービスは、メッセージが正常に送信されたことを報告する応答を送信者に返します。
    • 4b. 通知サービスは、 chat_queue上の新しいイベントを認識し、それを消費します。
  5. 通知サービスは、新しいメッセージの受信者の通知設定をデータベースで確認します。
  6. 通知サービスは、受信者の優先方法を使用して 1 つまたは複数の通知を送信します (このチュートリアルでは、方法の選択肢は SMS と電子メールです)。

テレメトリの目標

テレメトリ計測を設定するときは、「すべてを送信して洞察を期待する」よりも明確な計測の目標から始めるのが最適です。 このチュートリアルには、3 つの主要なテレメトリ目標があります。

  1. 新しいメッセージフロー中にリクエストが通過するすべてのステップを理解する
  2. 通常の条件下では、フローがエンドツーエンドで 5 秒以内に実行されることを確信できます。
  3. 通知サービスがメッセンジャーサービスによって送信されたイベントの処理を開始するのにかかる時間を確認します (遅延が長すぎる場合は、通知サービスがイベント キューからの読み取りに問題があり、イベントがバックアップされている可能性があります)

これらの目標は、システムの技術的な操作とユーザー エクスペリエンスの両方に関連していることに注意してください。

チュートリアルの前提条件とセットアップ

前提条件

独自の環境でチュートリアルを完了するには、次のものが必要です。

  • Linux/Unix互換環境

    注記: このチュートリアルの NGINX トレースを伴うアクティビティは、NGINX 用の OpenTelemetry モジュールに互換性がないため、ARM ベースのプロセッサでは動作しません。 (これには、Linux aarch64 アーキテクチャと、M1 または M2 チップを搭載した Apple マシンが含まれます。) メッセンジャーおよび通知サービスに関連するアクティビティは、すべてのアーキテクチャで動作します。

  • Linux コマンドライン、JavaScript、 bashの基本的な知識 (ただし、すべてのコードとコマンドが提供され、説明されているため、限られた知識でも成功できます)
  • DockerDocker Compose
  • Node.js 19.x 以降

    • バージョン 19.x をテストしましたが、Node.js の新しいバージョンでも動作すると予想されます。
    • Node.js のインストールの詳細については、メッセンジャーサービス リポジトリのREADMEを参照してください。 チュートリアルで使用されているのとまったく同じ Node.js バージョンを取得するには、 asdfをインストールすることもできます。
  • curl (ほとんどのシステムに既にインストールされています)
  • アーキテクチャとユーザーフローにリストされているテクノロジー:メッセンジャー通知機能(次のセクションでダウンロードします)、 NGINX Open SourceJaeger 、およびRabbitMQ

注記: メッセンジャーおよび通知サービスは Node.js で記述されているため、このチュートリアルでは JavaScript SDK を使用します。 また、OTel自動インストルメンテーション機能 (自動インストルメンテーションとも呼ばれます) を設定して、OTel から入手できる情報の種類を把握できるようにします。 このチュートリアルでは、OTel Node.js SDK について知っておく必要のあるすべてのことが説明されていますが、詳細については、 OTel のドキュメントを参照してください。

設定

  1. ターミナルセッションを開始します。
  2. ホーム ディレクトリに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
    

課題1: 基本的なOTel計測機器のセットアップ

このチャレンジでは、メッセンジャーサービスを開始しOTel 自動インストルメンテーションを構成してテレメトリをコンソールに送信します。

メッセンジャーサービスを起動する

  1. プラットフォームリポジトリに変更し、Docker Compose を起動します。

    cd ~/microservices-march/platformdocker を作成 -d --build
    

    これにより、後続のチャレンジで使用される RabbitMQ と Jaeger が起動します。

    • ‑dフラグは、コンテナが起動したら Docker Compose にコンテナからデタッチするように指示します (そうでない場合、コンテナはターミナルに接続されたままになります)。
    • --buildフラグは、起動時にすべてのイメージを再構築するように Docker Compose に指示します。 これにより、実行中のイメージは、ファイルへの潜在的な変更を通じて最新の状態に保たれます。
  2. メッセンジャーリポジトリのアプリディレクトリに変更し、Node.js をインストールします (必要に応じて別の方法に置き換えることもできます)。

    cd ~/microservices-march/messenger/appasdf をインストールします
    
  3. 依存関係をインストールします:

    npmインストール
    
  4. メッセンジャーサービス用の PostgreSQL データベースを起動します。

    ドッカー構成アップ -d
    
  5. データベース スキーマとテーブルを作成し、シード データを挿入します。

    npm 実行 refresh-db
    

コンソールに送信される OTel 自動計測を構成する

OTel 自動インストルメンテーションを使用すると、トレースを設定するためにメッセンジャーのコードベースを変更する必要はありません。 すべてのトレース構成は、アプリケーション コード自体にインポートされるのではなく、実行時に Node.js プロセスにインポートされるスクリプトで定義されます。

ここでは、トレースの最も基本的な出力先であるコンソールを使用して、メッセンジャーサービスの自動インストルメンテーションを構成します。 チャレンジ 2では、外部コレクターとして Jaeger にトレースを送信するように設定を変更します。

  1. 引き続きメッセンジャーリポジトリのアプリディレクトリで作業し、コア 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 リポジトリを確認してください。

  2. OTel トレースのセットアップおよび構成コードを格納するために、 tracing.mjsという新しいファイルを作成します。

    タッチトレーシング.mjs
    
  3. 好みのテキスト エディターで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();
    

    コードは次のことを行います。

    1. OTel SDK から必要な関数とオブジェクトをインポートします。
    2. NodeSDK の新しいインスタンスを作成し、次のように構成します。

      • スパンをコンソールに送信します ( ConsoleSpanExporter )。
      • 自動インストゥルメンターを計測の基本セットとして使用します。 このインストルメンテーションは、最も一般的な自動インストルメンテーション ライブラリをすべて読み込みます。 チュートリアルで関連するものは次のとおりです。

        • Postgres データベース ライブラリ ( pg ) の@opentelemetry/instrumentation-pg
        • Node.js Express フレームワーク@opentelemetry/instrumentation-express
        • RabbitMQ ライブラリ ( amqplib ) の@opentelemetry/instrumentation-amqplib
    3. SDK を起動します。
  4. 手順 3 で作成した自動インストルメンテーション スクリプトをインポートして、メッセンジャーサービスを開始します。

    ノード --import ./tracing.mjsindex.mjs
    

    しばらくすると、トレースに関連する多くの出力がコンソール (ターミナル) に表示され始めます。

    ...
    {
    トレースID: '9c1801593a9d3b773e5cbd314a8ea89c'、
    親 ID: 未定義、
    トレース状態: 未定義、
    名前: 'fs statSync'、
    ID: '2ddf082c1d609fbe'、
    種類: 0,
    タイムスタンプ: 1676076410782000、
    期間: 3,
    属性: {},
    ステータス: { コード: 0 },
    イベント: [],
    リンク: []
    }
    ...
    

注記: チャレンジ 2 で再利用できるように、ターミナル セッションを開いたままにしておきます。

チャレンジ2: すべてのサービスに OTel 計測とトレースの視覚化を設定する

トレースを表示および分析するために使用できるツールは多数ありますが、このチュートリアルではJaegerを使用します。 Jaeger は、スパンやその他のトレース データを表示するための Web ベースのユーザー インターフェイスが組み込まれた、シンプルなオープン ソースのエンドツーエンドの分散トレース フレームワークです。 プラットフォームリポジトリで提供されるインフラストラクチャには Jaeger (チャレンジ 1 のステップ 1 で開始) が含まれているため、複雑なツールを扱う代わりにデータの分析に集中できます。

Jaeger はブラウザのhttp://localhost:16686エンドポイントでアクセスできますが、今すぐエンドポイントにアクセスすると、システムに関する情報は何も表示されません。 これは、現在収集しているトレースがコンソールに送信されているためです。 Jaeger でトレース データを表示するには、 OpenTelemetry プロトコル(OTLP) 形式を使用してトレースをエクスポートする必要があります。

このチャレンジでは、以下のインストルメンテーションを構成することで、コア ユーザー フローをインストルメント化します。

外部コレクターに送信される OTel 自動計測を構成する

念のため、OTel 自動インストルメンテーションを使用するということは、トレースを設定するためにメッセンジャーのコードベースを変更する必要がないことを意味します。 代わりに、すべてのトレース構成は、実行時に Node.js プロセスにインポートされるスクリプト内にあります。 ここでは、メッセンジャーサービスによって生成されたトレースの送信先をコンソールから外部コレクター (このチュートリアルでは Jaeger) に変更します。

  1. チャレンジ 1 と同じターミナルで作業し、メッセンジャーリポジトリのappディレクトリに、OTLP エクスポーター Node.js パッケージをインストールします。

    インストールは、@opentelemetry/exporter-trace-otlp-http@0.36.0 です。
    

    @opentelemetry/exporter-trace-otlp-httpライブラリは、HTTP 経由で OTLP 形式でトレース情報をエクスポートします。 これは、OTel 外部コレクターにテレメトリを送信するときに使用されます。

  2. 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にあると想定しています。 実際のシステムでは、場所を明示的に設定することをお勧めします。

  3. コンソールに送信される OTel 自動インストルメンテーションの構成の手順 4 でこの端末で開始したメッセンジャーサービスを停止するには、 Ctrl+cを押します。 次に、手順 2 で設定した新しいエクスポーターを使用するために再起動します。

    ^cnode --import ./tracing.mjs index.mjs
    
  4. 2 番目の別のターミナル セッションを開始します。 (以降の手順では、これをクライアント端末と呼び、手順 1 と 3 で使用した元の端末をメッセンジャー端末と呼びます。) 約 10 秒待ってから、メッセンジャーサービスにヘルスチェック要求を送信します (複数のトレースを表示する場合は、これを数回実行できます)。

    curl -X GET http://localhost:4000/health
    

    リクエストを送信する前に 10 秒待つと、サービスの開始時に自動インストルメンテーションによって生成される多数のトレースの後にリクエストが送信されるため、トレースを見つけやすくなります。

  5. ブラウザで、 http://localhost:16686の Jaeger UI にアクセスし、OTLP エクスポーターが期待どおりに動作していることを確認します。 タイトル バーの[検索]をクリックし、 [サービス]フィールドのドロップダウン メニューから、名前がunknown_serviceで始まるサービスを選択します。 [トレースの検索]ボタンをクリックします。

  6. ウィンドウの右側にあるトレースをクリックすると、その中のスパンのリストが表示されます。 各スパンは、トレースの一部として実行された操作(複数のサービスが関係する場合もあります)を説明します。 スクリーンショットのjsonParserスパンは、メッセンジャーサービスのリクエスト処理コードのjsonParser部分の実行にかかった時間を示しています。

    自動インストルメンテーションが変更されて正しいサービス名が表示される前の、unknown_service のスパンのリストを表示する Jaeger GUI のスクリーンショット

  7. ステップ 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 データを生成するソース (このチュートリアルではメッセンジャーサービス) を表すオブジェクト (リソース) を定義します。
  8. テキスト エディターで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()], });
      
  9. メッセンジャーサービスを再起動します。

    ノード --import ./tracing.mjsindex.mjs
    
  10. 約 10 秒待ってから、クライアント ターミナル (手順 4 で開いたもの) で、サーバーに別のヘルスチェック要求を送信します (複数のトレースを表示する場合は、コマンドを数回実行できます)。

    curl -X GET http://localhost:4000/health
    

    注記: クライアント端末は次のセクションで再利用できるように開いたままにし、メッセンジャー端末はチャレンジ 3 で再利用できるように開いたままにしておきます。

  11. ブラウザの Jaeger UI にmessengerという新しいサービスが表示されていることを確認します (これには数秒かかる場合があり、Jaeger UI を更新する必要がある場合があります)。

    PSAN の詳細な検査に使用できるサービスのリストにメッセンジャーが表示されている Jaeger GUI のスクリーンショット

  12. [サービス] ドロップダウン メニューから[メッセンジャー]を選択し、 [トレースの検索]ボタンをクリックすると、メッセンジャーサービスから発生した最近のトレースがすべて表示されます (スクリーンショットには、20 個のうち最新の 2 個が表示されています)。

    メッセンジャー サービスの最新の 2 つのトレースを表示する Jaeger GUI のスクリーンショット

  13. トレースをクリックすると、その中のスパンが表示されます。 各スパンは、メッセンジャーサービスから発信されたものとして適切にタグ付けされます。

    単一のメッセンジャースパンの詳細を示す Jaeger GUI のスクリーンショット

通知サービスの OTel 自動計測を構成する

次に、基本的にメッセンジャーサービスの場合の前の 2 つのセクションと同じコマンドを実行して、通知サービスの自動インストルメンテーションを起動して構成します。

  1. 新しいターミナル セッション (以降の手順では通知ターミナルと呼ばれます) を開きます。 通知リポジトリのアプリディレクトリに変更し、Node.js をインストールします (必要に応じて別の方法に置き換えることもできます)。

    cd ~/microservices-march/notifier/appasdf install
    
  2. 依存関係をインストールします:

    npmインストール
    
  3. 通知サービス用の PostgreSQL データベースを起動します。

    ドッカー構成アップ -d
    
  4. データベース スキーマとテーブルを作成し、シード データを挿入します。

    npm 実行 refresh-db
    
  5. 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
    
  6. tracing.mjsという新しいファイルを作成します。

    タッチトレーシング.mjs
    
  7. 好みのテキスト エディターで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である点を除いて、メッセンジャーサービスのスクリプトとまったく同じです。

  8. OTel 自動計測機能を使用して通知サービスを開始します。

    ノード --import ./tracing.mjsindex.mjs
    
  9. 約 10 秒待ってから、クライアント端末で通知サービスにヘルスチェック要求を送信します。 このサービスは、ポート 4000 でリッスンしているメッセンジャーサービスとの競合を防ぐために、ポート 5000 でリッスンしています。

    カール http://localhost:5000/health
    

    注記: チャレンジ 3 で再利用できるように、クライアント端末と通知端末を開いたままにしておきます。

  10. ブラウザの Jaeger UI にnotifierという新しいサービスが表示されていることを確認します。

    スパンの詳細な検査に使用できるサービスのリストに通知機能を表示する Jaeger GUI のスクリーンショット

NGINXのOTelインストルメンテーションを構成する

NGINX の場合、OTel 自動インストルメンテーション メソッドを使用する代わりに、手動でトレースを設定します。 現在、OTel を使用して NGINX をインストルメントする最も一般的な方法は、 C で記述されたモジュールを使用することです。サードパーティのモジュールは NGINX エコシステムの重要な部分ですが、セットアップには多少の作業が必要です。 このチュートリアルではセットアップの手順を説明します。 背景情報については、ブログの「NGINX および NGINX Plus 用のサードパーティ製動的モジュールのコンパイル」を参照してください。

  1. 新しいターミナル セッション ( NGINX ターミナル) を開始し、ディレクトリをメッセンジャーリポジトリのルートに変更して、 load-balancerという新しいディレクトリと、 Dockerfilenginx.confopentelemetry_module.confという新しいファイルを作成します。

    cd ~/microservices-march/messenger/mkdir load-balancer
    cd load-balancer
    touch Dockerfile
    touch nginx.conf
    touch opentelemetry_module.conf
    
  2. 好みのテキスト エディターで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
    
  3. 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 に次のことを指示します。

    • メッセンジャーサービスインスタンスのグループを表すmessengerという上流グループを設定します。
    • ポート 8085 で HTTP リクエストをリッスンします
    • /で始まるパスのすべての着信リクエスト(つまり、すべての着信リクエスト)をメッセンジャーの上流に転送します。

    注記: これは、実稼働環境でのリバース プロキシおよびロード バランサーとしての NGINX の実際の構成にかなり近いものです。 唯一の大きな違いは、 upstreamブロックのserverディレクティブへの引数が、通常はlocalhostではなくドメイン名または IP アドレスであることです。

  4. opentelemetry_module.confを開き、以下を追加します。

    NginxModuleEnabled オン;NginxModuleOtelSpanExporter otlp;
    NginxModuleOtelExporterEndpoint localhost:4317;
    NginxModuleServiceName messenger-lb;
    NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
    NginxModuleServiceInstanceId DemoInstanceId;
    NginxModuleResolveBackends オン;
    NginxModuleTraceAsError オン;
    
  5. NGINX と NGINX OTel モジュールを含む Docker イメージをビルドします。

    ビルドを実行します。
    
  6. NGINX リバース プロキシとロード バランサの Docker コンテナを起動します。

    docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
    
  7. クライアント端末で、NGINX リバース プロキシとロード バランサーを介してメッセンジャーサービスにヘルス チェック要求を送信します (この要求を送信する前に待つ必要はありません)。

    カール http://localhost:8085/health
    

    注記: チャレンジ 3 で再利用できるように、NGINX とクライアント ターミナルを開いたままにしておきます。

  8. ブラウザで、以前に開始したサービスとともに、新しいmessenger-lbサービスが Jaeger UI にリストされていることを確認します。 ブラウザで Jaeger UI を再読み込みする必要がある場合があります。

    スパンの詳細な検査に使用できるサービスのリストを表示する Jaeger GUI のスクリーンショット。messenger-lb も含まれるようになりました。

課題3: OTelの痕跡の読み方を学ぶ

「アーキテクチャとユーザーフロー」では、ユーザーフローの段階について概説しましたが、要約すると次のようになります。

  1. ユーザーは別のユーザーにメッセージを送信して会話を開始します。
  2. NGINX リバース プロキシはメッセージを傍受し、メッセンジャーサービスに転送します。
  3. メッセンジャーサービスはメッセージをデータベースに書き込み、RabbitMQ を通じてイベントを送信します。
  4. 通知サービスはそのイベントを消費し、受信者 (2 番目のユーザー) の通知設定を検索し、優先方法で受信者に通知を送信します。

テレメトリを実装する目的は次のとおりです。

  1. 新しいメッセージ フローを実現するためにリクエストが実行するすべての手順を理解します。
  2. 通常の条件下では、フローがエンドツーエンドで 5 秒以内に実行されることに自信があります。
  3. 通知サービスがメッセンジャーサービスによって送信されたイベントの処理を開始するまでにかかる時間を確認します。

このチャレンジでは、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 を選択して、 [トレースの検索]ボタンをクリックします。 フローの最初から始まるトレースのリストが表示されます。 任意のトレースをクリックすると、次のスクリーンショットのようにその詳細が表示されます。

フロー内のスパンのセット全体を表示する Jaeger GUI のスクリーンショット

クリックして少し探検してみましょう。 次に進む前に、トレース内の情報が、チャレンジ 3 の概要に記載されている計測の目標をどのようにサポートするかを検討します。 関連する質問は次のとおりです。

  • 目標を達成するために役立つ情報は何ですか?
  • どのような情報が不足していますか?
  • 関連のない情報は何ですか?

トレースのNGINX (messenger-lb) セクションを調べる

目標1: 新しいメッセージフローでリクエストが実行されるすべてのステップを確認する

親スパン内に 11 個の子スパンがある NGINX スパンから始めます。 現在の NGINX 構成は非常に単純なので、子スパンはあまり興味深いものではなく、NGINX リクエスト処理ライフサイクルの各ステップにかかる時間を示すだけです。 ただし、親スパン (一番最初のスパン) には、いくつか興味深い洞察が含まれています。

トレースの NGINX (messenger-lb) セクションの親スパンを表示する Jaeger GUI のスクリーンショット

  • タグの下に次の属性が表示されます。

    • http.methodフィールド – POST ( REST用語では作成を意味します)
    • http.status_codeフィールド –201 (作成が成功したことを示します)
    • http.targetフィールド – conversations/1/messages (メッセージ エンドポイント)

    これら 3 つの情報を総合すると、次のことがわかります。 「 POSTリクエストが/conversations/1/messagesに送信され、応答が201(正常に作成されました)”。 これは、「アーキテクチャとユーザーフロー」のステップ 1 と 4a に対応します。

  • Processwebengine.nameフィールドに、これがリクエストの NGINX 部分であることが示されます。

さらに、 messengernotifierのスパンはmessenger-lb conversations/1スパン内にネストされているため ( 「トレースの読み取り準備」のスクリーンショットに示されているように)、NGINX リバース プロキシ経由でメッセンジャーサービスに送信されたリクエストが、フロー内のすべての予想されるコンポーネントにヒットしたことがわかります。

この情報は、NGINX リバース プロキシがフローの一部であったことがわかるため、目的を満たしています。

目標2: フローが5秒以内に実行されることを確認する

messenger-lbというラベルの付いたスパンのリストで、最新のスパン (リストの一番下にあります) を見て、リクエストの NGINX 部分にかかった時間を確認します。 スクリーンショットでは、スパンは 589 マイクロ秒 (µs) で始まり、24 µs 続きました。つまり、リバース プロキシ操作全体はわずか 613 µs、つまり約 0.6 ミリ秒 (ms) しかかかりませんでした。 (もちろん、チュートリアルを自分で実行した場合、正確な値は異なります。)

トレースの NGINX (messenger-lb) セクションのスパンを表示する Jaeger GUI のスクリーンショット

このような設定では、ほとんどの場合、値は他の測定値と比較してのみ有用であり、システムによって異なります。 ただし、この場合、この操作の長さが 5 秒に近づく危険性は明らかにありません。

この情報は、NGINX 操作に 5 秒近くかかっていないことがわかるため、目的を満たしています。 フロー内に非常に遅い操作がある場合、それは後で実行されるはずです。

目標3: 通知サービスがメッセンジャーサービスによって送信されたイベントを読み取るのにかかる時間を確認する

NGINX リバース プロキシ レイヤーにはこれに関する情報が含まれていないため、メッセンジャースパンの手順に進むことができます。

トレースのメッセンジャーセクションを調べる

目標1: 新しいメッセージフローでリクエストが実行されるすべてのステップを確認する

トレースのメッセンジャーサービス セクションには、さらに 11 個のスパンが含まれています。 繰り返しになりますが、子スパンのほとんどは、Express フレームワークがリクエストを処理するときに使用する基本的な手順に関するものであり、あまり興味深いものではありません。 ただし、親スパン (一番最初のスパン) にも、興味深い洞察が含まれています。

トレースのメッセンジャー セクションに親スパンを表示する Jaeger GUI のスクリーンショット

タグの下に次の属性が表示されます。

  • http.methodフィールド – POST (REST 用語では、これは作成を意味します)
  • http.routeフィールド – /conversations/:conversationId/messages (メッセージ ルート)
  • http.targetフィールド – /conversations/1/messages (メッセージ エンドポイント)

この情報は、メッセンジャーサービスがフローの一部であり、エンドポイント ヒットが新しいメッセージ エンドポイントであったことを示しているため、目標を満たしています。

目標2: フローが5秒以内に実行されることを確認する

次のスクリーンショットに示されているように、トレースのメッセンジャー部分は 1.28 ミリ秒で始まり、36.28 ミリ秒で終了し、全体の時間は 35 ミリ秒でした。 その時間のほとんどは、JSON の解析 (ミドルウェア- jsonParser ) と、さらに長い時間、データベースへの接続 ( pg-pool.connectおよびtcp.connect ) に費やされました。

メッセージの書き込みプロセス中に複数の SQL クエリも実行されることを考えると、これは理にかなっています。 つまり、これらのクエリのタイミングをキャプチャするために、自動インストルメンテーション構成を拡張する必要があることを意味します。 (チュートリアルではこの追加のインストルメンテーションは示されていませんが、チャレンジ 4 では、データベース クエリをラップするために使用できるスパンを手動で作成します。)

トレースのメッセンジャーセクションのスパンとその所要時間を示す Jaeger GUI のスクリーンショット

この情報は、メッセンジャーの操作に 5 秒近くかからないことを示しているため、目標を満たしています。 フロー内に非常に遅い操作がある場合、それは後で実行されるはずです。

目標3: 通知サービスがメッセンジャーサービスによって送信されたイベントを読み取るのにかかる時間を確認する

NGINX スパンの様に、メッセンジャースパンにはこの情報は含まれていないので、通知スパンに移動することができます。

トレースの通知セクションを調べる

目標1: 新しいメッセージフローでリクエストが実行されるすべてのステップを確認する

トレースの通知セクションには、次の 2 つのスパンのみが含まれます。

トレースの通知セクションに 2 つのスパンを表示する Jaeger GUI のスクリーンショット

  • chat_queueプロセススパン –通知サービスがchat_queueメッセージ キューからのイベントを処理したことを確認します。
  • pg-pool.connectスパン – イベント処理後に通知サービスがデータベースに何らかの接続を行ったことを示します。

これらのスパンから得られる情報は、すべてのステップを理解するという目標を部分的にしか達成しません。 通知サービスがキューからイベントを消費する段階に到達したことはわかりますが、次の点についてはわかりません。

  • このサービスによって送信されるメッセージ通知は、メッセンジャーサービスによって送信されたイベントに対応します。
  • 関連するメッセージ通知はメッセージの受信者に正しく送信されました

これは、通知サービス フローを完全に理解するには、次の操作を実行する必要があることを示しています。

  • 通知が送信されていることを示すスパンを手動で計測する
  • メッセンジャーサービスによってディスパッチされたイベントと通知サービスによって消費されたイベントの間に、トレースIDの形式で明示的な接続があることを確認します。

目標2: フローが5秒以内に実行されることを確認する

通知サービス スパンの全体的なタイミングを見ると、リクエストがフローの通知セクションで 30.77 ミリ秒を費やしたことがわかります。 ただし、フロー全体 (受信者への通知の送信) の「終了」を通知するスパンがないため、フローのこのセクションの合計タイミングや操作の全体的な完了時間を特定することはできません。

目標3: 通知サービスがメッセンジャーサービスによって送信されたイベントを読み取るのにかかる時間を確認する

ただし、通知サービスのchat_queueプロセススパンは、メッセンジャーサービスのchat_queue送信スパンが 4.12 ミリ秒で開始されてから 2 ミリ秒後に、6.12 ミリ秒で開始されたことがわかります。

メッセンジャー サービスによって送信されたイベントを通知サービスが消費していることを示す Jaeger GUI のスクリーンショット

この目標は、通知機能メッセンジャーサービスによって送信されてから 2 ミリ秒後にイベントを消費したことがわかっているため達成されます。 目標 2 とは異なり、この目標を達成するには、イベントが完全に処理されたかどうか、またはそれにかかった時間を知る必要はありません。

結論

現在の OTel 自動計測によって生成されたトレースの分析に基づくと、次のことが明らかです。

  • これらのスパンの多くは、現在の形式では役に立ちません。

    • NGINX は、認証チェックやファイル提供など、重要な役割であるリバース プロキシとは関係のない機能に関連するスパンを生成しています。 ただし、現時点では、NGINX の OTel インストルメンテーションでは無関係なスパンを省略できないため、何もできません。
    • Node.js サービス (メッセンジャーサービスと通知サービス) のスパンの中には、JSON 解析、リクエストハンドラー、およびすべてのデータベース操作のスパンが目標に関連しているようです。 一部のミドルウェア スパーン ( expressInitcorsMiddlewareなど) は関連性がないと思われるため、削除できます。
  • 次の重要なスパンが欠落しています:

    • 通知サービスによって送信された通知
    • メッセンジャーサービスから送信されたRabbitMQイベントと通知サービスによって処理されたイベント間の明確なマッピング

これは、基本的な計測が最後の目標を達成することを意味します。

  • メッセンジャーサービスによって送信されたイベントを通知サービスが処理し始めるまでにどれくらいの時間がかかるかを確認します。

しかし、最初の 2 つの目標を達成するには情報が不十分です。

  • 新しいメッセージフロー中にリクエストが実行されるすべての手順を理解する
  • 通常の状況では、フローがエンドツーエンドで 5 秒以内に実行されることに自信を持つ

課題4: トレースの読み取りに基づいて計測機器を最適化する

このチャレンジでは、チャレンジ 3 で行ったトレース分析に基づいて OTel 計測を最適化します。 これには、不要なスパンの削除新しいカスタム スパンの作成、通知サービスによって消費されるイベントがメッセンジャー サービスによって生成されたイベントであることを確認することが含まれます。

不要なスパンを削除する

  1. 好みのテキスト エディターで、メッセンジャーリポジトリのappディレクトリにあるtracing.mjsファイルを開き、上部のインポート ステートメントのリストの最後に次のコードを追加します。

    const IGNORED_EXPRESS_SPANS = new Set([ "ミドルウェア - expressInit",
    "ミドルウェア - corsMiddleware",
    ]);
    

    これは、次の Jaeger UI のスクリーンショットに示されているスパンのリストから派生したスパン名のセットを定義します。このスパン名のセットは、このフローに有用な情報を提供しないため、トレースから省略されます。 スクリーンショットにリストされている他のスパンも不要であると判断し、それらをIGNORED_EXPRESS_SPANSのリストに追加することもできます。

    関連情報を提供する可能性があり、トレースから省略できるメッセンジャー サービスからのいくつかのスパンのリストを表示する Jaeger GUI のスクリーンショット

  2. オレンジ色で強調表示された行を変更して、自動インストルメンテーション構成にフィルターを追加し、不要なスパンを省略します。

    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ステートメントはそのスパンをトレースから削除します。

  3. メッセンジャー ターミナルで、 Ctrl + cを押してメッセンジャーサービスを停止します。 次に再起動します。

    ^cnode --import ./tracing.mjs index.mjs
    
  4. 約 10 秒待ってから、クライアント端末で新しいメッセージを送信します。

    curl -X POST \ -H "ユーザーID: 2" \
    -H "コンテンツタイプ: application/json" \
    -d '{"content": 「これは 2 番目のメッセージです」}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. Jaeger UI でメッセンジャースパンを再確認します。 expressInitcorsMiddleware の2 つのミドルウェアスパンは表示されなくなりました (チャレンジ 3 のトレースのメッセンジャー セクションを調べる目標 2のスクリーンショットと比較できます)。

    インストルメンテーションを変更してトレースから 2 つのスパンを除外した後、トレースに 2 つのスパンが含まれなくなったことを示す Jaeger GUI のスクリーンショット

カスタムスパンを設定する

このセクションでは、初めてアプリケーション コードに触れます。 自動インストルメンテーションでは、アプリケーションを変更することなく大量の情報が生成されますが、特定のビジネス ロジックをインストルメント化することによってのみ、一部の洞察が得られます。

インストルメント化している新しいメッセージ フローの場合、この例としては、メッセージの受信者への通知の送信のトレースが挙げられます。

  1. 通知リポジトリのappディレクトリにあるindex.mjs を開きます。 このファイルには、サービスのビジネス ロジックがすべて含まれています。 ファイルの先頭にあるインポートステートメントのリストの最後に次の行を追加します。

    "@opentelemetry/api" から { trace } をインポートします。
    
  2. このコードを置き換えます(ファイルの約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 } );
    

    新しいコードは次のことを行います。

    1. OTel トレースと対話するためのグローバル オブジェクトであるトレーサーを取得します。
    2. notification.send_allという新しい親スパンを開始し、メッセージの送信者を識別するためにuser_id属性を設定します。
    3. 受信者の通知設定が列挙されるループに入り、 notification.send_allの下にnotification.sendという新しい子スパンが作成されます。 通知ごとに新しいスパンが生成されます。
    4. 子スパンにさらに属性を設定します:

      • notification_typeSMSまたはメールのいずれか
      • user_id – 通知を受信するユーザーのID
    5. 各子notification.sendスパンを順番に閉じます。
    6. notification.send_allスパンを閉じます。

    親スパンを設定すると、ユーザーの通知設定が検出されない場合でも、各「通知の送信」操作が報告されることが保証されます。

  3. 通知ターミナルで、 Ctrl + cを押して通知サービスを停止します。 次に再起動します。

    ^cnode --import ./tracing.mjs index.mjs
    
  4. 約 10 秒待ってから、クライアント端末で新しいメッセージを送信します。

    curl -X POST \ -H "ユーザーID: 2" \
    -H "コンテンツタイプ: application/json" \
    -d '{"content": 「これは 2 番目のメッセージです」}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. Jaeger UI で通知スパンを再確認します。親スパンと 2 つの子スパンが表示され、それぞれに「通知の送信」操作があります。

    通知サービスのコードに 3 つの新しいスパンを定義した結果を示す Jaeger GUI のスクリーンショット

新しいメッセージ フロー中にリクエストが実行するすべてのステップを確認できるため、最初の目標と 2 番目の目標を完全に達成できるようになりました。 各スパンのタイミングにより、これらのステップ間の遅延が明らかになります。

メッセンジャーと通知機能が同じイベントを処理していることを確認する

フローを完全に把握するために必要なものがもう 1 つあります。 通知サービスによって処理されているイベントは、実際にはメッセンジャーサービスによって送信されたイベントですか?

2 つのトレースを接続するために明示的な変更を加える必要はありませんが、自動インストルメンテーションの魔法を盲目的に信頼することも望ましくありません。

これを念頭に置いて、NGINX サービスで開始されるトレースが通知サービスによって使用されるトレースと実際に同じである (同じトレース ID を持つ) ことを確認するための簡単なデバッグ コードを追加します。

  1. メッセンジャーリポジトリのappディレクトリにあるindex.mjsファイルを開き、次の変更を加えます。

    • 上部のインポート ステートメントのリストの最後に次の行を追加します。

      "@opentelemetry/api" から { trace } をインポートします。
      
    • 既存の黒色の行の下に、オレンジ色で強調表示された行を追加します。

      非同期関数 createMessageInConversation(req, res) { const tracer = trace.getActiveSpan(); console.log("TRACE_ID: ", tracer.spanContext().traceId);
      

      新しい行は、新しいメッセージの作成を処理するメッセンジャーの関数内からTRACE_ID を出力します。

  2. 通知リポジトリのappディレクトリにあるindex.mjsファイルを開き、オレンジ色で強調表示された行を黒色で既存の行の下に追加します。

    非同期関数 handleMessageConsume(channel, msg, handlers) をエクスポートします { console.log("RABBIT_MQ_MESSAGE: ", msg);
    

    新しい行には、通知サービスによって受信された AMQP イベントの完全な内容が出力されます。

  3. メッセンジャー ターミナルと通知ターミナルの両方で次のコマンドを実行して、メッセンジャー サービス通知サービスを停止して再起動します。

    ^cnode --import ./tracing.mjs index.mjs
    
  4. 約 10 秒待ってから、クライアント端末でメッセージを再度送信します。

    curl -X POST \ -H "ユーザーID: 2" \
    -H "コンテンツタイプ: application/json" \
    -d '{"content": 「これは 2 番目のメッセージです」}' \
    'http://localhost:8085/conversations/1/messages'
    
  5. メッセンジャーおよび通知サービスのログを確認します。 メッセンジャーサービスのログには、メッセージのトレース ID を報告する次のような行が含まれます (チュートリアルを実行すると、実際の ID は異なります)。

    トレースID:  29377a9b546c50be629c8e64409bbfb5
    
  6. 同様に、通知サービス ログは次のような出力でトレース ID を報告します。

    _spanContext: {
    トレースID: '29377a9b546c50be629c8e64409bbfb5'、spanId: 'a94e9462a39e6dbf'、トレースフラグ: 1,
    トレース状態: 未定義
    },
    
  7. コンソールではトレース ID が一致していますが、最後のステップとして、Jaeger UI のトレース ID と比較することができます。関連するトレース ID エンドポイント (実際のエンドポイントは異なりますが、この例ではhttp://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5です) で UI を開き、トレース全体を表示します。 イェーガーの追跡により次のことが確認されました:

    • メッセンジャーサービスの AMQP 自動インストルメンテーションは、イベントがディスパッチされたときに、このトレース ID をメタデータの一部として追加します。
    • 通知サービス内の AMQP 自動インストルメンテーションでは、そのメタデータを期待し、トレース コンテキストを適切に設定します。

注記: 実際の運用システムでは、フローが期待どおりに動作していることを確認したら、このセクションで追加したコードを削除します。

リソースのクリーンアップ

このチュートリアルでは、いくつかのコンテナーとイメージを作成しました。 削除するには、次の手順に従ってください。

  • 実行中の 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
    

次のステップ

おめでとうございます。チュートリアルは完了です。

  • NGINX リバース プロキシと 2 つの Node.js サービスにわたって OTel インストルメンテーションを設定します。
  • OTel 自動計測によって提供されたデータを批判的な目で見て、OTel ラボの目標を達成するために不足していたテレメトリを追加しました。
    • アプリケーション コードを直接変更することなく、メッセージング システムを通じて特定の要求のフローを適切に把握できるようになりました。
    • 通常の状況では、フローがエンドツーエンドで 5 秒未満で実行されていることを確認しました。

しかし、理想的なトレース構成がどのようなものかは、まだほんの表面をなぞった程度にしか理解できていません。 実稼働環境では、各データベース クエリのカスタム スパンや、各サービスのコンテナー ID などの実行時の詳細を説明するすべてのスパンの追加メタデータなどを追加する必要がある場合があります。また、システムの健全性の全体像を把握するために、他の 2 種類の OTel データ (メトリックとログ) を実装することもできます。

マイクロサービスに関する学習を継続するには、「Microservices March 2023」をご覧ください。 ユニット4: 「可観測性によるマイクロサービスの混乱と複雑さの管理」では、可観測性データの 3 つの主要なクラス、インフラストラクチャとアプリの調整の重要性、および詳細なデータの分析を開始する方法について学習します。


「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"