BLOG | NGINX

NGINXチュートリアル: OpenTelemetryトレーシングを使ったマイクロサービスの可視化方法

NGINX-Part-of-F5-horiz-black-type-RGB
Javier Evans サムネール
Javier Evans
Published July 13, 2023

 

 

この記事は、Microservices July 2023: マイクロサービスのStart Delivering Microservicesの方法を実行するための4つのチュートリアルの1つです。

 

 

 

マイクロサービスアーキテクチャには、チームの独立性の向上やスケーリング、と導入における柔軟性の向上など、多くの利点があります。その反面、システム内のサービスが増えるほど(マイクロサービスアプリのサービスは数十から数百になることもあります)、システムの全体的な運用状況を明確に把握することが難しくなります。私たちは、複雑なソフトウェアシステムを構築および保守しているので、明確に把握することが極めて重要であることを知っています。可観測性ツールは、数多くのサービスやサポートインフラストラクチャ全体で状況を把握できます。

このチュートリアルでは、マイクロサービスアプリにとって非常に重要な可観測性の1つであるトレーシングに焦点を当てます。本題に入る前に、可観測性を議論するときによく使われる用語を定義しておきます。

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

これらすべての概念を使用して、マイクロサービスのパフォーマンスに関するインサイトを得ることができます。トレーシングは、リクエストが処理される際、複数の、一般的にゆるくつながっている、コンポーネントの全体でいったい何が起こっているかを示す「全体像」を把握できるので、可観測性戦略において特に有用な部分です。また、パフォーマンスのボトルネックの特定にも特に有効な方法です。

このチュートリアルでは、OpenTelemetry(OTel)のトレーシングツールキットを使用します。OTelは、テレメトリを収集、処理、エクスポートするためのベンダーニュートラルなオープンソース規格であり、急速に人気が高まってきています。OTelの概念では、トレースは、データフロー(複数のサービスを含むこともある)を時系列に並んだ一連の「チャンク」に分割し、以下のことを簡単に理解できるようにすることとされています。

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

OTelをご存じない方は、OpenTelemetryとは何か?で、この規格の概要や実装に関する注意事項をご覧ください。

チュートリアルの概要

このチュートリアルは、OTelを使用してマイクロサービスアプリの動作をトレースすることに焦点を当てています。このチュートリアルの以下の4つの課題では、システムでやり取りされるリクエストを追跡する方法や、あなたのマイクロサービスに関するあらゆる疑問に答える方法を学習できます。

これらの課題は、初めてトレーシングを設定するときに私たちが推奨している以下のようなプロセスを説明しています。

  1. システムだけでなく、計装する特定の運用について理解する。
  2. 稼働するシステムについて知る必要があることを決める。
  3. 「あるがまま」のシステムの状態を計測する。つまり、不要な情報を取り除いたり、カスタムデータポイントを収集したりすることなく、デフォルトの設定を使用し、その計測結果があらゆる疑問の答えに役立つかどうかを評価する。
  4. 報告される情報を微調整して、あらゆる疑問にもっと迅速に答えられるようにする。

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

  • このチュートリアルでは、KubernetesやNomadのようなコンテナオーケストレーションフレームワークは使用していません。これは、特定のフレームワークの性質に縛られることなく、マイクロサービスの概念を学ぶことができるようにするためです。ここで紹介するパターンは、これらのフレームワークのいずれかを実行しているシステムに移植できます。
  • サービスは、ソフトウェア工学の厳密さよりも、理解のしやすさに合わせて最適化されています。重要なことは、コードの性質ではなく、システムにおけるサービスの役割と通信のパターンを見ることです。詳細は、各サービスのREADMEファイルをご覧ください。

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

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

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

Diagram showing topology used in tutorial, with OpenTelemetry tracing of a messaging system with two microservices, NGINX, and RabbitMQ

以下の2つのマイクロサービスが使用されます。

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

  • NGINX Open Source – messengerサービスとシステム全般への入り口
  • RabbitMQ – サービスが非同期で通信できるようにする、一般的なオープンソースのメッセージブローカー
  • Jaeger – テレメトリを生成するシステムのコンポーネントからテレメトリを収集および可視化する、オープンソースのエンドツーエンド分散トレーシングシステム

一旦構成図からOTelを外し、私たちがトレースする一連のイベント、つまり、ユーザーが新しいチャットメッセージを送信し、受信者がそれを通知されたときに何が起こるか、ということについて整理します。

Diagram showing flow of information in messaging system used in tutorial

データフローは以下のように分けられます。

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

    以下を同時に実行する。

     

    • 4a: messengerサービスが、メッセージが正常に送信されたことを報告するレスポンスを送信者に返す。
    • 4b: notifierサービスが、chat_queueの新しいイベントを検知し、その処理を行う。
  5. notifierサービスが、受信者の新しいメッセージに関する通知をどのように処理する設定となっているかデータベースで確認する。
  6. notifierサービスが、受信者が設定した方法を使用して、1つまたは複数の通知を送信する(このチュートリアルでは、方法の選択肢はSMSと電子メールです)。

テレメトリの目的

