CLOVER🍀

That was when it all began.

Infinispan 14で追加された、OpenTelemetryトレーシングとのインテグレーションを試してみる(Server、Hot Rod)

これは、なにをしたくて書いたもの?

Infinispan 14で、OpenTelemetryトレーシングとのインテグレーションが追加されたようなので、試してみたいと思います。

Infinispan 14 OpenTelemetry tracing integration

Infinispan 14でのOpenTelemetryのトレーシングとのインテグレーション

Infinispan 14のOpenTelemetryトレーシングとのインテグレーションについては、こちらのブログに記載があります。

Infinispan 14 OpenTelemetry tracing integration

対応内容は、全部で3つあるようです。

  • Infninspan Serverでの対応
  • Hot Rod Clientでの対応
  • REST Clientでの対応

それぞれ見ていきます。

InfinispanのOpenTelemetryトレーシングへの対応で、ちょっと変わっているような気がするのは、Spanを記録するのは
Hot Rod ClientやREST Clientではなく、Infinispan Serverだということですね。
Hot Rod ClientやREST Clientは、そのアプリケーションが動作しているOpenTelemetry TraceとInfinispan Server側のSpanを
紐付ける役割を担います。

Infinispan ServerでのOpenTelemetryトレーシングとのインテグレーション

Infinispan ServerのOpenTeremetryトレーシングに関するドキュメントは、こちら。

Guide to Infinispan Server / Enabling and configuring Infinispan OpenTelemetry tracing

Infinispan ServerでOpenTelemetryトレーシングとのインテグレーションを有効化すると、OpenTelemetryのSpanを開始し、
キャッシュ操作に関連するトレースデータをエクスポートできるようになります。

Infinispan generates tracing spans compatible with the OpenTelemetry standard, allowing you to export, visualize, and analyze tracing data related to the most important cache operations.

有効化の方法は、Infinispan Serverの起動時にシステムプロパティで-Dinfinispan.tracing.enabled=trueを指定します。

ドキュメントでは-Dotel.〜システムプロパティが並びますが、これはOpenTelemetry SDKの設定です。

詳しく見る場合は、こちらですね。

https://github.com/open-telemetry/opentelemetry-java/blob/v1.31.0/sdk-extensions/autoconfigure/README.md

注意点としては、otel.exporter.otlp.endpointシステムプロパティ(またはOTEL_EXPORTER_OTLP_ENDPOINT環境変数)を
指定する時は、gRPCを使うOTLPのポートを指定する必要があります。Jaegerの場合は、4317ポートです。

このことは、ブログにのみ書かれています。

Infinispan 14 OpenTelemetry tracing integration

Hot Rod ClientでのOpenTelemetryトレーシングとのインテグレーション

Hot Rod ClientでのOpenTelemetryトレーシングとのインテグレーションに関するドキュメントは、こちら。

Using Hot Rod Java clients / Hot Rod client tracing propagation

Infinispan Serverだと「InfinispanのOpenTelemetryトレーシングの有効化と設定」という見出しだったのですが、こちらだと
「Hot Rod Clientでのトレーシングの伝播」という見出しになっています。
※トレースコンテキストの伝播、と呼んだ方がよさそうですが

これは、Infinispan ServertとHot Rod Clientを使うアプリケーションの 両方で OpenTelemetryトレーシングを有効にしていると、
Hot Rod Client側が関連付けられているTraceに、Infinispan Server側で作成されるSpanを紐付けられる機能です。

デフォルトで有効になっており、無効にすることもできますが、それだとInfinispan Serverで開始されるSpanはどこにも紐付かない
トレースデータになります…。

無効にする場合は、Hot Rod ClientのConfigurationBuilder#disableTracingPropagationで指定するか、hotrod-client.properties
infinispan.client.hotrod.tracing.propagation_enabledプロパティをfalseにします。
Hot Rod URIの場合はtracing.propagation_enabledですね。

org.infinispan.client.hotrod.configuration (Infinispan 14.0 JavaDoc)

