OpenTelemetry Collectorで「流れないtelemetry」を作って観測する

OpenTelemetry Collectorの収集ノードを描いたアイキャッチ画像
目次

ログだけでは追いきれなくなった自宅運用者へ

Docker Composeで小さなサービスを並べて運用していると、ある時点から「ログを横断して原因を追う」のがつらくなります。メトリクスやトレースにも手を伸ばしたい。そこでOpenTelemetryが候補に挙がるのですが、receiver・processor・exporter・pipelineと用語が多く、最初から可視化基盤まで一気に広げると、どこで詰まったのかが見えなくなりがちです。

この記事は、可視化ツールを増やす前に「telemetryが本当にCollectorまで届いているか」を最小構成で確かめたい方に向けています。使い捨てのDocker環境でCollectorを単体起動し、まず成功する送信を1本通し、その後で意図的に3種類の失敗を作って、失敗が送信側・設定・宛先のどこに現れるかを切り分けます。最後に正常設定へ戻し、復旧を何で確認できるかまで記録します。

結論を先に置くと、Collector単体で確認できることは多く、ダッシュボードはその後で十分です。

検証環境

  • 実行場所: 隔離した使い捨てVM上のDocker(本番サービス・既存データには一切接続せず、ポート公開はループバックのみ)
  • Docker Engine: 29.6.0、Docker Compose: v5.2.0(検証時点)
  • イメージ: otel/opentelemetry-collector:latest(起動ログ上は 0.155.0)
  • 送信ツール: curl 7.88.1

バージョンは検証時点のものです。OpenTelemetry Collectorは更新が速く、設定キーや警告メッセージは将来変わり得ます。ここでの目的は「最新版で必ず同じになる」ことの保証ではなく、切り分けの手順を一度自分の目で通すことです。

最小構成: 受けて、流して、まず1本通す

最初の設定は、OTLPのHTTP receiverで受け、batch processorを通し、debug exporter(標準出力に詳細表示)とfile exporter(ファイルに追記)の2つへ流すだけのtracesパイプラインです。file exporterを併用するのは、debug出力がコンテナログに流れて消えやすいのに対し、ファイルなら後から落ち着いて中身を確認できるからです。

設定ファイル otelcol.yaml:

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
processors:
  batch:
exporters:
  debug:
    verbosity: detailed
  file:
    path: /output/traces.json
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, file]

送信する最小のスパン span.json:

{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"home-lab-test"}}]},"scopeSpans":[{"scope":{"name":"manual-test"},"spans":[{"traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b173","name":"verify-span","kind":1,"startTimeUnixNano":"1700000000000000000","endTimeUnixNano":"1700000000000000100"}]}]}]}

Collectorを起動します。ポートはループバックにだけ公開します。

$ docker run -d --name otelcol-lab \
    -p 127.0.0.1:4318:4318 \
    -v "$PWD/otelcol.yaml:/etc/otelcol/config.yaml" \
    -v "$PWD/output:/output" \
    otel/opentelemetry-collector:latest --config /etc/otelcol/config.yaml

起動ログの要点。HTTPサーバが4318で立ち、Everything is ready. Begin running and processing data. が出れば受け入れ準備は完了です。

info  Starting otelcol...  {"Version": "0.155.0", "NumCPU": 2}
info  Starting 設定項目 server  {"otelcol.component.kind": "receiver", "endpoint": "[::]:4318"}
info  Everything is ready. Begin running and processing data.

設定項目/HTTPで1本送ります。

$ curl -sS -o /dev/null -w 'http_code=%{http_code}\n' \
    -X 設定項目 http://127.0.0.1:4318/v1/traces \
    -H 'Content-Type: application/json' \
    -d @span.json

結果は http_code=200。Collector側のdebug exporterにもスパンが現れました。

http_code=200

info  Traces  {"otelcol.component.kind": "exporter", "resource spans": 1, "spans": 1}
info  ResourceSpans #0
Resource attributes:
     -> service.name: Str(home-lab-test)
Span #0
    Trace ID       : 5b8efff798038103d269b633813fc60c
    ID             : eee19b7ec3c1b173
    Name           : verify-span
    Kind           : Internal

file exporter側の output/traces.json にも同じスパンが書き出されます。ここまでで「送信側→receiver→pipeline→exporter」が1本通ったことを、HTTPの200・debugの出力・ファイルの中身の3点で確認できました。この3点が後の失敗切り分けの基準になります。

失敗系1: receiverポート不一致(症状は送信側に出る)

まず、送信先ポートを4319へ取り違えてみます。Collectorは4318で待っています。

$ curl -sS -o /dev/null -w 'http_code=%{http_code}\n' --max-time 5 \
    -X 設定項目 http://127.0.0.1:4319/v1/traces \
    -H 'Content-Type: application/json' -d @span.json
curl: (7) Failed to connect to 127.0.0.1 port 4319 after 0 ms: Couldn't connect to server
http_code=000

ここで見るべきは、http_code=000 と curl の終了コード7、つまり接続自体が成立していない点です。そしてこのとき、Collector側のログには何も増えません。これは重要な切り分け線です。Collectorのログに受信の痕跡が無く、送信側だけがエラーを出すなら、原因はCollectorの中ではなく「宛先(ポート・ホスト)の不一致」です。設定をいくら読み返しても直りません。

失敗系2: pipeline構成ミス(起動時に即停止する)

次に、pipelineが存在しないexporterを参照する設定にします。exporters: [debug, doesnotexist] のように、定義していない名前を書いた状態です。このCollectorは即座に終了し、標準エラーに理由を出します。

