LoginSignup
29
18

概要

この記事は、OpenTelemetryについて全く知らない人に向けた入門記事です。前半でOpenTelemetryとは何か解説し、後半でデモアプリケーションを動かしながら、理解を深めていきます。

ot_logo.png

この記事の執筆時点のOpenTelemetryの最新バージョンは、1.8.0です。参照する公式ドキュメントや動作確認するデモアプリケーションのバージョンも1.8.0です。

OpenTelemetryとは

OpenTelemetryは、マイクロサービスアーキテクチャーで分散されたサービス(アプリケーション)の健全性や性能を示す「テレメトリーデータ」を生成、収集、管理、エクスポートするためのOSSです。

大規模な分散システムでは、健全性や性能を把握することが極めて困難なため、迅速なトラブルシューティングのためにはテレメトリーデータの収集が求められます。分散されたサービス(アプリケーション)からテレメトリーデータを収集するには、それらのアプリケーションが異なるプログラミング言語で実装していたとしても、それらに組み込むことができる、標準化された(ベンダーに依存しない)APIやSDKなどが必要です。それを提供するのがOpenTelemetryです。

OpenTelemetryの目的は、あくまでもテレメトリーデータを簡単に生成、収集、管理、エクスポートできるようにすることであり、テレメトリーデータの保存と可視化はJaegerやPrometheusのようなOSSまたは商用製品に任せるように、役割分担されています。

ちなみに、OpenTelemetryの公式サイトでは、以下のようにOpenTelemetryとは「Observability framework」(オブザーバビリティーフレームワーク)である説明しています。

OpenTelemetry is an Observability framework and toolkit designed to create and manage telemetry data such as traces, metrics, and logs.

オブザーバビリティーとは

オブザーバビリティー(可観測性)とは、システムやアプリケーションの内部の状態や挙動を把握し、問題(例えば、リクエストに対する処理の遅延など)を追跡および解決するための能力を指します。オブザーバビリティーを向上させる目的は、システム全体の健全性を維持し、問題を早期に検出して迅速に対処することです。これにより、システムの信頼性や可用性を向上させることができます。オブザーバビリティーを実現するためには、次に説明するテレメトリーデータが必要になります。

テレメトリーデータとは

OpenTelemetryにおけるテレメトリーデータとは、以下の3つを意味します。

  • ログ:主にトラブルシューティングを容易にすることを目的に記録するメッセージ(テキスト形式の情報)です。ログには、このメッセージとともに、ログ生成処理が実行された時刻やログレベルなどが含まれます。ログの形式には、プレーンテキスト、構造化、非構造化の3種類がありますが、プレーンテキストが最も一般的です。
  • メトリクス:一定の期間にわたって測定された何らかの数値です。日時、イベント名、イベント値などの属性が記録されます。ログとは異なり、形式は構造化が基本であるため、照会が容易です。保存にも適しており、大量のメトリクスを長期間保存して、システムの過去の傾向を把握することもできます。メトリクスを監視して、問題の兆候を検出した場合にアラートするようなこともできます。
  • トレース:分散システムにおけるリクエストの処理経路を表すデータです。分散システムでリクエストが処理される際には、さまざまな処理(外部APIの呼び出し、DBからのデータの取得、など)が実行されます。これらの各処理を表すデータは「スパン」と呼ばれ、操作の過程でトレースID、スパンID、操作名、開始/終了の日時、イベントなどが含まれます。このスパンにより構成されるトレースを監視・追跡すること(「分散トレーシング」と呼ばれる)により、エラー、遅延、リソースの可用性などの問題の原因となっているコードブロックを特定できます。

OpenTelemetryのアーキテクチャーと構成要素

以下の図は、後述するデモアプリケーションにおけるテレメトリーデータのフローを表した図ですが、OpenTelemetryのアーキテクチャーと構成要素を大雑把に示していると考えられます。

スクリーンショット 2024-03-12 161808.png