さらっと読んでいると勘違いしやすい気がしますが、 Hot Rod Client自体はトレースデータを生成しない という点には要注意です。

Hot Rod Client自体はSpanを開始しませんが、ブログにあるように任意の操作の際にSpanを定義することはできます。
※ふつうだと思いますが…

Infinispan 14 OpenTelemetry tracing integration

また、ドキュメントやブログには書かれていないのですが、新しいHot Rod ClientもOpenTelemetryトレーシングとのインテグレーションには
対応している感じがしました。

REST ClientでのOpenTelemetryトレーシングとのインテグレーション

REST ClientでのOpenTelemetryインテグレーションですが、実体としてはなにもありません。

ブログに書かれているように、Spanを開始して自分でW3CTraceContextPropagatorに関連付ければOKです。

Infinispan 14 OpenTelemetry tracing integration

REST Clientには、ドキュメントもないのでこれ以上言うことがありません…。

注意点

Infinispan 14時点でのOpenTelemetryトレーシングとのインテグレーションですが、すべての操作に関するトレースデータがエクスポート
されるわけではありません。

キャッシュに対するgetなどは対象外です。

Pull Requestとそのコメントを見ると、初期バージョンではすべてのread操作をトレーシングの対象にするわけではないというコメントが
あります。

ISPN-13725 Integration with OpenTelemetry Tracing by fax4ever · Pull Request #10177 · infinispan/infinispan · GitHub

https://github.com/infinispan/infinispan/pull/10177#discussion_r915489186

もっとも、どの操作が対象になっているかは、実行結果またはソースコードを見ないとわからないのですが…。

Pull Requestの元のissueを見ると意図がわかるのかなと思いきや、このissueは見れません…。

https://issues.redhat.com/browse/ISPN-13725

とりあえず、ここまでの点を踏まえたうえで、Infinispan ServerとHot Rod ClientでOpenTelemetryトレーシングとのインテグレーションを
試してみます。

お題

今回のお題は、以下のようにします。

flowchart LR
    クライアント --> |curl/HTTP| A
    subgraph アプリケーション
    A[JAX-RS Resource] --> B[Hot Rod Client]
    end
    subgraph Infinispanクラスター
    I1[Infinispan Server 1] --> I2[Infinispan Server 2]
    I2 --> I3[Infinispan Server 3]
    I3 --> I1
    end
    B --> |Hot Rod Protocol/テレメトリーデータの伝播| Infinispanクラスター
    A --> |テレメトリーデータ| J[Jaeger]
    Infinispanクラスター --> |テレメトリーデータ| J

JAX-RS(RESTEasy)を使ったアプリケーションを作成し、その中でHot Rod Clientを利用してInfinispan Serverへアクセスします。
この時、JAX-RSおよびInfinispan ServerでOpenTelemetry SDKを有効にしておき、Infinispan Server側で開始するSpanとアプリケーションで
開始するTracingを関連付けます。

テレメトリーデータは、Jaegerで収集します。

Infinispanで扱うデータのお題は、書籍にします。

環境

今回の環境は、こちらです。

