Grafana Tempoは、マイクロサービスをまたぐリクエストの経路(トレース)を保存・検索する分散トレーシングのバックエンドです。2026年5月の3.0でingesterが廃止され、書き込み経路はKafka経由の構成に作り替えられました。2.x時代の解説記事の多くは、もう現行の構成と合いません。この記事では、3.1.0を実際にローカルで動かした設定と実行結果をもとに、仕組み・TraceQLの書き方・2.xからの移行・導入判断までを整理します。版と料金は2026年10月時点の公式情報です。
まとめ:Grafana Tempo 3.1の要点と導入判断
Tempoはトレースにインデックスを張らず、Amazon S3などのオブジェクトストレージへParquet形式のブロックとして貯める設計です。最新版は3.1.0(2026年9月29日)で、3.0系も3.0.4(2026年10月16日)まで修正が出ています。ライセンスはAGPLv3で、ソフトウェア自体は無償です。
押さえるべき変化は3つあります。3.0で書き込み経路が「distributor→Kafka→block-builder」に変わり、直近データの検索はlive-storeが担うようになりました。ただしKafkaが要るのはマイクロサービスモードだけで、単一プロセスのモノリシックモードはKafkaなしで動きます。3.1では新規ブロックの既定形式がvParquet5になり、TraceQLメトリクスで割り算などの算術が使えるようになりました。
導入の判断は単純です。1日のスパン量が少なく運用者も少ないならモノリシックモードで始め、書き込みと検索の負荷がぶつかるようになってからKafkaを用意してマイクロサービスモードへ移ります。2.xを運用中なら、tempo-cli migrate configで設定を変換したうえで、保持期間の設定が消えていないかを必ず確かめてください(後述の実測で、指定した720hが変換後に消えました)。
Grafana Tempoの定義と分散トレーシングでの役割
TempoはGrafana Labsが開発するOSSで、2020年10月に発表され、2021年6月の1.0で一般提供が始まりました(Grafana Tempo公式ページ)。何を保存し、何と組み合わせるのかを先に確認します。
トレースとスパンの構造とTempoが保存する単位
1件のリクエストがサービスA→B→Cと渡る一連の処理が「トレース」で、各サービスでの処理区間が「スパン」です。スパンは開始・終了時刻、サービス名、HTTPステータスなどの属性を持ち、親スパンのIDで親子関係をつくります。サービス間ではtrace_idとspan_idがヘッダーで伝搬され、別々のプロセスで記録されたスパンが1本のトレースにまとまります。Tempoはスパンを受け取り、trace_id単位で引ける形でブロックに書き出します。後から引く方法は、trace_idの直接指定と、属性を条件にしたTraceQL検索の2通りです。
インデックスを持たずオブジェクトストレージに貯める設計
従来のバックエンドは検索可能なデータベースを保存先にするため、全件を残すとインデックスの費用がかさみ、サンプリングで一部だけ残す運用になりがちでした。Tempoの公式ページは、オブジェクトストレージ中心の設計を紹介し「トレースをインデックスしないことで、同じコストで桁違いに多くのトレースデータを保存できます」と説明しています。trace_id指定の取得ではブルームフィルターで対象外のブロックを除外し、属性検索ではParquetの列指向レイアウトを利用して必要な列を読みます。まれなエラーのトレースも捨てずに済む点が設計の芯です。
Loki・Prometheus・Grafanaとの役割分担
可観測性はメトリクス・ログ・トレースの3種のデータで成り立ちます。Grafana Labsのスタックでは、メトリクスをPrometheusで監視を構築する手順で扱うPrometheus(またはMimir)、ログをGrafana Loki、トレースをTempoが受け持ち、Grafanaが3つを1画面に束ねます。3本柱の考え方はオブザーバビリティ(可観測性)と監視の違いで整理しています。TempoとPrometheusの接点は2つあります。1つはmetrics-generatorがトレースから作ったメトリクスをPrometheusへremote writeする経路、もう1つはPrometheusのexemplarからトレースへ飛ぶ経路です。
Tempo 3.0で変わったアーキテクチャ
3.0のリリースノートは、この版を「新しい取り込み・書き込みアーキテクチャへの移行を完了し、2.xの非推奨コンポーネントを削除した」メジャーリリースと位置づけています(Tempo v3.0.0 リリースノート)。2.xの図で覚えた構成は、ここで読み替えが必要です。
ingesterを廃止したKafka経由の書き込み経路
2.xでは、distributorがgRPCでingesterへスパンを送り、ingesterがブロックを組んでオブジェクトストレージへ書き出し、compactorが後から統合していました。3.0のマイクロサービスモードでは、distributorがtrace_idでシャーディングしてKafkaへ書き込み、Kafkaが受領した時点でクライアントへ応答します。block-builderがKafkaから読んでParquetブロックを作り、オブジェクトストレージへ書き出します。compactorの役割はbackend-schedulerとbackend-workerに移りました。Kafkaが書き込みの耐久性を担うため、ingester時代の3重複製をやめ、複製数1(RF1)で動きます(Migrate from Tempo 2.x to 3.0)。ingester、scalable single binary(SSB)モード、compactorターゲットは削除済みです。
live-storeが担う直近データの検索
block-builderがブロックを書き出すまでの隙間を埋めるのがlive-storeです。Kafkaを独立に読んでトレースをメモリに保持し、通常は直近30分〜1時間のデータを受け持ちます。querierは直近分をlive-storeへ、それより古い分をオブジェクトストレージへ問い合わせ、query-frontendが結果をまとめます。モノリシックモードではKafkaもblock-builderも使わず、distributorがプロセス内でlive-storeへ直接渡し、live-storeがブロックを切って保存先へ書き出します。
vParquet5ブロック形式と保存先のオブジェクトストレージ
ブロックはApache Parquetの列指向形式で、3.1から新規ブロックの既定がvParquet5になりました(Tempo v3.1.0 リリースノート)。既存のvParquet4・vParquet3ブロックはデータ移行なしで読めます。vParquet3は非推奨で、書き込み形式に指定している場合はvParquet4以上へ変えます。vParquet2は2.10で削除済みです。保存先はAmazon S3、Google Cloud Storage、Azure Blob Storage、S3互換(MinIOなど)で、ローカルディスクは開発・検証用です。
Grafana Tempo 3.1をローカルで動かす手順(実測)
2026年10月18日に、macOS上でtempo 3.1.0(darwin/amd64の公式バイナリ)をモノリシックモードで起動した記録です。Dockerの通常のブリッジネットワークでホストから送信する場合は、OTLPのendpointを0.0.0.0:4317と0.0.0.0:4318に変更し、必要なポートをホストの127.0.0.1へ公開します。保存先のマウントと書き込み権限も設定してください。
モノリシックモードの最小設定ファイル
storageのパスだけを書き換えて起動すると、live-storeがmkdir /var/tempo: permission deniedで失敗し、依存するdistributorも止まりました。live-storeのWALとシャットダウンマーカー、backend-schedulerの作業ディレクトリは既定値が/var/tempo配下のままだからです。この3つも書き込める場所へ向けると、2秒で/readyが200を返しました。
stream_over_http_enabled: true
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "127.0.0.1:4317"
http:
endpoint: "127.0.0.1:4318"
live_store:
wal:
path: /tmp/tempo/data/live-store/traces
shutdown_marker_dir: /tmp/tempo/data/live-store/shutdown-marker
backend_scheduler:
local_work_path: /tmp/tempo/data/scheduler
storage:
trace:
backend: local
wal:
path: /tmp/tempo/data/wal
local:
path: /tmp/tempo/data/blocks
usage_report:
reporting_enabled: false
起動は./tempo -config.file=tempo.yamlです。/api/status/buildinfoは"version":"3.1.0"を返しました。HTTP APIは3200番、OTLPの受信はgRPCが4317番、HTTPが4318番です。本番ではstorageのbackendをs3などに替えます。
OTLP/HTTPによるテストトレースの送信と取得確認
計装済みのアプリが無くても、OTLPのJSONをcurlで送れば動作を確かめられます。次の1スパンの送信は200を返し、直後に/api/v2/traces/<trace_id>で取得できました。
NOW=$(date +%s)000000000; END=$(( $(date +%s) + 1 ))000000000
curl -X POST http://127.0.0.1:4318/v1/traces -H 'Content-Type: application/json' -d '{
"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"curl-demo"}}]},
"scopeSpans":[{"spans":[{"traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b174",
"name":"GET /hello","kind":2,"startTimeUnixNano":"'$NOW'","endTimeUnixNano":"'$END'","status":{"code":2}}]}]}]}'
一方、TraceQL検索に出たのは送信から15〜30秒後でした。3.0からquery_frontend.query_end_cutoffの既定が30秒になり、検索とメトリクスのクエリは直近30秒を範囲から外して不完全な結果を返さないようにしています。「送ったのに検索に出ない」ときは、まずtrace_id指定で取れるかを試すと、受信の失敗か検索の時間差かを切り分けられます。
続けて次のPythonスクリプトで、frontend→checkout→paymentの3サービスで構成したトレースを20件(60スパン)送りました。うち5件はpaymentがHTTP 500のエラー、4件は処理に2.3秒かかる遅延トレースです。live-storeが切り出したブロックはmeta.json上でformatがvParquet5、replicationFactorが1、totalObjectsが20で、data.parquetは28,991バイトでした。Tempoを再起動しても、同じ条件の検索で5件が返りました。
import json, os, time, urllib.request
now = time.time_ns()
for k in range(20):
tid, ids = os.urandom(16).hex(), [os.urandom(8).hex() for _ in range(3)]
ms = 2300 if k % 5 == 0 else 100 # 4件を2.3秒の遅延に
err = k % 4 == 0 # 5件をpaymentのHTTP 500に
t0 = now - (20 - k) * 10**9
def rs(i, svc, name, code=200, status=1):
span = {"traceId": tid, "spanId": ids[i], "name": name, "kind": 2,
"startTimeUnixNano": str(t0 + i * 10**7), "endTimeUnixNano": str(t0 + i * 10**7 + ms * 10**6),
"attributes": [{"key": "http.response.status_code", "value": {"intValue": code}}],
"status": {"code": status}}
if i: span["parentSpanId"] = ids[i - 1]
return {"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": svc}}]},
"scopeSpans": [{"spans": [span]}]}
body = {"resourceSpans": [rs(0, "frontend", "GET /checkout"), rs(1, "checkout", "POST /orders"),
rs(2, "payment", "charge", 500 if err else 200, 2 if err else 1)]}
urllib.request.urlopen(urllib.request.Request("http://127.0.0.1:4318/v1/traces",
data=json.dumps(body).encode(), headers={"Content-Type": "application/json"}))
TraceQLによるトレース検索とメトリクス集計
TraceQLは、スパンの属性とトレースの構造を条件にして検索する専用のクエリ言語です。Grafanaの探索画面でも書けますが、HTTP APIの/api/searchへ、URLエンコードしたクエリをq=で渡して検索できます。Grafanaと結果を比較するときは、検索時間範囲・テナント・件数上限も揃えてください。
属性と構造で絞り込むTraceQL検索
前章のスクリプトで送った20トレースに、query_end_cutoffの30秒を過ぎてから実行したクエリと件数です。スクリプトを別の空の環境で流し直しても、同じ件数が返りました。APIから引くときは、curlの-Gと--data-urlencodeでクエリをURLエンコードします。
curl -G -s http://127.0.0.1:3200/api/search \
--data-urlencode 'q={ duration > 2s }' \
--data-urlencode "start=$(( $(date +%s) - 600 ))" --data-urlencode "end=$(date +%s)"
# paymentでHTTP 500を返したスパンを含むトレース → 5件
{ resource.service.name = "payment" && span.http.response.status_code = 500 }
# 2秒を超えたスパンを含むトレース → 4件
{ duration > 2s }
# checkoutの子孫でエラーになったスパンを含むトレース → 5件
{ resource.service.name = "checkout" } >> { status = error }
resource.はサービス名など送信元の属性、span.はスパン自身の属性を指します。>>は「左の条件に合うスパンの子孫に、右の条件に合うスパンがある」という構造条件です。どのサービスの下流でエラーが起きたかを、trace_idを知らないまま症状から引けるのがTraceQLの強みです。公式のチューニング文書は、スコープを省いた属性はspanとresourceの両方を探すので遅くなるとして、常にスコープを付けるよう勧めています。なおdurationは1スパンの長さ(span:duration)で、トレース全体の長さはtrace:durationです。
TraceQLメトリクスによるエラースパン割合と分位の算出
TraceQLメトリクスは3.0で一般提供(GA)になり、/api/metrics/query_rangeでトレースから時系列を直接作れます。前章のスクリプトで送ったデータに、startを20分前、endを現在、step=60sで実行しました。{ status = error } | count_over_time()は5件、全スパンの{ } | count_over_time()は60件を返しました(20トレースが同じ1分に収まったため値は1点です)。3.1で入った算術を使うと、全スパンに占めるエラースパンの割合を1本のクエリで出せます。これはリクエスト単位のエラー率ではありません。
({ status = error } | count_over_time()) / ({ } | count_over_time())
結果は0.0833で、上の件数の比(5/60)と一致しました。{ } | quantile_over_time(duration, .9) by (resource.service.name)はサービスごとのp90を返しますが、各サービスのスパンが2.3秒4件・0.1秒16件という分布で、p90は0.7593秒でした。素直に数えた90パーセンタイルは2.3秒です。長さを乱数でばらつかせた最初の試行では、最長2.36秒の区間でp90が4.295秒と逆に大きく出ました。分位の値は近似なので、SLOの判定に使う前に既知の遅延を流して出方を確かめておきます。ヘッドサンプリングで間引いている場合は、3.1の実験的なヒントwith(extrapolate=true)を付けると、スパンに記録されたサンプリング確率から実際の件数を推定します。
metrics-generatorのサービスグラフとスパンメトリクス
常時ダッシュボードに出す指標は、クエリのたびに計算するTraceQLメトリクスより、metrics-generatorで前もって作る方が軽く済みます。overridesでprocessors: [service-graphs, span-metrics]を有効にすると、サービス間の呼び出し関係と遅延・エラー率を表すサービスグラフと、リクエスト数・エラー数・処理時間のスパンメトリクスを生成し、Prometheus互換の書き込み先へ送ります。3.1では、強くサンプリングしていてもサービス間の接続を検出できるtraces_service_graph_connection_infoが任意で追加されました。系列数の爆発には、3.0で入ったラベルごとの上限max_cardinality_per_labelで備えます。
MCPサーバーによるAIアシスタントからのトレース検索
Tempoはquery-frontendにMCP(Model Context Protocol)サーバーを内蔵しており、query_frontend.mcp_server.enabled: trueで有効になります(Tempo MCP server)。公開されるツールはtraceql-search、traceql-metrics-range、get-trace、3.1で加わった2トレースの差分traces-diffなど9種です。公式文書は、有効にするとトレースのデータがLLMの提供元へ渡りうると警告しています。属性に個人情報が入る環境では、送信前のマスキングか、3.0で入ったトレースの秘匿化(redaction)を先に整えてから有効にします。
2.xから3.0へ移行する手順と注意点
3.0から2.xへのその場でのダウングレードはできません。手順はモードで大きく違い、モノリシックモードは設定の変換とバイナリの差し替えで済みます。
tempo-cli migrate configによる設定変換と差分確認
3.0には2.xの設定を3.0用に書き換えるtempo-cli migrate configが付いています。ingesterとcompactorのブロックを持つ2.x形式の設定を3.1.0のtempo-cliで変換すると、次の警告を出して終了コード0で終わりました。
$ tempo-cli migrate config --mode=monolithic old-v2.yaml > new-v3.yaml
WARN: removed "ingester" section (not recognized by Tempo 3.0)
WARN: removed "compactor" section (not recognized by Tempo 3.0)
WARN: removed 'local-blocks' from metrics_generator.processors — block building is handled by the block-builder component in 3.0
注意すべきは、compactorの中に書いたblock_retention(保持期間)がブロックごと消えることです。720hを指定した設定を変換すると、出力に720hは1か所も残らず、既定の336h(14日)に戻る状態になりました。3.xでは保持期間をbackend-schedulerとbackend-workerのcompaction設定か、テナント別のoverrides.defaults.compaction.block_retentionで指定します。後者に720hを書いて起動すると、/status/configにblock_retention: 30dと反映されました。もう1点、変換コマンドは並行稼働用のcompaction_disabled: trueもoverrides.defaults.compactionへ挿入し、出力の先頭に「2.xの撤去後に外す」とコメントを残しました。モノリシックをその場で入れ替える場合は2.xが同時に動かないので、この行を外さないとcompactionも保持期間による削除も止まったままになります。変換後の差分は必ず目で確認します。
ほかに、フラットな旧形式のoverridesは既定で無効になり、検出されるとTempoが起動を拒否します。一時的にはenable_legacy_overrides: trueで回避できますが、tempo-cli migrate overrides-configで新形式へ移すのが本筋です。OpenCensusの受信も3.0で削除されました。TraceQLメトリクスはRF1のブロックしか読まないため、2.xでlocal-blocksを使っていなかった環境では、移行前のデータにメトリクスのクエリが効きません。
マイクロサービスモードの移行に必要なKafkaと並行稼働
マイクロサービスモードでは、Apache KafkaかRedpandaなどKafka互換の基盤を先に用意します。公式の移行ガイドは、パーティション数の目安を「ピーク時の取り込み1パーティションあたり約10MB/s」としています。移行は、まず3.0側のoverrides.defaults.compactionと各テナントのcompactionにcompaction_disabled: trueを書き、2.xと同じオブジェクトストレージに向けて並行稼働させます。動作を確かめたらトラフィックを切り替え、2.xのcompactorを停止・撤去してから、3.0側のcompaction_disabledを外します。並行期間は新旧環境のインフラ費が重なります。2.x撤去後に戻すには旧環境の再構築が必要で、3.xで書いたブロックを旧版で読めるかも形式ごとに確認します。3.1ではKafkaへの接続にSASL(SCRAM、OAUTHBEARER、AWS_MSK_IAM)とTLSが加わり、Amazon MSKにも直接つなげるようになりました。なお、2.x系も2.10.8(2026年8月13日)まで出ていますが、中身はGoや依存ライブラリの脆弱性修正で、ここで扱った3.xの機能は入っていません。
Grafana Tempoの導入判断:モード・費用・Jaegerとの比較
入れるかどうかと、どの形で入れるかを判断します。
モノリシックとマイクロサービスの選び分け
公式文書は、モノリシックモードを「導入初期、開発環境、独立したスケールより運用の簡単さを優先する低〜中程度のトレース量」向け、マイクロサービスモードを本番の推奨としています。モノリシックはすべての部品が同じリソースを分け合うため、検索の負荷が急増すると書き込みも遅くなり、量が増えるとlive-storeとquerierのメモリでOOMを起こしやすくなります。Kafkaを運用する体制がなく、単一障害点を許容できる検証・小規模用途なら、モノリシック1台で始められます。高可用性が必須なら、マネージドサービスも含めて構成を選んでください。移るきっかけは、検索の遅延が書き込みに引きずられ始めたときです。KubernetesならHelmのtempoチャートがモノリシック、tempo-distributedチャートがマイクロサービスに対応します。Kafkaもlive-storeも小さく始めたい規模でマイクロサービスを組むと、部品の調整だけが先に重くなります。
費用の内訳:OSS自前運用とGrafana Cloud Traces
OSS版はソフトウェアが無償で、費用はオブジェクトストレージの保管量、転送、Tempoを動かすコンピュート、マイクロサービスモードならKafkaにかかります。運用ごと任せる場合のGrafana Cloud Tracesの料金は次のとおりです(Grafana Cloud料金)。
| プラン | 基本料金 | トレース料金 | 保持 |
|---|---|---|---|
| Free | 0ドル | 月50GBの取り込みまで | 14日 |
| Pro | 月19ドル(50GB込み) | 処理0.05・書き込み0.40・保持0.10ドル/GB〜 | 30日 |
| Enterprise | 年2.5万ドル〜 | 個別見積もり | 個別 |
取り込み量が月50GBに収まる検証段階ならFreeで足ります。保持期間を30日より長くしたい場合は、Grafana Cloudの追加保持オプションとOSSの自前運用を比較します。データの配置先に制約がある場合は、EnterpriseのBring Your Own Cloudと自前運用について、要件を満たすか確認してください。
Tempoを採用すべき構成と見送るべき構成
採用が向くのは、Grafana・Prometheus・Lokiを既に使っていてトレースだけが欠けている構成と、全トレースを残してまれな障害を追いたい構成です。逆に、外部呼び出しの少ない単一アプリで、ログとメトリクスで原因まで十分たどれるなら、トレース基盤はまだ要りません。サービス境界とサンプリング方針を決めずに入れると、貯めるだけで誰も見ないデータが増えます。間引き方はトレースサンプリングのヘッドベースとテールベースの違いを参考に決めてください。Jaegerを含む他製品との比較はオブザーバビリティツール比較にまとめています。設計から運用の移管まで手が足りない場合は、システムの保守運用・内製化支援で伴走します。
Grafana Tempoの導入・運用に関するよくある質問
Grafana TempoとJaegerは何が違いますか?
どちらも分散トレーシングのバックエンドですが、保存先が違います。JaegerはElasticsearch・OpenSearch・Cassandraなど検索可能なストレージを選ぶ構成が基本で、Tempoはインデックスを持たずオブジェクトストレージだけに貯めます。Grafana・Loki・Prometheusと組み合わせるならTempo、Jaeger UIと既存のストレージ資産を活かすならJaegerが自然です。TempoはJaeger形式の受信にも対応しています。Jaeger v2の構成はJaegerとは?v2でOpenTelemetryを取り込んだ分散トレーシングの仕組みで解説しています。
Grafana Tempoは無料で使えますか?
OSS版はAGPLv3ライセンスで、ソフトウェアは無償です。費用はストレージとコンピュートにかかります。マネージドのGrafana Cloud Tracesは、Freeプランで月50GBの取り込みと14日の保持まで無料です。AGPLv3は、改変したTempoをネットワーク越しに利用者へ提供する場合にソースの開示義務が生じるライセンスなので、改変の有無、ネットワーク越しに利用する相手、複製物の配布の有無を法務と確認してください。第13条の条件は外部公開だけに限定されず、対象利用者への対応ソースの提供機会が求められます。
OpenTelemetryは必須ですか?
必須ではありません。TempoはOTLPのほか、Jaeger・Zipkinの形式も受信します(OpenCensusは3.0で削除)。新しく計装するなら、送信先をコードの書き換えなしで変えられるOpenTelemetryが無難です。計装とCollectorの基本はOpenTelemetryとは|LinuxでCollectorを動かしテレメトリを集める手順、Grafanaで3種のデータを束ねる構成はGrafana OpenTelemetry連携の構成が対応します。
Grafana Tempoの最新バージョンはいくつですか?
2026年10月時点の最新は3.1.0(2026年9月29日公開)です。3.0系は3.0.4(2026年10月16日)、2.x系は2.10.8(2026年8月13日)まで修正版が出ています。最新はGitHubのリリース一覧で確認してください。
送ったトレースがTempoで見つからないときは何を確認しますか?
まず、trace_id指定(/api/v2/traces/<id>)で取れるかを試します。取れるなら受信は成功しており、検索に出ない原因の一つは、既定で直近30秒を除くquery_end_cutoffによる時間差です。時間を置いても出ない場合は、検索時間範囲・TraceQL条件・検索結果の件数上限も確認します。取れないなら、送信先のポート(OTLPはgRPC 4317・HTTP 4318)と受信設定のendpointを確認します。もう1点、検索APIは既定でtrace_idの先頭の0を落として返します。実測でも08a343…で送ったトレースが8a343…と表示されました。32桁で揃えたいときはoverridesのleft_pad_trace_idsを有効にします。
関連記事
- OpenTelemetryとは|LinuxでCollectorを動かしテレメトリを集める手順とPrometheusとの違い
- Grafana Lokiとは?仕組み・Alloyでの導入手順とLogQLの書き方【v3.7】
- Jaegerとは?v2でOpenTelemetryを取り込んだ分散トレーシングの仕組みとX-Rayとの選び分けを解説【2026年版】
- トレースサンプリングとは?ヘッドベースとテールベースの仕組み・OpenTelemetry/X-Ray実装・サンプリング率の決め方を実装目線で解説【2026年版】
- Amazon Managed Grafanaとは?料金・対応バージョン・作成手順とGrafana Cloudとの違い