Elasticsearchを利用したjavaアプリの状態監視

# はじめに

Elastic Stack 6.3からAPMにJavaがサポートされたので(Betaですが)のアプリケーションパフォーマンスモニタリングについてちょっと調べてみた。

またSpring Bootのサンプルアプリケーションである[Spring PetClinic](https://github.com/spring-projects/spring-petclinic)の監視を行う。

# パフォーマンスモニタリング手法の比較

Javaのパフォーマンスモニタリングを行う方法は一般的には以下の2つ。

* Java Mission Control(jmc)などでのJMX接続
* [Java Fright Recoder(JFR)](https://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/toc.htm)

Elastic Stackを利用する場合はJavaにかかわる情報を取得できるのは以下の方法が考えられる。

* [APM](https://www.elastic.co/jp/solutions/apm)のJava Agent
* [Metricbeat](https://www.elastic.co/jp/products/beats/metricbeat)のJolokia module

それぞれの手法の比較

| 手法 | 記録方法 | 記録内容 | Javaオプションへの追加 | 備考 |
| --- | -------- | ------- | -------------------- | ---- |
| JMX| jmcで接続したPC | MBeanサーバで取得できる項目をユーザが設定 | JMXポートの開放とアクセス許可 | JDK同梱 |
| JFR | ローカルもしくはjmcでフライトレコーダーを起動したPC | JFRで記録される項目 | JFR記録用の起動オプション。jmcで記録する場合はJMXと同じ設定を追加 | |
| Elastic APM | APM Clientで取得しAPMサーバ経由でElasticsearchへ | Webアプリケーション向けの情報 | APMのjarやAPMサーバのアドレス | [APM ClientのAPI](https://www.elastic.co/guide/en/apm/agent/java/current/index.html) |
| Metricbeat | Jolokiaで取得しMetricbeatからElasticsearchへ | MBeanサーバで取得できる項目をユーザが設定 | Jolokiaのjarの追加、またはJMXポートの開放とアクセス許可 | [Jolokia](https://jolokia.org/index.html) |

# Kibana + Elasticsearch + APM Server の環境準備

Elastic社が出している公式のイメージを使って最低限の環境を準備。

https://www.docker.elastic.co/

```docker-compose.yml
version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.5.1
    volumes:
      - ./esdata:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
  kibana:
    image: docker.elastic.co/kibana/kibana:6.5.1
    ports:
      - 5601:5601
  apm:
    image: docker.elastic.co/apm/apm-server:6.5.1
    ports:
     - 8200:8200
```

# Spring PetClinicのビルド

今回はUbuntu Server 18.04のOpenJDK 11を利用してビルドを行う。
ただし、そのままではビルドできないのでひと手間加える。

```bash:terminal
sudo apt install default-jdk
git clone https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic
```

ひと手間

```xml:pom.xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    <configuration>
        <useSystemClassLoader>false</useSystemClassLoader>
    </configuration>
</plugin>
```

ビルド

```bash:terminal
./mvnw package
```

起動

```bash:terminal
java -jar target/*.jar
```

ブラウザにアクセスして画面が表示されればOK

![SpringPetClinic.png](https://qiita-image-store.s3.amazonaws.com/0/64655/1e9bac94-c4ec-8e80-8ebc-8a55c27abb01.png)

# APMを利用したモニタリング

## APMのダウンロード

```bash:terminal
curl -O https://search.maven.org/remotecontent?filepath=co/elastic/apm/elastic-apm-agent/1.0.1/elastic-apm-agent-1.0.1.jar
```

APM Clientをjavaの実行時引数に追加する。
詳細な設定方法は[公式](https://www.elastic.co/guide/en/apm/agent/java/current/application-server-setup.html)やKibanaのSetup Instructionsを確認すること。

```bash:terminal(追加例)
java -javaagent:elastic-apm-agent-1.0.1.jar -Delastic.apm.service_name=petclinic -Delastic.apm.application_packages=org.example -Delastic.apm.server_urls=http://localhost:8200 -jar target/*.jar
```

PetClinicが起動すればAPMの正しく起動している。
起動できない場合は、エラーメッセージがターミナルに出ているはずなのでエラーメッセージを確認する。

## 起動後の設定

KibanaからAPMのSetup Instructionsを選択して一番下のLoad Kibana objectsのボタンを押下する。
成功すれば、監視対象のデータが読み込まれる。

![apm_check.png](https://qiita-image-store.s3.amazonaws.com/0/64655/c1d5c03e-e2ac-e482-07b8-149ee1ca6af9.png)

## 取得できる情報

PetClinicの画面を適当に操作した結果のスクリーンショット

### APMのダッシュボード

![apm_dash_1.png](https://qiita-image-store.s3.amazonaws.com/0/64655/5dfe15da-d546-b7c6-5fbd-4c8b0d327e04.png)

### リクエストの処理状況

リクエストの平均処理時間の一覧が確認できる。
詳細が確認したいリクエストに関しては画面下部の表のURLをクリックすると詳細画面に遷移できる。

![apm_request.png](https://qiita-image-store.s3.amazonaws.com/0/64655/6147ca58-68b5-d7d5-bf1a-6ac91d5bef93.png)

### リクエストの詳細

各処理にかかる時間から、クエリの発行状況、リクエストのパラメータなど詳細が確認できる。

![apm_transaction_1.png](https://qiita-image-store.s3.amazonaws.com/0/64655/15aa4b7d-eccb-d75d-5658-3e5b93476f71.png)
![apm_transaction_2.png](https://qiita-image-store.s3.amazonaws.com/0/64655/30b92bef-978c-6030-5866-36fb044523d9.png)
![apm_transaction_3.png](https://qiita-image-store.s3.amazonaws.com/0/64655/7f4c6555-175a-8b25-6869-24f048292aec.png)

### Javaアプリケーションエラー

![apm_error.png](https://qiita-image-store.s3.amazonaws.com/0/64655/6e0925c3-c72b-55cc-cee8-73ce020640ad.png)
![apm_error_detail.png](https://qiita-image-store.s3.amazonaws.com/0/64655/3b250c04-8336-1c61-0a11-bdabbf41b72d.png)

# Metricbeatを用いたモニタリング

Metricbeatを利用したモニタリングの構成は2種類存在する。

1. JavaアプリケーションにJolokiaのJVM Agentサービスやwarを同居させる
1. JMXで接続しJolokiaのproxyモード経由でアクセスする

今回はそれぞれの解説を行う。
導入方法がそれぞれ異なるため、環境に合わせて選択してほしい。
例えば新規でサーバを構築する場合は、apmのjarとJolokiaのjarを起動パラメータにあらかじめ追加する。
既存のサービスに追加する場合は、既存のサービスに大きな影響を与えないために、既存のアプリケーションサーバにJMXで接続するJolokiaのproxyモードを利用して接続する。

概要は以下の通り。
前者に関してはJVM Angetのモードを利用する。warに関してはアプリケーションサーバにデプロイするだけなので今回は後者と近いので割愛。
後者に関しては軽量なWebアプリケーションサーバを立ち上げてそこにwarをデプロイする形で利用する。
また、どちらもmetricbeatは同一のサーバで動作させることとする。
※もしJolokiaとmetricbeatを異なるサーバで実行する場合は、Elasticsearchに格納する日付がmetricbeatが実行されるサーバとなるため注意が必要。

また、Jolokiaの設定方法やできることなどは[公式](https://jolokia.org/reference/html/index.html)を参考にしてほしい。

## Metricbeatの初期設定

```bash:terminal
curl -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.5.1-linux-x86_64.tar.gz
```

### metricbeat.ymlの設定

Elastic SearchのURLを設定する。

```yml:metricbeat.yml
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["localhost:9200"]
```

## Jolokiaの設定

### 1.JavaアプリケーションにJolokiaのJVM Agentサービスを同居させる場合

JolokiaのJVM Agentをダウンロードする。

```bash:terminal
curl -O http://search.maven.org/remotecontent?filepath=org/jolokia/jolokia-jvm/1.6.0/jolokia-jvm-1.6.0-agent.jar
```

### Javaオプション

```bash:terminal
java -javaagent:jolokia-jvm-1.6.0-agent.jar -jar target/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar
```

### Jolokiaの起動確認

末尾に/を忘れるとJSONのレスポンスが返ってこない

```bash:terminal
curl http://localhost:8778/jolokia/ | jq
```

### metricbeatのJolokia jvm agent向けの設定

Jolokiaのモジュールを有効化する。
今回は説明のため、sytemモジュールの情報は不要なので無効化している。

```bash:terminal
./metricbeat modules disable system
./metricbeat modules enable jolokia
```

### Jolokia.ymlの設定

今回はJVMの起動時間と、Heapメモリの使用率を取得している。
取得したい情報はjmcなどで実際に接続して確認して選ぶこと。

```yml:module.d/jolokia.yml
- module: jolokia
  metricsets: ["jmx"]
  period: 10s
  hosts: ["localhost:8778"]
  path: "/jolokia/?ignoreErrors=true&canonicalNaming=false"
  namespace: "metrics"
  jmx.mappings:
    - mbean: 'java.lang:type=Runtime'
      attributes:
        - attr: Uptime
          field: uptime
    - mbean: 'java.lang:type=Memory'
      attributes:
        - attr: HeapMemoryUsage
          field: memory.heap_usage
        - attr: NonHeapMemoryUsage
          field: memory.non_heap_usage
```

### Jolokiaモジュールの動作確認

```bash:terminal
./metricbeat test modules
```

### metricbeatの起動と動作の確認

metricbeatのサービス化とKibanaの使用方法については割愛

```bash:terminal
./metricbeat
```

![heap_usage.png](https://qiita-image-store.s3.amazonaws.com/0/64655/2b73f885-aded-8920-03d4-2a42cbe6c69c.png)


## 2.JMXで接続しJolokiaのproxyモード経由でアクセスする

設定が多いので以下の順番で行う。

 * アプリケーションサーバのJMXの有効化
 * Jolokiaのproxyモードを利用したアプリケーションサーバへの接続

### アプリケーションサーバのJMXの有効化

今回は認証等のセキュリティ設定は行わなずに最低限の起動パラメータを追加した。
metricbeatのJolokiaモジュールはユーザIDとパスワードによる認証は対応している。

```bash:termainl
java -Dcom.sun.management.jmxremote.port=7091 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar target/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar
```

JMCを利用して接続を確認する。

![jmc.png](https://qiita-image-store.s3.amazonaws.com/0/64655/32e1f1e8-84d9-e1a6-0576-8d1a08b3174c.png)

### Jolokiaのproxyモードを利用したアプリケーションサーバへの接続

Jolokiaのプロキシモードを利用してJMXで対象のJavaアプリケーションに接続する方法
技術的な詳細や詳細な設定内容は[こちら](https://jolokia.org/reference/html/proxy.html)

#### jettyサーバの設定

jettyサーバのダウンロードとポート番号等の変更を行う。
今回は　PetClinicが8080ポートを利用しているため8081ポートに変更する。
jettyのバージョンは必要に応じて[公式](https://www.eclipse.org/jetty/)から選択すること

```bash:terminal
curl -O https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-distribution/9.4.14.v20181114/jetty-distribution-9.4.14.v20181114.tar.gz
tar xf jetty-distribution-9.4.14.v20181114.tar.gz
cd jetty-distribution-9.4.14.v20181114/
```

```ini:start.ini
## Connector port to listen on
jetty.http.port=8081
```

次にJolokiaのproxyモードを利用して接続する場合はオプションを追加する必要がある。
今回は必要な設定をweb.xmlに追記する。
また、今回の構成では認証機能は有効にできないため無効化する。

```bash:terminal
cd webapps/
curl -O http://search.maven.org/remotecontent?filepath=org/jolokia/jolokia-war/1.6.0/jolokia-war-1.6.0.war
mv jolokia-war-1.6.0.war jolokia.war
mkdir jolokia
cd jolokia
jar xf ../jolokia.war 
```
servletタグの中に以下の設定を追加する。

```xml:WEB-INF/web.xml
<servlet>
  <init-param>
    <param-name>dispatcherClasses</param-name>
    <param-value>org.jolokia.jsr160.Jsr160RequestDispatcher</param-value>
  </init-param>
  ...
</servlet>
```

認証機能に関するところを無効化する。

```xml:WEB-INF/web.xml
<web-app>
  ...
  <!--
  Security enabled by default. Please update to match you specific security setup (e.g. the auth-method)
  -->
  <!--
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>jolokia</realm-name>
  </login-config>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Jolokia-Agent Access</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>jolokia</role-name>
    </auth-constraint>
  </security-constraint>

  <security-role>
    <role-name>jolokia</role-name>
  </security-role>
  -->
</web-app>
```

### Jolokiaサーバの起動確認

末尾に/を忘れるとJSONのレスポンスが返ってこない

```bash:tarminal
curl -O http://localhost:8081/jolokia/ | jq
```

### Jolokiaサーバのproxyモードによる接続の確認

```bash:tarminal(リクエスト)
curl -X POST -H 'Content-Type:application/json' -d '{"type":"read","mbean":"Tomcat:type=Server", "attribute" : "serverInfo", "target" : { "url": "service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi" } }' http://localhost:8081/jolokia/ | jq
```

```json:terminal(リザルト例)
{
  "request": {
    "mbean": "Tomcat:type=Server",
    "attribute": "serverInfo",
    "type": "read",
    "target": {
      "url": "service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi"
    }
  },
  "value": "Apache Tomcat/9.0.12",
  "timestamp": 1543418665,
  "status": 200
}
```

設定が不足している場合はエラーメッセージが出力されるのでその内容を見て修正する。

### metricbeatのJolokia proxyモードの設定

Jolokiaのモジュールを有効化する。
今回は説明のため、sytemモジュールの情報は不要なので無効化している。

```bash:terminal
./metricbeat modules disable system
./metricbeat modules enable jolokia
```

### jolokia.ymlの設定

今回はJVMの起動時間と、Heapメモリの使用率を取得している。
取得したい情報はjmcなどで実際に接続して確認して選ぶこと。
また、本当にリモートから取得できているかを確認するためにテスト用にTomcatのバージョンを取得するMBeanも追加している。

```yml:module.d/jolokia.yml
- module: jolokia
  metricsets: ["jmx"]
  period: 10s
  hosts: ["localhost:8081"]
  path: "/jolokia/?ignoreErrors=true&canonicalNaming=false"
  namespace: "metrics"
  jmx.mappings:
    - mbean: 'java.lang:type=Runtime'
      attributes:
        - attr: Uptime
          field: uptime
      target:
        url: 'service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi'
    - mbean: 'java.lang:type=Memory'
      attributes:
        - attr: HeapMemoryUsage
          field: memory.heap_usage
        - attr: NonHeapMemoryUsage
          field: memory.non_heap_usage
      target:
        url: 'service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi'
    - mbean: 'Tomcat:type=Server'
      attributes:
        - attr: serverInfo
          field: tomcat.serverInfo
      target:
        url: 'service:jmx:rmi:///jndi/rmi://localhost:7091/jmxrmi'
```

### Jolokiaモジュールの動作確認

```bash:terminal
./metricbeat test modules
```

ここから先は前述の動作確認を同じなので割愛。

# おわりに

apm agentを利用すると簡単にWebアプリケーションのメトリックを取得することができた。
マイクロサービスととても相性が良いと感じた。
新規で作成するWebアプリケーションに関しては導入を検討するべき。

metricbeatのJolokiaモジュールはMBeanを利用した柔軟なデータ取得が魅力的である。
Webアプリケーション以外にも応用が利く。
また、JMXさえ準備できていれば既存のサービスのメトリックも取得できる。