🔭

OpenTelemetryベースで始めるAzure Monitorでのアプリケーション監視

に公開

はじめに

アプリケーション開発・運用をしているとユーザー体験の継続的な改善のためにシステムのボトルネック分析といった作業はつきものです。
そこでアプリケーションを監視を構成し、改善のための情報を収集するのが重要です。
これは「Application Performance Monitoring(APM)」と呼ばれます。
APM を実現するためには、いわゆる APM ツールを導入し、アプリケーションのログデータやメトリクスなどを収集します。

本記事では、アプリケーションから OpenTelemetry ベースで Azure Monitor にテレメトリデータを集約し、アプリケーションの監視を実現する方法を紹介します。

OpenTelemetry とは

OpenTelemetry は特定のベンダーやツールに依存せずにアプリケーションのテレメトリデータ(トレース、メトリクス、ログ)を収集、送信するための標準化された仕組みです。
OpenTelemetry 自体はテレメトリデータを保存・可視化する仕組みは持っていません。

アプリケーションでは OpenTelemetry の SDK を利用することでログデータなどを OpenTelemetry ベースで収集し、エクスポーターと呼ばれるコンポーネントでテレメトリデータを外部の APM ツール(バックエンド)などに送信します。

OpenTelemetry で収集したログデータは「分散トレース」という形でアプリケーションのリクエストと関連付けて記録・可視化できます。
以下のウォーターフォール図は分散トレースを可視化したものです。

OpenTelemetry では上記のウォーターフォール図の 1 つのブロックを「スパン」と呼びます。
通常、分散トレースは 1 つ以上のスパンから構成され、アプリケーションのログがスパンと紐づくことで、ログにどの処理で吐かれたものであるかのコンテキスト情報を持たせることができます。
またスパンには、「属性(attribute)」という形で Key-Value 形式のメタデータを付与できます。
こういった仕組みを活用することで、アプリケーションの監視を充実させることができます。

Azure Monitor と Application Insights

Azure Monitor は Azure が提供する監視サービス群の総称です。
その中でも、Application Insights はアプリケーションの監視に特化した、APM ツールです。

Application Insights も OpenTelemetry ベースでのテレメトリデータの保存をサポートしています。
Microsoft が公式に提供している OpenTelemetry ディストリビューションを利用することで、Application Insights にテレメトリデータを送信できます。

なお Application Insights ではテレメトリデータの実態は、Application Insights リソースに紐づいた Log Analytics ワークスペースに保存されています。
Log Analytics へのデータのインジェスト(取り込み)時に課金が発生するのでコストについては考慮が必要です。

アプリケーションに監視を導入する

今回は Python の FastAPI ベースのサンプルアプリケーションを例に試してみます。

https://github.com/fastapi/full-stack-fastapi-template

https://fastapi.tiangolo.com/project-generation/

まずはバックエンド側に以下の 2 つのパッケージを依存関係に追加します。

https://pypi.org/project/azure-monitor-opentelemetry/

https://pypi.org/project/opentelemetry-instrumentation-fastapi/

# バックエンドアプリケーションのディレクトリへ移動
cd backend/app

# 依存関係の追加
uv add azure-monitor-opentelemetry opentelemetry-instrumentation-fastapi

環境変数APPLICATIONINSIGHTS_CONNECTION_STRINGに Application Insights の接続文字列をセットし、FastAPI のエントリーポイントであるbackend/app/main.pyで Azure Monitor との接続と FastAPI アプリケーションの計装をセットアップします。

# Opentelemetryベースでの計装の設定
if settings.APPLICATIONINSIGHTS_CONNECTION_STRING:
    from azure.monitor.opentelemetry import configure_azure_monitor
    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

    configure_azure_monitor(
        connection_string=settings.APPLICATIONINSIGHTS_CONNECTION_STRING,
    )
    FastAPIInstrumentor.instrument_app(app)

Application Insights でログデータを見る

上記の設定後にアプリケーションにアクセスすると、Application Insights 側で分散トレースを可視化できます。
まずは Application Insights の「パフォーマンス」から見てみます。

「パフォーマンス」にはバックエンド API の各エンドポイントごとのレスポンスタイムの情報が集約されています。
API エンドポイントからサンプルを指定することで、個別のリクエストの分散トレースが確認できます。

特定の API エンドポイントの情報を辿りたいケースでは「パフォーマンス」からの絞り込みが有効ですが、柔軟に検索をしたい場合は「トランザクションの検索」を利用するのがおすすめです。

「トランザクションの検索」では非常に情報量が多く見えますが、フィルターを利用して「イベントの種類」を「Request」に絞ることでトランザクションの起点となるデータのみに絞ることができます。

このイベントの種類には OpenTelemetry のスパンの種類(SpanKind)が関係しています。
OpenTelemetry ではスパン(Span)には、5 種類のスパンの種類(SpanKind)が定義されスパンに設定可能です。

しかしながら Application Insights を利用する場合、スパンの種類は 2 種類にイベントに集約される形でそれ以上の使い分けは叶いません。
以下が OpenTelemetry のスパンの種類(SpanKind)と Application Insights でのマッピングされるイベント名の対応表です。