最上部の「OpenTelemetry Demo」の「Microservice」は、様々のプログラミング言語で実装された複数のマイクロサービスです。これらのサービスに、実装するプログラミング言語に対応したOpenTelemetryのSDKが組み込まれています。そのSDKにより生成されたテレメトリーデータがOpenTelemetry Collectorの構成要素であるReceiverにHTTPやgRPCで送られ、それをProcessorが処理します。処理されたデータは、ExporterによりPrometeusやJaegerにHTTPやgRPCで送信されます。

動作検証

OpenTelemetryプロジェクトでは、デモアプリケーション「OpenTelemetry Demo」を公開しているので、これを動かしながら、OpenTelemetryの理解を深めていきましょう。

以下のように、GitHubからクローンして、opentelemetry-demoディレクトリに移動します。

$ git clone https://github.com/open-telemetry/opentelemetry-demo.git
$ cd opentelemetry-demo/

デモの前提条件

OpenTelemetry Demoを起動する前提条件は、以下になっています。

  • Docker
  • Docker Compose v2.0.0以上
  • アプリケーション用に6GBのRAM

デモのアーキテクチャー

OpenTelemetry Demoは、gRPCとHTTPを介して互いに対話する、異なるプログラミング言語で書かれたマイクロサービスと、ユーザートラフィックを偽造するためにLocustを使用する負荷ジェネレータで構成されています。このデモのアーキテクチャーは以下のようになっています。

スクリーンショット 2024-03-05 094319.png

詳細は、ドキュメントを確認してください。

デモを動かしてみよう

では実際にOpenTelemetry Demoを動かしてみましょう。以下のように、docker compose upコマンドで起動しますが、

$ docker compose up --force-recreate --remove-orphans --detach

私の環境ではKafkaが起動せず、エラーになります。

[+] Running 18/18
 ✔ Container grafana                  Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container ffs-postgres             Healthy                                                                                                                                                                                                                                                            0.0s 
 ✔ Container jaeger                   Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container redis-cart               Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container prometheus               Running                                                                                                                                                                                                                                                            0.0s 
 ✘ Container kafka                    Error                                                                                                                                                                                                                                                              0.0s 
 ✔ Container opensearch               Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container feature-flag-service     Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container otel-col                 Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container ad-service               Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container currency-service         Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container email-service            Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container product-catalog-service  Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container shipping-service         Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container cart-service             Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container quote-service            Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container payment-service          Running                                                                                                                                                                                                                                                            0.0s 
 ✔ Container recommendation-service   Running                                                                                                                                                                                                                                                            0.0s 
dependency failed to start: container kafka is unhealthy

原因は未調査ですが、とりあえず単独で起動させれば、よさそうです。

$ docker start kafka

ただ、上のログで分かるように起動するサービスも多すぎますし、メモリーも大量に消費するので、もう少し小さな構成にできると、ありがたいです。ということで、調べてみると、カレントディレクトリーにdocker-compose.ymlの他にdocker-compose.minimal.ymlというファイルがあることに気づきました。

差分を見ると、Feature Flagsとそれが使用するPostgreSQLなどのコンテナー、アカウントサービスやテスト用のコンテナーなどがdocker-compose.minimal.ymlには含まれていないことが分かります。また、各コンテナーのメモリーの使用量が大幅に制限されていることも読み取れます。

Screenshot from 2024-03-07 09-59-14.png

前述のデモのアーキテクチャーの図では、赤枠で囲まれたサービスが含まれていません。

スクリーンショット 2024-03-05 094319.png

前回起動したものをいったん終了させて、

$ docker compose down

このファイルを指定して、起動してみましょう。

$ docker compose -f docker-compose.minimal.yml up --remove-orphans --detach
[+] Running 19/19
 ✔ Container redis-cart               Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container jaeger                   Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container opensearch               Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container grafana                  Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container prometheus               Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container otel-col                 Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container email-service            Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container payment-service          Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container ad-service               Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container quote-service            Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container cart-service             Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container currency-service         Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container shipping-service         Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container product-catalog-service  Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container recommendation-service   Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container checkout-service         Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container frontend                 Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container load-generator           Running                                                                                                                                                                                                                                                    0.0s 
 ✔ Container frontend-proxy           Started  

今度は正常に起動したようです。これにより、以下のアプリケーションにアクセスできるようです。

