Spring Cloudでマイクロサービスを構成する（５）：トレーシング編


Spring Cloudシリーズの第5回です。

### 概要

今回は「OpenTracing API」と「Jaeger（イェーガー）」を使ってリクエストをトレーシング（追跡）してみましょう。

![services.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/7b785297-c11d-4f81-fbe5-7146cdc50eba.png)

前回までで、上記の構成を作りましたが、コンポーネントやサービスが多くなったり、冗長化して負荷分散したりすると、１つのリクエスト（トランザクション）がどこのインスタンスを通り、それぞれどれだけの処理時間を要しているのかが、わかりにくくなります。
トレーシング技術を使うことによって、それらを可視化することができます。

### 環境

以下が動く環境を前提とします。

* docker & docker-compose
* JDK11

[前回までの投稿](https://qiita.com/takayoshi-shiraki/items/f0dc0919c0a721ebdb2b
)の続きですので、前回のゴール地点のソースを手元に用意し、それをベースとします。

### 作業ステップ

本記事では以下のステップで進めていきます。

1. Jaegarサービスの追加
1. トレーシングの設定追加（Dependencyの追加と設定ファイルの記述）
1. R2DBCのトレーシングの追加


### ステップ1：Jaegarサービスの追加

[Jaegerの全部入りdockerイメージ](https://www.jaegertracing.io/docs/1.20/getting-started/#all-in-one)が用意されていますので、docker-compose.ymlに追加するだけです。実にお手軽に開発環境を構築できますね。

```yml:docker-compose.yml
volumes:
  pgdata:
networks:
  default:
services:
  db:
    ・・・
  adminer:
    ・・・
  gw:
    ・・・
 　 ・・・
  jaeger:
    image: jaegertracing/all-in-one
    container_name: jaegar
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=9411
    ports:
      - 5775:5775/udp
      - 6831:6831/udp
      - 6832:6832/udp
      - 5778:5778
      - 16686:16686
      - 14268:14268
      - 9411:9411
    networks:
      - default
```

以下で起動・停止ができます。

```console
（起動）
$ docker-compose up -d jaeger
（停止）
$ docker-compose stop jaeger
```


### ステップ2：トレーシングの設定追加（Dependencyの追加と設定ファイルの記述）

今回もあまりやることがありません。２つだけ。

まず１つ目。pom.xmlにdependencyを追加します。

Spring Cloud Gateway には `opentracing-spring-jaeger-cloud-starter` を追加します。

```xml:gateway/pom.xml
	<dependencies>
                ・・・
		<dependency>
			<groupId>io.opentracing.contrib</groupId>
			<artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
			<version>3.2.0</version>
		</dependency>
                ・・・
	</dependencies>
```

Account には `opentracing-spring-jaeger-web-starter` を追加します。

```xml:account/pom.xml
	<dependencies>
                ・・・
		<dependency>
			<groupId>io.opentracing.contrib</groupId>
			<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
			<version>3.2.0</version>
		</dependency>
                ・・・
	</dependencies>
```

やること2つ目。
設定ファイル「application.yml」にJaegerへの接続設定を追加します。

ここではHTTPでJaegerにトレーシング情報を送信する設定を追加しています。
JaegerへのリクエストURLは環境変数で上書きできるようにしておきましょう。

```yml:gateway/src/main/resources/application.yml
server:
  ・・・
spring:
  ・・・
management:
  ・・・
eureka:
  ・・・
opentracing:
  jaeger:
    log-spans: false
    http-sender:
       url: ${JAEGER_HTTP_URL:http://localhost:14268/api/traces}
```

**account/src/main/resources/application.yml** にも上記と同様の「opentracing」の定義を追加します。

docker-compose.ymlで環境変数「JAEGER_HTTP_URL」を定義します。
docker-composeではサービス名をホスト名にしておくと、よしなに名前解決してくれるので、「jaeger」をホスト名に指定します。

```yml:docker-compose.yml
 ・・・

  gw:
    ・・・
    environment:
      ・・・
      - JAEGER_HTTP_URL=http://jaeger:14268/api/traces

  account:
    ・・・
    environment:
      ・・・
      - JAEGER_HTTP_URL=http://jaeger:14268/api/traces
  account2:
    ・・・
    environment:
      ・・・
      - JAEGER_HTTP_URL=http://jaeger:14268/api/traces

  account3:
    ・・・
    environment:
      ・・・
      - JAEGER_HTTP_URL=http://jaeger:14268/api/traces
```
ここまでで、とくに追跡コードなしに自動的にgateway-->accountの範囲でトレーシングができるようになります。
親切な世の中です。

```console
$ cd account
$ ./mvnw clean package -DskipTests=true
$ docker-compose up -d --build account
```

http://localhost:5000/api/account にアクセスした後に、
http://localhost:16686/ にアクセスし、リクエストを選択すると以下のように呼び出し階層が見えると思います。

![jaeger2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/35de3c09-e5a0-1949-a39f-89d348ecf0df.png)



### ステップ3：R2DBCのトレーシングの追加

R2DBCを使ったDBアクセスもトレーシングできるようにしてみましょう。
これでDBアクセスがどのタイミングでどのくらい処理時間がかかっているかが把握できるようになります。
R2DBC+Springbootで自動的にトレーシング情報を送信するコンポーネントはまだ存在しないようなので、現在公開されている情報を頼りに少し手作りします。

以下を行います。

* 【1】 r2dbc-proxyを使ったリスナーを用意し、R2DBCのConnectionとQuery実行のトレーシング情報を送信する。
    * 以下の公式サンプルを参考にし、Zipkin(?)のAPIをOpentracingのAPIに置き換える。
        * https://github.com/r2dbc/r2dbc-proxy#distributed-tracing
* 【2】 r2dbc-proxyと【1】のリスナーを有効化する。
    * ConnectionFactoryのセットアップとプロキシ／リスナーの追加を設定する。（ベタで）
    * 以下のプルリクエストと既存の[ConnectionFactory(R2DBC)のセットアップコード](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java)を参考にする。
        * https://github.com/spring-projects/spring-boot/pull/21689
        * なお、このautoconfigurationへのPRは、まだmasterに取り込まれていない（2020/10/05現在）

```text
spring-msa
├── account
│   └── src/main/java/org/example/account
│       ├── AccountApplication.java
│       ├── AccountConfiguration.java  <-- ★新規追加【2】
│       ├── controller
│       ├── entity
│       ├── repository
│       └── TracingExecutionListener.java  <-- ★新規追加【1】
├── db
├── docker-compose.yml
├── gateyway
└── sd 
```

【1】TracingExecutionListener

[R2DBCの公式サイトにあるExample](https://github.com/ttddyy/r2dbc-proxy-examples/blob/master/listener-example/src/main/java/io/r2dbc/examples/TracingExecutionListener.java) をベースにし、OpenTracing APIを使うように加工する。

ソースはこちら
[account/src/main/java/com/example/account/TracingExecutionListener.java](https://github.com/Piecemeal-Technology-Inc/spring-msa/blob/add_jaeger_trace/account/src/main/java/com/example/account/TracingExecutionListener.java)

【2】AccountConfiguration

```java:account/src/main/java/com/example/account/AccountConfiguration.java
package com.example.account;

import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import io.r2dbc.proxy.ProxyConnectionFactory;
import io.r2dbc.proxy.listener.LifeCycleListener;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBuilder;
import org.springframework.boot.autoconfigure.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;

@Configuration(proxyBeanMethods = false)
public class AccountConfiguration {

    @Bean
    public LifeCycleListener tracingExecutionListener() {
        return new TracingExecutionListener();
    }

    // https://github.com/spring-projects/spring-boot/pull/21689
    // 上記のPull Request が取り込まれたら、以下のコードは不要となる
    @Bean
    public ConnectionFactory connectionFactory(R2dbcProperties properties,
                                        ResourceLoader resourceLoader,
                                        LifeCycleListener tracingExecutionListener) {

        ConnectionFactory original = ConnectionFactoryBuilder
                .of(properties, () ->
                        EmbeddedDatabaseConnection.get(resourceLoader.getClassLoader())).build();
        ConnectionFactory proxyConnectionFactory =
                ProxyConnectionFactory.builder(original).listener(tracingExecutionListener).build();

        R2dbcProperties.Pool pool = properties.getPool();
        ConnectionPoolConfiguration.Builder builder
                = ConnectionPoolConfiguration.builder(proxyConnectionFactory)
                .maxSize(pool.getMaxSize()).initialSize(pool.getInitialSize()).maxIdleTime(pool.getMaxIdleTime());
        if (StringUtils.hasText(pool.getValidationQuery())) {
            builder.validationQuery(pool.getValidationQuery());
        }
        return new ConnectionPool(builder.build());
    }
}
```

さて、これでビルドして動かしてみましょう。

```console
$ cd account
$ ./mvnw clean package -DskipTests=true
$ docker-compose up -d --build account
```

http://localhost:5000/api/account にアクセスした後に、
http://localhost:16686/ にアクセスし、リクエストを選択すると以下のように呼び出し階層が見えると思います。
R2DBCのクエリ内容も見えてますね。

![jaeger3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534936/165dc975-aa70-5666-4a0a-8a9d6f327196.png)

### さいごに

これまでと同様に、ここまでのソースを以下に置いています。

https://github.com/Piecemeal-Technology-Inc/spring-msa/tree/add_jaeger_trace

さて、ここからはPrometheusを立てて、JVMやコンテナのモニタリングをしてみたいところですが、
この記事シリーズはいったんここまでとさせていただきます。

また別の記事を投稿予定ですので、そちらをお待ちください。
ではでは。