以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/09/15/003156より取得しました。


WildFlyのMicroProfile Telemetryサブシステムを使って、トレースとメトリクスを送信してみる

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

以前、WildFlyのMicroProfile Telemetryサブシステムを使って、トレースシグナルをJaegerに送信してみました。

WildFlyのMicroProfile Telemetryサブシステムを使って、トレースを試す - CLOVER🍀

今回はもっと範囲を広げて、メトリクスも含めてやってみようと思います。WildFly 37を使います。

MicroProfile Telemetry

少しおさらい的にMicroProfile Telemetryの情報を見返してみます。

WildFly 37で実装しているMicroProfileの仕様は7.0です。

WildFly 37 is released!

MicroProfile 7.0に含まれるMicroProfile Telemetryは2.0です。

Telemetry 2.0 - MicroProfile

MicroProfile Telemetry

MicroProfile Telemetry 2.0は、OpenTelemetry Java実装の1.39をベースにしています。

This specification is based on the Java implementation v1.39.0 of OpenTelemetry.

MicroProfile Telemetry / Architecture

MicroProfile Telemetryには固有のAPIはありません。

ソースコードを見るとわかりますが、pom.xmlがあるだけです。

microprofile-telemetry/api at 2.0 · microprofile/microprofile-telemetry · GitHub

    <dependencies>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-instrumentation-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.semconv</groupId>
            <artifactId>opentelemetry-semconv</artifactId>
        </dependency>
    </dependencies>

https://github.com/microprofile/microprofile-telemetry/blob/2.0/api/pom.xml#L28-L41

つまり、すごくざっくりと言うとMicroProfile TelemetryというのはOpenTelemetry SDKとインテグレーションする仕様です。

MicroProfile Telemetry / SDK integration

設定は、MicroProfile Configによって行います。

またOpenTelemetry APIが提供するクラスに対して、Jakarta Contexts and Dependency Injection(CDI)によるBeanを提供します。

ちなみにログシグナルに対してはロギングフレームワークによるブリッジに任せる方針としているので、MicroProfile Telemetryではほとんど
仕様がありません。
ただ、MicroProfile Telemetry(OpenTelemetryのサポート)を有効化するとログのサポートも有効になります。

WildFlyのMicroProfile Telemetryサブシステム

WildFly 37には、テレメトリーデータに関するいくつかのサブシステムがあります。

いろいろあってどうなっているのかよくわからないのですが、こういう使い分けでよさそうです。

  • メトリクス
    • WildFlyとJavaVMのMBeanからの基本メトリクスをHTTPエンドポイントで公開する場合は、Metricsサブシステムを使う
    • メトリクスをMicrometerで扱いたい場合は、Micrometerサブシステムを使う
    • OpenTelemetryを使いたい場合は、OpenTelemetryサブシステムを使う
    • MicroProfile Telemetryを使いたい場合は、MicroProfile Telemetryサブシステムを使う
  • トレース
    • OpenTelemetryを使いたい場合は、OpenTelemetryサブシステムを使う
    • MicroProfile Telemetryを使いたい場合は、MicroProfile Telemetryサブシステムを使う

Micrometerサブシステムの立ち位置はわかりやすいのですが、OpenTelemetryサブシステムとMicroProfile Telemetryサブシステムの関係はちょっと
よくわからない感じになります。

OpenTelemetryサブシステムは、WildFlyでOpenTelemetry SDKの設定を行うサブシステムです。

MicroProfile Telemetryサブシステムは、MicroProfile Telemetryをアプリケーションで利用できるようにするサブシステムです。
よって両者には依存関係があり、MicroProfile TelemetryサブシステムはOpenTelemetryサブシステムに依存しています。
レイヤー定義はこちら。

https://github.com/wildfly/wildfly/blob/37.0.1.Final/galleon-pack/galleon-shared/src/main/resources/layers/standalone/microprofile-telemetry/layer-spec.xml

OpenTelemetryサブシステムのレイヤー定義はこちら。

https://github.com/wildfly/wildfly/blob/37.0.1.Final/galleon-pack/galleon-shared/src/main/resources/layers/standalone/opentelemetry/layer-spec.xml

ソースコードはこのあたりですね。

OpenTelemetryサブシステムの設定はこちら。

WildFly 36 Model Reference

このあたりの設定を見ていると、テレメトリーデータの送信先など全体の共通設定はWildFlyアプリケーションサーバー)側で管理して、
アプリケーション固有の設定はMicroProfile Telemetry+MicroProfile Config経由で設定、という考え方に見えます。