Error: invalid configuration: service::pipelines::traces: references exporter "doesnotexist" which is not configured

この失敗は分かりやすい部類です。コンテナが起動して数秒で落ちる、docker ps に残らない、そしてログの先頭に invalid configuration と参照元(service::pipelines::traces)・参照先(doesnotexist)が明記されます。送信を試す前に止まるので、失敗系1とは現れる場所が違います。起動しない時はまず起動ログの先頭を読む、というだけで原因にたどり着けます。

失敗系3: 到達不能exporter(起動も受信も成功するのに届かない)

3つ目が、家庭ラボで一番ハマりやすいケースです。設定は文法的に正しく、receiverも生きているのに、exporterの送信先が到達不能、というパターンです。ここではOTLP exporterの宛先を、誰も待っていない 127.0.0.1:4999 にしました。

Collectorは正常に起動し、設定項目/HTTPの受信も http_code=200 を返します。送信側から見ると成功しているように見えます。しかしCollectorのログには、警告が出続けます。

warn  "otlp" alias is deprecated; use "otlp_grpc" instead
warn  [core] ... failed to connect to {Addr: "127.0.0.1:4999"} ... connection refused
info  Everything is ready. Begin running and processing data.

ここでの教訓は2つです。1つ目、送信側の200は「Collectorが受け取った」ことしか保証せず、「最終的な宛先まで届いた」ことは保証しません。「送ったのに見えない」時は、送信側ではなくCollectorのexporterログを見ます。2つ目、connection refused の警告は宛先側が居ない・ポートが違うことを指しています。失敗系1が送信側に出たのに対し、これはCollector側に出ます。出る場所で原因の層が分かります。

なお "otlp" alias is deprecated; use "otlp_grpc" instead という警告も同時に出ました。これは設定キー名の移行案内で、検証時点の版では otlp_grpc が推奨です。

ロールバックと復旧確認

壊した設定を、最初の正常な otelcol.yaml(debug + file exporter)へ戻して起動し直します。

$ docker rm -f otelcol-lab
$ docker run -d --name otelcol-lab \
    -p 127.0.0.1:4318:4318 \
    -v "$PWD/otelcol.yaml:/etc/otelcol/config.yaml" \
    -v "$PWD/output:/output" \
    otel/opentelemetry-collector:latest --config /etc/otelcol/config.yaml

復旧の確認は、壊す前と同じ3点を使います。起動ログに Everything is ready、送信して http_code=200、そして output/traces.json の末尾に新しいスパンが追記されること。

http_code=200

{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"home-lab-test"}}]},"scopeSpans":[{"scope":{"name":"manual-test"},"spans":[{"traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b173","name":"verify-span","kind":1, ... ,"status":{}}]}]}]}

設定ファイルを差し替えてコンテナを作り直すだけで戻せること、復旧の判定基準が「壊す前と同じ3点」で済むことが、最小構成で先に試しておく価値です。

後片付け

使い捨て環境なので、コンテナと一時ファイルは消して終わります。

$ docker rm -f otelcol-lab

コンテナ一覧に残骸が無いこと、作業ディレクトリが消えていることを確認して完了です。

比較: debug/file/外部基盤前のCollector単体

可視化基盤を入れる前にどこまでCollector単体で見るべきか、3つの構え方を整理します。

構成 見えるもの 向いている確認
debug exporterだけ 標準出力に流れる詳細。手早いが流れて消える 起動直後に「届いているか」を一目で見る
file exporterを併用 ファイルに残るので後から読み返せる 送ったスパンの中身・件数を落ち着いて確認
外部可視化基盤へ送る前のCollector単体 receiver・pipeline・exporterの切り分け線 本番投入前に失敗の現れ方と戻し方を把握

外部基盤(バックエンド)を最初からつなぐと、見えない原因が「送信側」「Collector」「基盤側」のどこにあるか分からなくなります。debug + file の単体構成で基準を作っておけば、後で基盤を足したときも切り分けが効きます。

残課題と制約(再現条件)

  • 確認したのはtracesの単一signalのみ。metrics・logsは別途同じ手順で確認が要ります。
  • ポート公開はループバック限定、本番サービス・既存データには未接続。家庭ラボの実サービスへ入れる際はネットワーク到達性・認証・保持先の設計が別に必要です。
  • otlp exporterは検証時点で deprecation 警告が出ました(otlp_grpc 推奨)。設定キー名は版で変わり得ます。
  • バージョンは検証時点のもの。最新版での同一動作を保証するものではありません。
  • 再現は、同じVM+Docker構成で同一手順を再実行することで確認できます。

まとめ: ダッシュボードより先に、壊して戻す

「Collectorを入れれば観測がすぐ始まる」かと言えば、入口はそう難しくありません。最小構成なら数コマンドで受信まで届きます。ただし価値があるのは、可視化を広げる前に次の切り分け線を一度自分で通しておくことです。

  • 送信側だけがエラー・Collectorに痕跡なし → 宛先(ポート/ホスト)の不一致
  • 起動して即停止・ログ先頭に invalid configuration → pipeline構成ミス
  • 受信は200なのに見えない・Collectorに connection refused → exporterの宛先が到達不能

この3つを区別できれば、本番サービスへ入れた後に「送ったのに見えない」となっても、どの層を見ればよいか迷いません。ダッシュボードは、その基準を作ってからで十分です。

この検証を回している環境

この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。

ラボ構成のまとめを見る →
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次