LoginSignup
7
0

Helidon を用いた MicroProfile Telemetry Tracing

Last updated at Posted at 2023-12-04

はじめに

こちらの記事は、Oracle Cloud Infrastructure Advent Calender 2023 Day 4 の記事として書かれています。

2023/10 に Java のマイクロサービス開発用のフレームワークである Helidon は 4.0.0 がリリースされ、MicroProfile 6.0 に対応しました。それ以外にも Web サーバーの実装が Netty から Virtual Threads ベースの Níma に置き換わったりと大きく変化をしています。

MicroProfile 6.0 の文脈で言えば、近年の分散トレーシングの標準化に伴い、MicroProfile Telemetry Tracing という新しい仕様が追加されています。これは、OpenTelemetry 1.13 仕様に準拠するもので、MicroProfile アプリケーションが OpenTelemetry(以下、OTel) を介して分散トレーシングが有効になっている環境に簡単に参加できるようにするための振舞いが定義されています。ただし、仕様にも記載のある通り、OTel の Metrics, Logging の統合は、MicroProfile Telemetry Tracing の範囲外となっており、実装者は必要に応じて Metrics, Logging の両方のサポートを自由に実装を提供することができます。

そこで今回は、MicrpProfile 仕様を実装したフレームワークである Helidon を用いて MicroProfile Telemetry Tracing を試してみようと思います。OTel のバックエンドは、Oracle Cloud Infrastructure の 通称 APM(Application Performance Monitoring)というサービスを使用します。

全体の構成図

以下のように、Kubernetes(OKE)上で Helidon を用いたアプリケーションを稼働させ、OTLP(OpenTelemetry Protocol)を用いて、テレメトリーデータを APM へ送信します。

image01.png

尚、使用するサンプルアプリケーションは作成済みの以下のアプリケーションを使用します。

エンドポイントは、2 つ存在し、それぞれ以下のように振舞います。

  • /api/cowsay/say: MicroProfile REST Client を用いて、/remote/cowsay/say にリクエストを送信
  • /api/cowsay/delay: MicroProfile REST Client を用いて、/remote/cowsay/delay にリクエストを送信

Automatic Instrumentation

