EC2で動作するKeycloakのJVMメトリクスをcloudwatchに送信する

株式会社GxPの@rasahina-gxpです。
本記事はグロースエクスパートナーズアドベントカレンダーの21日目の記事です。

皆さんKeycloak使っていますでしょうか？
私の所属する部署でSSOを実現する場合、プロバイダとしてはKeycloakを利用することが多いです。
普段はSSOのクライアント側としてSpringBootでKeycloakAdapterを利用しての開発が多いのですが、今回KeycloakのJVMメトリクスを取得する作業を実施しましたのでご紹介させて頂きます。

# Keycloakとは
Wildflyベースで作られているオープンソースのアイデンティティ管理ソフトウェアです。[公式サイト](https://www.keycloak.org)
詳細は[こちらの記事](https://qiita.com/daian183/items/30f01e162e03567ff21b)がわかりやすいのでご参照ください。
前述の様にSpringBoot向けにクライアントアダプターが用意されているように、
様々な言語、フレームワーク向けに[クライアントアダプター](https://keycloak-documentation.openstandia.jp/master/ja_JP/securing_apps/index.html#%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0)が用意されています。

# KeycloakのJVMメトリクス取得方法検討
ざっと調べてみた結果以下のメトリクス取得が考えられます。(SaaSを除く)

| 方法 | 特徴 |
|:-----------|:------------|
| Prometheusエクステンション|公式でも紹介されている[エクステンション](https://www.keycloak.org/extensions.html)を追加することで利用可能|
| Wildfly設定ファイルで有効化|設定ファイルを変更することで利用可能|
| Jolokiaをエージェントとして利用|Keycloakに手を入れる必要がなく、Keycloakの外側から利用可能|

どれを採用するかについて今回の要件を踏まえて以下の様に判断しました。
### Prometheusエクステンション利用
Keycloak公式サイトにも記載されているエクステンションのため、安心感が高いです。
また、エクステンションを追加してからメトリクスを見れるようになるまでの手間が少ないです。(参考：https://qiita.com/t-mogi/items/3bac5e45afec8d5b4ed2)
ですが、今回メトリクス取得対象のKeycloakが存在する環境にはPrometheusが存在せず、Cloudwatchにてメトリクスを集中監視しているため、今回は対象外となります。
### Wildfly設定ファイルで有効化
standalone.xmlなどで管理エンドポイントを有効化するだけです。
JConsoleではあっという間に接続できるようになるのですが、
jmx取得のクライアントツール([Command-line JMX Client](http://crawler.archive.org/cmdline-jmxclient/)、[Jmxterm](https://docs.cyclopsgroup.org/jmxterm)等)ではそのまま取得できず、一工夫必要です。
また、監視対象Keycloakの構築はお客様にて行われているということもあり、Keycloak自体にはなるべく手を入れたくありません。
従ってこの手法については今回はデメリットとなるため、対象外とします。
### Jolokiaエージェント利用
JolokiaはJMXメトリクスをHTTPで取得できるように公開するツールです。cURL等でアクセスすることでJSONにてメトリクスを取得することができます。
Keycloakの設定を変更することなく、JVMメトリクスを利用できるようになるものポイントです。


ということで今回は要件に最もマッチするJolokiaを利用する方法をご紹介したいと思います。

# 設定手順
## 前提
Keycloakのバージョンは7.0.0です。
ダウンロードし、stanndaloneモードで起動しておきます。[^1]　
参考：https://qiita.com/ryota_i/items/69ded3fd950c7f9b70f5
また、サーバーにAWS CLIがインストールされている必要があります。
参考：https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html

## Jolokiaダウンロード
[こちら](https://jolokia.org/download.html)のページからJVM-Agentをダウンロードします。

## Jolokia起動
Jololiaを起動するには二つの方法があります。
#### 方法1:対象のプロセスIDを指定してプロセスにアタッチ
以下のコマンドを実行すると、アタッチ可能なJavaプロセスが一覧で表示されます。

``` 
$ java -jar jolokia-jvm-1.6.2-agent.jar list
23997   jolokia-jvm-1.6.2-agent.jar list
23838   /opt/oss/keycloak-7.0.0/jboss-modules.jar -mp /opt/oss/keycloak-7.0.0/modules org.jboss.as.standalon
```
続いて対象のプロセスIDをパラメータとして再度実行します。

```
$ java -jar jolokia-jvm-1.6.2-agent.jar start 23838
Couldn't start agent for PID 23838
Possible reason could be that port '8778' is already occupied.
Please check the standard output of the target process for a detailed error message.
```
8778が埋まっているのでスタートできないというエラーメッセージが出力されていますが、
Keycloak側のログには以下が出力されていますので、jolokiaの起動には成功しています。

```
02:50:55,576 INFO  [stdout] (JolokiaStart) I> No access restrictor found, access to any MBean is allowed
02:50:56,803 INFO  [stdout] (JolokiaStart) Jolokia: Agent started with URL http://127.0.0.1:8778/jolokia/
```

ではcURLでJVMメトリクスを取得してみましょう。
アクセス方法の詳細については[こちら](https://jolokia.org/reference/html/index.html)をご参照ください。

```
# ヒープ利用量の情報を取得
$ curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage | jq
{
  "request": {
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": {
    "init": 268435456,
    "committed": 705691648,
    "max": 954728448,
    "used": 293532344
  },
  "timestamp": 1608432046,
  "status": 200
}
```
パスに属性を付けることで取得する結果を絞ることもできます。

```
# ヒープ利用量の最大値を取得
$ curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/max | jq
{
  "request": {
    "path": "max",
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": 954728448,
  "timestamp": 1608432086,
  "status": 200
}
```

#### 方法2:JAVA_OPTSに-javaagentとして指定してアタッチ
JVMメトリクス取得対象アプリを実行する際の環境変数`JAVA_OPTS`に`-javaagent`として指定することでもJolokiaを対象プロセスにアタッチすることができます。
(Jolokiaのポートに外部からアクセスできないようにセキュリティグループやファイアーウォールで制御する必要があります。)

```
$ JAVA_OPTS="$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS -javaagent:/home/centos/jolokia-jvm-1.6.2-agent.jar=port=8778,host=127.0.0.1" \
./standalone.sh
JAVA_OPTS already set in environment; overriding default settings with values:  -Djboss.modules.system.pkgs= -javaagent:/home/centos/jolokia-jvm-1.6.2-agent.jar=port=8778,host=127.0.0.1
=========================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /opt/oss/keycloak-7.0.0

  JAVA: java

  JAVA_OPTS:  -server  -Djboss.modules.system.pkgs= -javaagent:/home/centos/jolokia-jvm-1.6.2-agent.jar=port=8778,host=127.0.0.1

=========================================================================

02:59:36,102 INFO  [org.jboss.modules] (main) JBoss Modules version 1.9.1.Final
I> No access restrictor found, access to any MBean is allowed
Jolokia: Agent started with URL http://127.0.0.1:8778/jolokia/
～～省略～～
02:59:48,945 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 7.0.0 (WildFly Core 9.0.2.Final) started in 13262ms - Started 589 of 884 services (601 services are lazy, passive or on-demand)
```
方法1と同じようにJolokia起動のメッセージが表示されていますね。
ポイントは`-Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS`を指定する必要があるところです。
こちらを指定しないで`-javaagenet`を指定すると、logmanager関連の問題が発生しKeycloakが起動しません。
同じようにcURLで取得してみましょう。

```
$ curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage | jq
{
  "request": {
    "mbean": "java.lang:type=Memory",
    "attribute": "HeapMemoryUsage",
    "type": "read"
  },
  "value": {
    "init": 62914560,
    "committed": 361758720,
    "max": 883949568,
    "used": 136322976
  },
  "timestamp": 1608433444,
  "status": 200
}
```
こちらも同じように取得できました。

## 取得できるメトリクスの一覧
`/jolokia/list`にアクセスすることで取得できるメトリクスの一覧が取得できます。

```
$ curl -s http://localhost:8778/jolokia/list | jq
# 取得結果はかなり多いので注意が必要
```

## メトリクスをCloudwatchに送信する
ここまで来ると後はCloudwatchに送信するだけです。
弊社メンバー@ttanaka-gxpによる[2020年アドベントカレンダーの17日目の記事](https://qiita.com/ttanaka-gxp/items/4958ed2e14a40d076109)を参考にスクリプトを書きます。

``` send-metrics.sh
#!/bin/bash

function put_metics_data() {
    aws cloudwatch put-metric-data --dimensions $1 --timestamp $TIMESTAMP --namespace Jmx --metric-name JmxHeapMemoryUsageCommitted --value $HEAPUSAGE_COMMITED --unit Bytes
    aws cloudwatch put-metric-data --dimensions $1 --timestamp $TIMESTAMP --namespace Jmx --metric-name JmxHeapMemoryUsageUsed --value $HEAPUSAGE_USED --unit Bytes
    aws cloudwatch put-metric-data --dimensions $1 --timestamp $TIMESTAMP --namespace Jmx --metric-name JmxNonHeapMemoryUsageCommitted --value $NONHEAPUSAGE_COMMITED --unit Bytes
    aws cloudwatch put-metric-data --dimensions $1 --timestamp $TIMESTAMP --namespace Jmx --metric-name JmxNonHeapMemoryUsageUsed --value $NONHEAPUSAGE_USED --unit Bytes
}

# Set environment variables
INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)

# jolokiaからメトリクスを取得する
# Heap
HEAPUSAGE_COMMITED=`curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/committed | jq '.value'`
HEAPUSAGE_USED=`curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/used | jq '.value'`

# NonHeap
NONHEAPUSAGE_COMMITED=`curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/NonHeapMemoryUsage/committed | jq '.value'`
NONHEAPUSAGE_USED=`curl -s http://localhost:8778/jolokia/read/java.lang:type=Memory/NonHeapMemoryUsage/used | jq '.value'`

# メトリクスをCloudwatchに送信する
put_metics_data $INSTANCE_ID

```
このスクリプトを定期的に実行することでCloudwatchにメトリクスを送信することができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/963532/65b66ecc-9062-3722-4b31-bad025448391.png)

# まとめ
Jolokiaを使用してシンプルにKeycloakのJVMメトリクスを取得する方法を紹介させて頂きました。
Prometheusが使える環境ではPrometheusエクステンションを使用することを第一に考え、Prometheusを利用できない場合は上記のような方法でメトリクスを取得するのが良いかと思われます。

[^1]: 通常本番環境ではstandaloneモードは使用しません。

# 参考
WildFlyのJMXへスクリプトからアクセスする
https://nekop.hatenablog.com/entry/20140414/1397464827

LogstashからWildflyのJMX情報を取得する
https://qiita.com/mkyz08/items/928ff9089ce971470ef0

俺の Jolokia チートシート
https://cloudpack.media/10332