AWS X-RayでSolrの検索処理をトレースする


AWS X-Ray(プレビュー版)を使ってSolrの検索処理をトレースしてみる。
Solrへリクエストを投げるところからトレースを開始する。

# システム構成

## サーバ

使用するインスタンスは2台。
Amazon Linuxを使う。

- Webサーバ(express/node.js - t2.micro)
- 検索エンジン(Solr/Jetty - t2.medium)

## サービスマップ

こんな感じになる。
`localhost`と書いてあるけどSolrへのリクエスト。

![servicemap.png](https://qiita-image-store.s3.amazonaws.com/0/379/80fc29d8-4299-d397-8ad2-f8b9e4c87904.png "servicemap.png")

# サーバの構築

## 共通

### セキュリティグループ

ローカルマシン→Webサーバの80番ポートは開けておく。
Webサーバ→検索エンジンの80番ポートも開けておく。
ローカルマシン→Solrにアクセスするならそちらの8983番ポートも開けておく。

### IAMロール/インスタンスプロファイル

後述のX-RayデーモンがAWS X-Rayサービスにデータを送るので書き込み権限を設定してあげる。
インスタンスに関連付けるIAMロールを適当に作り、AWSマネージドポリシーから`AWSXrayWriteOnlyAccess`ポリシーをアタッチする。
ポリシーの内容はこんな感じ。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords"
               
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

### X-Rayデーモン

X-Rayデーモンのインストール。
もう起動してる。気が早い。

```
$ curl -sL https://s3.amazonaws.com/aws-xray-assets.us-east-1/xray-daemon/aws-xray-daemon-1.x.rpm -o /home/ec2-user/xray.rpm
$ sudo yum install -y xray.rpm
$ sudo initctl status xray
xray start/running, process 2607
```

インストール内容はこんな感じ。
xrayデーモンはGolang製の模様。
systemdの設定もあるようだがデフォルトだとUpstartで起動している。

```
$ rpm -ql xray
/etc/amazon/xray/cfg.yaml
/etc/init/xray.conf
/etc/systemd/system/xray.service
/usr/bin/xray
```

設定はデフォルトのままでいじらず。

## Webサーバ

検索エンジンのIPアドレスを`xray-solr`としてを`/etc/hosts`に登録した。

### node.js

`epel`からインストールすると0.10系になるので最新版になる方法でインストールする。
レポジトリを登録。

```
$ curl -sL https://rpm.nodesource.com/setup_7.x | sudo bash -

## Installing the NodeSource Node.js 7.x repo...


## Inspecting system...

+ rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release || rpm -q --whatprovides sl-release
+ uname -m

## Confirming "el7-x86_64" is supported...

+ curl -sLf -o /dev/null 'https://rpm.nodesource.com/pub_7.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Downloading release setup RPM...

+ mktemp
+ curl -sL -o '/tmp/tmp.OHBteTsWWV' 'https://rpm.nodesource.com/pub_7.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Installing release setup RPM...

+ rpm -i --nosignature --force '/tmp/tmp.OHBteTsWWV'

## Cleaning up...

+ rm -f '/tmp/tmp.OHBteTsWWV'

## Checking for existing installations...

+ rpm -qa 'node|npm' | grep -v nodesource

## Run `yum install -y nodejs` (as root) to install Node.js 7.x and npm.
## You may also need development tools to build native addons:
##   `yum install -y gcc-c++ make`
```

v7.6.0をインストール。

```
$ sudo yum install -y nodejs
$ node -v
v7.6.0
```

### yarn

`npm`は`package.json`を管理する気があるのかないのか分からないので`yarn`をインストールする。

```
$ sudo curl -sL https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
$ sudo yum install -y yarn
```

### express

`/home/ec2-user/web`以下にプロジェクトを作った。

```
$ yarn init -y
$ yarn add express aws-sdk aws-xray-sdk
```

ここまでで`package.json`はこんな感じ。

```json
{
  "name": "web",
  "version": "1.0.0",
  "main": "index.js",
  "repository": {},
  "license": "MIT",
  "dependencies": {
    "aws-sdk": "^2.12.0",
    "aws-xray-sdk": "^1.0.4-beta",
    "express": "^4.14.1"
  }
}
```

リクエストが来たら検索エンジンで検索するWebアプリを書く。

```js:/home/ec2-user/web/index.js
const express = require('express');
const app = express();

const AWSXRay = require('aws-xray-sdk');
AWSXRay.config([AWSXRay.plugins.EC2]);

const http = require('http');
AWSXRay.captureHTTPs(http);


app.use(AWSXRay.express.openSegment('xray-web'));

app.get('/', (req, res) => {
  const options = {
    hostname: 'xray-solr',
    port: 8983,
    path: '/solr/xray/select?indent=on&q=*:*&wt=json'
  };
  http.get(options, (response) => {
    let data = '';
    response.on('data', (chunk) => data += chunk);
    response.on('end', () => {
      res.send(data);
    });
  });
});

app.use(AWSXRay.express.closeSegment());


app.listen(80);
```

EC2プラグインを指定するとAWS X-Rayに渡される情報にインスタンス情報が付加される。
今のところインスタンスIDとAZのみ。

```js
AWSXRay.config([AWSXRay.plugins.EC2]);
```

サンプリングルールの設定も出来る模様。

```js
AWSXRay.setSamplingRules('sampling-rules.json');
```

公式ドキュメントに記載されている形式だとエラーで動かないので下記のような形式にすると動いた。
ただ`Default`の設定しか反映されないので、どこか間違っていそう。
毎秒`fixed_target`数のリクエストをトレース、その後は`rate`の割合でトレース。

```json:sampling-rules.json
{
  "rules": {
    "1": {
      "service_name": "*",
      "http_method": "*",
      "url_path": "*",
      "fixed_target": 10,
      "rate": 0.1
    },
    "Default": {
      "fixed_target": 10,
      "rate": 0.1
    }
  }
}
```

express用のミドルウェアが用意されているので使わせて頂く。
これでWebサーバに来たリクエストをトレース出来る。

```js
app.use(AWSXRay.express.openSegment('xray-web'));
  :
app.use(AWSXRay.express.closeSegment());
```

`http`や`https`を使った通信をトレースすることが出来る。
内部的に`http.request`を置き換えている。
これで検索エンジンとの通信をトレース出来る。

```js
AWSXRay.captureHTTPs(http);
```

`http.get('http://.../')`のようにURLを直接渡すと`http://localhost/`へのリクエストが無限ループになって死ぬ。
接続先が未知の場合はlocalhostに設定されるのはnode.jsの仕様らしい。誰が喜ぶんだこれ。
今のところ`options`はハッシュで渡されることが期待されている。

```js
const options = {
  hostname: 'xray-solr',
  port: 8983,
  path: '/solr/xray/select?indent=on&q=*:*&wt=json'
};
http.get(options, (response) => {...});
```

## 検索エンジン

自分のIPアドレスを`xray-solr`としてを`/etc/hosts`に登録した。

### node.js

SolrへのプロキシとしてWebサーバと同様に node.js + express をインストールした。
コードはWebサーバとほぼ同じだが、検索エンジンのクライアントは色々ある前提でDynamic Naming Modeにしてみた。
`defaultName`を与えないとエラーになった。

```js
AWSXRay.middleware.enableDynamicNaming();
app.use(AWSXRay.express.openSegment('defaultName'));
```

### java

1.8以上が必要らしい。

```
$ sudo yum install java-1.8.0-openjdk
$ sudo alternatives --config java

2 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:2
```

### Solr

`/home/ec2-user/solr/`以下にSolrをインストールした。

```
$ curl -sL 'http://ftp.jaist.ac.jp/pub/apache/lucene/solr/6.4.1/solr-6.4.1.tgz' -o /home/ec2-user/solr.tgz
$ tar zxf solr.tgz
$ mv solr-6.4.1 solr
```

デフォルトの8983番ポートで起動。

```
$ cd solr
$ ./bin/solr start
```

コアを新規作成する。
solrコマンドのヘルプは`./bin/solr create_core -help`などと打てば見られる。

```
$ ./bin/solr create_core -c xray -d sample_techproducts_configs
```

### Servlet Filter

GradleやMavenでのビルド方法がよく分からないので、とりあえず必要そうなJARを持ってきてぶち込んだ。

```
$ curl -sL 'http://central.maven.org/maven2/com/amazonaws/aws-xray-recorder-sdk-core/1.0.4-beta/aws-xray-recorder-sdk-core-1.0.4-beta.jar' -o aws-xray-recorder-sdk-core-1.0.4-beta.jar
$ curl -sL 'http://central.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.91/aws-java-sdk-core-1.11.91.jar' -o aws-java-sdk-core-1.11.91.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.6/jackson-databind-2.8.6.jar' -o jackson-databind-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.8.6/jackson-dataformat-cbor-2.8.6.jar' -o jackson-dataformat-cbor-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/commons-logging/commons-logging/1.2/commons-logging-1.2.jar' -o commons-logging-1.2.jar
$ curl -sL 'http://central.maven.org/maven2/joda-time/joda-time/2.9.7/joda-time-2.9.7.jar' -o joda-time-2.9.7.jar
$ curl -sL 'http://central.maven.org/maven2/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar' -o ion-java-1.0.2.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.8.6/jackson-annotations-2.8.6.jar' -o jackson-annotations-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.6/jackson-core-2.8.6.jar' -o jackson-core-2.8.6.jar
$ mv *.jar /home/ec2-user/solr/server/solr-webapp/webapp/WEB-INF/lib/
```

`/home/ec2-user/solr/server/solr-webapp/webapp/WEB-INF/web.xml`に`AWSXRayServletFilter`の設定を追加。
`SolrRequestFilter`の手前に入れた。

```xml:web.xml
<filter>
  <filter-name>AWSXRayServletFilter</filter-name>
  <filter-class>com.amazonaws.xray.javax.servlet.AWSXRayServletFilter</filter-class>
  <init-param>
    <param-name>fixedName</param-name>
    <param-value>xray-solr</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>AWSXRayServletFilter</filter-name>
  <url-pattern>/solr/*</url-pattern>
</filter-mapping>
```

# リクエストとトレースの確認

node.jsとSolrを起動して`xray-web`にリクエストを投げてみる。
トレース結果は下記のようになった。

![trace.png](https://qiita-image-store.s3.amazonaws.com/0/379/1d3bfc63-9114-e858-1675-33cd21fb7b20.png "trace.png")

時間がずれるとこううまく表示されないのが気持ち悪い。
なにが悪いのか・・・

![trace2.png](https://qiita-image-store.s3.amazonaws.com/0/379/784f74eb-5d40-e0ac-6f4b-5cdd3a02d69f.png "trace2.png")