テレメトリによる計測を設定する場合、「すべての情報を送信してインサイトがあることを期待する」ことから始めるのではなく、より詳細な計測の目的を定義することから始めることをお勧めします。このチュートリアルでは、以下の3つの主要なテレメトリの目的を設定します。

  1. 新しいメッセージフローにおいてリクエストの処理が行われるすべてのステップを理解する。
  2. フローが通常時で5秒以内にエンドツーエンドで実行されることを確認できるようにする。
  3. messengerサービスからディスパッチされたイベントの処理をnotifierサービスが開始するまでの時間を確認する(過度の遅延がある場合、notifierサービスによるイベントキューからの読み込みに問題があり、イベントが停滞している可能性があります)。

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

チュートリアルの前提条件と設定

前提条件

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

  •  

    Linux/Unixに対応した環境

     

     

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

     

  • Linuxコマンドライン、JavaScript、bashに関する基本的な知識(ただし、すべてのコードとコマンドは提供され、説明されているので、限られた知識でも問題ありません)
  • DockerDocker Compose
  •  

    Node.js 19.x以上

     

    • Node.jsバージョン19.xをテストしましたが、より新しいバージョンでも動作するものと思われます。
    • Node.jsのインストールに関する詳細は、messengerサービスのリポジトリにあるREADMEをご覧ください。また、asdfをインストールすることで、チュートリアルで使用されるものと同じNode.jsバージョンを入手できます。
  • curl(ほとんどのシステムですでにインストールされています)
  • アーキテクチャとユーザーフローで挙げた技術:messengernotifier(次のセクションでダウンロードします)、NGINX Open SourceJaegerRabbitMQ

 

注:messengerおよびnotifierサービスはNode.jsで書かれているため、チュートリアルではJavaScript SDKを使用しています。また、OTelから利用できる情報の種類に慣れることができるように、OTel自動計測機能(自動計測とも呼ばれます)を設定します。このチュートリアルでは、OTel Node.js SDKについて知っておくべきことをすべて説明していますが、詳細については、OTelのドキュメントをご覧ください。

 

セットアップ

  1. ターミナルセッションを開始します。
  2.  

    ホームディレクトリで、microservices-marchディレクトリを作成して、このチュートリアルのGitHubリポジトリのクローンをその中に作成します(別のディレクトリ名を使用し、それに応じて手順を合わせることもできます)。

     

     

    注:このチュートリアルでは、ターミナルにコマンドをコピー&ペーストしやすいように、Linuxコマンドラインのプロンプトを省略しています。チルダ(~)は、実際のホームディレクトリを表します。)

     

    mkdir ~/microservices-march

    cd ~/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を用いた計測環境の設定

この課題では、messengerサービスを開始し、OTel自動計測を設定し、テレメトリをコンソールに送信します。

 

messengerサービスの起動

  1.  

    platformリポジトリに移動し、Docker Composeを起動します。

     

    cd ~/microservices-march/platform

    docker compose up -d --build

     

     

    これにより、以降の課題で使用するRabbitMQとJaegerが起動します。

     

    • ‑dフラグを使用すると、Docker Composeはコンテナをデタッチモードで起動します(このフラグを使用しないと、コンテナがターミナルに接続されたままになります)。
    • --buildフラグを使用すると、Docker Composeは起動時にすべてのイメージを再構築します。これにより、実行中のイメージは、ファイルの潜在的な変更が常に反映された状態に保たれます。
  2.  

    messengerリポジトリのappディレクトリに移動し、Node.jsをインストールします(必要に応じて別の方法で代用できます)。

     

    cd ~/microservices-march/messenger/app

    asdf install

     

  3.  

    依存関係をインストールします。

     

    npm install
    
  4.  

    messengerサービス用のPostgreSQLデータベースを起動します。

     

    docker compose up -d
    
  5.  

    データベーススキーマとテーブルを作成して、いくつかのシードデータを挿入します。

     

    npm run refresh-db
    

コンソールに送信されるOTel自動計測の設定

OTel自動計測では、トレーシング設定のためにmessengerのコードベースの何かを変更する必要はありません。すべてのトレーシング設定は、アプリケーションのコード自体にインポートされるのではなく、実行時にNode.jsプロセスにインポートされるスクリプトで定義されます。