MicroProfile に限らず、Java の自動計装は専用の Java Agent を実行中の JVM にアタッチするだけでテレメトリーデータの収集が可能です。簡単で良きですね!
コンテナで実行する場合は、Dockerfile を以下のように書いておけば環境によって変わり得る部分は、環境変数から読み込む等の工夫がしやすいと思います。( Kubernetes 上で稼働させる場合は、initContainers などで Agent の JAR ファイルを取得するのも良いかもしれません。

OpenTelemetry Operator の存在をこの記事を執筆した後に知ったので、合わせて記事としました。実施していることは、initContainers を用いた方法と同じですが、既に存在する Operator があるのでこちらを使うのがよろしいかと思います。

FROM maven:3.9.5 as build
WORKDIR /helidon
ADD pom.xml pom.xml
RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip -Declipselink.weave.skip -DskipOpenApiGenerate
ADD src src
RUN mvn package -DskipTests
RUN echo "done!"

# Multistage build の 1 stage で使用する OpenTelemetry Java Agent をダウンロードする
FROM busybox as download
WORKDIR /var/cache
RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.32.0/opentelemetry-javaagent.jar
RUN echo "done! - download stage"

FROM eclipse-temurin:21-jre
WORKDIR /helidon
COPY --from=build /helidon/target/otel-helidon.jar ./
COPY --from=build /helidon/target/libs ./libs
COPY --from=download /var/cache ./agent
ENV JAVA_TOOL_OPTIONS=-javaagent:/helidon/agent/opentelemetry-javaagent.jar
CMD ["java", "-jar", "otel-helidon.jar"]
EXPOSE 8080

上記のように JAVA_TOOL_OPTIONS を設定しても良いですし、java -javaagent=<path-to-otel-javaagent>.oteltelemetry-javaagent.jar -jar xxx.jar のように起動時にパラメータとして渡してあげても良いです。

これを Kubernetes 上で稼働させるための Deployment の定義は以下のようになっています。

deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: otel-helidon
  namespace: examples
spec:
  replicas: 1
  selector:
    matchLabels:
      app: otel-helidon
  template:
    metadata:
      labels:
        app: otel-helidon
        version: v1
    spec:
      containers:
        - name: otel-helidon
          image: shukawam/otel-helidon:1.0.4
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          env:
            - name: OTEL_SERVICE_NAME
              value: otel-helidon
            - name: OTEL_TRACES_EXPORTER
              value: otlp
            - name: OTEL_METRICS_EXPORTER
              value: none
            - name: OTEL_LOGS_EXPORTER
              value: none
            - name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
              value: http/protobuf
            - name: OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
              value: http/protobuf
            - name: OTEL_EXPORTER_OTLP_LOGS_PROTOCOL
              value: http/protobuf
            - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
              valueFrom:
                secretKeyRef:
                  key: otlp-traces-endpoint
                  name: otel-helidon-secret
            - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
              valueFrom:
                secretKeyRef:
                  key: otlp-metrics-endpoint
                  name: otel-helidon-secret
            # - name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
            #   valueFrom:
            #     secretKeyRef:
            #       key: otlp-logs-endpoint
            #       name: otel-helidon-secret
            - name: OTEL_EXPORTER_OTLP_HEADERS
              valueFrom:
                secretKeyRef:
                  key: otlp-headers
                  name: otel-helidon-secret
            - name: OTEL_EXPORTER_OTLP_TRACES_HEADERS
              valueFrom:
                secretKeyRef:
                  key: otlp-headers
                  name: otel-helidon-secret
            - name: OTEL_EXPORTER_OTLP_METRICS_HEADERS
              valueFrom:
                secretKeyRef:
                  key: otlp-headers
                  name: otel-helidon-secret
            # - name: OTEL_EXPORTER_OTLP_LOGS_HEADERS
            #   valueFrom:
            #     secretKeyRef:
            #       key: otlp-headers
            #       name: otel-helidon-secret

OTLP exporter の設定は、Java system properties or 環境変数から行うことができますが今回は環境変数から行っています。

env:
  - name: OTEL_SERVICE_NAME
    value: otel-helidon
  - name: OTEL_TRACES_EXPORTER
    value: otlp
  - name: OTEL_METRICS_EXPORTER
    value: none
  - name: OTEL_LOGS_EXPORTER
    value: none
  - name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
    value: http/protobuf
  - name: OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
    value: http/protobuf
  - name: OTEL_EXPORTER_OTLP_LOGS_PROTOCOL
    value: http/protobuf
  - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
    valueFrom:
      secretKeyRef:
        key: otlp-traces-endpoint
        name: otel-helidon-secret
  #   - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
  #     valueFrom:
  #       secretKeyRef:
  #         key: otlp-metrics-endpoint
  #         name: otel-helidon-secret
  # - name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
  #   valueFrom:
  #     secretKeyRef:
  #       key: otlp-logs-endpoint
  #       name: otel-helidon-secret
  - name: OTEL_EXPORTER_OTLP_HEADERS
    valueFrom:
      secretKeyRef:
        key: otlp-headers
        name: otel-helidon-secret
  - name: OTEL_EXPORTER_OTLP_TRACES_HEADERS
    valueFrom:
      secretKeyRef:
        key: otlp-headers
        name: otel-helidon-secret
#   - name: OTEL_EXPORTER_OTLP_METRICS_HEADERS
#     valueFrom:
#       secretKeyRef:
#         key: otlp-headers
#         name: otel-helidon-secret
# - name: OTEL_EXPORTER_OTLP_LOGS_HEADERS
#   valueFrom:
#     secretKeyRef:
#       key: otlp-headers
#       name: otel-helidon-secret

OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS に何を設定すれば良いか?は、APM のドキュメントに記載があります。

  • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: https://<dataUploadEndpoint>/20200101/opentelemetry/private/v1/traces
  • OTEL_EXPORTER_OTLP_HEADERS: Authorization=dataKey <your-datakey>

コンソールだと以下部分から参照すれば良いです。

image02.png

実際に動かして /api/cowsay/say にリクエストを送ってみると、以下のように APM にテレメトリーデータが収集されていることがわかります。

image03.png

Manual Instrumentation

自動計装で不十分な場合は、明示的にスパン情報を追加できます。スパンを追加するには、CDI ベースで測定したいメソッドに @WithSpan というアノテーションをつけるか、io.opentelemetry.api.trace.Tracer.spanBuilder を使って、スパンの開始と終了を宣言すれば良いです。

@WithSpan を用いる例

@WithSpan(value = "cdi.span")
private void delayWithCDISpan() {
    delayWithSpanBuilder();
    try {
        Thread.sleep(DELAY);
    } catch (InterruptedException | IllegalArgumentException e) {
        throw new RuntimeException(e);
    }
}

io.opentelemetry.api.trace.Tracer.spanBuilder を用いる例

import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.github.ricksbrown.cowsay.Cowsay;

import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

@Path("/remote/cowsay")
public class CowsayRemoteExecutor {
    private final Tracer tracer;

    @Inject
    public CowsayRemoteExecutor(Tracer tracer) {
        this.tracer = tracer;
    }
    // ...

    private void delayWithSpanBuilder() {
        var span = tracer.spanBuilder("builder.span")
                .setSpanKind(SpanKind.INTERNAL)
                .setParent(Context.current())
                .setAttribute("my.attribute","value")
                .startSpan();
        try {
            Thread.sleep(DELAY);
        } catch (InterruptedException | IllegalArgumentException e) {
            throw new RuntimeException(e);
        }
        span.end();
    }
}

io.opentelemetry.api.trace.Tracer.spanBuilder を用いた場合には、独自の属性が追加できたり、スパンの開始時刻を設定できたりとスパンの生成をより柔軟に行うことができます。

使用したサンプルアプリケーションの /api/cowsay/delay の方には、CDI ベースと spanBuilder を使うパターンでカスタムスパンを 2 つ仕込んでいます。実際に、エンドポイントを実行し APM を参照してみると以下のようになっています。

image04.png

きちんとトレーシングの情報を参照することができました。

終わりに

今回は、Helidon 4.0.0 で使えるようになった MicroProfile 6.0 に含まれている MicroProfile Telemetry Tracing を試してみました。OpenTelemetry のような言語非依存な仕様を活用することで Cloud Native な環境下におけるテレメトリー収集をいい感じに効率よく(大事!)行えると思います。

参考

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0