ちょっと気をつけておいた方がよさそうなのは、プロトコルはgRPCで固定されているみたいです。

https://github.com/wildfly/wildfly/blob/37.0.1.Final/observability/opentelemetry-api/src/main/java/org/wildfly/extension/opentelemetry/api/WildFlyOpenTelemetryConfig.java#L57

Exporterの実装に使われているSmallRye OpenTelemetryおよびVert.xではgRPCもhttp/protobufも両方扱えそうなので、プロトコルを指定できても
いいように思うのですが。

https://github.com/smallrye/smallrye-opentelemetry/tree/2.9.2/implementation/exporters/src/main/java/io/smallrye/opentelemetry/implementation/exporters/sender

またシグナルごとにエンドポイントを変えるといった設定はできなさそうですね。つまり、OpenTelemetry Collector相当のものが必要になります。

とはいえ、いざとなればアプリケーション側で設定すればよい気はしますが。

MicroProfile Telemetry / Configuration

情報を見るのはこれくらいにして、試していってみましょう。

お題

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

flowchart LR
    クライアント --> |curl/HTTP| A
    subgraph WildFly
      A[JAX-RS Server]
    end
    subgraph Telemetry
      A -- OTLP over gRPC --> B["OpenTelemetry Collector Contrib"]
      B -- OTLP over http/protobuf --> C["Grafana Tempo"]
      C -- read/write --> D[("MinIO")]
      B -- Remote Write --> E["Prometheus"]
      F["Grafana"] --> C
      F --> E
    end

ふだんなら複数のアプリケーションやデータベースを用意してトレーシングデータを収集するのですが、ちょっと登場要素が多すぎるので
今回はアプリケーション側は簡易的な構成にしました。

WildFlyとOpenTelemetry Collector Contribの間がgRPCなのに、その後のGrafana Tempoとの間がhttp/protobufになっているのは、個人的な好みです。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.8 2025-07-15
OpenJDK Runtime Environment (build 21.0.8+9-Ubuntu-0ubuntu124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.8+9-Ubuntu-0ubuntu124.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.8, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-79-generic", arch: "amd64", family: "unix"

OpenTelemetry Collector Contrib。172.19.0.2で動作しているものとします。

$ otelcol-contrib --version
otelcol-contrib version 0.135.0

Grafana Tempo。172.19.0.3で動作しているものとします。

$ tempo --version
tempo, version 2.8.2 (branch: HEAD, revision: 6b9261586)
  build user:
  build date:
  go version:       go1.24.5
  platform:         linux/amd64
  tags:             unknown

MinIO。172.19.0.4で動作しているものとします。

$ minio --version
minio version RELEASE.2025-09-07T16-13-09Z (commit-id=07c3a429bfed433e49018cb0f78a52145d4bedeb)
Runtime: go1.24.6 linux/amd64
License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html
Copyright: 2015-2025 MinIO, Inc.


$ mcli --version
mcli version RELEASE.2025-08-13T08-35-41Z (commit-id=7394ce0dd2a80935aded936b09fa12cbb3cb8096)
Runtime: go1.24.6 linux/amd64
Copyright (c) 2015-2025 MinIO, Inc.
License GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>

Prometheus。172.19.0.5で動作しているものとします。

$ ./prometheus --version
prometheus, version 3.5.0 (branch: HEAD, revision: 8be3a9560fbdd18a94dedec4b747c35178177202)
  build user:       root@4451b64cb451
  build date:       20250714-16:15:23
  go version:       go1.24.5
  platform:         linux/amd64
  tags:             netgo,builtinassets

Grafana。172.19.0.6で動作しているものとします。

$ grafana-server --version
Version 12.1.1 (commit: df5de8219b41d1e639e003bf5f3a85913761d167, branch: release-12.1.1)

Terraformも使います。

$ terraform version
Terraform v1.13.2
on linux_amd64

準備(ミドルウェア

各種ミドルウェアの準備をしていきます。

Prometheus。

設定はこのようにしました。

prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:

storage:
  tsdb:
    out_of_order_time_window: 30m

起動オプションはこんな感じで、リモート書き込みとネイティブヒストグラムを有効にします。

$ ./prometheus --web.enable-remote-write-receiver --enable-feature=native-histograms

--enable-feature=native-histogramsがないと、この構成だとリモート書き込み時にこんなエラーを見ることになります。

time=2025-09-14T14:44:42.719Z level=ERROR source=write_handler.go:192 msg="Error while remote writing the v1 request" component=web err="native histograms are disabled"

今回は主旨で追いませんが、たぶんこれはGrafana TempoのMetrics generatorですね。

Metrics-generator | Grafana Tempo documentation

MinIO。

$ export MINIO_ROOT_USER=minioadmin
$ export MINIO_ROOT_PASSWORD=minioadmin
$ minio server /var/lib/minio --console-address :9001

バケットの作成。

$ mcli alias set myminio http://minio:9000 minioadmin minioadmin
$ mcli mb myminio/tempo-blocks

Grafana Tempo。

/etc/tempo/config.yml

stream_over_http_enabled: true
server:
  http_listen_port: 3200
  log_level: info

query_frontend:
  search:
    duration_slo: 5s
    throughput_bytes_slo: 1.073741824e+09
    metadata_slo:
        duration_slo: 5s
        throughput_bytes_slo: 1.073741824e+09
  trace_by_id:
    duration_slo: 5s

distributor:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: "0.0.0.0:4317"
        http:
          endpoint: "0.0.0.0:4318"

metrics_generator:
  registry:
    external_labels:
      source: tempo
      cluster: docker-compose
  storage:
    path: /var/tempo/generator/wal
    remote_write:
      - url: http://172.19.0.5:9090/api/v1/write
        send_exemplars: true
  traces_storage:
    path: /var/tempo/generator/traces

storage:
  trace:
    backend: s3
    s3:
      bucket: "tempo-blocks"
      endpoint: "172.19.0.4:9000"
      insecure: true
      forcepathstyle: true
      access_key: "minioadmin"
      secret_key: "minioadmin"
    wal:
      path: /var/tempo/wal             # where to store the wal locally

overrides:
  defaults:
    metrics_generator:
      processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
      generate_native_histograms: both

起動。

$ tempo -config.file /etc/tempo/config.yml

OpenTelemetry Collector Contrib。

/etc/otelcol-contrib/config.yaml

extensions:
  health_check:
  pprof:
    endpoint: 0.0.0.0:1777
  zpages:
    endpoint: 0.0.0.0:55679

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318


processors:
  batch:


exporters:
  otlphttp:
    endpoint: http://172.19.0.3:4318

  prometheusremotewrite:
    endpoint: "http://172.19.0.5:9090/api/v1/write"
    resource_to_telemetry_conversion:
      enabled: true

  debug:
    verbosity: detailed


service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp, debug]

    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheusremotewrite, debug]

  extensions: [health_check, pprof, zpages]

