MicroProfile OpenTracing with Helidon

# はじめに
UL Systems Advent Calendar 2019 - 18日目。

本日は、[Helidon](https://helidon.io/#/)の[Tracing MP Guide](https://helidon.io/docs/latest/#/guides/12_tracing_mp_guide)を参考に[Eclipse MicroProfile OpenTracing](https://github.com/eclipse/microprofile-opentracing)と[Jaeger](https://www.jaegertracing.io/)で分散トレーシングの可視化を試したいと思います。(Tracing MP GuideではZipkinを使用していますが今回はロゴの可愛さからJaegerを使用してみたいと思います。)

最近では、マイクロサービスアーキテクチャでシステム全体を構成する事も多くなってきました。その際、従来のモノリシックなシステムで使用していたメソッドトレースなどでは複数のサービスを跨がる処理に障害や遅延が起きた場合、どこで発生しているのか判別が難しい為、全体を通してトレースする分散トレーシングと呼ばれる仕組みが必要になります。


<img width="153" alt="jaeger.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/3212e402-159c-729e-ca56-08cfecdb59da.png">

# Helidon とは
2018年9月にOracle社より発表された、Java向けのマイクロサービス作成に活用できる軽量マイクロサービス開発のためのオープンソースベースのフレームワークです。

## Helidonの特徴
* 2つのプログラミングモデル
    * Helidon SE
        * 関数型スタイル開発、シンプル
        * インジェクション非対応
    * Helidon MP
        * MaicroProfile APIs
        * Java EE/Jakarta EEスタイル(JAX-RS/CDI/など)
* Docker, k8s との連携
* Oracleによる有償サポートサービス有り
* GraaalVMへの対応

今回は MaicroProfile をサポートしているHelidon MPをしようします。

# Eclipse MicroProfile とは
Eclipse MicroProfileは、マイクロサービス向け Enterprise Java API の仕様です。現在では、JAX-RS、JSON-P、CDIなどのAPIが含まれており、構成、メトリック、フォールトトレランスなどのAPIも存在します。
今回使用する Helidon MP は Eclipse MicroProfile 3.2 をサポートしています。

# OpenTracing とは
OpenTracingは、API仕様とそれを実装したフレームワークとライブラリ、およびプロジェクトのドキュメントで構成されています。 OpenTracingを使用すると、開発者は特定の製品やベンダーにロックされていないAPIを使用してアプリケーションコードにトレーシング機能を追加できます。

# Jaeger とは
DapperとOpenZipkinにインスパイヤーされUber TechnologiesによってリリースされたオープンソースのOpenTracing互換の分散トレースシステムです。
今回は下記の図の OpenTracing API 部分が MicroProfile OpenTracing になりjaeger-client の部分が Helidon から提供されているJaeger用のClientになります。

![20181125234256.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/ad0b5cd0-9c6a-aca4-b618-211a8e711688.png)

## Jaeger のセットアップ
それではまず、Client以外の部分に関してセットアップを行いたいと思います。セットアップに関してはAll-in-oneのDockerイメージが公開されているのでそれを使用します。
（必要最低限のポートのみを指定して起動しています）

``` sh
$ docker run -d --name jaeger \
   -p 5778:5778 \
   -p 16686:16686 \
   -p 14268:14268 \
   jaegertracing/all-in-one:1.15
```

## 各ポートの説明
Helidon の jaeger-client はデフォルトではHTTPの`5778`を使用しているようです。
（これは設定により変更可能です）

![キャプチャ004.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/ec46494b-bfe0-a14a-c012-d1dc5286d891.jpeg)

# Main サービス の作成
次にメインとなるサービスプロジェクトを Helidon MP Maven archetype の quickstart を使用して作成します。

``` sh
$ mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=1.4.0 \
    -DgroupId=io.helidon.main_service \
    -DartifactId=main_service \
    -Dpackage=io.helidon.main_service
```

## Jaeger Clientを追加
pom.xml に以下の依存関係を追加し Jaeger Client を追加します。

``` xml:pom.xml(追加)
<dependency>
    <groupId>io.helidon.tracing</groupId>
    <artifactId>helidon-tracing-jaeger</artifactId>
</dependency>
```

## トレーシング サービス名の追加
Helidon から Jaeger に送信されるトレーシングデータへ紐付ける為のサービス名を`META-INF/microprofile-config.properties`へ指定します。

``` properties:microprofile-config.properties
tracing.service=helidon-main-service
```

## Main サービス を動かして見る
では、ここまで作成した結果を見てみたいと思います。quickstart で作成したプロジェクトは、`io.helidon.main_service.Main`クラスの`Main`メソッドを実行する事でサーバが起動するように作成されています。

サーバ起動後に`http://localhost:8080/greet`へアクセスすると quickstart で作成されたサービスが実行され`{"message":"Hello World!"}`が表示されるはずです。

``` sh
$ curl http://localhost:8080/greet
{"message":"Hello World!"}
```

数回アクセスした後に jaeger UI(`http://localhost:16686/search`) からトレーシングデータを見ると以下のようなトレーシングデータが可視化されるはずです。
![キャプチャ003.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/5d4cb26a-2794-48e8-40f9-ad62e9d204da.jpeg)

さらに各トレースをクリックすると、スパンがリストされているトレース詳細ページが表示されます。 ルートスパンと、トレース内のすべてのスパン間の関係、およびタイミング情報を明確に確認できます。
![キャプチャ005.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/034874b3-91c7-7cd9-8767-33133efdc17c.jpeg)

各スパン行をクリックすると、スパンの詳細も確認できます。
![キャプチャ006.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/df162085-0865-6305-ccdb-4b84c8c9af95.jpeg)



## クラスやメソッドレベルでのトレーシング
さらに、MicroProfile OpenTracingで提供されている`@Traced`アノテーションを付与することでクラスやメソッドレベルでのトレーシングも可能です。

``` java:GreetingProvider.java
@Traced
@ApplicationScoped
public class GreetingProvider {
...
}
```
![キャプチャ007.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/4a55f5a0-8fbf-ba2e-92cd-4d13734d293a.jpeg)

# サービス間を跨いだトレーシング
次にサービス間を跨いだトレーシングを行って見たいと思います。

## Second サービスの作成
Main サービスと同様に Helidon MP Maven archetype の quickstart を使用して作成し、Jaeger Client を追加します。

``` sh
$ mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=1.4.0 \
    -DgroupId=io.helidon.second_service \
    -DartifactId=second_service \
    -Dpackage=io.helidon.second_service
```

``` xml:pom.xml(追加)
<dependency>
    <groupId>io.helidon.tracing</groupId>
    <artifactId>helidon-tracing-jaeger</artifactId>
</dependency>
```

### 設定変更
* greetingで返される文字をSecond サービスからの返却だと識別しやすいように`Hello From Second Service`と変更します。
* 起動ポートを`8081`へ変更します。

``` properties:microprofile-config.properties(変更後)
# Application properties. This is the default greeting
app.greeting=Hello From Second Service

# Microprofile server properties
server.port=8081
```

### 動作確認
Second サービスを実行し動作確認をすると以下のような結果になります。

``` sh
$ curl http://localhost:8081/greet
{"message":"Hello From Second Service World!"}
```

## Main サービスからSecond サービスの呼出
最初に作成したMain サービスからSecond サービスを呼び出すように変更します。

まずは、Second サービスを呼び出すためのRest ClientをMain サービスに作成する必要があります。
Tracing MP Guide とは違いここでは`MicroProfile Rest Client`を使用して作成してみたいと思います。

``` java:SecondServiceClient.java
package io.helidon.main_service;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.json.JsonObject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@RegisterRestClient(baseUri="http://localhost:8081")
public interface SecondServiceClient {
    @Path("/greet")
    @GET
    JsonObject getDefaultMessage();
}
```

そして、作成したClientを使用しMain サービスからSecond サービスの呼出を行います。
作成したSecondServiceClientを呼出クラスへインジェクションし元々レスポンスを返していたメソッドへ SecondServiceClient を使用しSecond サービスの呼出を行います。

``` java:GreetResource.java
@Path("/greet")
@RequestScoped
public class GreetResource {
    @Inject
    @RestClient
    SecondServiceClient secondServiceClient;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public JsonObject getDefaultMessage() {
        // return createResponse("World");
        return secondServiceClient.getDefaultMessage();
    }

```

### 動作確認
ポート`8080`のMain サービスを実行しSecond サービスが呼ばれていることを確認します。

``` sh
$ curl http://localhost:8080/greet
{"message":"Hello From Second Service World!"}
```

## 分散トレーシングデータの確認
今回の処理は、コンソールからのリクエストがMain サービスを回しSecond サービスを呼び出すと言う構成になっています。
![キャプチャ009.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/a7cfb486-8631-3e24-a747-0b980869673d.jpeg)

このリクエストのトレーシングデータをJaeger UIで見てみると全体でそれだけの処理時間がかかり、処理の流れが確認できます。
![キャプチャ008.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52014/542e1d6a-14ca-3ebb-42e0-2dafa417c6b6.jpeg)



# まとめ
今回はEclipse MicroProfile OpenTracingを使用し分散トレーシングを試してみました。
分散システムを採用した場合に分散トレーシングの可視化が出来ている状態であれば、仮にシステムに障害や遅延が発生しても把握しやすいのではないでしょうか。

また、Eclipse MicroProfile には OpenTracing の他にもEclipse MicroProfile MetricsやEclipse MicroProfile Fault Toleranceなどの仕様が有り Helidon もそれらをサポートしています。現在JavaEEを使用しているアプリケーションからの移行など Spring Framework 以外の選択として検討する事が出来るのではないでしょうか。