Grafana Tempo


2020年10月に[Grafana Labsから発表](https://grafana.com/blog/2020/10/27/announcing-grafana-tempo-a-massively-scalable-distributed-tracing-system/)された[Grafana Tempo](https://grafana.com/oss/tempo/)を試してみました。

## Grafana Tempoとは

Grafana Tempoは分散トレーシング用のバックエンドで以下の特徴があります。

1. 依存関係はオブジェクトストレージのみ
2. Grafana、Prometheus、Lokiと相性がよい
3. Jaeger、Zipkin、OpenTelemetryに対応

1点目の依存関係がオブジェクトストレージのみという点は、他のトレーシングバックエンドより導入・運用がしやすいというメリットがあります。Jaeger等の既存の分散トレーシングのバックエンドでは、トレース情報の検索のためにElasticsearchやCassandraを用意する必要があります。しかし、Tempoではログやメトリクス(e.g. レスポンスが遅い特定のクエリのログ等)を起点として、それに紐づいたトレースを参照するという使い方を想定しています。この使い方であればログやメトリクスにtraceIDを紐づけてトレースを辿れるようにしておくことでトレース全体の検索が不要になり、ElasticsearchやCassandraを使わないシンプルなバックエンドで十分ということになります。この設計方針はログ全体のインデックスを作成せずPrometheusのようなラベルで参照するLokiと似ています。
2点目の特徴は、上述したTempoの使い方にあるように、GrafanaのUIでログとトレースを紐づけて参照できるという点です。実は[Grafanaは他のバックエンドもサポートしている](https://grafana.com/docs/grafana/latest/datasources/)ので、現時点ではTempoだけの利点というわけではないのですが、Grafana Labsが開発しているということで今後Grafanaの機能が増えた時にTempoのデータソースが優先してサポートされるということは考えられます。
3点目は、Tempoはあくまでもバックエンドだけを提供していて、トレースを送信するアプリケーション側のInstrumentationについては既存のJaeger等のライブラリを使うことになります。

## アーキテクチャ

Tempoは以下のコンポーネントで構成されています。[Loki](https://grafana.com/docs/loki/latest/architecture/)とよく似た構成です。
データストアのコア部分以外ではOpenTelemetry CollectorやJeager Queryといった他のOSSが使用されているようです。
![tempo_arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100092/fa96b5a4-ddd8-02d0-5a73-a2eddcf787a5.png)
出典: https://grafana.com/docs/tempo/latest/architecture/architecture/


| コンポーネント | 説明 |
|:---------------|:-----|
| Distributor    | Jaeger、OpenTelemetry、Zipkin形式のスパンを受け取り、`traceID`を作成してIngesterにルーティングする。Receiver層には[OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector)を使用。 |
| Ingester       | トレースをblock、bloom、indexにまとめて、バックエンドに保存する。 |
| Query Frontend | クエリ用に検索領域をシャーディングして、クエリリクエストを各キューに入れる。 |
| Querier        | シャーディングされたクエリに紐づくトレースをIngesterとバックエンドストレージから取得する。 |
| Compactor      | バックエンドストレージのブロック数を削減する。 |
| Tempo Query    | トレースを可視化する。中身は[Jaeger Query](https://www.jaegertracing.io/docs/1.19/deployment/#query-service--ui) + Tempoと通信するgRPC Plugin | 

## ローカル環境で試す

デモ用に[docker-composeを使用した手順](https://github.com/grafana/tempo/blob/master/example/docker-compose/readme.md)が用意されているので、Tempoがどのようなものかローカル環境で簡単に試せます。
事前準備としてTempoのリポジトリをcloneしておきます。

```
$ git clone git@github.com:grafana/tempo.git
$ cd tempo
```

いくつかデモ用の設定ファイルが用意されていますが、Tempoの使用感がわかりやすい[Lokiのログに紐づいたトレースを参照する例](https://github.com/grafana/tempo/blob/master/example/docker-compose/readme.md#loki-derived-fields)を試してみます。
事前にLoki docker driverをインストールして、dockerコンテナのログがLokiに送信されるようにします。

```
$ docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
```

docker-composeでLoki、Tempoなどの一式を起動します。

```
$ docker-compose -f docker-compose.loki.yaml up -d
```

http://localhost:3000/explore でGrafanaのExplore画面にアクセスできるので、Lokiが選択されている状態で、クエリ入力フォームで`{container_name="docker-compose_loki_1"}`を入力するとLoki自体のログが参照できます。
この例では以下のようにLokiがTempoにトレースを送信する設定になっています。

```docker-compose.loki.yaml
...

  loki:
...
    environment:
      - JAEGER_AGENT_HOST=tempo
      - JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo
      - JAEGER_SAMPLER_TYPE=const
      - JAEGER_SAMPLER_PARAM=1
...
```

何度かクエリを実行するとクエリのログ出力されるので、適当なログをクリックしてParsed FieldsのtraceIDの横にある`Tempo`ボタンを押すと、以下のように画面がスプリットされてログに対応するトレースが表示されます。

![スクリーンショット 2021-02-05 17.12.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100092/9fe8fc98-6428-4318-e33c-07ab62ea0a09.png)
検証後は以下のコマンドでクリーンアップします。

```
$ docker-compose -f docker-compose.loki.yaml down -v
$ docker plugin disable loki:latest
$ docker plugin rm loki:latest
```

## Kubernetesにデプロイする

デモ用の[Helm](https://github.com/grafana/tempo/tree/master/example#helm)と[Jsonnet](https://github.com/grafana/tempo/tree/master/example#jsonnettanka)があり、どちらもシングルバイナリとマイクロサービスの2つのモードが用意されています。
マイクロサービスの方は上述のアーキテクチャで説明した各コンポーネントが別リソースで定義されているので、それぞれスケールできます。

### ローカルディスクを利用する

Tempoの依存関係はオブジェクトストレージのみということでしたが、実は以下のように設定でローカルディスクも指定できます。

```
    storage:
      trace:
        backend: local
        local:
          path: /var/tempo/traces
        wal:
          path: /var/tempo/wal
```

この設定を利用して、[Longhorn](https://github.com/longhorn/longhorn)などでマウントした永続ボリュームに保存すれば、外部のオブジェクトストレージなしでもトレースを永続化できます。[^1]
PrometheusやLokiも同じように永続化できるので、外部依存なしで手軽に永続化されたモニタリング環境を構築できます。

## まとめ

Grafana Tempoは、Lokiのように割り切ってユースケースを絞ることでシンプルな構成を実現し、他のシステムと差別化しています。
分散トレーシングの導入に必要なステップとして、

- アプリケーションのInstrumentation
- 送信先のバックエンドの構築

がありますが、Grafana Tempoを使用することで少なくとも2点目のハードルはかなり下がるのではないかと思いました。
まだメジャーバージョンは出ていない(2021年2月時点でv0.5.0)ので、今後破壊的変更も入ると思いますが、引き続きウォッチしていきたいと思います。

（2021/05追記） 2021年4月にTempoも含め、Grafana関連のOSSのライセンスがApache 2.0からAGPLv3に変更になることが発表されました。AGPLv3は利用しない方針の会社もあるかと思いますのでご注意ください。
https://grafana.com/blog/2021/04/20/grafana-loki-tempo-relicensing-to-agplv3/

[^1]: シングルバイナリモードでTempoのStatefulSetのレプリカ数を1にしてシングルトン構成にしています。マイクロサービスモードで利用する場合は構成上オブジェクトストレージが必須になると思います。