ここでは、トレースの送信先として最も基本的なコンソールで、messengerサービスの自動計測を構成します。課題2では、外部コレクターとしてJaegerにトレースを送信するように設定を変更します。

  1.  

    messengerリポジトリのappディレクトリのまま、OTel Node.jsのコアパッケージをインストールします。

     

    npm install @opentelemetry/sdk-node@0.36.0 \

    @opentelemetry/auto-instrumentations-node@0.36.4

     

     

    これらのライブラリは、以下の機能を提供します。

     

    • @opentelemetry/sdk-node – OTelデータの生成とエクスポートを行います。
    • @opentelemetry/auto-instrumentations-node – 最も一般的なNode.jsを計測するために必要となるデフォルト設定を用いたOTelの自動設定を行います。

     

    注:OTelにおける特質として、JavaScript SDKは非常に細かく分かれています。そのため、このチュートリアルの基本的な例のためだけにも、さらにいくつかのパッケージをインストールすることになります。このチュートリアルでは扱われていないOTelの計測で必要とするパッケージを理解するには、(非常に優れた)OTelの入門ガイドを熟読して、OTelGitHubリポジトリを調べてください。

     

  2.  

    OTelトレーシング用の設定および構成コードを含むtracing.mjsという新しいファイルを作成します。

     

    touch tracing.mjs
    
  3.  

    お好みのテキストエディタで、tracing.mjsを開き、以下のコードを追加します。

     

    //1

    import opentelemetry from "@opentelemetry/sdk-node";

    import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

    //2

    const sdk = new opentelemetry.NodeSDK({

    traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),

    instrumentations: [getNodeAutoInstrumentations()],

    });

    //3

    sdk.start();

     

     

    このコードは以下のことを実行します。

     

    1. OTel SDKから必要な関数とオブジェクトをインポートする。
    2.  

      NodeSDKの新しいインスタンスを作成して、以下のことを実行するように構成する。

       

      • スパンをコンソールに送る(ConsoleSpanExporter).
      •  

        基本の計測のセットとして自動計測を使用します。この計測は、最も一般的な自動計測ライブラリをすべてロードします。このチュートリアルに関連するものは以下のとおりです。

         

        • @opentelemetry/instrumentation-pg – Postgresデータベースライブラリ (pg)用
        • @opentelemetry/instrumentation-express – Node.js Expressフレームワーク
        • @opentelemetry/instrumentation-amqplib – RabbitMQのライブラリ(amqplib)用
    3. SDKを起動する。
  4.  

    ステップ3で作成した自動計測のスクリプトをインポートして、messengerサービスを起動します。

     

    node --import ./tracing.mjs index.mjs
    

     

    しばらくすると、トレーシングに関する大量の出力がコンソール(ターミナル)に表示されます。

     

    ...

    {

    traceId: '9c1801593a9d3b773e5cbd314a8ea89c',

    parentId: undefined,

    traceState: undefined,

    name: 'fs statSync',

    id: '2ddf082c1d609fbe',

    kind: 0,

    timestamp: 1676076410782000,

    duration: 3,

    attributes: {},

    status: { code: 0 },

    events: [],

    links: []

    }

    ...

     

 

注:課題2で再利用するため、ターミナルセッションは開いたままにしておきます。

 

課題2:すべてのサービスでのOTelを用いた計測の実施とトレース可視化の設定

トレースの表示や解析に使えるツールはたくさんありますが、このチュートリアルではJaegerを使用します。Jaegerはオープンソースで、シンプルなエンドツーエンド分散トレースフレームワークであり、スパンやその他のトレースデータを表示するためのWebベースのユーザーインターフェイスが組み込まれています。プラットフォームリポジトリで提供されるインフラストラクチャには、Jaegerが含まれているので(課題1のステップ1で起動)、複雑なツールに対処する必要なく、データの分析に集中できます。

 

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

 

この課題では、以下の計測に利用するコンポーネントを設定して、このチュートリアルで中心となるユーザーフローを計測します。

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

前述のように、OTelの自動計測では、トレーシング設定のためにmessengerのコードベースの何かを変更する必要はありません。代わりに、すべてのトレーシング設定は、実行時にNode.jsプロセスにインポートされるスクリプトで定義されます。ここでは、messengerサービスによって生成されるトレースの送信先をコンソールから外部コレクター(このチュートリアルではJaeger)に変更します。

  1.  

    課題1と同じターミナルのまま、messengerリポジトリのappディレクトリにOTLPエクスポーターNode.jsパッケージをインストールします。

     

    npm install @opentelemetry/exporter-trace-otlp-http@0.36.0
    

     

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

     

  2.  

    tracing.mjs(課題1で作成および編集したもの)を開き、以下のように変更します。

     

    •  

      この行をファイルの先頭にあるimport文のセットに追加します。

       

      import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
      
    •  

      OTel SDKに提供する「エクスポーター」を、課題1で使用したコンソールエクスポーターから、HTTP経由でOTLPデータをOTLP対応コレクターに送信できるものに変更します。以下の行を、

       

      traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),
      

       

      以下の行に置き換えます

       

      traceExporter: new OTLPTraceExporter({ headers: {} }),
      

     

    注:わかりやすいように、このチュートリアルでは、コレクターがデフォルトの場所、http://localhost:4318/v1/tracesにあると仮定しています。実際のシステムでは、場所を明示的に設定することをお勧めします。

     

  3.  

    Ctrl+cを押して、messenger サービスを停止します。これは、OTel自動計測の構成でコンソールに計測結果を送信するために、ステップ4のターミナルで開始したものです。その後、再起動すると、ステップ2で構成した新しいエクスポーターが使用されます。

     

    ^c

    node --import ./tracing.mjs index.mjs

     

  4.  

    2つ目の別のターミナセッションを開始します(以降の説明では、これをクライアントターミナルと呼び、ステップ1および3で使用した元のターミナルをmessengerターミナルと呼びます)。10秒ほど待ち、messengerサービスにヘルスチェックとなるHTTPリクエストを送信します(複数のトレースを確認したい場合は、コマンドを数回実行できます)。

     

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

     

    リクエストを送信する前に10秒待つと、サービス開始時に自動計測が生成する多くのトレースの後に表示されるため、トレースを見つけやすくなります。

     

  5.  

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

     

     

     

  6.  

  7.  

    ウィンドウの右側でトレースをクリックすると、そのトレース内のスパンのリストが表示されます。各スパンは、トレースの一部として実行された操作(複数のサービスを含む場合もありまます)を記述しています。スクリーンショットのjsonParserスパンは、messengerサービスのリクエスト処理コードのjsonParser部分の実行に要した時間を示しています。

     

     

    Screenshot of Jaeger GUI showing list of spans for unknown_service, before auto-instrumention is changed to show correct service names

     

  8.  

  9.  

    ステップ5で述べたように、OTel SDKによりエクスポートされるサービス名(unknown_service)が意味をなさないことがあります。これを修正するには、messengerターミナルでCtrl+cを押して、messengerサービスを停止します。次に、いくつかのNode.jsパッケージをインストールします。

     

    ^c

    npm install @opentelemetry/semantic-conventions@1.10.0 \

    @opentelemetry/resources@1.10.0

     

     

    これら2つのライブラリは、以下の機能を提供します。

     

    • @opentelemetry/semantic-conventions – OTel仕様で定義されているトレースの標準属性を定義します。
    • @opentelemetry/resources – OTelデータを生成するソース(このチュートリアルでは、messengerサービス)を表すオブジェクト(resource)を定義します。
  10.  

    テキストエディタでtracing.mjsを開き、以下のように変更します。

     

    •  

      以下の行を、ファイルの先頭にあるimport文のセットに追加します。

       

      import { Resource } from "@opentelemetry/resources";

      import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

       

    •  

      最後のimport文の後に以下の行を追加して、OTel仕様の正しいキーの下にmessengerというresourceを作成します。

       

      const resource = new Resource({

      [SemanticResourceAttributes.SERVICE_NAME]: "messenger",

      });

       

    •  

      以下の黒字の行の間にオレンジ色の行を追加して、resourceオブジェクトをNodeSDKコンストラクタに渡します。

       

      const sdk = new opentelemetry.NodeSDK({

      resource,

      traceExporter: new OTLPTraceExporter({ headers: {} }),

      instrumentations: [getNodeAutoInstrumentations()],

      });

       

  11.  

    messengerサービスを再起動します。

     

    node --import ./tracing.mjs index.mjs
    
  12.  

    10秒ほど待ち、ステップ4で開いたクライアントターミナルで、もう一度サーバーにヘルスチェックリクエストを送信します(複数のトレースを確認したい場合は、コマンドを数回実行できます)。

     

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

     

    注:クライアントターミナルは次のセクションで再利用し、messengerターミナルは課題3で再利用するため、開いたままにしておきます。

     

  13.  

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

     

     

    Screenshot of Jaeger GUI showing messenger in the list of services available for in-depth inspection of psans

     

  14.  

  15.  

    Serviceドロップダウンメニューからmessengerを選択し、Find Tracesボタンをクリックすると、messengerサービスから発信された最近のすべてのトレースが表示されます(スクリーンショットは、20のうち最新の2つを示しています)。

     

     

    Screenshot of Jaeger GUI showing 2 most recent traces for the messenger service

     

  16.  

  17.  

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

     

     

    Screenshot of Jaeger GUI showing details of a single messenger span

     

  18.  

