🔭

curl -> OpenTelemetry Collector -> otel-tui で簡単 OTLP trace 入門

に公開

概要

本記事では、OpenTelemetry(OTel)のトレース入門として、curl -> OpenTelemetry Collector -> otel-tui を使った簡単な実装を紹介します。
OTel や OpenTelemetry Collector(OTel Collector)に興味があるものの、どのように試せば良いかわからない方の学習の一助になれば幸いです。

サンプルコードは以下のリポジトリで公開しています。一部のソースコードおよび OTel に送信するリクエストボディは AI を利用して作成しました。

https://github.com/Msksgm/otel-collector-curl-otel-tui

実装

プロジェクトを作成

ディレクトリを作成してください。

mkdir otel-collector-curl-otel-tui

docker-compose.yml を作成

作成したディレクトリ内で、OTel Collector を起動するための docker-compose.yml を作成します。

./otel-collector-curl-otel-tui/docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector:latest
    container_name: otel-collector
    ports:
      - "4318:4318" # OTLP/HTTP
    volumes:
      - type: bind
        source: ${PWD}/otelcol/config.yaml
        target: /etc/otelcol/config.yaml
    restart: unless-stopped

OpenTelemetry Collector の config.yaml を作成

続いて、OpenTelemetry Collector の設定ファイルである config.yaml を作成します。
設定におけるポイントは以下です。

  • receiver が OTLP を受信するポートを 4318 に設定
  • exporter が OTLP を送信する先を Docker を起動した host(host.docker.internal)の 4317 ポートに設定
  • トレースの pipeline を設定
./otel-collector-curl-otel-tui/otelcol/config.yaml
receivers:
  otlp:
    protocols:
      http: # OTLP over HTTP: 4318
        endpoint: 0.0.0.0:4318

exporters:
  debug: {} # 標準出力で debug ログを出力
  otlp:
    endpoint: host.docker.internal:4317 # docker を起動した host の 4317 ポートに送信
    tls:
      insecure: true

service:
  pipelines:
    # trace のパイプライン設定
    traces:
      receivers: [otlp]
      exporters: [debug, otlp]

docker compose up を実行して、以下の画像のように動作したら成功です。本記事の解説ではこのまま起動したままで進めますが、中断したい場合や確認を終えたら、Ctrl + cで停止して、docker compose downでコンテナを削除してください。

docker-compose-up-otel-collector.drawio.png

otel-tui の用意

以下の記事を参考に、otel-tui をインストールしてください。

https://zenn.dev/ymtdzzz/articles/a3a809ca1ba440

インストールが完了したら、otel-tui --http 8888 で起動してください。画像のように起動したら準備完了です。otel-tui も同様に起動したまま進めます。中断したい場合や確認を終えたら、停止するには Ctrl + c を実行します。

up-otel-tui.drawio.png

動作確認

これから動作確認を実施します。curl コマンドで実行した結果を otel-tui で確認します。

確認観点は以下です。

  • 単一のトレースを送信する
  • 親スパンをもつトレースを送信する

単一のトレースを送信する

curl コマンドで、ローカルの OTel Collector に送信するシェルスクリプトを作成します。
以下のファイルを作成してください。

./otel-collector-curl-otel-tui/otlp-curl-trace.sh
#!/bin/bash

# ナノ秒単位の現在時刻を取得
# macOSの場合はgdate、Linuxの場合はdateコマンドを使用
if command -v gdate &> /dev/null; then
    NOW=$(gdate +%s%N)
else
    NOW=$(date +%s%N)
fi

# 32文字の16進数文字列を生成 (16バイト)
TRACE_ID=$(openssl rand -hex 16)

# 16文字の16進数文字列を生成 (8バイト)
SPAN_ID=$(openssl rand -hex 8)

START_TIME=$NOW
END_TIME=$((NOW + 1000000000))

curl -sS -X POST http://localhost:4318/v1/traces \
  -H 'Content-Type: application/json' \
  --data-binary @- <<JSON
{
  "resourceSpans": [{
    "resource": {
      "attributes": [
        {"key":"service.name","value":{"stringValue":"curl-client"}},
        {"key":"service.host","value":{"stringValue":"mac"}}
      ]
    },
    "scopeSpans": [{
      "spans": [{
        "traceId": "$TRACE_ID",
        "spanId": "$SPAN_ID",
        "name": "example-span",
        "kind": 1,
        "startTimeUnixNano": "$START_TIME",
        "endTimeUnixNano": "$END_TIME",
        "attributes": [
          {"key":"http.method","value":{"stringValue":"GET"}},
          {"key":"http.url","value":{"stringValue":"http://example.com"}}
        ],
        "status": {
          "code": 1
        }
      }]
    }]
  }]
}
JSON

echo "Trace sent with ID: $TRACE_ID"

作成したら、bash otlp-curl-trace.sh で実行してみましょう。
実行すると、以下のようなログが出力されます。

実行結果
> bash otlp-curl-trace.sh
{"partialSuccess":{}}Trace sent with ID: 13b02495491cd4df8a5d0bbed050a42a

otel-tui にトレースが表示されていれば成功です。このように、Collector に送信した内容が、オブザーバビリティバックエンドである otel-tui に転送・表示されます。

single-trace-on-otel-tui-01.drawio.png

single-trace-on-otel-tui-02.drawio.png

親スパンをもつトレースを送信する

続いて、親子関係を持つスパンを含むトレースを送信するシェルスクリプトを作成します。
長いため折りたたんでいます。
parentSpanId を持つスパンが、親子関係にあることを示しています。
実際にリクエストを送って確認してみましょう。

