AWS X-RayをJavaで色々動かして試してみる

# 前提条件
X-Ray初心者向け。なんとなくは知ってるけど、具体的に何がどこまでできて、どうやって実装するかを色々試してみながら確認した記事。

環境としては、Spring Boot＋MavenでWebアプリを実装している。依存関係の解決方法以外はGradleも同じはず。


# X-RayのDockerコンテナイメージを作る。
まずは、適当なEC2とかでコンテナイメージを作る。
今回は、AWSのサンプルをベースに作ってみよう。

```
$ git clone https://github.com/aws-samples/aws-xray-fargate
```

buildspec.yml に書いてあるコマンドを、実行していってみる。

```
$ aws ecr get-login --no-include-email --region ap-northeast-1
```

でログイン情報を取得後、実行。```ap-northeast-1```は利用しているリージョンを指定する。

```
$ docker build -t xray:latest .
```

作ったコンテナイメージは正常性確認をしてみよう。以下を参考にする。
それぞれのdocker runのオプションの意味も記載されているので読んでみると良い。

【公式】[ローカルで X-Ray デーモンを実行する](https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-daemon-local.html)

```
$ docker run \
  --attach STDOUT \
  -v ~/.aws/:/root/.aws/:ro \
  --net=host \
  -e AWS_REGION=ap-northeast-1 \
  --name xray \
  -p 2000:2000/udp \
  xray -o
```

あと、EC2のロールに```AWSXRayDaemonWriteAccess```のIAMポリシをつけておく。

# アプリケーションのX-Ray対応
## 受信リクエスト対応
以下の開発者ガイドを参考にしながら、アプリケーション側でX-Rayのログをデーモンに送るようにする。

【公式】[AWS X-Ray SDK for Java](https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-java.html)

受信リクエストの対応をするだけなら簡単なようだ。```WebConfig.java```なクラスを以下の様に実装する。```ApigwTest```は、サンプルで作ったWebアプリの名前。

```Java:WebConfig.java
package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import javax.servlet.Filter;
import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter;

@Configuration
public class WebConfig {

  @Bean
  public Filter TracingFilter() {
    return new AWSXRayServletFilter("ApigwTest");
  }
}
```

X-RayはAWSのSDKを使う必要があるため、pom.xmlに以下を記述して依存関係を解決する。

```xml:pom.xml
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>com.amazonaws</groupId>
				<artifactId>aws-xray-recorder-sdk-bom</artifactId>
				<version>2.4.0</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>com.amazonaws</groupId>
			<artifactId>aws-xray-recorder-sdk-core</artifactId>
		</dependency>
	</dependencies>
```


さて、このアプリを起動してアクセスすると、↓こんな感じでトレースができるようになる。

![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/6705eb9a-f3e2-ce1f-5e47-1659e69823d0.png)

## 詳細情報の取得
さらに、受信リクエストを受けたノードの情報を取得できるように、↑で作った```WebConfig```クラスに以下のコードを入れてみる。

```
import com.amazonaws.xray.AWSXRay;
import com.amazonaws.xray.AWSXRayRecorderBuilder;
import com.amazonaws.xray.plugins.EC2Plugin;
import com.amazonaws.xray.plugins.ECSPlugin;

  static {
	  AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard().withPlugin(new EC2Plugin()).withPlugin(new ECSPlugin());
	  
	  AWSXRay.setGlobalRecorder(builder.build());
  }
```

これをEC2上で実行した場合は、以下のように、サービスマップにEC2インスタンスであることが表示されるようになる。

![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/0f666fc1-2371-54f3-242e-a614225e52fc.png)

## ダウンストリームのトレース対応
さらに、HTTPクライアントとして、ダウンストリームのWebサービスの情報も取得する場合は、コントローラの中に↓こんなのを入れてみる。

```Java
import com.amazonaws.xray.proxies.apache.http.HttpClientBuilder;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
(中略)
	public int HttpClient(String URL) throws IOException {
		int statusCode = 500;
		
		CloseableHttpClient httpclient = HttpClientBuilder.create().build();
		HttpGet httpGet = new HttpGet(URL);
		CloseableHttpResponse clientResponse = httpclient.execute(httpGet);

		try {
			statusCode = clientResponse.getStatusLine().getStatusCode();
		} finally {
			clientResponse.close();
		}
		
		return statusCode;
	}
```

また、```com.amazonaws.xray.proxies.apache.http.HttpClientBuilder```をインポートするために、pom.xmlにも以下の依存関係を追記する。

```XML:pom.xml
	<dependencies>
		<dependency>
			<groupId>com.amazonaws</groupId>
			<artifactId>aws-xray-recorder-sdk-apache-http</artifactId>
		</dependency>
	</dependencies>
```

すると、以下のようにダウンストリームの情報も取得できるようになる。

![キャプチャ8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/7b2e1f70-d167-cff1-938a-0ad836004738.png)

ちなみに、このダウンストリームのサービスはLambda関数で実装していたので、ふと気になってLambda関数側でX-Rayの設定を有効化すると、↓こんな感じで、重複した要素は取得しなくなった。賢い。

![キャプチャ9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/e521b9ed-2291-0dde-c0de-b3892213141f.png)

# ECS on Fargateで動かす
まずは、ここまでで動作確認したX-RayのコンテナをECRにPUSHする。
事前にxrayのリポジトリを作っておくのを忘れないように。

```
$ docker tag xray:latest [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latest
$ docker push [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latest
```

また、ここでもECSのタスクロールとタスク実行ロールに```AWSXRayDaemonWriteAccess```を付与しておく。

コンテナのタスク定義に、↑でPUSHしたxrayのイメージを入れる。
CloudFormationテンプレートで言えば、```ContainerDefinitions```内で、既存のコンテナ定義に並べて以下の定義を書く。awsvpcを使わないECS on EC2の場合は他にも設定が必要だが、Fargateの場合は、awsvpcで動くので、簡単に加えられるようだ。

```YAML
    - Name: X-Ray-Daemon
      Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/xray:latest
      Cpu: 32
      MemoryReservation: 256
      PortMappings:
        - ContainerPort: 2000
          Protocol: udp
```

Lambda側のアクティブトレースを有効化すると、EC2と同様に取得することができた。

![キャプチャ10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/5a30004b-30b7-9a2a-31b3-72f2acfe1ced.png)

ただし、Lambda側のアクティブトレースを無効にすると、リクエストに対するトレースは取得できたが、ダウンストリームのトレースが取得できなかった。何かまだ足りていない設定があるのかもしれない。