アプリケーション URL アプリケーションの概要 最小構成(※)
Webストア http://localhost:8080/ オンラインストアのサンプルアプリ。様々なマイクロサービスと連携して、機能を実現している。
Grafana http://localhost:8080/grafana/ OpenTelemetryがテレメトリーデータを視覚化し、システム状態の分析するためのアプリ。
Feature Flags http://localhost:8080/feature/ 様々なマイクロサービスの特定の機能を意図的に異常動作させて、一定の確率でエラーを発生させるかように切り替えるためのUI。 ×
Locust(ロードジェネレーター) http://localhost:8080/loadgen/ UIのある負荷テストツール。現実的なユーザーの買い物の流れを模したリクエストをフロントエンドに送り続けるサービス。
Jaeger http://localhost:8080/jaeger/ui/ OpenTelemetryが生成したテレメトリーデータを収集し、それを可視化、分析するUI。

※最小構成のdocker-compose.minimal.ymlに含まれるかどうか。

Webストア

まずはWebストアにアクセスしてみましょう。トップページは以下のようになっています。「Go Shopping」をクリックして、適当に買い物をしてみましょう。

Screenshot from 2024-02-20 15-34-12.png

適当に商品を選択し、

Screenshot from 2024-03-06 13-42-11.png

購入してみます。

Screenshot from 2024-03-06 13-42-46.png

購入が完了しました。非常に単純なオンラインストアアプリですね。

Screenshot from 2024-03-06 13-43-11.png

これで様々サービスへリクエストが送信されたことになります。

Jaeger

次にJaegerのUIにアクセスします。以下のような画面が表示されるので、「Service」に「frontend」を選択して、検索してみましょう。

Screenshot from 2024-02-20 15-44-52.png

リクエストの送信時刻と処理時間を可視化したものが表示されます。

Screenshot from 2024-03-06 14-00-10.png

この中の●の1つをクリックしてみると、以下のように内部でカートサービスなどのサービスにリクエストしていることが分かります。

Screenshot from 2024-03-06 14-00-32.png

System ArchitectureタブのDAGタブをクリックします。これを見ると、サービス間でどのようにリクエストが行われているかが分かります。矢印に付与されている数値は、各サービス間のトレース数を表しています。これは、あるサービスから別のサービスにトレースがどれだけの数で移動しているかを示しています。つまり、トレース数が多い矢印は、サービス間の通信がより頻繁に行われていると言えます。

Screenshot from 2024-03-05 10-03-06.png

Monitorタブをクリックすると、全体や各URLにおけるレイテンシーやエラー率が確認できます。

Screenshot from 2024-03-06 14-01-24.png

Grafana

Grafanaにもアクセスてみましょう。

Screenshot from 2024-02-20 15-41-40.png

「Create your first dashboard」でダッシュボードを作成すると、様々なメトリクスを可視化することができます。

Screenshot from 2024-03-06 17-33-00.png

Locust

ロードジェネレーターであるLocustのUIにもアクセスしてみましょう。

Screenshot from 2024-02-20 15-44-04.png

デフォルトでは10ユーザーがオンラインストアアプリにアクセスしているようなリクエストが自動的に転送されているようです。

STOPボタンをクリックして、このリクエストの転送をやめてみます(負荷を無くしてみます)。しばらく時間をおいてグラフを見てみると、いくつかのメトリクスが急降下してそのまま上昇しないことがわかります(※以下のグラフでは3時間ほど放置しています)。

Screenshot from 2024-03-06 21-10-48.png

では負荷を最初の10倍にしてみましょう。

Screenshot from 2024-03-06 21-14-04.png

これにより、いくつかのメトリクスが急上昇します。

Screenshot from 2024-03-06 21-28-29.png

Feature Flags

ちなみにFeature Flagsが起動している場合、アクセスすると以下のような画面が表示されます。

Screenshot from 2024-02-20 15-43-03.png

特定のサービスに意図的にエラーを発生させることで、どのような違いが出るか確認することができます。

ソースコード解析

各アプリケーションのソースコードにはどのようなOpenTelemetryのコードが追加されているのでしょうか?ソースコードを解析してみましょう。

デモのアーキテクチャーの図を見てわかるように、サービスは様々なプログラミング言語で実装されているので、自分が見やすい言語のサービスのソースコードを読むことができます。

