はじめに
「Grafana?なにそれおいしいの?」
当時の私の正直な感想は、「グラファナ……? 名前も聞いたことないけど、南国のフルーツか何か?」 というレベル。 そもそも私は「監視ツール」というもの自体に触れたことがなく、サーバーの何をどうやって見るものなのか、全く検討もつかない状態でした。
しかし、仕事で関わる可能性が高いなら、今のうちにどんなものか知っておきたい。 「ドキュメントを眺めていてもピンと来ない。実際に自分の手で監視環境を自作して、動かしながら勉強するのが一番早いのではないか?」
そう考えた私は、生成AI(Gemini) を「専属メンター」として迎え、知識ゼロの状態からローカル環境での構築実験に挑むことにしました。
本記事は、右も左も分からない未経験者が、AIと二人三脚で「Pythonアプリ → OpenTelemetry Collector → Grafana Tempo → Grafana」 という本格的な分散トレース環境を構築し、Grafanaとは何者かを理解するまでの自主学習の記録です。
AIは優秀なコードを書いてくれましたが、それでもDockerのネットワーク設定など、環境固有の問題で「動かない!」と頭を抱える場面が何度もありました。 この記事では、未経験者がどこでハマり、どうやってエラーを解消して動く状態まで持っていったのか、そのリアルな過程と解決策(コード)を共有します。
今回作るアーキテクチャ
-
App (Python/Flask): トレースデータの発生源。AIに教えてもらった「自動計装」を使用
-
OpenTelemetry Collector: データの中継・加工役
-
Grafana Tempo: トレースデータの保存バックエンド
-
Grafana: データの可視化画面
1. 設定ファイルの準備
ディレクトリを作成し、以下の3つのファイルを用意します。
1. docker-compose.yaml
各コンテナの定義です。
version: "3"
services:
# 1. 可視化 (Grafana)
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-storage:/var/lib/grafana
# 2. トレース保存 (Tempo)
tempo:
image: grafana/tempo:latest
command: [ "-config.file=/etc/tempo.yaml" ]
volumes:
- ./tempo.yaml:/etc/tempo.yaml
ports:
- "3200" # 管理用
- "4317" # OTLP受信 (内部通信用だが明示)
# 3. データ中継 (OTel Collector)
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317" # アプリからの受信ポート
- "4318:4318"
depends_on:
- tempo
volumes:
grafana-storage:
2. tempo.yaml (Tempoの設定)
【ハマりポイント1】 デフォルト設定だと、Dockerネットワーク越しにポートがうまく開かないことがありました。0.0.0.0 を明示することで解決します。
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317" # ★重要: ここで外部からの接続を許可
http:
endpoint: "0.0.0.0:4318"
ingester:
trace_idle_period: 10s
max_block_bytes: 1_000_000
max_block_duration: 5m
compactor:
compaction:
compaction_window: 1h
max_block_bytes: 100_000_000
block_retention: 1h
compacted_block_retention: 10m
storage:
trace:
backend: local
local:
path: /tmp/tempo/blocks
wal:
path: /tmp/tempo/wal
3. otel-collector-config.yaml (Collectorの設定)
【ハマりポイント2】 こちらも同様に受信側で 0.0.0.0 を指定する必要があります。また、デバッグ用に debug エクスポーターを入れておくと、データが届いているかログで確認できて便利です。
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317" # ★重要: ホスト側のアプリから受け取るため
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
exporters:
otlp:
endpoint: tempo:4317 # Docker内でのホスト名解決
tls:
insecure: true
debug:
verbosity: detailed # ログに詳細を出す
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp, debug] # Tempoへの送信とログ出力を並行
2. アプリケーションの準備(自動計装)
Pythonコードには、OpenTelemetryの記述を一切書きません。
import time
import random
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
# ランダムな遅延を入れてグラフを見やすくする
wait = random.uniform(0.1, 0.5)
time.sleep(wait)
return f"処理完了: {wait:.2f}秒"
if __name__ == "__main__":
app.run(port=8081)
実行コマンド(魔法の呪文) ライブラリ(opentelemetry-distro 等)を入れた後、以下のようにラップして起動するだけで、Flaskの通信が全て計測されます。
Bash
opentelemetry-instrument \
--traces_exporter otlp \
--service_name my-flask-app \
--exporter_otlp_endpoint http://localhost:4317 \
--exporter_otlp_insecure true \
python app.py
3. 起動とGrafanaの設定
docker compose up -d
Grafana (http://localhost:3000) にアクセス。
Connections -> Data sources から Tempo を追加。
URLに http://tempo:3200 を設定して Save & test。
緑色の「Data source is working」が出れば成功です!
構築中に遭遇したトラブルと解決策
今回の構築で実際にハマったポイントを共有します。
1. connection refused エラー
Collectorのログに dial tcp ... connection refused が出続けました。
原因: Tempoコンテナがデフォルト設定で起動しており、4317番ポートをリッスンしていなかった(またはローカルループバックのみだった)。
解決: tempo.yaml の grpc 設定で endpoint: "0.0.0.0:4317" を明記することで解消しました。
2. Collectorにデータが届かない
アプリを実行してもCollectorのログが反応しませんでした。
原因: Collector側の receivers 設定も 127.0.0.1 などのデフォルト値になっており、コンテナ外(ホストPC)からの通信を拒否していた。
解決: otel-collector-config.yaml の受信設定も 0.0.0.0:4317 に変更しました。
3. ポート競合 (bind: address already in use)
設定を変えて何度も docker compose up を繰り返していると、古いコンテナがゾンビのように残ってポートを占有していました。
解決: docker compose down でしっかり停止・削除してから起動し直すのが吉です。
成果:Grafanaでの可視化
Explore -> Tempo -> Search でクエリを実行すると、無事にトレース一覧が表示されました! Trace IDをクリックすると、以下のようなウォーターフォール図が表示され、処理時間が可視化できました。
まとめ
OpenTelemetryの自動計装は非常に強力で、コード変更なしに導入できるのが魅力です。 一方で、Docker環境で組む際は「コンテナ間の通信(ホスト名)」と「バインドアドレス(0.0.0.0)」の設定が一番のハードルになると感じました。
これから入門する方の参考になれば幸いです!