$ java --version
openjdk 17.0.8.1 2023-08-24
OpenJDK Runtime Environment (build 17.0.8.1+1-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.8.1+1-Ubuntu-0ubuntu122.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.5 (57804ffe001d7215b5e7bcb531cf83df38f93546)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.8.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.15.0-87-generic", arch: "amd64", family: "unix"

Infinispan Serverは、172.18.0.2〜172.18.0.4の3ノードで動作しているものとします。

$ java --version
openjdk 17.0.8.1 2023-08-24
OpenJDK Runtime Environment Temurin-17.0.8.1+1 (build 17.0.8.1+1)
OpenJDK 64-Bit Server VM Temurin-17.0.8.1+1 (build 17.0.8.1+1, mixed mode, sharing)


$ java --version
openjdk 17.0.8.1 2023-08-24
OpenJDK Runtime Environment Temurin-17.0.8.1+1 (build 17.0.8.1+1)
OpenJDK 64-Bit Server VM Temurin-17.0.8.1+1 (build 17.0.8.1+1, mixed mode, sharing)
ispn@5dc68b8abf64:/opt/infinispan-server$ bin/server.sh --version

Infinispan Server 14.0.19.Final (Flying Saucer)
Copyright (C) Red Hat Inc. and/or its affiliates and other contributors
License Apache License, v. 2.0. http://www.apache.org/licenses/LICENSE-2.0

Jaegerは、172.18.0.5で動作しているものとします。

$ ./jaeger-all-in-one version
2023/10/30 07:56:16 maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined
2023/10/30 07:56:16 application version: git-commit=d482f3f7bf780f72a20ddfedbe3aa6ec5c5e4613, git-version=v1.50.0, build-date=2023-10-08T00:14:18Z
{"gitCommit":"d482f3f7bf780f72a20ddfedbe3aa6ec5c5e4613","gitVersion":"v1.50.0","buildDate":"2023-10-08T00:14:18Z"}

Infinispan Serverの設定を行う

まず最初に、Infinispan Serverの設定を行いましょう。

以下のコマンドで、管理用ユーザーとアプリケーション用ユーザーを作成しておきます。

$ bin/cli.sh user create -g admin -p password ispn-admin
$ bin/cli.sh user create -g application -p password ispn-user

そして、1度OpenTelemetryに関する設定をせずに起動してみます。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=$(hostname -i)

すると、ログには以下のように「OpenTelemetryとのインテグレーションが無効だ」と出力されました。

2023-10-30 08:01:30,797 INFO  (ForkJoinPool.commonPool-worker-2) [org.infinispan.server.core.telemetry.TelemetryServiceFactory] ISPN000953: OpenTelemetry integration is disabled

今度は-Dinfinispan.tracing.enabled=trueを指定して起動してみます。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=$(hostname -i) \
    -Dinfinispan.tracing.enabled=true

今度は、OpenTelemetryに関する情報が出力されるようになりました。OpenTelemetryとのインテグレーションが有効になったようです。

2023-10-30 08:03:06,438 INFO  (ForkJoinPool.commonPool-worker-2) [org.infinispan.server.core.telemetry.TelemetryServiceFactory] ISPN000952: OpenTelemetry instance loaded: OpenTelemetrySdk{tracerProvider=SdkTracerProvider{clock=SystemClock{}, idGenerator=RandomIdGenerator{}, resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.9.0, attributes={service.name="unknown_service:java", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.15.0"}}, spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, spanProcessor=BatchSpanProcessor{spanExporter=io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter@63df352f, scheduleDelayNanos=5000000000, maxExportBatchSize=512, exporterTimeoutNanos=30000000000}}, meterProvider=SdkMeterProvider{clock=SystemClock{}, resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.9.0, attributes={service.name="unknown_service:java", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.15.0"}}, metricReaders=[], views=[]}}

ですが、このままだとOpenTelemetry SDKの設定をなにも行っていません。

1度Infinispan Serverを停止します。

OpenTelemetry SDKの設定は、システムプロパティではなく環境変数で設定することにしました。

$ export OTEL_TRACES_EXPORTER=otlp
$ export OTEL_METRICS_EXPORTER=none
$ export OTEL_LOGS_EXPORTER=none
$ export OTEL_EXPORTER_OTLP_ENDPOINT=http://172.18.0.5:4317
$ export OTEL_SERVICE_NAME=infinispan-server
$ export OTEL_NODE_RESOURCE_DETECTORS='env,host,os,process,container'

あとでわかりますが、OTEL_NODE_RESOURCE_DETECTORS環境変数は設定する意味がなさそうでした…。

これで再びInfinispan Serverを起動すると

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=$(hostname -i) \
    -Dinfinispan.tracing.enabled=true

ログで、少なくともサービス名は認識していることが確認できます。