OpenTelemetry のスパンの種類(SpanKind) Application Insights でのマッピング先イベント名
Server Request
Client Dependency
Internal Dependency
Producer Dependency
Consumer Dependency

基本的にサーバサイドアプリケーションのルートスパンとなる場合はスパンの種類が「Server」であるため、イベントを「Request」でフィルターすると必要な情報に辿りつきやすいというカラクリでした。

より賢く収集する

ここまででアプリケーションのテレメトリデータを Application insights で収集できるようになりましたが、現在の設定だとまだ十分とは言えません。
現状の設定だけでは、アプリケーションのレスポンスタイムとエンドポイントを呼び出し時のログの情報は取得できますが、データベース操作の時間などが取得できていません。

これらの情報を取得して、分散トレースの情報をよりリッチなものにしていきます。

データベース操作の計装

一部の O/R マッパーライブラリには、OpenTelemetry ベースでデータベースの操作を自動で計装する計装ライブラリが存在します。
今回のサンプルアプリケーションは SQLModel を O/R マッパーに利用していますが SQLModel の内部では SQLAlchemy が使われているので SQLAlchemy の計装ライブラリを使って、自動計装が可能です。

https://pypi.org/project/opentelemetry-instrumentation-sqlalchemy/

ライブラリの追加とセットアップ設定を追記します。

# ライブラリの追加
uv add opentelemetry-instrumentation-sqlalchemy

データベースとの接続を実施しているbackend/app/core/db.pyに自動計装の設定を追加します。

from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor

engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))

SQLAlchemyInstrumentor().instrument(engine=engine)

この状態でアプリケーションの操作をすると、スパンの情報にデータベースへの操作情報までが追加で取得できるようになります。

またコマンド部分で発行されている SQL 文の情報も取得できます。

スパンにカスタム属性を付与する

次にスパンにカスタム属性を付与して情報をリッチにしていきましょう。
今回はリクエストにユーザーの情報を付与することを考えてみます。

サンプルアプリでは、FastAPI の DI(Dependency Injection)機能を利用して、ユーザー情報はbackend/app/api/deps.pyget_current_user関数から取得されています。
このget_current_user関数でスパンに対し、カスタム属性を付与してみましょう。

# スパンの情報を取得するためにOpenTelemetryライブラリのtraceをインポートに追加
from opentelemetry import trace

def get_current_user(session: SessionDep, token: TokenDep) -> User:
    try:
        payload = jwt.decode(
            token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
        )
        token_data = TokenPayload(**payload)
    except (InvalidTokenError, ValidationError):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Could not validate credentials",
        )
    user = session.get(User, token_data.sub)
    # 現在のスパンの情報を取得
    current_span = trace.get_current_span()
    # 現在のスパンにカスタム属性`user.id`を追加。今回は視認性のためにemailをuser.idとして設定
    current_span.set_attribute("user.id", user.email)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    if not user.is_active:
        raise HTTPException(status_code=400, detail="Inactive user")
    return user

この状態でアプリケーションを操作すると、Application Insights の「Custom Properties」欄にuser.idというキーに対して、操作したユーザーのメールアドレスがバリューとしてセットされ記録されます。

カスタム属性の強みはクエリが効くことです。
「トランザクションの検索」の検索欄で絞り込みたいユーザーのメールアドレスを入力することで特定のユーザーの操作だけを取得できるようになります。

ユーザー情報以外にもアプリケーションのユースケースに沿ってカスタム属性をスパンに付与しておくことで、ビジネスロジックに応じた操作の抽出やトラブル調査に役立つものになります。

収集する情報の絞り込み

情報をリッチにしていくのも重要ですが、不要な情報を取り込まないことも重要です。
例えばヘルスチェックエンドポイントの情報はアプリケーションの改善には不要なケースが多いでしょう。
このようなものはフィルタしていきましょう。

opentelemetry-instrumentation-fastapiライブラリでは、セットアップ時にexcluded_urlsパラメータを使って計装対象の URL の除外設定ができます。

# Opentelemetryベースでの計装の設定
if settings.APPLICATIONINSIGHTS_CONNECTION_STRING:
    from azure.monitor.opentelemetry import configure_azure_monitor
    from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

    configure_azure_monitor(
        connection_string=settings.APPLICATIONINSIGHTS_CONNECTION_STRING,
    )
    # ヘルスチェックエンドポイントを計装対象から除外する
    FastAPIInstrumentor.instrument_app(app, excluded_urls="api/v1/utils/health-check/")

これでヘルスチェックエンドポイントの情報は Application Insights 側には流れなくなります。

おわりに

本記事では、OpenTelemetry ベースで Azure Monitor(Application Insights) にテレメトリデータを集約し、アプリケーションを監視する方法を紹介しました。
OpenTelemetry ベースでアプリケーションの計装を行うことでベンダーに依存せずにシンプルに監視基盤を構築できます。

ただし、監視そのものはアプリケーションの根本的な問題を解決するものではありません。
監視が提供するのは「現状を可視化し、問題に気づく仕組み」に過ぎません。
収集したデータをもとに改善のアクションを起こしてこそ、ユーザー体験の向上につながります。

監視をゴールとしてではなく、改善のために出発点として捉えることで、アプリケーションの品質向上や継続的な改善へと結び付けられるでしょう。

Discussion