起動。

$ otelcol-contrib --config /etc/otelcol-contrib/config.yaml

Grafanaのデータソース設定。

terraform.tf

terraform {
  required_version = "1.13.2"

  required_providers {
    grafana = {
      source  = "grafana/grafana"
      version = "4.7.1"
    }
  }
}

main.tf

provider "grafana" {
  url  = "http://172.19.0.6:3000"
  auth = "admin:admin"
}

resource "grafana_data_source" "tempo" {
  name = "tempo"
  type = "tempo"
  url  = "http://172.19.0.3:3200"
}

resource "grafana_data_source" "prometheus" {
  name = "prometheus"
  type = "prometheus"
  url  = "http://172.19.0.5:9090"

  is_default = true

  json_data_encoded = jsonencode({
    httpMethod     = "POST"
    prometheusType = "Prometheus"
  })
}

作成。

$ terraform init
$ terraform apply

準備(アプリケーション)

次はアプリケーションの準備です。簡単なJakarta RESTful Web Services(JAX-RS)を使ったアプリケーションを作成します。

Maven依存関係など。

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-ee-with-tools</artifactId>
                <version>37.0.1.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-expansion</artifactId>
                <version>37.0.1.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.inject</groupId>
            <artifactId>jakarta.inject-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.eclipse.microprofile.config</groupId>
            <artifactId>microprofile-config-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-context</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        </plugins>
    </build>

JAX-RSの有効化。

src/main/java/org/littlewings/wildfly/telemetry/RestApplication.java

package org.littlewings.wildfly.telemetry;

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

@ApplicationPath("/")
public class RestApplication extends Application {
}

src/main/java/org/littlewings/wildfly/telemetry/HelloResource.java

package org.littlewings.wildfly.telemetry;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.Map;

@ApplicationScoped
@Path("/hello")
public class HelloResource {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> message() {
        return Map.of("message", "Hello World!!");
    }
}

CDI管理Beanにしていますが、特に意味はありません…。

MicroProfile Configを使った、OpenTelemetry SDKの設定。

src/main/resources/META-INF/microprofile-config.properties