2023-10-30 08:09:44,404 INFO  (ForkJoinPool.commonPool-worker-1) [org.infinispan.SERVER] ISPN080018: Started connector HotRod (internal)
2023-10-30 08:09:44,569 INFO  (ForkJoinPool.commonPool-worker-2) [org.infinispan.server.core.telemetry.TelemetryServiceFactory] ISPN000952: OpenTelemetry instance loaded: OpenTelemetrySdk{tracerProvider=SdkTracerProvider{clock=SystemClock{}, idGenerator=RandomIdGenerator{}, resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.9.0, attributes={service.name="infinispan-server", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.15.0"}}, spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, spanProcessor=BatchSpanProcessor{spanExporter=io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter@b0a04db, scheduleDelayNanos=5000000000, maxExportBatchSize=512, exporterTimeoutNanos=30000000000}}, meterProvider=SdkMeterProvider{clock=SystemClock{}, resource=Resource{schemaUrl=https://opentelemetry.io/schemas/1.9.0, attributes={service.name="infinispan-server", telemetry.sdk.language="java", telemetry.sdk.name="opentelemetry", telemetry.sdk.version="1.15.0"}}, metricReaders=[], views=[]}}

ここまでは、すべてのInfinispan Serverで行います。

最後にキャッシュを作成します。どれかひとつのInfinispan Serverのインスタンスを選び、管理CLIでログインします。

$ bin/cli.sh -c http://ispn-admin:password@localhost:11222

作成するキャッシュの定義は、エンコーディングをProtoStreamにしたDistributed Cacheにします。

cache.xml

<?xml version="1.0" encoding="utf-8"?>
<distributed-cache>
    <encoding>
       <key media-type="application/x-protostream"/>
       <value media-type="application/x-protostream"/>
    </encoding>
</distributed-cache>

キャッシュの作成。名前はbookCacheとしました。

[a2bfdcf42e2d-4538@cluster//containers/default]> create cache --file=/path/to/cache.xml bookCache

確認。

[a2bfdcf42e2d-4538@cluster//containers/default]> describe caches/bookCache
{
  "bookCache" : {
    "distributed-cache" : {
      "mode" : "SYNC",
      "encoding" : {
        "key" : {
          "media-type" : "application/x-protostream"
        },
        "value" : {
          "media-type" : "application/x-protostream"
        }
      }
    }
  }
}

アプリケーションを作成する

では、アプリケーションを作成していきます。

構成は、RESTEasyとJAX-RSのSeBootstrapを使うことにしましょう。

Maven依存関係など。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod-jakarta</artifactId>
            <version>14.0.19.Final</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <version>4.6.5.Final</version>
            <optional>true</optional>
        </dependency>

        <dependency>
           <groupId>io.opentelemetry</groupId>
           <artifactId>opentelemetry-api</artifactId>
            <version>1.31.0</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-core</artifactId>
            <version>6.2.5.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-undertow-cdi</artifactId>
            <version>6.2.5.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>6.2.5.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.6.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>libs/</classpathPrefix>
                            <mainClass>org.littlewings.infinispan.remote.otlp.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

Maven Pluginのところは、java -jarで起動できるようにするためです。

今回の依存関係のポイントは、こちらですね。

        <dependency>
           <groupId>io.opentelemetry</groupId>
           <artifactId>opentelemetry-api</artifactId>
            <version>1.31.0</version>
        </dependency>

こちらがないと、Hot Rod ClientでOpenTelemetryトレーシングデータを扱うことができません。

続いて、ソースコードの方へ。

お題となる書籍クラス。このクラスは、ProtoStreamでマーシャリング/アンマーシャリングします。

src/main/java/org/littlewings/infinispan/remote/otlp/Book.java

package org.littlewings.infinispan.remote.otlp;

import org.infinispan.protostream.annotations.ProtoField;
import org.infinispan.protostream.descriptors.Type;

public class Book {
    @ProtoField(number = 1, type = Type.STRING)
    String isbn;

    @ProtoField(number = 2, type = Type.STRING)
    String title;

    @ProtoField(number = 3, type = Type.INT32, defaultValue = "0")
    int price;


    〜getter/setterは省略〜

}

Protocol BuffersのIDLとMarshallerを自動生成するためのインターフェース。

src/main/java/org/littlewings/infinispan/remote/otlp/EntitiesInitializer.java

package org.littlewings.infinispan.remote.otlp;

import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;

@AutoProtoSchemaBuilder(
        includeClasses = {Book.class},
        schemaFileName = "entities.proto",
        schemaFilePath = "proto/",
        schemaPackageName = "entity"
)
public interface EntitiesInitializer extends SerializationContextInitializer {
}

JAX-RSリソースクラス。このクラスで、Hot Rod Clientを扱います。

src/main/java/org/littlewings/infinispan/remote/otlp/BooksResource.java

package org.littlewings.infinispan.remote.otlp;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;

import java.util.Comparator;
import java.util.List;

@Path("books")
@ApplicationScoped
public class BooksResource {
    private RemoteCacheManager remoteCacheManager;
    private RemoteCache<String, Book> bookCache;

    @PostConstruct
    public void init() {
        String uri = """
                hotrod://ispn-user:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222\
                ?context-initializers=org.littlewings.infinispan.remote.otlp.EntitiesInitializerImpl\
                """;

        Configuration configuration = new ConfigurationBuilder()
                .uri(uri)
                .build();
        remoteCacheManager = new RemoteCacheManager(configuration);

        bookCache = remoteCacheManager.getCache("bookCache");
    }

    @PreDestroy
    public void destroy() {
        remoteCacheManager.close();
    }

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book find(@PathParam("isbn") String isbn) {
        return bookCache.get(isbn);
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Book> findAll() {
        return bookCache
                .values()
                .stream()
                .sorted(Comparator.<Book, Integer>comparing(b -> b.getPrice()).reversed())
                .toList();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Book register(Book book) {
        bookCache.put(book.getIsbn(), book);

        return book;
    }
}

JAX-RSの有効化。

src/main/java/org/littlewings/infinispan/remote/otlp/JaxrsActivator.java

package org.littlewings.infinispan.remote.otlp;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("")
public class JaxrsActivator extends Application {
}

mainクラス。RESTEasyを起動し、Enterで終了するようにしています。

src/main/java/org/littlewings/infinispan/remote/otlp/App.java

package org.littlewings.infinispan.remote.otlp;

import jakarta.ws.rs.SeBootstrap;
import org.jboss.logging.Logger;

import java.util.concurrent.ExecutionException;

public class App {
    public static void main(String... args) throws ExecutionException, InterruptedException {
        Logger logger = Logger.getLogger(App.class);

        SeBootstrap.Configuration configuration =
                SeBootstrap
                        .Configuration
                        .builder()
                        .host("0.0.0.0")
                        .port(8080)
                        .build();

        SeBootstrap.Instance instance =
                SeBootstrap
                        .start(new JaxrsActivator(), configuration)
                        .toCompletableFuture()
                        .get();

        logger.info("server startup.");
        System.console().readLine("> Enter stop.");

        instance
                .stop()
                .toCompletableFuture()
                .get();
    }
}

CDIの有効化のため、beans.xmlを作成。

src/main/resources/META-INF/beans.xml




中身はありません。

以上でソースコードは作成できました。

アプリケーションを動作させ、OpenTelemetryトレースデータを記録してみる

では、動かしてみましょう。まずはパッケージング。

$ mvn package

実行する…前に、OpenTelemetry Collector Agentをダウンロードします。

$ curl -LO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.31.0/opentelemetry-javaagent.jar

OpenTelemetry SDKの設定を環境変数で指定。

$ export OTEL_TRACES_EXPORTER=otlp
$ export OTEL_METRICS_EXPORTER=none
$ export OTEL_LOGS_EXPORTER=none
$ export OTEL_EXPORTER_OTLP_ENDPOINT=http://172.18.0.5:4317
$ export OTEL_SERVICE_NAME=api
$ export OTEL_NODE_RESOURCE_DETECTORS='env,host,os,process,container'

起動は、以下のコマンドになります。

$ java -javaagent:opentelemetry-javaagent.jar -jar target/remote-opentelemetry-tracing-0.0.1-SNAPSHOT.jar

起動したら、まずはデータを登録。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{ "isbn": "978-1484280782", "title": "Java EE to Jakarta EE 10 Recipes: A Problem-Solution Approach for Enterprise Java", "price": 7163 }'
{"isbn":"978-1484280782","title":"Java EE to Jakarta EE 10 Recipes: A Problem-Solution Approach for Enterprise Java","price":7163}


$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{ "isbn": "978-1484282137", "title": "Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development", "price": 8373 }'
{"isbn":"978-1484282137","title":"Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development","price":8373}

この時点で、JaegerのWeb UIを確認してみます。http://[Jaegerが動作しているホストのIPアドレス]:16686/にアクセスしてみます。

アプリケーションのサービス名をappで登録したので、こちらで検索するとinfinispan-serverのSpanも紐付けられていることが確認できます。

Traceを選択して見てみましょう。

Infinispan ServerのSpanを開くと、こんな感じです。

OpenTelemetryのInstrumentationが不足しているので、リソースの情報が記録されませんね…。
OTEL_NODE_RESOURCE_DETECTORS環境変数で記録するリソースを指定していましたが、意味がなさそうです…。

Infinispan Serverに含まれているOpenTelemetry SDKの依存関係は、これくらいです。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/core/pom.xml#L127-L147

JAX-RS側だと、こんな感じに記録されます。

続いて、参照系のアクセスを行ってみましょう。

4 curl localhost:8080/books
[{"isbn":"978-1484282137","title":"Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development","price":8373},{"isbn":"978-1484280782","title":"Java EE to Jakarta EE 10 Recipes: A Problem-Solution Approach for Enterprise Java","price":7163}]


$ curl localhost:8080/books/978-1484282137
{"isbn":"978-1484282137","title":"Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development","price":8373}

ここで、JaegerのWeb UIを確認してみます。

参照系の時は、Infinispan ServerのSpanが記録されていません。

サービスinfinispan-serverで絞り込んでも表示されないので、本当にありません。

というわけで、前段にも書きましたが、現時点ですべての操作のトレースデータが記録されるわけではありません。

ひとまず、最低限の動作確認としてはOKです。

Hot Rod Clientのトレースコンテキストの伝播を無効にしてみる

最後にHot Rod Clientのトレースコンテキストの伝播を無効にしてみましょう。

Hot Rod ClientでInfinispan Serverに接続する時のURIを、こちらから

        String uri = """
                hotrod://ispn-user:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222\
                ?context-initializers=org.littlewings.infinispan.remote.otlp.EntitiesInitializerImpl\
                """;

こうしてみます。tracing.propagation_enabledfalseにしました。

        String uri = """
                hotrod://ispn-user:password@172.18.0.2:11222,172.18.0.3:11222,172.18.0.4:11222\
                ?tracing.propagation_enabled=false\
                &context-initializers=org.littlewings.infinispan.remote.otlp.EntitiesInitializerImpl\
                """;

あとは、ビルドして再実行。

$ mvn package
$ java -javaagent:opentelemetry-javaagent.jar -jar target/remote-opentelemetry-tracing-0.0.1-SNAPSHOT.jar

トレースデータが記録される、更新処理を行ってみます。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{ "isbn": "978-1484280782", "title": "Java EE to Jakarta EE 10 Recipes: A Problem-Solution Approach for Enterprise Java", "price": 7163 }'
{"isbn":"978-1484280782","title":"Java EE to Jakarta EE 10 Recipes: A Problem-Solution Approach for Enterprise Java","price":7163}


$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/books -d '{ "isbn": "978-1484282137", "title": "Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development", "price": 8373 }'
{"isbn":"978-1484282137","title":"Pro Jakarta EE 10: Open Source Enterprise Java-based Cloud-native Applications Development","price":8373}

JaegerのWeb UIで確認してみましょう。上2つのトレースデータが、URI変更後に行った操作に該当します。

今回は、appinfinispan-serverの紐付けがなくなりましたね。

トレースコンテキストが伝播していないことは、サービス名infinispan-serverで検索するとわかります。

というわけで、これでHot Rod Client側が行っているトレースコンテキスト伝播の役割が確認できましたね。

トレースコンテキストの伝播を無効にすると、トレースが分断されてしまうので嬉しくないというか、Infinispan ServerのSpanが
独立したトレースになってしまうので意味がありません…。

これで、今回やりたかったことは確認できました。

少し実装を追ってみる

最後に、少し実装を追ってみましょう。

Hot Rod Client

まずはHot Rod Client側です。

Hot Rod Client側では、トレースコンテキスの伝播が無効になっていない場合(デフォルト)

infinispan/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/operations/OperationsFactory.java at 14.0.19.Final · infinispan/infinispan · GitHub

OpenTelemetry SDKに含まれるW3CTraceContextPropagatorクラスがクラスパス上に存在する場合、TelemetryServiceImpl
インスタンスTelemetryServiceインターフェースの実装クラス)を作成します。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/telemetry/impl/TelemetryService.java#L13-L28

TelemetryServiceImplがやっているのは、トレースコンテキストの伝播そのものですね。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/telemetry/impl/TelemetryServiceImpl.java#L18-L24

これがどこで使われるかというと、RetryOnFailureOperation抽象クラスです。TelemetryServiceインスタンスnullでない場合に
使われます。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/operations/RetryOnFailureOperation.java#L52

RetryOnFailureOperationクラスは、キャッシュの各操作を表すクラスの親クラスになっています。

たとえばCache#putに対応するPutOperationではコンストラクタでTelemetryServiceインスタンスを渡しています。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/operations/PutOperation.java#L30-L38

一方で、Cache#getに相当するGetOperationでは、親クラスのコンストラクタを呼び出す際に渡すTelemetryServiceインスタンス
常にnullです。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/operations/GetOperation.java#L28-L33

つまり、仮にInfinispan Server側で参照系の処理のトレースデータを記録するようにしても、ここが追従しないと参照系の処理の
トレースコンテキストの伝播は途切れることになります。現時点だと意図的にこうなっているようなので仕方ありません。
現時点でなんとかしたかったら、手動で伝播させるようにするんでしょうね。

どの操作がOpenTelemetryに対応していて、どの操作が対応していないかはわからないので、以下のパッケージからひとつひとつ
見ていくか、実際に動作させるしかありません…。

https://github.com/infinispan/infinispan/tree/14.0.19.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/operations

Server側

Server側です。

まず、システムプロパティinfinispan.tracing.enabledを参照してOpenTelemetryとのインテグレーションを切り替えているのは、こちら。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/core/src/main/java/org/infinispan/server/core/telemetry/TelemetryServiceFactory.java#L23-L46

そしてトレースデータ生成のところですが。

Infinispan Server(Hot Rodプロトコル)では、こちらを見るとよいでしょう。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/hotrod/src/main/java/org/infinispan/server/hotrod/CacheRequestProcessor.java

たとえばput操作だと、HotRodTelemetryService#requestStartHotRodTelemetryService#requestEndでSpanを開始、終了していることが
わかります。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/hotrod/src/main/java/org/infinispan/server/hotrod/CacheRequestProcessor.java#L221-L242

反対にget操作だと、HotRodTelemetryServiceに対する操作が登場しません。

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/hotrod/src/main/java/org/infinispan/server/hotrod/CacheRequestProcessor.java#L77-L82

https://github.com/infinispan/infinispan/blob/14.0.19.Final/server/hotrod/src/main/java/org/infinispan/server/hotrod/CacheRequestProcessor.java#L107-L114

これが操作によってトレースデータが生成されたり、されなかったりすることになる理由ですが。対応範囲は、
CacheRequestProcessorクラスを見ることで良さそうです。

おわりに

Infinispan Server 14で追加された、OpenTelemetryトレーシングとのインテグレーションを試してみました。

トレースデータが生成される場所がInfinispan Serverだったり、クライアント側の対応はトレースコンテキストの伝播の役割だったりと
ちょっと不思議なのですが。

使い方はわかったので、今回は良しとしておきましょう。

今回作成したソースコードは、こちらに置いています。

https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-opentelemetry-tracing