OpenTelemetryに入門してみる(トレースを取得してみる)

とりあえずブラウザ - サーバ間のトレース が取得できるところまでを試してみる。

試した結果は https://github.com/piyoppi/otel-browser-playground にある。 このサンプルアプリケーションはVite + Honoの構成で、シンプルなバックエンドをもつフロントエンドアプリケーションを想定している。

デモ

公式ドキュメントに、Dockerで動作する、いくつかのサービスで構成されるアプリケーションの計測に関するデモがある。雰囲気をつかむのに手っ取り早く動かすことができる。

概念の理解

日本語のドキュメントがある。

OpenTelemetryは、シグナル(トレース、メトリクス、ログ、バゲッジ)を収集、処理、エクスポートするための仕様やインタフェース、および計装コンポーネントなどで構成されている

  • トレース: あるエンドポイントにアクセスしたときに何がどのくらいの時間行われているかを追跡するもの(データフェッチ, 描画, などなど...)。これらはスパンによって表現される。
  • メトリクス: 測定値。(CPU使用率、レスポンスタイム、とかとか...)

ブラウザ側のイベントをトレースしてみる

OpenTelemetryのGetting Started を見ながらソースコードを書いてみる。 内容を理解するために、コードにコメントを書いてみた。

import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'
import { ZoneContextManager } from '@opentelemetry/context-zone'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource, browserDetector } from '@opentelemetry/resources'
import { detectResourcesSync } from '@opentelemetry/resources/build/src/detect-resources'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

// OTLPTraceExporter を用いてトレースをサーバに送信するexporterを定義
const exporter = new OTLPTraceExporter({
  // Collectorのエンドポイントを指定
  url: 'http://localhost:55681/v1/traces',
});

// リソース(=テレメトリを生成する対象を示す属性値)の定義
// see: https://opentelemetry.io/ja/docs/concepts/resources/
let resource = new Resource({
  // サービス名を設定する
  [ATTR_SERVICE_NAME]: 'my-service-frontend',
});

const detectedResources = detectResourcesSync({ detectors: [browserDetector] });
resource = resource.merge(detectedResources);

// トレーサープロバイダー(= Tracerのファクトリ)を生成
// https://opentelemetry.io/ja/docs/concepts/signals/traces/#tracer-provider
// トレーサープロバイダーからトレーサーが生成され、トレーサーがスパンを生成する
const provider = new WebTracerProvider({
  resource,
  // BatchSpanProcessorを使うことで、いくつかのスパンをまとめてエクスポートするようになる
  spanProcessors: [new BatchSpanProcessor(exporter)],
});

// provider.registerを呼び出すことで、グローバルな部分にトレーサープロバイダが登録され、
// 各種instrumentationsで利用されるということぽい
// ref: https://github.com/open-telemetry/opentelemetry-js/blob/c00f36ee436f58906ff82cd9da978c44b69ec1e9/packages/opentelemetry-sdk-trace-base/src/BasicTracerProvider.ts#L119
provider.register({
  contextManager: new ZoneContextManager(),
})

// instrumentations (=計測器)を登録する
registerInstrumentations({
  instrumentations: [
    // HTMLのロード時の様子を計測する
    new DocumentLoadInstrumentation(),
  ],
})

Collectorでシグナルを収集して可視化する

生成したトレースを収集するサーバをたてる。これをCollectorと言う。

otel-collector-config.yml を記述してCollectorを設定する

receivers:
  otlp:
    protocols:
      http:
        # CORSのための設定(フロントエンドアプリケーションがlocalhost:5174で動作しているという前提がある)
        cors:
          allowed_origins:
            - "http://localhost:5174"
        endpoint: "0.0.0.0:55681"
      grpc:

exporters:
  # debug exporterを使うとControllerのサーバログにトレースが流れるようになる(=トレースが流れていることを確認できる)
  debug:
  otlp:
    endpoint: "tempo:4317"
    # (開発環境での動作確認のためTLSを無効化している)
    tls:
      insecure: true

service:
  # Pipelineを定義する
  # ref: https://opentelemetry.io/docs/collector/architecture/#pipelines
  # シグナルはreceiversから取得され、processorsを経てexportersに出力される
  pipelines:
    # (今回はトレースしか設定しない)
    traces:
      receivers: [otlp]
      exporters: [debug, otlp]

docker-compose.yml でcollectorを起動できるようにしておく

version: '3.8'

services:
  sample-otel-collector:
    image: otel/opentelemetry-collector
    container_name: sample-otel-collector
    command: ["--config=/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "55681:55681"
    networks:
      - monitoring

networks:
  monitoring:

トレースはどこかに記録しておかないといけない。このためのストレージエンジンとしてTempoをたてる。 Tempoは分散トレーシングのためのバックエンドで、トレースを保存、リストアする役割。S3などのオブジェクトストレージを保存先として利用できる。

Tempoのリポジトリ に、シグナルの可視化を担うGrafanaとの連携サンプルがある。 Grafanaは様々なシグナルを可視化するためのウェブアプリケーション(というのが現状の筆者の認識)。様々なデータソースを指定することができ、ダッシュボードを構成できる。

tempo.yml を設定しておく(筆者はまだ設定項目を詳細に理解していないので、説明は割愛)。

TempoとGrafanaを起動するように追記したdocker-compose.ymlは最終的には以下のようになった。

version: '3.8'

services:

  sample-otel-collector:
    image: otel/opentelemetry-collector
    container_name: sample-otel-collector
    command: ["--config=/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      # フロントエンドアプリケーションから収集するためのポートをあけておく
      - "55681:55681"
    networks:
      - monitoring

  # Tempo / Grafana 関連の記述は以下のサンプルコードから引用
  # ref: https://github.com/grafana/tempo/tree/main/example/docker-compose/
  init:
    image: &tempoImage grafana/tempo:latest
    user: root
    entrypoint:
      - "chown"
      - "10001:10001"
      - "/var/tempo"
    volumes:
      - ./tempo-data:/var/tempo
    networks:
      - monitoring

  memcached:
    image: memcached:1.6.29
    container_name: memcached
    ports:
      - "11211:11211"
    environment:
      - MEMCACHED_MAX_MEMORY=64m  # Set the maximum memory usage
      - MEMCACHED_THREADS=4       # Number of threads to use
    networks:
      - monitoring

  tempo:
    image: *tempoImage
    container_name: tempo
    command: [ "-config.file=/etc/tempo.yaml" ]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
      - ./tempo-data:/var/tempo
    ports:
      - "3200:3200"   # tempo
      - "9095:9095"   # tempo grpc
      - "4317:4317"   # otlp grpc
      - "4318:4318"   # otlp http
    depends_on:
      - init
      - memcached
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:11.2.0
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_DISABLE_LOGIN_FORM=true
      - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor metricsSummary
      - GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-exploretraces-app/grafana-exploretraces-app-latest.zip;grafana-traces-app
    ports:
      - "3000:3000"
    networks:
      - monitoring

networks:
  monitoring:

volumes:
  grafana-storage:
  tempo-storage:

ここまでの状態で、フロントエンドアプリケーションと計測バックエンド(上記のdocker-compose.ymlに記述したものたち)を起動したところ、ブラウザ側でHTMLの描画に利用される各種アセットの取得のトレースを得ることができた。

trace1

Node.jsバックエンドのトレース

書きかけ...