SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

「OpenTelemetry」で始めるオブザーバビリティ入門

エラーやボトルネックの発見に役立つ「計装」とは? OpenTelemetryを活用してトラブルに備えよう!

  • X ポスト
  • このエントリーをはてなブックマークに追加

手動計装へのステップアップ

 自動計装の手軽さはご理解いただけたかと思いますが、一方で、自動計装には限界があります。

 自動計装に対応していないライブラリやフレームワークに関しては取得できるテレメトリーは限定的になってしまいます。また、自動計装されたとしてもトラブルシューティングなどで必要になる情報が必ず含まれるわけではありません。アプリケーションのロジックは業務仕様やビジネスルール、ドメイン知識などに基づいて作られており、その業務仕様やビジネスルールなどは要件によって異なるものである以上、自動計装のような汎用的な手法だけでは、痒いところに手が届かない部分が出てきます。これに対処していくには、手動計装にステップアップしていく必要があります。[3]

 例えば、あるユーザーからクレームがあった場合に、そのユーザーが実施した処理を処理順に抽出し、その時にユーザーが入力した情報や出力されたエラーなどをすぐに確認できれば、問題解決はスムーズに進むことでしょう。

 今回は、このサンプルアプリケーションでユーザー登録を実施する際に、ユーザが入力したEmailアドレスとユーザ名をスパンタグで確認できるように計装を加えていきます(なお、以下でいくつかのコードを参照しますが、パスはクローンしたコードレポジトリのディレクトリからの相対パスです)。

 手動計装の基本的なアプローチは、(1)OpenTelemetry API、SDK をインポートし、(2)計装コードを追加する(トレーサーの取得・初期化、スパンの生成・設定、Attributeの追加・設定など)というものです。

 では、やってみましょう。

 今回のサンプルアプリケーションにおいて、“Register User” 処理が “POST /api/users” で実行されることはSwaggerを見ると一目瞭然です。が、これらが明確でない場合は、一度自動計装の状態でその処理を動かしてみるのがよいでしょう。実際に、Register User処理を一度実施してみると、オブザーバビリティバックエンド側にトレースが取得され、実行された処理内容が確認できるはずです。

トレースを見つけて確認してみる

 詳細なウォーターフォール図も一度見ておきましょう。バックエンドのDBに対してINSERT処理などを行っているようです。

詳細を見てみる

 さて、“POST /api/users” というエンドポイントに関するルート定義を、実際のアプリケーションコードから読み解いていきます。

 conduit/api/router.pyというファイルがあるのでこれを開いてみると、/usersauthentication.routerに関連づいているようです。

conduit/api/router.py
router.include_router(
    router=authentication.router, tags=["Authentication"], prefix="/users"
)

 conduit/api/routes/authentication.pyを見てみると、POST処理でregister_userという関数が定義されています。

conduit/api/routes/authentication.py
from fastapi import APIRouter

from conduit.api.schemas.requests.user import UserLoginRequest, UserRegistrationRequest
from conduit.api.schemas.responses.user import (
    UserLoginResponse,
    UserRegistrationResponse,
)
from conduit.core.dependencies import DBSession, IUserAuthService

router = APIRouter()

@router.post("", response_model=UserRegistrationResponse)
async def register_user(
    payload: UserRegistrationRequest,
    session: DBSession,
    user_auth_service: IUserAuthService,
) -> UserRegistrationResponse:
    """
    Process user registration.
    """
    user_dto = await user_auth_service.sign_up_user(
        session=session, user_to_create=payload.to_dto()
    )
    return UserRegistrationResponse.from_dto(dto=user_dto)

//
// 以下省略
//

 ユーザーが “/api/users” にPOSTリクエストを実施すると、register_user関数が、ユーザーが指定した情報を受け取り、sign_up_user処理を実施して、結果をクライアントに返却しているようです。

 payload: UserRegistrationRequestsにユーザーが指定した情報が入っているはずで、これは import 句を見るとどうやらconduit.api.schemas.responses.userに定義されているようなので、これも事前に確認しておきましょう。

 conduit/api/schemas/responses/user.pyを開くと、以下のようにclassが定義されています。

