🏄‍♀️

OpenTelemetryでPythonを使う時の無難な設定

2023/09/12に公開

OpenTelemetryの公式のSDKにはバッチの設定やHTTPの接続など色々な設定項目があります。
この記事ではPythonでTraceを送信するときに使う無難な設定を紹介します。

具体例

最初に、このページで使用する具体的な設定を紹介します。
OpenTelemetryのために以下の3箇所を変更します。

  1. OpenTelemetry用の関数を作る
  2. manage.pyの初期化中に作成した関数を呼び出す
  3. GunicornやuWSGIのフォーク時に作成した関数を呼び出す

この3箇所を設定をすれば、DjangoのOpenTelemetryのデータをサーバーに送信できます。
※ この例ではDjangoを例に自動計装していますが、FastAPIなど別のフレームワークを使用している場合にも「自動計装」の節にあるコードを変える以外、実装はほとんど変わりません。

1. OpenTelemetry用の関数を作る

まず、コードを使い回せるようにOpenTelemetryの設定をする関数を作ります。

import os
import socket
import MySQLdb
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.django import DjangoInstrumentor
from opentelemetry.instrumentation.dbapi import trace_integration
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.semconv.resource import ResourceAttributes

def instrument():
    resource = Resource.create({
        ResourceAttributes.SERVICE_NAME: "サービス名",
        ResourceAttributes.SERVICE_VERSION: "バージョン名",
        ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "環境の名前",
        ResourceAttributes.HOST_NAME: socket.gethostname(),
        ResourceAttributes.PROCESS_PID: os.getpid(),
    })

    tracer_provider = TracerProvider(resource=resourece)
    tracer_provider.add_span_processor(
        BatchSpanProcessor(OTLPSpanExporter(
            endpoint="https://xxx.example.com/v1/traces",
            headers={
                "ヘッダー名": "ヘッダーの値"
            },
            compression=Compression.Gzip
        ))
    )
    trace.set_tracer_provider(tracer_provider)

    DjangoInstrumentor().instrument()
    trace_integration(MySQLdb, "connect", "mysql")

設定しているのは大まかに次の3箇所です。

  1. Resource
  2. TracerProviderとSpanProcessor
  3. 自動計装

各項目の詳しい説明は後に回して、他の設定も見ていきます。

2. manage.pyの初期化中で、作成した関数を呼び出す

作成した関数を manage.pyの中で呼び出すと計装ができます。

def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'message.settings')

    # ↓の行を追加
    instrument()

    try:
        from django.core.management import execute_from_command_line
    ...

このように、manage.pyの中にある main()の中で、作成した関数を呼び出すと計装ができます。

3. GunicornやuWSGIのフォーク時に作成した関数を呼び出す

本番で運用する場合、データをまとめて一括で送信する設定が一般的です。
しかし、GunicornuWSGIなどpreforkモデルのサーバーの場合、そういう設定をしたTracerProverをフォーク前に作るとデッドロックが起きてしまいます。
そのため、フォーク後に設定する必要があります。

具体的には、Gunicornの場合には post_forkで設定します。

def post_fork(server, worker):
    instrument()

また、uWSGIの場合には @postforkで設定します。

@postfork
def init_tracing():
    instrument()

以上、具体的なコードを紹介しました。

設定内容の説明

ここまでで必要なコードは揃ったので、具体例で使った設定について詳しくみていきます。

1. Resourceの設定

まず、Resourceの設定についてです。

OpenTelemetryではResourceにサービス名やバージョンを設定することでデータの発生元の情報を付与することができます。
Resourceに用意されている項目は色々ありますが、サービスを運用する上では最小限「サービス名」と「バージョン」、「環境の名前」は欲しいところです。
また、プロセスIDやホスト名を付与すると、特定のサーバーでだけ起きている問題が把握できたりと便利です。

各項目は具体例のように設定できます。

    resource = Resource.create({
        ResourceAttributes.SERVICE_NAME: "サービス名",
        ResourceAttributes.SERVICE_VERSION: "バージョン名",
        ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "環境の名前",
        ResourceAttributes.HOST_NAME: socket.gethostname(),
        ResourceAttributes.PROCESS_PID: os.getpid(),
    })

2. TracerProviderとSpanProcessorの設定

次に、TracerProviderとSpanProcessorを設定します。

TracerProviderは計装に必要な情報を持つオブジェクトで、Spanを作るために使われます。
また、SpanProcessorではデータを収集した後に実行する作業を設定します。

具体例では以下を設定しています。

  • ResourceをTracerProviderに設定する
  • データの送信先と送信方法
  • データを一括で送信するためにバッファリングする
  tracer_provider = TracerProvider(resource=resourece)
  tracer_provider.add_span_processor(
      BatchSpanProcessor(OTLPSpanExporter(
          endpoint="https://xxx.example.com/v1/traces",
          headers={
            "ヘッダー名": "ヘッダーの値"
          },
          compression=Compression.Gzip
      ))
  )
  trace.set_tracer_provider(tracer_provider)

この例は次のことをしています。

  • TracerProvider(resource=resourece)でResourceを指定する。
  • endpointでデータの送信先を指定する。
  • headersで送信時のヘッダーを指定する。認証が必要な時にトークンを渡すために使うことが多いです。
  • compressionでデータの圧縮方法を設定する。gzipを使うようにすると送信量が減るので便利なことが多いです。
  • BatchSpanProcessor をSpanProcessorとして使用する。
    BatchSpanProcessorは作成したデータをまとめて送信する機能です。SimpleSpanProcessorを使うことで逐次送信することもできますが、本番で使う場合にはBatchSpanProcessorを使う選択肢以外無いでしょう。

3. 自動計装

次に、計装(instrumentation)を行います。

    DjangoInstrumentor().instrument()
    trace_integration(MySQLdb, "connect", "mysql")

この例では、DjangoとMySQLの計装しています。
計装と言っても、書くべきコードはそれぞれ1行だけです。

※ FlaskやFastAPIを使っている場合はopentelemetry-instrumentation-djangoのDjangoInstrumentorの代わりにopentelemetry-instrumentation-flaskopentelemetry-instrumentation-fastapiを使って計装できます。

4. サーバーの設定

最後に、具体例にあるように、Gunicornなど各種サーバーからOpenTelemetryを設定できるようにコードを追加します。

終わりに

以上、OpenTelemetryでPythonを使う時の無難な設定を紹介しました。

Vaxila Labs

Discussion