notifierサービスのOTel自動計測の構成

ここでは、notifierサービスで利用する自動計測の起動および構成を行います。基本的に、前の2つのセクションでmessengerサービスに関する手順で実行したコマンドと同じコマンドを実行します。

 

  1.  

    新しいターミナルセッションを開きます(以降のステップでは、これをnotifierターミナルと呼びます)。notifierリポジトリのappディレクトリに移動して、Node.jsをインストールします(必要に応じて別の方法で代用可能です)。

     

    cd ~/microservices-march/notifier/app

    asdf install

     

  2.  

    依存関係をインストールします。

     

    npm install
    
  3.  

    notifierサービス用のPostgreSQLデータベースを起動します。

     

    docker compose up -d
    
  4.  

    データベーススキーマとテーブルを作成して、いくつかのシードデータを挿入します。

     

    npm run refresh-db
    
  5.  

    OTel Node.jsパッケージをインストールします(パッケージについては、コンソールに送信されるOTel自動計測の構成のステップ1および3をご覧ください)。

     

    npm install @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という新しいファイルを作成します。

     

    touch tracing.mjs
    
  7.  

    お好みのテキストエディタで、tracing.mjsを開き、以下のスクリプトを追加して、OTel SDKを起動します。

     

    import opentelemetry from "@opentelemetry/sdk-node";

    import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

    import { Resource } from "@opentelemetry/resources";

    import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

    const resource = new Resource({

    [SemanticResourceAttributes.SERVICE_NAME]: "notifier",

    });

    const sdk = new opentelemetry.NodeSDK({

    resource,

    traceExporter: new OTLPTraceExporter({ headers: {} }),

    instrumentations: [getNodeAutoInstrumentations()],

    });

    sdk.start();

     

     

    注:このスクリプトは、SemanticResourceAttributes.SERVICE_NAMEフィールドの値がnotifierであることを除き、messengerサービスのものと同じです。

  8.  

  9.  

    OTel自動計測でnotifierサービスを開始します。

     

    node --import ./tracing.mjs index.mjs
    
  10.  

    10秒ほど待ち、クライアントターミナルで、notifierサービスにヘルスチェックのリクエストを送信します。ポート4000でリスニングしているmessengerサービスと競合をさけるため、このサービスはポート5000で通信を待ち受けています。

     

    curl http://localhost:5000/health
    

     

    注:課題3で再利用するため、クライアントおよびnotifierターミナルは開いたままにしておきます。

     

  11.  

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

     

     

    Screenshot of Jaeger GUI showing notifier in the list of services available for in-depth inspection of spans

     

  12.  

NGINXのOTel計測の構成