conduit/api/schemas/responses/user.py
class UserRegistrationData(BaseModel):
    email: str
    password: str
    username: str

// 中略

class UserRegistrationRequest(BaseModel):
    user: UserRegistrationData

    def to_dto(self) -> CreateUserDTO:
        return CreateUserDTO(
            username=self.user.username,
            email=self.user.email,
            password=self.user.password,
        )

 UserRegistrationRequestクラスでuserが定義されていて、そのuserは、UserRegistrationDataクラスを参照しているようです。UserRegistrationDataに含まれるemail, usernameなどを参照してスパンタグに埋め込むことができると良さそうです。

 では、計装ライブラリを追加していくところからやっていきましょう。

 まずは、必要となるライブラリをrequirements.txtに追加しておきます。

 追加するのは以下の2つです。

requirements.txt
opentelemetry-api
opentelemetry-sdk

 次に、ライブラリをインポートします。

 conduit/api/routes/authentication.pyに以下を追加します。

conduit/api/routes/authentication.py
from opentelemetry import trace

 さらに、ユーザー登録処理にて、ユーザーが指定した情報を取得できるようにコードを追加します。

conduit/api/routes/authentication.py
@router.post("", response_model=UserRegistrationResponse)
async def register_user(
    payload: UserRegistrationRequest,
    session: DBSession,
    user_auth_service: IUserAuthService,
) -> UserRegistrationResponse:

    """
    Process user registration.
    """
    # 現在のスパンを取得
    current_span = trace.get_current_span()
    # スパン属性としてユーザーのEmailとユーザ名を追加
    current_span.set_attribute("payload.email", payload.user.email)
    current_span.set_attribute("payload.username", payload.user.username)

    user_dto = await user_auth_service.sign_up_user(
        session=session, user_to_create=payload.to_dto()
    )
    return UserRegistrationResponse.from_dto(dto=user_dto)

 “Process user registration”というコメントの直後、current_span = trace.get_current_span()で、この関数を実施する際に生成されているアクティブなスパンを取得しています。

 さらにその取得したアクティブなスパンに対してcurrent_span.set_attribute()によって、属性名(key) “payload.email”および“payload.username” に対して、値(value) としてpayload.user.emailおよびpayload.user.usernameの変数の値を参照して設定しています。

 ファイルを保存して、アプリケーションを起動しなおします。

 SwaggerでRequest Bodyを指定して登録を実施してみましょう。

Swaggerでユーザ登録用のAPIを実行

 うまくいけば、HTTP=200で登録が成功するはずです。

 オブザーバビリティバックエンド側でも見てみましょう。

実行したトレースをバックエンド側でみつける

 トレースウォーターフォールで、POST /api/usersを実施しているスパンを選択すると、スパンタグとして先ほどユーザー登録時に指定した情報が確認できるようになったのではないでしょうか。

スパンタグとして登録情報が確認できるようになった

 これでユーザーが入力したメールアドレスやユーザ名をトレースから確認できるようになりました。

 例えば、オブザーバビリティバックエンド側に取得されたトレースを、このpayload.usernameの値に基づいて、トレース数やエラー数を集計したり、

ユーザ名の属性に基づいてトレース数を集計

特定のユーザ名を持つキーに、該当するトレースを抜き出したりすることができるようになります。