親スパンをもつトレースを送信するシェルスクリプト

./otel-collector-curl-otel-tui/otlp-curl-trace-with-parent-child.sh
#!/bin/bash

# ナノ秒単位の現在時刻を取得
# macOSの場合はgdate、Linuxの場合はdateコマンドを使用
if command -v gdate &> /dev/null; then
    NOW=$(gdate +%s%N)
else
    NOW=$(date +%s%N)
fi

# 32文字の16進数文字列を生成 (16バイト)
TRACE_ID=$(openssl rand -hex 16)

# 16文字の16進数文字列を生成 (8バイト)
PARENT_SPAN_ID=$(openssl rand -hex 8)

# 16文字の16進数文字列を生成 (8バイト)
CHILD_SPAN_ID=$(openssl rand -hex 8)

# 16文字の16進数文字列を生成 (8バイト)
CHILD_SPAN_ID_2=$(openssl rand -hex 8)

PARENT_START_TIME=$NOW
PARENT_END_TIME=$((NOW + 2000000000))

CHILD_START_TIME=$((NOW + 100000000))
CHILD_END_TIME=$((NOW + 1500000000))

CHILD_START_TIME_2=$((NOW + 200000000))
CHILD_END_TIME_2=$((NOW + 1800000000))

curl -sS -X POST http://localhost:4318/v1/traces \
  -H 'Content-Type: application/json' \
  --data-binary @- <<JSON
{
  "resourceSpans": [{
    "resource": {
      "attributes": [
        {"key":"service.name","value":{"stringValue":"parent-child-service"}},
        {"key":"service.version","value":{"stringValue":"1.0.0"}},
        {"key":"service.host","value":{"stringValue":"mac"}}
      ]
    },
    "scopeSpans": [{
      "spans": [
        {
          "traceId": "$TRACE_ID",
          "spanId": "$PARENT_SPAN_ID",
          "name": "parent-operation",
          "kind": 2,
          "startTimeUnixNano": "$PARENT_START_TIME",
          "endTimeUnixNano": "$PARENT_END_TIME",
          "attributes": [
            {"key":"http.method","value":{"stringValue":"POST"}},
            {"key":"http.url","value":{"stringValue":"http://api.example.com/process"}},
            {"key":"http.status_code","value":{"intValue":"200"}},
            {"key":"operation.type","value":{"stringValue":"parent"}}
          ],
          "status": {
            "code": 1,
            "message": "Success"
          }
        },
        {
          "traceId": "$TRACE_ID",
          "spanId": "$CHILD_SPAN_ID",
          "parentSpanId": "$PARENT_SPAN_ID",
          "name": "child-operation-database-query",
          "kind": 3,
          "startTimeUnixNano": "$CHILD_START_TIME",
          "endTimeUnixNano": "$CHILD_END_TIME",
          "attributes": [
            {"key":"db.type","value":{"stringValue":"postgresql"}},
            {"key":"db.statement","value":{"stringValue":"SELECT * FROM users WHERE id = ?"}},
            {"key":"db.operation","value":{"stringValue":"SELECT"}},
            {"key":"operation.type","value":{"stringValue":"child"}}
          ],
          "status": {
            "code": 1,
            "message": "Success"
          }
        },
        {
          "traceId": "$TRACE_ID",
          "spanId": "$CHILD_SPAN_ID_2",
          "parentSpanId": "$PARENT_SPAN_ID",
          "name": "child-operation-cache-lookup",
          "kind": 3,
          "startTimeUnixNano": "$CHILD_START_TIME_2",
          "endTimeUnixNano": "$CHILD_END_TIME_2",
          "attributes": [
            {"key":"cache.type","value":{"stringValue":"redis"}},
            {"key":"cache.operation","value":{"stringValue":"GET"}},
            {"key":"cache.key","value":{"stringValue":"user:session:12345"}},
            {"key":"cache.hit","value":{"boolValue":true}},
            {"key":"operation.type","value":{"stringValue":"child"}}
          ],
          "status": {
            "code": 1,
            "message": "Cache hit"
          }
        }
      ]
    }]
  }]
}
JSON

echo "Trace sent with:"
echo "  Trace ID: $TRACE_ID"
echo "  Parent Span ID: $PARENT_SPAN_ID"
echo "  Child Span ID 1: $CHILD_SPAN_ID"
echo "  Child Span ID 2: $CHILD_SPAN_ID_2"

作成したら、bash otlp-curl-trace-with-parent-child.sh で実行してみましょう。
実行すると、以下のようなログが出力されます。

実行結果
> bash ./otlp-curl-trace-with-parent-child.sh

{"partialSuccess":{}}Trace sent with:
  Trace ID: 22aaa3bfa06fb4dbb0725973c8af512e
  Parent Span ID: c6ff9719c672de56
  Child Span ID 1: 58f281e985dfa5c9
  Child Span ID 2: 038af19e2dc54bbe

otel-tui にトレースが表示され、スパンに の記号が付いています。これは親子関係を持つスパンであることを示しています。
curl で任意のトレースや属性を送信することで、親子関係を持つスパンの動作を確認できました。

multiple-trace-on-otel-tui-01.drawio.png

multiple-trace-on-otel-tui-02.drawio.png

まとめ

本記事では、curl、OTel Collector、otel-tui を組み合わせて OTLP 形式のトレース送受信を確認しました。
この構成により、OpenTelemetry の動作や仕様を手軽に検証できます。
OpenTelemetry に興味がある方の学習の一助になれば幸いです。

Discussion