🔭

OpenTelemetryを使ってSpanをW&Bに記録する (2)

2024/12/21に公開

はじめに

こんにちは、@Getty708 です。ML パイプライン開発において、開発と運用へのシームレスな統合についてアイデアを試しています。前回の記事では、OpenTelemetry を使って Span を W&B に記録する方法を考えました。今回はより運用よりの観点から、W&B への記録に加えて、Grafana と Jeager での Span の可視化を行います。

前回の記事: OpenTelemetry を使って Span を W&B に記録する

今回の目標

目標とするシステムの全体像は以下の図のとおりです。前回の記事で「Model Development」の部分を実装したので、今回は「Operation」の部分を追加していきます。具体的には、OTel Collector を追加します。そして、OTel Collector 経由で、Prometheus にメトリクスを、Jeager に Span を送信します。アーキテクチャは以下のようになります。

System Architecture

トレースの可視化はJeagerで行います。Jeager は、マイクロサービスのデータフローなどを可視化するための分散トレーシングのプラットフォームです。メトリクスは、Grafana を Prometheus に連携して可視化します。

実装の詳細

実装の詳細は GitHub のリポジトリを参照してください。

https://github.com/getty708/mlops-sandbox/tree/20241006-wandb-spanmetrics-exporter/services/monitoring

OTel Collector + 各種サービスのデプロイ

実装の詳細は GitHub のリポジトリを参照してください。

https://github.com/getty708/mlops-sandbox/tree/20241006-wandb-spanmetrics-exporter/services/monitoring

Docker Compose

OTel Collector は、ML パイプラインとは独立したプロセスで、ベンダー依存ではない方法で ML パイプラインからのテレメトリデータを Recieve/Process/Export します。今回は各種サービスと一緒に docker でデプロイします。OTel Collector に関しては以下の記事が参考になります。

https://zenn.dev/ryoyoshii/books/opentelemetry-firststep/viewer/collector

今回使用した docker-compose-monitor.yaml は以下のようになります。各種モジュールの設定は公式ドキュメントを参考にしました。

docker-compose-monitor.yaml
services:
  otel-collector:
    container_name: mlops-otel-collector
    image: otel/opentelemetry-collector-contrib:0.111.0
    ports:
      - 8888:8888 # Prometheus metrics exposed by the Collector
      - 8889:8889 # Prometheus exporter metrics
      - 4317:4317 # OTLP gRPC receiver
      - 4318:4318 # OTLP http receiver
    volumes:
      - ./docker/otel-collector/config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
    networks:
      - mlops-network

  prometheus:
    container_name: mlops-prometheus
    env_file:
      - .env
    image: prom/prometheus:v2.54.1
    volumes:
      - ./docker/prometheus/config/prometheus.yaml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    ports:
      - 9090:9090
    networks:
      - mlops-network

  grafana:
    container_name: mlops-grafana
    labels:
      prometheus-scrape.enabled: "false"
      service: "grafana"
    env_file:
      - .env
    image: grafana/grafana-oss:11.2.2
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    networks:
      - mlops-network

  jaeger:
    image: jaegertracing/all-in-one:1.62.0
    container_name: mlops-jaeger
    ports:
      - "16686:16686"
    networks:
      - mlops-network

volumes:
  grafana-data: {}
  prometheus-data: {}

networks:
  mlops-network:

OTel Collector の設定

続いて、OTel Collector の設定ファイルです。以下のようになります。設定ファイルは、receiversexportersconnectorsservice の 4 つのセクションに分かれています。
receivers では、ML Pipeline の OTel SDK から送られるデータを受け取れるようにするために OTLP Reciever の設定を記述しています。GRPC の場合は0.0.0.0:4317がエンドポイントになります。exporters には、OTel Collector で集約したデータの送信先を記述します。今回は Prometheus と Jeager 側の OTel Collector にデータを送る設定が記述されています。connectorについては後述します。service では、データの流れを設定します。

otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  prometheus:
    endpoint: "0.0.0.0:8000"
    namespace: "otel"
  otlp:
    endpoint: "jaeger:4317"
    tls:
      insecure: true