ここでは、Pythonで実装されたレコメンデーションサービスのソースコードをチェックしてみます。このサービスは、ユーザーが閲覧した商品に基づいて、ユーザーに推奨される商品のリストを取得する役割を果たします。

まずはレコメンデーションサービスのディレクトリーに移動します。

$ cd src/recommendationservice/

treeコマンドでディレクトリー構成を見てみましょう。

$ tree
.
├── Dockerfile
├── README.md
├── logger.py
├── metrics.py
├── recommendation_server.py
└── requirements.txt

6ファイルだけで、非常にシンプルですね。requirements.txtを見ると、OpenTelemetryのライブラリーが依存関係にあるのが分かります。

opentelemetry-distro==0.43b0
opentelemetry-exporter-otlp-proto-grpc==1.22.0

どのような実装になっているのでしょうか。メインの処理が実装されていると思われるrecommendation_server.pyを覗いてみましょう。

まず、OpenTelemetryでテレメトリーデータ(トレース、メトリクス、ログ)を生成するためのモジュールをインポートしています。

from opentelemetry import trace, metrics
from opentelemetry._logs import set_logger_provider

これらのモジュールを使って、トレース、メトリクス、ログを生成するためのコードを見てみましょう。

トレースを生成するためのコード

トレースを生成するためのOpenTelemetryのTracerオブジェクトは、以下のコードで初期化するようです。

    tracer = trace.get_tracer_provider().get_tracer(service_name)

スパンは、以下のようにOpenTelemetryのTracerオブジェクトのstart_as_current_span()メソッドを使って生成します。with文とともに生成すれば、with文のコードブロックが終了する時点でスパンも終了します。

    with tracer.start_as_current_span("get_product_list") as span:

get_product_listは、推奨する商品リストを取得するためのスパンの名前です。

メトリクスを生成するためのコード

メトリクスを生成するオブジェクトは、以下のコードで初期化していますが、

    meter = metrics.get_meter_provider().get_meter(service_name)
    rec_svc_metrics = init_metrics(meter)

このinit_metrics()は以下のような実装になっており、meter.create_counter()Counterオブジェクトを生成し、

def init_metrics(meter):

    # Recommendations counter
    app_recommendations_counter = meter.create_counter(
        'app_recommendations_counter', unit='recommendations', 
        description="Counts the total number of given recommendations"
    )

    rec_svc_metrics = {
        "app_recommendations_counter": app_recommendations_counter,
    }

    return rec_svc_metrics

サービスの呼び出し1回当たりの推奨商品数の合計をメトリクスとして保存するようです。

        rec_svc_metrics["app_recommendations_counter"].add(len(prod_list), 
            {'recommendation.type': 'catalog'})

なお、CPUやメモリーの消費量、GCの回数などのメトリクスは自動的に取得できるようになっています。そのためのコードをアプリケーションに追加する必要はありません。

ログを生成するためのコード

ログを生成するオブジェクトの初期化は、以下のようになっています。

    # Initialize Logs
    logger_provider = LoggerProvider(
        resource=Resource.create(
            {
                'service.name': service_name,
            }
        ),
    )
    set_logger_provider(logger_provider)
    log_exporter = OTLPLogExporter(insecure=True)
    logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
    handler = LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider)

    # Attach OTLP handler to logger
    logger = logging.getLogger('main')
    logger.addHandler(handler)

初期化が完了したら、以下のようにメッセージを含むログをレベルに応じて生成することができます。

        logger.info(f"Receive ListRecommendations for product ids:{prod_list}")

今回はPythonで実装されたサービスを確認してみましたが、他のプログラミング言語のソースコードにも同様の実装になっていると思います。

感想

OpenTelemetryを利用することで、複雑な分散システムでもプログラミング言語に依存せず標準化されたテレメトリーデータを生成できる点は非常に有用だと思いました。テレメトリーデータは分散システムの健全性や性能を把握するだけでなく、機械学習を活用して障害の予兆の検出やリソースの削減などにも利用できそうです。ただし、実装を誤っていると、かえって混乱を招きトラブルシューティングの時間を遅くしたり、不要な作業を発生させてしまうかもしれません。

参考

29
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
18