NGINXの場合、OTel自動計測方法を使わず、手動でトレーシングを設定します。現在、OTelを使用してNGINXを計測する最も一般的な方法は、C言語で書かれたモジュールを使用することです。サードパーティモジュールは、NGINXエコシステムの重要な部分ですが、いくつかの設定作業が必要になります。このチュートリアルでは、設定について紹介します。このモジュールの背景となる情報については、当社ブログのCompiling Third‑Party Dynamic Modules for NGINX and NGINX Plus(NGINXおよびNGINX Plus用のサードパーティ動的モジュールのコンパイル)をご覧ください。

 

  1.  

    新しいターミナルセッション(NGINX ターミナル)を起動し、ディレクトリをmessengerリポジトリのルートに変更して、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

    # Replace the nginx.conf file with our own

    COPY nginx.conf /etc/nginx/nginx.conf

    # Define the version of the NGINX OTel module

    ARG OPENTELEMETRY_CPP_VERSION=1.0.3

    # Define the search path for shared libraries used when compiling and running NGINX

    ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib

    # 1. Download the latest version of Consul template and the OTel C++ web server module, 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. Extract the module files

    && tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \

    && rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \

    # 3. Install and add the 'load_module' directive at the top of the main NGINX configuration file

    && /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. Copy in the configuration file for the NGINX OTel module

    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;

    upstream messenger {

    server localhost:4000;

    }

    server {

    listen 8085;

    location / {

    proxy_pass http://messenger;

    }

    }

    }

     

     

    この極めて基本的なNGINX構成ファイルは、NGINXに次のことを指示します。

     

    • messengerサービスのインスタンスのグループを表すmessengerというアップストリームグループを設定する。
    • ポート8085でHTTPリクエストを待ち受ける。
    • /で始まるパスで受信するすべてのリクエスト(つまり、受信するすべてのリクエスト)をmessengerアップストリームに転送する。

     

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

     

  4.  

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

     

    NginxModuleEnabled ON;

    NginxModuleOtelSpanExporter otlp;

    NginxModuleOtelExporterEndpoint localhost:4317;

    NginxModuleServiceName messenger-lb;

    NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;

    NginxModuleServiceInstanceId DemoInstanceId;

    NginxModuleResolveBackends ON;

    NginxModuleTraceAsError ON;

     

  5.  

    NGINXとNGINX OTelモジュールを含むDockerイメージをビルドします。

     

    docker build -t messenger-lb .
    
  6.  

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

     

    docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
    
  7.  

    クライアントターミナルで、NGINXのリバースプロキシとロードバランサーを介して、messengerサービスにヘルスチェックリクエストを送信します(このリクエストを送信する前に待つ必要はありません)。

     

    curl http://localhost:8085/health
    

     

    注:課題3で再利用するため、NGINXおよびクライアントターミナルは開いたままにしておきます。

     

  8.  

    ブラウザで、Jaeger UIに、起動したサービスとともに、新しいmessenger-lbサービスが表示されていることを確認します。ブラウザでJaeger UIの再読み込みが必要になる場合があります。

     

     

    Screenshot of Jaeger GUI showing list of services available for in-depth inspection of spans, now including messenger-lb

     

  9.  

課題3:OTelトレースの読み方の習得

 

アーキテクチャとユーザーフローでは、ユーザーフローの処理について概説しましたが、改めて以下に示します。

 

  1. ユーザーが、他のユーザーにメッセージを送信して、会話を開始する。
  2. NGINXのリバースプロキシが、メッセージをインターセプトして、messengerサービスに転送する。
  3. messengerサービスが、メッセージをデータベースに書き込み、RabbitMQを通じてイベントをディスパッチする。
  4. notifierサービスがそのイベントの処理を行い、受信者(第2のユーザー)の通知に関する設定を調べ、指定された方法で受信者に通知を送信する。

テレメトリを導入する目的は、以下のとおりです。

  1. 新しいメッセージフローを実現するためにリクエストが通過するすべてのステップを理解する。
  2. フローが通常時で5秒以内にエンドツーエンドで実行されることを確信できるようにする。
  3. messengerサービスからディスパッチされたイベントの処理をnotifierサービスが開始するまでの時間を確認する。

この課題では、OTelの計測によって生成されるトレースが前述の目的を満たすかどうかを評価する方法を学びます。まず、システムを稼働させ、いくつかのトレースを作成します。次に、メッセージフローと、NGINXmessengerサービスnotifierサービスによって生成されたそのセクションのトレースを検査します。

 

トレースデータの作成

クライアントターミナルで、2人のユーザー間で会話を設定し、いくつかのメッセージを送信します。

curl -X POST \

-H "Content-Type: application/json" \

-d '{"participant_ids": [1, 2]}' \

'http://localhost:8085/conversations'

curl -X POST \

-H "User-Id: 1" \

-H "Content-Type: application/json" \

-d '{"content": "This is the first message"}' \

'http://localhost:8085/conversations/1/messages'

curl -X POST \

-H "User-Id: 2" \

-H "Content-Type: application/json" \

-d '{"content": "This is the second message"}' \

'http://localhost:8085/conversations/1/messages'

 

以下のような出力がnotifierサービスによって生成され、notifierターミナルに表示されます。