connectors:
  spanmetrics:
    histogram:
      disable: false
    aggregation_temporality: "AGGREGATION_TEMPORALITY_CUMULATIVE"
    metrics_flush_interval: 1s
    metrics_expiration: 10s

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [spanmetrics, otlp]
    metrics:
      receivers: [otlp, spanmetrics]
      exporters: [prometheus]

connectorでは、Span Metrics Connector という、スパンをメトリクスに変換するモジュールの設定が記述されています。メトリクスとしては、各 Span の duration の sum, count などがメトリクスとして出力されます。Span Metrics Connector については、以下の記事が参考になりました。

https://zenn.dev/k6s4i53rx/articles/2023-advent-calender-otel

Prometheus の設定

最後に、Prometheus が OTel Collector からデータを受け取る設定を、Prometheus の設定ファイルに追加します。

prometheus.yaml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["0.0.0.0:9090"]

  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8000"]

パイプライン側の実装

前回の記事では W&B への Exporter を実装しましたが、今回はそこに OTel Collector を追加します。具体的には以下のコードを追加します。

from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

tracer_provider.add_span_processor(
    span_processor=BatchSpanProcessor(
        span_exporter=OTLPSpanExporter(endpoint="localhost:4317", insecure=True)
    )
)

追加するのはこれだけです。Span の生成などは、W&B に出力する際に設定したものをそのまま使うことができます。実際には、モデル開発フェーズにおける W&B への結果の出力か、運用時の Prometheus への出力かのどちらかを使いますが、今回は動作確認のため両方同時に出力します。

いざ、実行

まずは、docker-compose で先ほど定義したモニタリングツールを起動します。

docker compose -f docker-compose-monitor.yaml up -d

続いて、パイプラインを実行します。

poetry run python services/monitoring/tools/run_otel_in_wandb.py -n 1000 -w online

ではここからモニタリングツールを確認していきます。 W&B については前回と同じなので省略します。

Jaeger

まず、Jaeger でトレースを見ます。http://localhost:16686 から GUI にアクセスできます。下の図のように検索をかけると、収集されたトレースが表示されます。Span の数はパイプライン全体の親 Span と、各モデル 3 個分で計 4 個を作成しているので、表示されているスパンの数と一致していることが確認できます。

Jaeger
Jaeger 検索画面

Jaeger
Jaeger トレース詳細

Prometheus

次に Prometheus で収集されたメトリクスを確認します。Prometheus には http://localhost:9090 からアクセスできます。otel_traces_span_metrics_duration_milliseconds_sum などで検索すると、 SpanMetricsConnector で生成されたメトリクスを確認できます。

Prometheus

Grafana

最後に Grafana です。Grafana は起動後に、http://localhost:3000 にアクセスするとログイン画面が表示されますので、管理者アカウントでログインします。デフォルトのユーザー名とパスワードは admin です。ログイン後、ダッシュボードを作成が必要になります。私の作成したものは、こちらの JSON (ml_pipeline_observability.json) を使ってインポートできます。SpanMetricsConnector で出力されるメトリクスはスパンの合計数 (sum) と スパンの数 (count) ですが、特定区間ごとに sum の変化量を count の変化量で割ることで、平均的な値を算出しています。詳細はダッシュボードを参照してください。

ダッシュボードを開くと、一番上にパイプラインの Latency、次に各モデルの Latency、最後に検出された人数のグラフが表示されています。W&B と見比べると変化がより滑らかみえると思います。W&B に表示されているのは全データポイントに対して、Grafana に表示されているのは集計値であるためです。

Grafana
Grafana のダッシュボード

W&B
W&B のダッシュボード

まとめ

OTel を上手に活用することで、 モデル開発時と運用時でほぼ同じメトリクスを一つの実装で記録・確認することができました。モデル開発と運用のギャップを少しだけ減らせたのではないかと思っています。今後も同様に、開発と運用をシームレスにするためのアイデアを模索していきたいと思います。

Discussion