谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Spring Boot 4.0 の OpenTelemetry 対応がほぼ理想的になった件。

すっかりポイ活ブログになりつつある当ブログですが、本業はITエンジニアですので、久々に技術的な投稿をします。

Spring Boot 4.0 で OpenTelemetry 対応がようやく理想的な形になったという話です。*1

0. はじめに

監視・可視化はこの10数年くらいの間、特に分散サービス化が進むと共に重要度が上がり、オブザバビリティ(Observability)という概念で語られるようになりました。

そしてその標準化も進み、最近では OpenTelemetry に準拠した監視データを転送・収集することがデファクトスタンダードになってきました。

Spring Boot も 3.x の頃からその流れに乗り始め、昨年11月にリリースされた Spring Boot 4.0 ではメトリクス・トレース・ログの3種類をすべて OpenTelemetry で転送できるようになりました。

そこで今回は「Spring Boot 4.0 で OpenTelemetry をやってみた」的な内容でその概要をお伝えします。

このエントリーで紹介するサンプルアプリケーションはGitHubに置いてあります。 github.com

また Spring の公式 blog でも OpenTelemetry with Spring Boot が紹介されているので、こちらも参照してください。 spring.io

1. 概要

Spring Boot 4.0 で作ったアプリケーションのメトリクス、ログ、トレースを OTLP を使って Grafana の LGTM スタックに送り、Grafana で可視化するという流れです。

OpenTelemetry で Grafana スタックに監視情報を送る流れ

1-1. Webアプリケーション

WebアプリケーションはいわゆるECサイト的なマイクロサービスで、次のような構成になっています。

マイクロサービスの構成

Store アプリケーションを入口として様々なマイクロサービスにアクセスして必要な処理を行うという、マイクロというよりナノサービスみたいな粒度のアーキテクチャですが、分散トレーシングの可視化で見映えするようこんな構成にしています。

1-2. オブザバビリティスタック

監視・可視化には Grafana スタック、通称 LGTM スタックを利用します。LGTM スタックとは

  • Loki(ログの収集)
  • Grafana(可視化)
  • Tempo(トレースの収集)
  • Prometheus(メトリクスの収集)

の一部を抜き出して Looking Good To Me と掛けたものですが、やや強引なスタック名の付け方ですね。

オブザバビリティ界隈は Elasticsearch + Logstash + Kibana による ELK スタックに人気が集まって以来、他社も真似して◯◯スタックと名付ける文化となりました。これは豆知識です。

ちなみにメトリクス収集については分散アーキテクチャで動作する Mimir を Prometheus の代わりに利用することもでき、Mimir を使えば頭文字を取ってきちんと LGTM スタックとなります。豆知識2連続ですね。

1-3. 転送・収集方法

Spring Boot アプリケーションから LGTM スタックに監視データを送る際には OpenTelemetry の OTLP というプロトコルを利用します。

元々 Prometheus は pull 型アーキテクチャ、つまり監視対象のサービスにアクセスしてメトリクス情報を集める方式でしたし、Loki は Promtail や Loki4j などを利用してログを Loki に送る、Tempo は Zipkin や Jaeger 互換のAPIなどを通じてトレース情報を受け取るなど、それぞれ収集方法がバラバラでした。

しかし現在ではいずれも OpenTelemetry の OTLP で監視情報を受け取れるようになりました。

厳密に言えば、今回使う docker-otel-lgtm は内部で OpenTelemetry Collector が動作しており、これが OTLP で監視情報を受け取って Prometheus、Loki、Tempo に転送しているため、各ミドルウェアが直接 OTLP を受け取っているわけではないのですが、その辺りはまた別の機会に説明します。

2. オブザバビリティスタックの構築

まずは監視に用いるオブザバビリティスタックを構築します。

2-1. オススメの選択肢: docker-otel-lgtm

オブザバビリティスタックをローカル環境で動かしたい場合は、Grafana Labs が提供している docker-otel-lgtm がオススメです。同種のツールの中でトップレベルに高機能で、導入もとても手軽です。 grafana.com

次のコマンドで構築・起動できます。

docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm

起動後に http://localhost:3000 にアクセスすれば Grafana のダッシュボードが閲覧できます。また4317ポートは gRPC、4318ポートは HTTP で OTLP のデータを受け取るために利用されます。

デモや開発のために難儀しながら Docker 上に Prometheus、Loki、Tempo を構築してたのは何だったのか… というレベルの簡単さですね。

2-2. さらに手軽な選択肢: OTEL Viewer

より手軽にローカルで OpenTelemetry 互換のスタックを動かしたい場合は、OTEL Viewer というツールを利用できます。 github.com

OTEL Viewer は簡易的な OTLP 収集・可視化ツールです。Go で開発されていてシングルバイナリで動作します。

本格的に可視化したいというよりも、きちんと OTLP で情報を送れていることを確認したいだけの時や、ローカル開発時にとりあえず OTLP の監視情報を受け取るだけのサーバを立てたい時に便利です。

ダッシュボードのURLは http://localhost:8000 に変わりますが、OTLP用のgRPC/HTTPエンドポイントは LGTM スタックと同じなので、どちらを使うにせよ Spring Boot アプリケーション側の設定変更の必要はありません。

3. Spring Boot アプリケーションの作成

それでは監視対象となる Spring Boot アプリケーションの作成です。

3-1. OpenTelemetry Spring Boot Starterの利用

監視データの収集用に Spring Boot 4.0 と同時にリリースされた spring-boot-starter-opentelemetry を利用します。

これまで Spring Boot から監視データを収集するためには、Spring Cloud Sleuth や Micrometer、またそれぞれのミドルウェア向けの exporter などを個別に導入する必要がありましたが、Spring Boot 4.0 は spring-boot-starter-opentelemetry を一つ入れるだけで、メトリクス、ログ、トレースの3つを OTLP 経由で外部に送れるようになりました。

ただ、ログだけは諸事情により autoconfigure が用意されていないため、@making さんが公開している otel-logs-autoconfigure も合わせて利用します。

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-opentelemetry</artifactId>
        </dependency>
        <dependency>
            <groupId>am.ik.spring.opentelemetry</groupId>
            <artifactId>otel-logs-autoconfigure</artifactId>
        </dependency>

なぜログだけ autoconfigure が用意されていないのか、otel-logs-autoconfigure とは何者なのかについては @making さんのブログを参照してください。 ik.am

3-2. OpenTelemetry Spring Boot Starterの設定

続いて application.properties に監視データの送り先などを設定します。

management.opentelemetry.resource-attributes.service.instance.id=${HOSTNAME:local-dev}

management.otlp.metrics.export.url=http://localhost:4318/v1/metrics

management.tracing.sampling.probability=1.0
management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4318/v1/traces

management.opentelemetry.logging.export.otlp.endpoint=http://localhost:4318/v1/logs
management.opentelemetry.instrumentation.logback-appender.capture-experimental-attributes=true
management.opentelemetry.instrumentation.logback-appender.capture-key-value-pair-attributes=true

設定するのは

  • メトリクス、ログ、トレースの OTLP 用のエンドポイント = LGTM スタックの待ち受けアドレス
  • management.opentelemetry.resource-attributes.service.instance.id : インスタンスIDの設定
  • management.tracing.sampling.probability : トレースの何割を収集するかという設定。1.0が100%
  • management.opentelemetry.instrumentation.logback-appender.* : logback-appender の追加設定

です。

ログとトレースは management.opentelemetry.*.export.otlp.endpoint なのに、メトリクスだけ management.otlp.metrics.export.url になっている不一致感に少し可愛げがありますが、歴史的経緯もありますし、細かいところは気にしないでおきましょう。いずれ統一されるかもしれませんね。

インスタンスIDはマイクロサービスなどで同じサービスを複数のインスタンス(ホスト)で起動する際に識別するためのIDです。特に可視化する際に必要となるため、環境変数の HOSTNAME を指定し、デフォルトでは開発用の local-dev を指定しています。

logback-appender の追加設定は @making さんのブログを真似しただけですが、2つあるうちの1つめがスレッド名、スレッドIDをログに出力する設定、2つめがログの属性をKey/Valueペアとして出力する設定のようです。

いずれにせよ、たったこれだけの設定でメトリクス、ログ、トレースの3つを LGTM スタックに送ることができるようになります。

4. サンプルアプリケーションの実行と確認

お膳立てが整ったところで、実際にアプリケーションを動かして Grafana で監視データを眺めてみましょう。

ここでは GitHub に置いたサンプルアプリケーションを前提に説明します。 https://github.com/cero-t/spring-store-2026

4-1. サンプルアプリケーションの起動

サンプルアプリケーションに必要なスタックを順番に起動します。

① UI と LGTM スタックの起動

まず UI と LGTM スタックを docker-compose で起動します。

cd docker/lgtm
docker compose up -d

UI アプリケーションは個別に起動しても良いのですが、Java エンジニアの皆さんの環境に Node.js をインストールさせるのは心苦しいので Docker で起動できるようにしておきました。

② マイクロサービスの起動

続いて各サービスを起動します。起動方法の詳細は GitHub リポジトリの README を参照してください。

cd services
./mvnw -pl item/item-service -am spring-boot:run
./mvnw -pl stock/stock-service -am spring-boot:run
...

すべてのサービスが起動したらWebアプリ(http://localhost:5173)からカタログの閲覧、カートへの追加、注文などを何度か操作して、監視データを発生させておきます。

4-2. Grafana でメトリクス、ログ、トレースの確認

それでは収集した情報を Grafana(http://localhost:3000)で見てみましょう。

① メトリクスの確認

Grafana を開き、左メニューから Dashboards を選んで JVM Overview (OpenTelemetry) を開きます。ダッシュボードの反映までに少し時間が掛かるため、データが表示されない場合は数分ほど時間を置いてからリフレッシュしてください。

JVM Overview (OpenTelemetry) ダッシュボード

Grafana に最初から用意されている簡易的なダッシュボードなのでCPU使用率やGCや、ヒープ使用率、スレッド数など限られたメトリクスしか表示されていませんが、いずれにせよこれらは spring-boot-starter-opentelemetry を入れるだけで収集・送信しているものです。

もっと詳しい情報を表示することもできますが、それはまた別の機会に紹介します。

② ログの確認

Grafana の左メニューから Explore を選び、データソースとして Loki を選択します。

Label filters で service_instance_id を選択し、local-dev を指定すると、全サービスのログがまとめて表示されます。

Loki に集められたログの表示

ログをクリックすると追加のメタ情報が表示されます。各ログには trace_idspan_id などが自動的に付与されており、トレースとの紐付けも可能です。

③ トレースの確認

データソースを Tempo に切り替えて、Search タブを選択するとトレースの一覧が表示されます。

Tempo に集められたトレースの一覧

いずれかのトレースを選択すると、ウォーターフォールビューで各サービスへの呼び出しがどのような順序・タイミングで行われたかを視覚的に確認できます。

http post /order のトレース

たとえば注文処理のトレースの一番長い部分を見ると、store → order-service → messaging → payment-service / delivery-service という一連の流れが繋がっていることが分かります。複数のサービスをまたいだ処理の全体像が一目で分かるのは、分散トレーシングの醍醐味ですね。

そんなわけで、きちんとメトリクス、ログ、トレースが LGTM スタックに送られていることを確認できたと思います。

5. まとめにかえて

私が Spring Boot で分散トレーシングに手を出し始めたのは Spring Boot 1.x の頃で、当時は Spring Cloud Sleuth でトレース情報を Zipkin に送っていました。そこからメトリクスやログの可視化も試し始めて、Prometheus、Elasticsearch の ELK スタック、Tick スタック、Grafana、Datadog など様々なプロダクトに触れてきました。

その過程で常に悩まされてきたのが、プロダクトごとに異なる収集方法と、それに伴うライブラリや設定の複雑さです。メトリクスは Micrometer + Prometheus 用 exporter、トレースは Sleuth + Zipkin あるいは Brave、ログは Logback + Loki4j… と、収集する対象ごとに別のライブラリを導入して個別の設定を書く必要があり、かなりストレスフルでした。

それが Spring Boot 4.0 では spring-boot-starter-opentelemetryapplication.properties の数行だけになりました。送り先が LGTM スタックでも、Grafana Cloud でも、CloudWatch でも、OTLP に対応していればアプリケーション側の変更は不要です。

これまでのストレスがあったがゆえに、Spring Boot 4.0 の OpenTelemetry 対応には「ついにここまで来たか!」と感慨深いものがあります。

長い道のりでしたが、ようやく「starter を入れて、エンドポイントを設定するだけ」という理想的な形になりました。

今後はこのサンプルを使って、Grafana Cloud や Amazon CloudWatch など様々なオブザバビリティスタックとの連携も試そうと思います。そのあたりはまた別のエントリーで。

*1:ロギングの一部を除く