Received new_message: {"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}

Sending notification of new message via sms to 12027621401

Received new_message: {"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}

Sending notification of new message via email to the_hotstepper@kamo.ze

Sending notification of new message via sms to 19147379938

 

トレースを読み取る準備

ブラウザでJaeger UIを開き、Serviceドロップダウンメニューからmessenger-lbを選択して、Find Tracesボタンをクリックします。フローの始まりの瞬間からのトレースのリストが表示されます。このスクリーンショットのように、任意のトレースをクリックすると、そのトレースの詳細が表示されます。

Screenshot of Jaeger GUI showing entire set of spans in the flow

いろいろなトレースをクリックして、少し調べてみてください。次に進む前に、課題3の冒頭に挙げた計測の目的にトレースの情報がどのように役立っているかを考えてみてください。適切な質問は以下のとおりです。

 

  • 目的に役立つ情報は何か?
  • どのような情報が不足しているか?
  • 関連性のない情報は何か?

トレースのNGINX(messenger-lb)セクションの検証

目的1:新しいメッセージフローにおいてリクエストが通過するすべてのステップを確認する

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

Screenshot of Jaeger GUI showing the parent span in the NGINX (messenger-lb) section of the trace

  •  

    Tagsの下の以下の属性に注目してください。

     

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

     

    これら3つの情報を組み合わると、次のような意味になります。「POSTリクエストが、/conversations/1/messagesに送信され、レスポンスは201(作成に成功)でした。」これは、アーキテクチャとユーザーフローのステップ1および4aに相当します。

     

  • Processの下にあるwebengine.nameフィールドは、これがリクエストのNGINX部分であることを示しています。

さらに、messengernotifierのスパンは、messenger-lb conversations/1のスパンの中に入れ子になっているので(トレースを読み取る準備のスクリーンショットに示すように)、NGINXのリバースプロキシを介してmessengerサービスに送信されたリクエストが、フロー内の期待されるすべてのコンポーネントにヒットしたことがわかります。

 

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

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

messenger-lbと表示されたスパンのリストで、最新(リストの一番下)のスパンを見て、リクエストのNGINX部分にかかった時間を確認します。スクリーンショットでは、スパンは589マイクロ秒(µs)から始まり、24µs続いています。つまり、完全なリバースプロキシ操作には613µs(約0.6ミリ秒(ms))しかかかっていません(正確な値は、チュートリアルを実際に実行したときには異なります)。

Screenshot of Jaeger GUI showing spans in the NGINX (messenger-lb) section of the trace

このような設定では、ほとんどの場合、値は他の測定値との相対的な関係でしか役に立ちませんし、システム間でも異なります。しかしこの場合、この操作が5秒に近づく危険性は明らかにありません。

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

目的3:messengerサービスからディスパッチされたイベントをnotifierサービスが読み取るまでの時間を確認する

NGINXのリバースプロキシ層には、これに関する情報は含まれていないので、messengerスパンに移動してください。

トレースのmessengerセクションの検証

目的1:新しいメッセージフローにおいてリクエストが通過するすべてのステップを確認する

トレースのmessengerサービスセクションには、さらに11のスパンが含まれています。ここでも子スパンのほとんどは、リクエストを処理するときにExpressフレームワークが使用する基本的なステップに関するもので、あまり興味深いものではありません。しかし親スパン(一番最初のスパン)には、興味深いインサイトが含まれています。

Screenshot of Jaeger GUI showing the parent span in the messenger section of the trace

Tagsの下の以下の属性に注目してください。

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

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

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

以下のスクリーンショットに示すように、トレースのmessenger部分は、1.28msで始まり、36.28msで終わっていて、全体の時間は35msです。この時間のほとんどは、JSONの解析(middleware - jsonParser)と、それ以上にデータベースへの接続(pg-pool.connecttcp.connect)に費やされています。

 

メッセージを書く過程でいくつかのSQLクエリが作成されることを考えると、これは理にかなっています。つまり、これらのクエリのタイミングをとらえるためにはOTelの自動計測の構成を強化することが必要であると示唆されます(チュートリアルでは、この追加の計測について示しませんが、課題4では、データベースクエリをラップするために使用できるスパンを手動で作成します)。

Screenshot of Jaeger GUI showing spans in the messenger section of the trace and how long they took

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

目的3:messengerサービスからディスパッチされたイベントをnotifierサービスが読み取るまでの時間を確認する

NGINXスパン同様、messengerスパンにはこの情報は含まれていないので、notifierスパンに移動します。

 

トレースのnotifierセクションの検証

目的1:新しいメッセージフローにおいてリクエストが通過するすべてのステップを確認する

トレースのnotifierセクションには、2つのスパンしか含まれていません。

Screenshot of Jaeger GUI showing the two spans in the notifier section of the trace

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

これらのスパンから得られる情報は、すべてのステップを理解するという目的を部分的に満たしているだけです。notifierサービスがキューのイベントを処理するところまで到達したことはわかりますが、以下のことはわかりません。

  • このサービスにより送信されるメッセージ通知が、messengerサービスによりディスパッチされたイベントに対応しているかどうか
  • 関連するメッセージの通知が、メッセージの受信者に正しく送信されたかどうか

つまり、notifierサービスフローを完全に理解するためには、次のことを行う必要があります。

  • 通知が送信されていることを示すスパンを手動で計測する
  • messengerサービスによってディスパッチされたイベントと、notifierサービスによって処理されたイベントの間に、トレースIDの形で明示的な接続があることを確認する

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

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

目的3:messengerサービスからディスパッチされたイベントをnotifierサービスが読み取るまでの時間を確認する

しかし、messengerサービスのchat_queue sendスパンが4.12msで開始された2ms後に、notifierサービスのchat_queue processスパンが6.12msで開始されていることを確認できます。

Screenshot of Jaeger GUI showing the notifier service consuming an event dispatched by the messenger service

messengerサービスからイベントがディスパッチされた2ms後に、notifierがイベントを処理したことがわかるので、この目的は満たされています。目的2とは異なり、この目的を達成するためにイベントが完全に処理されたかどうか、またはそれに要した時間を知る必要はありません。

まとめ

現在のOTel自動計測により生成されるトレースを分析した結果、以下のことが明らかになりました。

  •  

    現在の形では以下に関連するスパンの多くは役に立つとは言えません

     

    • NGINXは機能に関連するスパンを生成しており、これは、認可チェックやファイルサービングなど、あなたが関心を持つ機能ではないリバースプロキシとしての動作です。しかしこの時点では、NGINXのOTelによる計測で、無関係なスパンを省略できないので、何もできません。
    • Node.jsサービス(messengerおよびnotifierサービス)のスパンのうち、JSON解析、request handler、すべてのデータベース操作のスパンは、目的に関連していると思われます。middlewareスパン(expressInitcorsMiddlewareなど)の中には、関連性がないと思われるものもあり、これらは削除できます。
  •  

    以下のような重要なスパンが記録できていません

     

    • notifierサービスから送信される通知
    • messengerサービスからディスパッチされたRabbitMQイベントと、notifierサービスにより処理されるイベントとの間の明確な関係

つまり基本の計測構成では、以下の最後の目的を満たすことがわかります。

  • messengerサービスからディスパッチされたイベントの処理をnotifierサービスが開始するまでの時間を確認する。

しかし、以下の最初の2つの目的を満たすための情報は十分にはありません。

  • 新しいメッセージフローにおいてリクエストが通過するすべてのステップを理解する。
  • フローが通常時で5秒以内にエンドツーエンドで実行されることを確信できるようにする。

課題4:トレース情報に基づく計測の最適化

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

不要なスパンの削除

  1.  

    お好みのテキストエディタで、messengerリポジトリのappディレクトリにあるtracing.mjsファイルを開き、先頭部分にあるimport文のリストの最後に以下の行を追加します。

     

    const IGNORED_EXPRESS_SPANS = new Set([

    "middleware - expressInit",

    "middleware - corsMiddleware",

    ]);

     

     

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

     

     

    Screenshot of Jaeger GUI showing list of several spans from the messenger service that might provide relevant information and so can be omitted from the trace

     

  2.  

  3.  

    自動計測の構成にフィルターを追加し、以下のオレンジ色でハイライトされている行をその下のように変更して、不要なスパンを省略します。

     

    const sdk = new opentelemetry.NodeSDK({

    resource,

    traceExporter: new OTLPTraceExporter({ headers: {} }),

    instrumentations: [getNodeAutoInstrumentations()],

    });

     

     

    変更後:

     

    const sdk = new opentelemetry.NodeSDK({

    resource,

    traceExporter: new OTLPTraceExporter({ headers: {} }),

    instrumentations: [

    getNodeAutoInstrumentations({

    "@opentelemetry/instrumentation-express": {

    ignoreLayers: [

    (name) => {

    return IGNORED_EXPRESS_SPANS.has(name);

    },

    ],

    },

    }),

    ],

    });

     

     

    getNodeAutoInstrumentations関数は、ステップ1で定義したスパンのセットを参照し、@opentelemetry/instrumentation-expressが生成するトレースからスパンを除外します。つまり、IGNORED_EXPRESS_SPANSに属するスパンに対してreturn文がtrueと判定して、ignoreLayers文がトレースからそのスパンを削除します。

     

  4.  

    messengerターミナルで、Ctrl+cを押して、messengerサービスを停止します。

     

    ^c

    node --import ./tracing.mjs index.mjs

     

  5.  

    10秒ほど待って、クライアントターミナルで新しいメッセージを送信します。

     

    curl -X POST \

    -H "User-Id: 2" \

    -H "Content-Type: application/json" \

    -d '{"content": "This is the second message"}' \

    'http://localhost:8085/conversations/1/messages'

     

  6.  

    Jaeger UIでmessengerスパンを再確認します。expressInitcorsMiddlewareという2つのmiddlewareスパンが表示されなくなりました(課題3トレースのmessengerセクションの検証の目的2のスクリーンショットと比較してみてください)

     

     

    Screenshot of Jaeger GUI showing that the trace no longer includes two spans after you change the instrumentation to filter them out of the trace

     

  7.  

カスタムスパンの設定

このセクションで、初めてアプリケーションコードに触れます。自動計測は、アプリケーションを変更することなく大量の情報を生成しますが、ビジネスロジックの特定の部分を計測して初めてわかるインサイトもあります。

計測する新しいメッセージフローでは、メッセージの受信者に対する通知の送信をトレースすることがその例です。

  1.  

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

     

    import { trace } from "@opentelemetry/api";
    
  2.  

    このコード(ファイルの91行目あたり)を、

     

    for (let pref of preferences) {

    console.log(

    `Sending notification of new message via ${pref.address_type} to ${pref.address}`

    );

    }

     

     

    から以下の内容に変更します

     

    const tracer = trace.getTracer("notifier"); // 1

    tracer.startActiveSpan( // 2

    "notification.send_all",

    {

    attributes: {

    user_id: msg.user_id,

    },

    },

    (parentSpan) => {

    for (let pref of preferences) {

    tracer.startActiveSpan( // 3

    "notification.send",

    {

    attributes: { // 4

    notification_type: pref.address_type,

    user_id: pref.user_id,

    },

    },

    (span) => {

    console.log(

    `Sending notification of new message via ${pref.address_type} to ${pref.address}`

    );

    span.end(); // 5

    }

    );

    }

    parentSpan.end(); // 6

    }

    );

     

     

    この新しいコードは以下のことを実行します。

     

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

      子スパンの属性をさらに設定する。

       

      • notification_type – smsまたはemailのいずれか
      • user_id – 通知を受け取るユーザーのID
    5. 各子notification.sendスパンを順番に閉じる。
    6. notification.send_allスパンを閉じる。

     

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

     

  3.  

    notifierターミナルで、Ctrl+cを押して、notifierサービスを停止します。その後、再起動します。

     

    ^c

    node --import ./tracing.mjs index.mjs

     

  4.  

    10秒ほど待って、クライアントターミナルで新しいメッセージを送信します。

     

    curl -X POST \

    -H "User-Id: 2" \

    -H "Content-Type: application/json" \

    -d '{"content": "This is the second message"}' \

    'http://localhost:8085/conversations/1/messages'

     

  5.  

    Jaeger UIでnotifierスパンを再確認します。親スパンと2つの子スパンが表示され、それぞれ「通知送信」操作を行っていることがわかります。

     

     

    Screenshot of Jaeger GUI showing the result of defining three new spans in the code for the notifier service

     

  6.  

新しいメッセージフローにおいてリクエストが通過するすべてのステップを確認できるので、これで最初と2番目の目的を完全に満たすことができます。各スパンのタイミングにより、これらのステップ間のラグが明らかになります。

messengerとnotifierが同じイベントを処理していることの確認

フローを完全に把握するために必要なことがもう1つあります。それは、notifierサービスによって処理されているイベントが、実際にmessengerサービスによってディスパッチされたものかどうか、ということです。

 

2つのトレースを接続するために明示的な変更は必要ありませんが、自動計測の魔法をそのまま信じるわけにもいきません。

これを踏まえ、NGINXサービスで開始されるトレースが、本当にnotifierサービスで消費されるトレースと同じである(同じトレースIDを持つ)ことを確認するために、いくつかの簡単なデバッグコードを追加します。

  1.  

    messengerリポジトリのappディレクトリにあるindex.mjsファイルを開き、以下のように変更します。

     

    •  

      先頭部分にあるimport文のリストの最後に、次の行を追加します。

       

      import { trace } from "@opentelemetry/api";
      
    •  

      オレンジ色でハイライトされた行を、黒色の既存の行の下に追加します。

       

      async function createMessageInConversation(req, res) {

      const tracer = trace.getActiveSpan();

      console.log("TRACE_ID: ", tracer.spanContext().traceId);

       

       

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

       

  2.  

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

     

    export async function handleMessageConsume(channel, msg, handlers) {

    console.log("RABBIT_MQ_MESSAGE: ", msg);

     

     

    新しい行は、notifierサービスにより受信されるAMQP(RabbitMQが利用するプロトコル。Advanced Message Queuing Protocol)イベントのすべての内容を表示します。

     

  3.  

    messengernotifier両方のターミナルで以下のコマンドを実行し、messengerとnotifierのサービスを停止して再起動します。

     

    ^c

    node --import ./tracing.mjs index.mjs

     

  4.  

    10秒ほど待って、クライアントターミナルでメッセージを再送信します。

     

    curl -X POST \

    -H "User-Id: 2" \

    -H "Content-Type: application/json" \

    -d '{"content": "This is the second message"}' \

    'http://localhost:8085/conversations/1/messages'

     

  5.  

    messengernotifierのサービスのログを確認します。messengerサービスのログには、メッセージのトレースIDを報告する以下のような行が含まれています(実際のIDは、チュートリアルを実行するときには異なります)。

     

    TRACE_ID:  29377a9b546c50be629c8e64409bbfb5
    
  6.  

    同様に、notifierサービスのログでは、以下のような出力でトレースIDが報告されます。

     

    _spanContext: {

    traceId: '29377a9b546c50be629c8e64409bbfb5',

    spanId: 'a94e9462a39e6dbf',

    traceFlags: 1,

    traceState: undefined

    },

     

  7.  

    コンソールではトレースIDが一致していますが、最後のステップとして、Jaeger UIでトレースIDと比較してみてください。関連するトレースIDのエンドポイント(実際には異なりますが、この例ではhttp://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5)でUIを開き、トレース全体を確認します。Jaegerのトレースで以下のことを確認できます。

     

    • messengerサービスのAMQPに関する自動計測が、イベントがディスパッチされるときに、このトレースIDをメタデータの一部として追加している
    • notifierサービスのAMQPに関する自動計測がそのメタデータを予想し、トレースコンテキストを適切に設定している

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

リソースクリーンアップ

このチュートリアルでは、いくつかのコンテナやイメージを作成しました。これらを以下の手順で削除します。

  •  

    実行中のDockerコンテナを削除する

     

    docker rm $(docker stop messenger-lb)
    
  •  

    platformサービス、およびmessengernotifierのデータベースサービスを削除する。

     

    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 July 2023」をチェックしてください。Unit 4: マイクロサービスの複雑性をObservabilityで管理するでは、可観測性データの3つの主要クラス、インフラストラクチャとアプリの整合性の重要性、深いデータの分析を開始する方法について学ぶことができます。


"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."