ユーザ名の属性に基づいて検索し、トレースを特定

 こんな感じで、分析や調査のキーとなる情報をスパンタグとして埋め込んでおくことで、調査すべき対象の処理にすぐに辿り着くことができるようになるわけです。

 今回はスパン属性の追加を試みましたが、例えばアプリケーションコードの任意の場所で新たなスパンを作り、より処理の順序や流れを精緻に確認できるようにしていったり、ある特定の条件を満たした場合に強制的にエラー状態やExceptionをスパンに記録したりすることもできます。あるいは、あるアプリケーション処理に関する個別のメトリクスを生成することも可能です。要件に応じて、OpenTelemetry公式のドキュメントなどを参照しながら試してみてもらえればと思います(Pythonの場合の例

[3]逆に言うと、従来的な運用においては開発/アプリチーム(Dev)と運用/インフラチーム(Ops)が明確に分離されていることもしばしばで、運用/インフラチームが監視を含めた非機能要件に対応するような慣習もあったかもしれません。エンドユーザー体感やビジネスを支える信頼性を高めていく、そのためにオブザーバビリティを高めていく上では、運用/インフラチームだけがその責任を負うべきものではないですし、逆に運用/インフラチームだけで手に負えるものでもないはずです。ビジネスサイドやアプリケーション開発に携わる方々もまた信頼性に責任を持ち、オブザーバビリティを高めるべく取り組むことが必須と言えます。

計装を行う際は互換性に注意!

 ここまで見ていただいた通り、自動計装・手動計装ともに、アプリケーションの言語別に構成をしていく必要があります。それはつまり、言語ごとに互換性や前提となるランタイムバージョンなどが定められていることを意味します。計装を行う際には、対象のアプリケーションについて互換性情報を確認しておくことが必要です。

 例えば、Javaアプリケーションの場合、Java 8以上が原則で、AndroidやKotlinなどでは異なる前提が置かれています(Javaに関するポリシー)。他の言語でも同様のポリシーがありますので、事前に確認をしていきましょう。

 自動計装にあたって、今回は環境変数を通じたエージェントの構成を行い、起動コマンドを変更しましたが、これらの環境変数や起動コマンドの管理は、それぞれアーキテクチャや利用しているフレームワークによっても異なるはずです。例えば、コンテナ化されたアプリケーションの場合は、環境変数をコンテナ作成時に使用するDockerfileに明記したり、起動時のコマンド引数にしたり、あるいは構成ファイル上に指定するようなこともできるはずです。あるいは、Javaアプリケーションにおいて、TomcatやJBoss(Wildfly)などのミドルウェアを使っており、そのミドルウェアごとの設定の管理が行われているケースもあるでしょう。

 いずれの場合も、OpenTelemetryのエージェントがアプリケーションと共に起動できる状態で、かつ、必要な環境変数が与えられさえすればよいので、それぞれのアーキテクチャに合わせた形で設定を行うようにしてください(Javaアプリケーションのミドルウェアごとの設定例)。

 この他、Kubernetes環境において、OpenTelemetry Operatorを利用することで自動計装を行う方法が公開されています。cert-managerが前提で、Podデプロイ時に自動計装用のエージェントをPodにInjectする方式を取ります。Kubernetes環境を利用中の場合は、こういった方法もご検討ください。

まとめ

 いかがだったでしょうか。自動計装と手動計装のメリットと、その実装イメージを理解いただけたでしょうか。

 今回のご紹介では、比較的シンプルな構成のアプリケーションにおいて計装を実施していったため、それほど難しくないように感じていただいたかもしれませんが、実際のアプリケーションやシステム環境においては必ずしも計装がスムーズに進まないケースもあります。次回はOpenTelemetryに基づく計装やその活用に関するトラブルシューティングについてご紹介しますので、引き続きお楽しみにしていただければと思います。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
「OpenTelemetry」で始めるオブザーバビリティ入門連載記事一覧

もっと読む

この記事の著者

中上 健太朗(Splunk Services Japan合同会社)(ナカガミ ケンタロウ)

 Splunk Services Japan合同会社でオブザーバビリティソリューションを担当するエンジニア。Splunk Observabilityのご紹介・ご提案・導入支援などに従事。従来の運用から一歩進んだアプローチへとお客様がステップアップできるように日々活動を行っている。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20275 2024/10/31 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング