これは、なにをしたくて書いたもの?
OpenTelemetryについて、1度見ておきたいと思いまして。
OpenTelemetryの前身のひとつであるOpenTracingを使ったエントリーを過去に書いていたので、こちらをOpenTelemetryに置き換える形で
進めていくことにします。
Jaeger/OpenTracing API/JAX-RS/MySQLで、Distributed Tracing - CLOVER🍀
OpenTracingとOpenCensus
OpenTelemetryは、OpenTracingとOpenCensusが統合されたものです。概要は、InfoQの記事を見るのが良いと思います。
Merging OpenTracing and OpenCensus into a Single Distributed Tracing Framework
OpenTelemetryがメトリック仕様のロードマップを発表
OpenTracingはトレースAPIを提供し、OpenCensusはトレースAPIとメトリクスAPIを提供するものでした。
現在は両プロジェクトとも、OpenTelemetryを使うことを勧めています。
The OpenTracing project is deprecated. Please use OpenTelemetry, which provides backwards compatibility with OpenTracing.
OpenCensus and OpenTracing have merged to form OpenTelemetry, which serves as the next major version of OpenCensus and OpenTracing. OpenTelemetry will offer backwards compatibility with existing OpenCensus integrations, and we will continue to make security patches to existing OpenCensus libraries for two years.
OpenTelemetry
では、OpenTelemetryについて見ていきましょう。
Webサイトの説明を見ると、OpenTelemetryとははツール、API、SDKのコレクションであり、テレメトリーデータ(メトリクス、ログ、トレース)を
計測、生成、収集およびエクスポートして、ソフトウェアのパフォーマンスと動作の分析に役立てるものだということが書かれています。
OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior.
ドキュメントはこちら。
まずは、ざっくりOpenTelemetryの概要を見ていきます。
コンセプトからたどっていきましょう。
What is OpenTelemetry? | OpenTelemetry
OpenTelemetryは、以下のコンポーネントで構成されているそうです。
各コンポーネント(というか仕様)は、以下の要素で定義されています。
- API … トレース、メトリクス、ログデータを生成、関連付けるためのデータ型とオペレーションを定義する
- SDK … APIの言語固有の実装要件を定義する
- データ(プロトコル) … OpenTelemetryバックエンドがサポートできるOLTP(OpenTelemetry Line Protocol)を定義する
各コンポーネントのステータスは、以下にまとめられています。
コレクター、言語SDK、自動instrumentationについてもう少し掘り下げられているので、見てみましょう。
- コレクター
- テレメトリーデータを受信、処理およびエクスポートできるプロキシ
- 複数の形式(OLTP、Jaeger、Prometheus、その他商用、独自のツールなど)でのテレメトリーデータの受信と、ひとつ以上のバックエンドへのデータの送信をサポートする
- エクスポートされる前のデータの処理やフィルタリングも可能
- 詳細は Data Collection | OpenTelemetry へ
- 言語SDK
- OpenTelemetry APIを使い、テレメトリーデータを生成してバックエンドにエクスポートする
- instrumentationはアプリケーションに手動で組み込むことも、一般的なライブラリやフレームワークに対しては自動で組み込むことも可能
- 詳細は Instrumenting | OpenTelemetry へ
- 自動instrumentation
それぞれの「詳細」ページも、少し見ていきましょうか。
データソース
データソースに関するページは、こちら。
データソースは、以下のようなものが定義されています。
- トレース
- 単一のリクエストを追跡する単位
- トレース内は、「スパン」という単位で構成され、トレースはスパンのツリーとして表現される
- OpenTelemetryではスパンの作成や管理に使う、
tracer
インターフェースを提供する - トレースの仕様は Specification / Tracing Signal を参照
- メトリクス
- サービスの実行中にキャプチャされた測定値
- 測定値自体だけではなく、キャプチャされた時間と関連するメタデータで構成される(メトリクスイベント)
- counter、measure、observerの3つの計測方法を定義している
- メトリクスの仕様は Specification / Metric Signal を参照
- ログ
- メタデータを使用した、構造化(こちらが推奨)または非構造化のタイムスタンプ付きテキストレコード
- スパンに添付することが可能
- OpenTelemetryでは、分散トレーシングやメトリクスの一部ではないデータを、すべてログとして扱う
- ログの仕様は Specification / Log Signal を参照
- Baggage
- 名前と値のペアのこと
- 同じトランザクションにおいて、前のサービスにより提供される属性を使用して、イベントに対してインデックス付けする目的としている
- Baggageの値はメトリクスの追加のディメンションや、ログやトレースの追加のコンテキストとして利用される
- Baggageの仕様は Specification / Baggage Signal を参照
Instrumenting
Instrumentingには、主にSDKに関する話が書かれています。
提供形態は言語ごとに差があるようですが、たとえばJavaの場合は以下になります。
- コア … アプリケーションに手動で組み込むためのAPIおよびSDK
- instrumentation … コア機能に加えて、様々なライブラリやフレームワークへ自動でinstrumentationを行う機能
- contrib … JMXメトリクス収集など、オプションのコンポーネント
あとは、instrumentationを自動または手動で行うための手順のイメージが書かれています。
自動instrumentationについてはさらに別ページで解説があり、APIおよびSDKを使ったライブラリの作成方法(APIの使い方)が書かれている
感じですね。
Instrumenting libraries | OpenTelemetry
データ収集
最後に、データ収集について
Data Collection | OpenTelemetry
データ収集にはOpenTelemetry Collectorという、テレメトリーデータを受信、処理およびエクスポートする方法を提供するものです。
OpenTelemetry Collectorには、以下の2つの実行形態があります。
- エージェント … アプリケーションとともに、またはアプリケーションと同じホスト(サイドカーやDaemon Set等)で実行されるインスタンス
- ゲートウェイ … スタンドアロンやクラスター、データセンター、リージョンなどで実行される、ひとつ以上のインスタンス
また、OpenTelemetry Collectorは以下のコンポーネントで構成されます。
- レシーバー … データをコレクターに取り込む。これは、PushまたはPullで行うことができる
- プロセッサー … 受信したデータに処理を行う
- エクスポーター … 受信したデータを送信する。これは、PushまたはPullで行うことができる
用語集
いろいろ言葉が出てきましたが…用語集も見るとよいでしょう。
と、説明ばかりが続いたので、そろそろ本題に戻ってOpenTelemetryを使ってみましょう。
OpenTelemetry Java imstrumentation
今回はJavaを使うので、Java用のinstrumentationのドキュメントを見てみます。
import
するBOMについても書いてあります。
※自動instrumentationのみを使用する場合は、特にBOMやAPIへの依存関係は必要ありません
Javaの自動instrumentationについてのドキュメントは、こちら。
Automatic Instrumentation | OpenTelemetry
自動instrumentationで、サポートされているライブラリやフレームワーク等のリストは、こちら。
自動instrumentationが適用できない場合は、手動での適用になります。
Manual Instrumentation | OpenTelemetry
今回は、JAX-RSとJDBCを選択して自動instrumentationを適用します。
お題と環境
最初に書いたとおり、こちらのエントリーをOpenTelemetryに置き換える形で書いていきます。
Jaeger/OpenTracing API/JAX-RS/MySQLで、Distributed Tracing - CLOVER🍀
今回扱うのは、OpenTelemetryの"トレーシング"のみです。メトリクスなども含めようかと思いましたが、トレーシングだけでだいぶ
苦労したのでここで区切ります…。
構成としては、2つのAPIサーバーを用意して間をHTTPでつなぎます。また、背後のAPIサーバーの後ろにはMySQLを配置してアクセスします。
api-frontend[JAX-RS Server - JAX-RS Client] → api-backend[JAX-RS Server - Doma 2] → MySQL
各APIサーバーにはOpenTelemetry Collectorのエージェントを組み込み、自動instrumentationを使用してJAX-RSのクライアントおよびサーバー、
JDBCをトレーシングします。
一方でテレメトリーデータは、以下のように流れていくようにします。OpenTelemetryのエージェントがOpenTelemetry Collector Gatewayに
データを送り、さらにJaegerにエクスポートする(JaegerがOpenTelemetry Collector Gatewayのバックエンドとなる)形ですね。
OpenTeletry Collector Agent[api-frontendおよびapi-backend] → OpenTelemetry Collector Gateway → Jaeger
OpenTelemetry Collector GatewayとJaegerの位置関係が最初よくわからなくなっていたのですが、こちらの図を眺めつつコレクターの説明を
見返すとなんとかなりました。
JAX-RSの実装にはRESTEasy(+Vert.x)を使用します。
また、環境についてはこちら。
$ java --version openjdk 17.0.1 2021-10-19 OpenJDK Runtime Environment (build 17.0.1+12-Ubuntu-120.04) OpenJDK 64-Bit Server VM (build 17.0.1+12-Ubuntu-120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.1, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-99-generic", arch: "amd64", family: "unix"
$ mysql --version mysql Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)
OpenTelemetry Collector Gatewayは、こちらから取得します。
GitHub - open-telemetry/opentelemetry-collector-releases: OpenTelemetry Collector Official Releases
$ curl -OL https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.43.0/otelcol_0.43.0_linux_amd64.tar.gz $ tar xf otelcol_0.43.0_linux_amd64.tar.gz $ ./otelcol --version otelcol version 0.43.0
Jaeger。
$ curl -OL https://github.com/jaegertracing/jaeger/releases/download/v1.31.0/jaeger-1.31.0-linux-amd64.tar.gz $ tar xf jaeger-1.31.0-linux-amd64.tar.gz $ cd jaeger-1.31.0-linux-amd64 $ ./jaeger-all-in-one version 2022/02/09 15:08:48 maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined {"gitCommit":"845d73de7250b7ee703c35853412ca8dd53f8c2d","gitVersion":"v1.31.0","buildDate":"2022-02-04T20:49:21Z"}
Jaegerは、jaeger-all-in-one
で起動しておきます。
$ ./jaeger-all-in-one
各サーバーのIPアドレスは、以下のようになっているものとします。
- api-frontend … 172.17.0.2
- api-backend … 172.17.0.3
- MySQL … 172.17.0.4
- OpenTelemetry Collector Gateway … 172.17.0.5
- Jaeger … 172.17.0.6
アプリケーションを作成する
では、アプリケーションを作成していきます。
テーブル定義
まずは、MySQLに定義するテーブルから。お題は書籍とします。
create table book ( isbn varchar(14), title varchar(255), price int, primary key(isbn) );
api-backend
アプリケーション側。api-backendから。
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-bom</artifactId> <version>5.0.2.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-vertx</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>4.1.8</version> </dependency> <dependency> <groupId>org.seasar.doma</groupId> <artifactId>doma-core</artifactId> <version>2.51.0</version> </dependency> <dependency> <groupId>org.seasar.doma</groupId> <artifactId>doma-processor</artifactId> <version>2.51.0</version> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.3</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
RESTEasy+Vert.x Container、MySQLへのアクセスにはDoma 2を使用します。Spring BootのMaven Pluginは、Uber JARの作成に
利用しているだけです。
こちらを見ているとRESTEasyは5.0.xにしておいた方が良さそうだったので、5.0.2.Finalを選びました(現時点での最新版は6.0.0.Final)。
ソースコードは、さらっと書きます。
Doma 2のEntityクラス。
src/main/java/org/littlewings/opentelemetry/backend/Book.java
package org.littlewings.opentelemetry.backend; import org.seasar.doma.Column; import org.seasar.doma.Entity; import org.seasar.doma.Id; @Entity public class Book { @Id String isbn; @Column String title; @Column Integer price; // getter/setterは省略 }
Doma 2のDaoインターフェース。
src/main/java/org/littlewings/opentelemetry/backend/BookDao.java
package org.littlewings.opentelemetry.backend; import java.util.List; import org.seasar.doma.Dao; import org.seasar.doma.Insert; import org.seasar.doma.Select; import org.seasar.doma.Sql; @Dao public interface BookDao { @Insert public int insert(Book book); @Sql("select /*%expand*/* from book") @Select public List<Book> findAll(); @Sql("select /*%expand*/* from book where isbn = /* isbn */'1'") @Select public Book findByIsbn(String isbn); }
Doma 2のConfigクラス。
src/main/java/org/littlewings/opentelemetry/backend/DomaConfig.java
package org.littlewings.opentelemetry.backend; import javax.sql.DataSource; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.Naming; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.MysqlDialect; import org.seasar.doma.jdbc.tx.LocalTransactionDataSource; import org.seasar.doma.jdbc.tx.LocalTransactionManager; import org.seasar.doma.jdbc.tx.TransactionManager; public class DomaConfig implements Config { private static final DomaConfig CONFIG = new DomaConfig(); Dialect dialect; LocalTransactionDataSource dataSource; TransactionManager transactionManager; public static DomaConfig singleton() { return CONFIG; } private DomaConfig() { dialect = new MysqlDialect(); dataSource = new LocalTransactionDataSource( "jdbc:mysql://172.17.0.4:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin", "kazuhira", "password" ); transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger())); } @Override public DataSource getDataSource() { return dataSource; } @Override public Dialect getDialect() { return dialect; } @Override public TransactionManager getTransactionManager() { return transactionManager; } @Override public Naming getNaming() { return Naming.SNAKE_LOWER_CASE; } }
JAX-RSリソースクラス。リクエストやレスポンスには、Entityクラスをそのまま使います。
src/main/java/org/littlewings/opentelemetry/backend/BookResource.java
package org.littlewings.opentelemetry.backend; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.seasar.doma.jdbc.tx.TransactionManager; @Path("book") public class BookResource { TransactionManager tm = DomaConfig.singleton().getTransactionManager(); BookDao bookDao = new BookDaoImpl(DomaConfig.singleton()); @GET @Produces(MediaType.APPLICATION_JSON) public List<Book> findAll() { return tm.required(() -> bookDao.findAll()); } @GET @Path("{isbn}") @Produces(MediaType.APPLICATION_JSON) public Book findByIsbn(@PathParam("isbn") String isbn) { return tm.required(() -> bookDao.findByIsbn(isbn)); } @PUT @Path("{isbn}") @Consumes(MediaType.APPLICATION_JSON) public void register(@PathParam("isbn") String isbn, Book book) { tm.required(() -> bookDao.insert(book)); } }
JAX-RS Applicationのサブクラス。
src/main/java/org/littlewings/opentelemetry/backend/JaxrsActivator.java
package org.littlewings.opentelemetry.backend; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { @Override public Set<Object> getSingletons() { return Set.of(new BookResource()); } }
mainクラス。
src/main/java/org/littlewings/opentelemetry/backend/App.java
package org.littlewings.opentelemetry.backend; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServer; import org.jboss.logging.Logger; import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler; import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment; import org.jboss.resteasy.spi.ResteasyDeployment; public class App { public static void main(String... args) { Logger logger = Logger.getLogger(App.class); Vertx vertx = Vertx.vertx(); HttpServer server = vertx.createHttpServer(); ResteasyDeployment deployment = new VertxResteasyDeployment(); deployment.setApplication(new JaxrsActivator()); deployment.start(); server.requestHandler(new VertxRequestHandler(vertx, deployment)); server.listen(9080); logger.infof("start server."); } }
api-backendは、9080ポートでリッスンすることにしました。
ここまで、特にOpenTelemetryに関するキーワードは登場しません。あくまで、JAX-RSとJDBCを使ったアプリケーションです。
パッケージング。
$ mvn package
ここで、通常なら以下のようにしてアプリケーションを起動するわけですが。
$ java -jar target/api-backend-0.0.1-SNAPSHOT.jar
今回はOpenTelemetry Collector Agentとしての設定を行います。
こちらのドキュメントに沿って、OpenTelemetry Collector AgentのJARファイルを取得します。
Automatic Instrumentation | OpenTelemetry
$ curl -OL https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.10.1/opentelemetry-javaagent.jar
こちらのJARファイルを、-javaagent:path/to/opentelemetry-javaagent.jar
という形で実行時に指定します。
エージェントの設定は、環境変数、システムプロパティ、設定ファイルの選択肢から行えるのですが、今回は設定ファイルで行うことに
します。
エージェントで設定できる項目は、こちらを参照します。
設定は、こんな感じにしました。最低限の設定ですね。
otel-agent.properties
otel.service.name=api-backend otel.traces.exporter=otlp otel.exporter.otlp.endpoint=http://172.17.0.5:4317
otel.service.name
は論理的なサービス名、otel.traces.exporter
はトレーシングで使用するエクスポーター、otel.exporter.otlp.endpoint
は
OTLP(OpenTelemetry Line Protorol)のエンドポイントを指定します。
OLTPのエンドポイントは、OpenTelemetry Collector Gatewayを指します。
otel.traces.exporter
にotlp
を指定しているのは、OpenTelemetry Collector Gatewayの設定を見る必要があります。
OpenTelemetry Collector Agentとその設定ファイルを組み込んでの起動方法は、以下のようになります。
$ java -javaagent:opentelemetry-javaagent.jar \ -Dotel.javaagent.configuration-file=otel-agent.properties \ -jar target/api-backend-0.0.1-SNAPSHOT.jar
api-frontend
最後のアプリケーションは、api-frontendです。
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-bom</artifactId> <version>5.0.2.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-vertx</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client-vertx</artifactId> </dependency> <!-- vertx-core 4.1.0がresteasy-client-vertxの推移的依存関係に含まれる --> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>4.1.8</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.3</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
今度は、JAX-RSのクライアントも入ります。vertx-core
はresteasy-client-vertx
から推移的に依存関係に入るのですが、今回は
api-backendとバージョンを合わせるために明示しました。resteasy-vertx
の方は、vertx-core
はprovided
になっているんですよね。
ソースコードは、こちらもさらっと。
リクエストやレスポンスで使うクラス。
src/main/java/org/littlewings/opentelemetry/frontend/Book.java
package org.littlewings.opentelemetry.frontend; public class Book { String isbn; String title; Integer price; // getter/setterは省略 }
JAX-RSリソースクラス。api-backendを呼び出すJAX-RSクライアントも含みます。
src/main/java/org/littlewings/opentelemetry/frontend/BookResource.java
package org.littlewings.opentelemetry.frontend; import java.util.List; import java.util.Optional; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.engines.vertx.VertxClientHttpEngine; @Path("book") public class BookResource { Client client; public BookResource() { VertxClientHttpEngine engine = new VertxClientHttpEngine(); client = ((ResteasyClientBuilder) ClientBuilder.newBuilder()).httpEngine(engine).build(); } @GET @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public List<Book> findAll() { Response response = null; try { response = client .target("http://172.17.0.3:9080/book") .request() .get(); return response.readEntity(List.class); } finally { Optional.ofNullable(response).ifPresent(Response::close); } } @GET @Path("{isbn}") @Produces(MediaType.APPLICATION_JSON) public Book findByIsbn(@PathParam("isbn") String isbn) { Response response = null; try { response = client .target(UriBuilder.fromUri("http://172.17.0.3:9080/book/{isbn}").build(isbn)) .request() .get(); return response.readEntity(Book.class); } finally { Optional.ofNullable(response).ifPresent(Response::close); } } @PUT @Path("{isbn}") @Consumes(MediaType.APPLICATION_JSON) public void register(@PathParam("isbn") String isbn, Book book) { Response response = null; try { response = client .target(UriBuilder.fromUri("http://172.17.0.3:9080/book/{isbn}").build(isbn)) .request() .put(Entity.entity(book, MediaType.APPLICATION_JSON_TYPE)); } finally { Optional.ofNullable(response).ifPresent(Response::close); } } }
JAX-RS Applicationのサブクラス。
src/main/java/org/littlewings/opentelemetry/frontend/JaxrsActivator.java
package org.littlewings.opentelemetry.frontend; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { @Override public Set<Object> getSingletons() { return Set.of(new BookResource()); } }
mainクラス。
src/main/java/org/littlewings/opentelemetry/frontend/App.java
package org.littlewings.opentelemetry.frontend; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServer; import org.jboss.logging.Logger; import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler; import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment; import org.jboss.resteasy.spi.ResteasyDeployment; public class App { public static void main(String... args) { Logger logger = Logger.getLogger(App.class); Vertx vertx = Vertx.vertx(); HttpServer server = vertx.createHttpServer(); ResteasyDeployment deployment = new VertxResteasyDeployment(); deployment.setApplication(new JaxrsActivator()); deployment.start(); server.requestHandler(new VertxRequestHandler(vertx, deployment)); server.listen(8080); logger.infof("start server."); } }
api-frontendは、8080ポートでリッスンすることにします。
OpenTelemetry Collector Agentの設定ファイルは、こちら。
otel-agent.properties
otel.service.name=api-frontend otel.traces.exporter=otlp otel.exporter.otlp.endpoint=http://172.17.0.5:4317
api-backendの時とは、otel.service.name
が違うだけです。
パッケージングして
$ mvn package
OpenTelemetry Collector Agentを組み込んで起動。
$ java -javaagent:opentelemetry-javaagent.jar \ -Dotel.javaagent.configuration-file=otel-agent.properties \ -jar target/api-frontend-0.0.1-SNAPSHOT.jar
ここまでで、アプリケーション側の準備は完了です。
Jaeger
Jaegerは、単純に起動しているわけです。
$ ./jaeger-all-in-one
OpenTelemetry Collector Gatewayを設定する
最後は、OpenTelemetry Collector Gatewayです。
GitHub - open-telemetry/opentelemetry-collector-releases: OpenTelemetry Collector Official Releases
ダウンロードは、こんな感じで行っていました。
$ curl -OL https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.43.0/otelcol_0.43.0_linux_amd64.tar.gz $ tar xf otelcol_0.43.0_linux_amd64.tar.gz $ ./otelcol --version otelcol version 0.43.0
OpenTelemetry Collector Gatewayには、設定が必要になります。
オフィシャルなDockerイメージに含まれているデフォルトの設定ファイルは、以下のようです。
https://github.com/open-telemetry/opentelemetry-collector-releases/blob/v0.43.0/configs/otelcol.yaml
ですが、この設定を見てもよくわからないので…。
OpenTelemetry Collector Gatewayの設定方法は、こちらを参照します。
設定ファイルはYAMLで作成し、まずは以下の要素が基本になります。
- receivers … OpenTelemetry Collector Gatewayがデータを取り込む方法(pullおよびpush)をレシーバーを定義する
- 使用可能なReceiversは こちら
- processors … 取り込んだデータを受信してから、エクスポートするまでにデータを処理するプロセッサーを定義する
- exporters … 1つ以上のバックエンドに、データを送信する(pushまたはpull)エクスポーターを定義する
- 使用可能な Exporters はこちら
また、extensionsという要素もあり、テレメトリーデータを扱わないタスクで使用できます。たとえば、ヘルスチェックやサービスディスカバリー
などです。
これらの要素(receivers、processors、exporters、extensions)は定義しただけでは有効にならず、servicesという要素で有効にする必要が
あります。
otel-collector-config.yaml
receivers: otlp: protocols: grpc: http: # jaeger: # protocols: # grpc: # thrift_binary: # thrift_compact: # thrift_http: processors: batch: exporters: logging: loglevel: debug jaeger: endpoint: 172.17.0.6:14250 tls: insecure: true extensions: health_check: pprof: zpages: memory_ballast: size_mib: 512 service: extensions: [health_check, pprof, zpages,memory_ballast] pipelines: traces: receivers: [otlp] processors: [batch] exporters: [logging,jaeger] # metrics: # receivers: [otlp] # processors: [batch] # exporters: [logging]
使っていない部分はコメントアウトしていますが、こんな感じの設定です。
- receivers
- otlp … OTLP(OpenTelemetry Line Protocol)でテレメトリーデータを受け取る
- processors
- batch … データをバッチ処理し、データの送信に必要な接続回数を減らす
- exporters
- logging … データをコンソールにログ出力する(デバッグ用途)
- jaeger … データをJaegerに送信する
- extensions
- とりあえず、ドキュメントに記載のあるextensionを並べておきました
- services
今回はコメントアウトしていますが、レシーバーとしてJaegerのプロトコルで受け付けることもできます。
receivers: otlp: protocols: grpc: http: # jaeger: # protocols: # grpc: # thrift_binary: # thrift_compact: # thrift_http:
有効にするかどうかは、servicesでのパイプライン定義次第です。
ここで、OpenTelemetry Collector Agentの設定に立ち戻ると、トレース用のテレメトリーデータの送信先にOLTPを選択していることが繋がります。
otel-agent.properties
otel.service.name=api-backend otel.traces.exporter=otlp otel.exporter.otlp.endpoint=http://172.17.0.5:4317
あとは、設定ファイルを使用してOpenTelemetry Collector Gatewayを起動します。
$ ./otelcol --config otel-collector-config.yaml
動作確認
では、動作確認してみます。
api-frontendに向けて、データを登録してみます。
$ curl -XPUT -H 'Content-Type: application/json' \ 172.17.0.2:8080/book/978-4774182179 \ -d '{"isbn": "978-4774182179", "title": "[改訂新版]Spring入門 ――Javaフレームワーク ・より良い設計とアーキテクチャ", "price": 4104}' $ curl -XPUT -H 'Content-Type: application/json' \ 172.17.0.2:8080/book/978-4798142470 \ -d '{"isbn": "978-4798142470", "title": "Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発", "price": 4320}' $ curl -XPUT -H 'Content-Type: application/json' \ 172.17.0.2:8080/book/978-4777519699 \ -d '{"isbn": "978-4777519699", "title": "はじめてのSpring Boot―スプリング・フレームワークで簡単Javaアプリ開発", "price": 2700}'
この時、OpenTelemetry Collector Gatewayには、exportersで設定したlogging exporterにより標準出力にログが書き出されます。
2022-02-10T15:31:59.919Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 3} 2022-02-10T15:31:59.919Z DEBUG loggingexporter/logging_exporter.go:49 ResourceSpans #0 Resource SchemaURL: https://opentelemetry.io/schemas/1.8.0 Resource labels: -> container.id: STRING(1b081521d1fb7ec1973065518fd8f58cb89a7f547c258305dd33474bd936c15d) -> host.arch: STRING(amd64) -> host.name: STRING(api-backend) -> os.description: STRING(Linux 5.4.0-99-generic) -> os.type: STRING(linux) -> process.command_line: STRING(/opt/java/openjdk:bin:java -javaagent:opentelemetry-javaagent.jar -Dotel.javaagent.configuration-file=otel-agent.properties) -> process.executable.path: STRING(/opt/java/openjdk:bin:java) -> process.pid: INT(10) -> process.runtime.description: STRING(Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.2+8) -> process.runtime.name: STRING(OpenJDK Runtime Environment) -> process.runtime.version: STRING(17.0.2+8) -> service.name: STRING(api-backend) -> telemetry.auto.version: STRING(1.10.1) -> telemetry.sdk.language: STRING(java) -> telemetry.sdk.name: STRING(opentelemetry) -> telemetry.sdk.version: STRING(1.10.1) InstrumentationLibrarySpans #0 InstrumentationLibraryMetrics SchemaURL: InstrumentationLibrary io.opentelemetry.jaxrs-2.0-common 1.10.1 Span #0 Trace ID : 7764aa073ebbd4015ebc1955cff85959 Parent ID : 45d4f51a90086df5 ID : b5a29f5f12419436 Name : BookResource.register Kind : SPAN_KIND_INTERNAL Start time : 2022-02-10 15:31:58.899855 +0000 UTC End time : 2022-02-10 15:31:58.9657288 +0000 UTC Status code : STATUS_CODE_UNSET Status message : Attributes: -> thread.name: STRING(vert.x-eventloop-thread-0) -> thread.id: INT(22) -> code.function: STRING(register) -> code.namespace: STRING(org.littlewings.opentelemetry.backend.BookResource) InstrumentationLibrarySpans #1 InstrumentationLibraryMetrics SchemaURL: InstrumentationLibrary io.opentelemetry.netty-4.1 1.10.1 Span #0 Trace ID : 7764aa073ebbd4015ebc1955cff85959 Parent ID : 738bf23b4280535f ID : 45d4f51a90086df5 Name : /book/{isbn} Kind : SPAN_KIND_SERVER Start time : 2022-02-10 15:31:58.898511038 +0000 UTC End time : 2022-02-10 15:31:58.966471625 +0000 UTC Status code : STATUS_CODE_UNSET Status message : Attributes: -> net.peer.ip: STRING(172.17.0.2) -> thread.name: STRING(vert.x-eventloop-thread-0) -> http.method: STRING(PUT) -> thread.id: INT(22) -> http.scheme: STRING(http) -> http.host: STRING(172.17.0.3:9080) -> http.target: STRING(/book/978-4774182179) -> http.user_agent: STRING(Vertx) -> http.flavor: STRING(1.1) -> net.peer.port: INT(55674) -> http.status_code: INT(204) InstrumentationLibrarySpans #2 InstrumentationLibraryMetrics SchemaURL: InstrumentationLibrary io.opentelemetry.jdbc 1.10.1 Span #0 Trace ID : 7764aa073ebbd4015ebc1955cff85959 Parent ID : b5a29f5f12419436 ID : 8c10a2ac662effd8 Name : INSERT practice.book Kind : SPAN_KIND_CLIENT Start time : 2022-02-10 15:31:58.933422769 +0000 UTC End time : 2022-02-10 15:31:58.937315169 +0000 UTC Status code : STATUS_CODE_UNSET Status message : Attributes: -> thread.name: STRING(vert.x-eventloop-thread-0) -> thread.id: INT(22) -> db.name: STRING(practice) -> db.sql.table: STRING(book) -> db.operation: STRING(INSERT) -> db.statement: STRING(insert into book (isbn, title, price) values (?, ?, ?)) -> db.system: STRING(mysql) -> net.peer.port: INT(3306) -> db.connection_string: STRING(mysql://172.17.0.4:3306) -> net.peer.name: STRING(172.17.0.4) -> db.user: STRING(kazuhira) 2022-02-10T15:32:00.097Z info jaegerexporter@v0.43.0/exporter.go:186 State of the connection with the Jaeger Collector backend {"kind": "exporter", "name": "jaeger", "state": "READY"}
データの確認。
$ curl 172.17.0.2:8080/book [{"isbn":"978-4774182179","title":"[改訂新版]Spring入門 ――Javaフレームワーク ・より良い設計とアーキテクチャ","price":4104},{"isbn":"978-4798142470","title":"Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発","price":4320}] $ curl 172.17.0.2:8080/book/978-4774182179 {"isbn":"978-4774182179","title":"[改訂新版]Spring入門 ――Javaフレームワーク ・より良い設計とアーキテクチャ","price":4104} $ curl 172.17.0.2:8080/book/978-4798142470 {"isbn":"978-4798142470","title":"Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発","price":4320}
では、Jaegerに送信されたデータを見てみましょう。
http://[Jaegerが動作しているホスト]:16686/
にアクセスしてWeb UIを表示すると、2つのサービスが認識されています。
api-frontendを選んで「Find Traces」ボタンを押してみます。
こんな感じで、トレースデータが表示されます。
トレースデータを選択すると(今回はPUTを選択)、
各スパンの中身を表示できます。
TagsやProcessを開くと、詳細な情報も確認できます。
全件取得の例。
依存関係の表示。
トレースデータのみですか、今回はこんなところでしょうか。
まとめ
OpenTelemetry、Jaegerを使って、Distributed Tracing…というかトレースデータを収集、表示してみました。
OpenTelemetryを扱うのは初めてだったので、各構成要素の役割がなかなか押さえられず苦戦しましたが、とりあえずなんとかなりました。
今回はボリュームの関係でメトリクスやロギングは外しトレースのみにしましたが、それはまたそのうち…。
OpenTracingとOpenCensusから、OpenTelemetryになったあたりの情報も見ておいて良かったと思います。