otel.sdk.disabled=false
otel.service.name=api
otel.logs.exporter=none

動作確認はこの時点では省略します。

パッケージングまでは行っておきます。

$ mvn package

WildFlyでMicroProfile Telemetryサブシステムを有効にする

準備ができたので、本題に入っていきましょう。

デフォルト設定のWildFlyではMicroProfile Telemetryサブシステムは有効になっていないので、こちらを有効にするところからですね。
もっとも、先にOpenTelemetryサブシステムを有効にする必要がありますが。

The MicroProfile Telemetry subsystem depends on the opentelemetry subsystem, so it must be added prior to adding MicroProfile Telemetry.

WildFly Admin Guide / Subsystem configuration / OpenTelemetry Subsystem

WildFlyをダウンロードして起動。

$ curl -LO https://github.com/wildfly/wildfly/releases/download/37.0.1.Final/wildfly-37.0.1.Final.zip
$ unzip wildfly-37.0.1.Final.zip
$ cd wildfly-37.0.1.Final
$ bin/standalone.sh \
    -Djboss.bind.address=0.0.0.0 \
    -Djboss.bind.address.management=0.0.0.0

WildFlyに管理CLIで接続。

$ bin/jboss-cli.sh -c

OpenTelemetryサブシステムを追加。

[standalone@localhost:9990 /] /extension=org.wildfly.extension.opentelemetry:add()
[standalone@localhost:9990 /] /subsystem=opentelemetry:add()

設定。プロトコルはOpenTelemetryプロトコル(gRPC)、エンドポイントはOpenTelemetry Collector Contribに向けます。

[standalone@localhost:9990 /] /subsystem=opentelemetry:write-attribute(name=exporter-type,value=otlp)
[standalone@localhost:9990 /] /subsystem=opentelemetry:write-attribute(name=endpoint,value=http://172.19.0.2:4317)

MicroProfile Telemetryサブシステムを追加。

[standalone@localhost:9990 /] /extension=org.wildfly.extension.microprofile.telemetry:add()
[standalone@localhost:9990 /] /subsystem=microprofile-telemetry:add()

ここまで設定したら、WildFlyを再起動。

[standalone@localhost:9990 /] reload

動作確認

では、動作確認していきます。

アプリケーションをデプロイ。

$ cp /path/to/target/ROOT.war standalone/deployments

curlで何回かアクセスしてみます。

$ curl localhost:8080/hello
{"message":"Hello World!!"}


$ curl localhost:8080/hello
{"message":"Hello World!!"}


$ curl localhost:8080/hello
{"message":"Hello World!!"}


$ curl localhost:8080/hello
{"message":"Hello World!!"}


$ curl localhost:8080/hello
{"message":"Hello World!!"}

Grafanaで確認。

メトリクス。

トレース。

OKですね。

オマケ: WildFly Maven Pluginを使って設定する

WildFly Maven Pluginを使って設定する場合は、こんな感じでしょうか。

            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>5.1.4.Final</version>
                <executions>
                    <execution>
                        <id>package</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <overwrite-provisioned-server>true</overwrite-provisioned-server>
                    <discover-provisioning-info>
                        <version>37.0.1.Final</version>
                        <add-ons>
                            <add-on>wildfly-cli</add-on>
                        </add-ons>
                        <layers-for-jndi>
                            <layer>microprofile-telemetry</layer>
                        </layers-for-jndi>
                    </discover-provisioning-info>
                    <scripts>
                        <script>src/main/jboss-cli-scripts/configure-server.cli</script>
                    </scripts>
                    <packaging-scripts>
                        <packaging-script>
                            <scripts>
                                <script>src/main/jboss-cli-scripts/configure-server.cli</script>
                            </scripts>
                        </packaging-script>
                    </packaging-scripts>
                </configuration>
            </plugin>

設定はスクリプトで。

src/main/jboss-cli-scripts/configure-server.cli

/subsystem=opentelemetry:write-attribute(name=exporter-type,value=otlp)
/subsystem=opentelemetry:write-attribute(name=endpoint,value=http://172.19.0.2:4317)

おわりに

WildFlyのMicroProfile Telemetryサブシステムを使って、トレースとメトリクスを送信してみました。

久しぶりに扱ったのでMicroProfile TelemetryサブシステムとOpenTelemetryサブシステムの関係を忘れていたり、WildFly以外の準備がかなり大変
だったりしたのですが、ひとまず通せてよかったです。

OpentTelemetryまわりはとにかく準備が大変なのですが、このあたりもうちょっと簡単になるようにしたいですね…。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/09/15/003156より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14