のJMX restConnectorではなくmpMetricsを使用した版。こちらの方が、認証を無効にでき、HTTPSでアクセスする必要もないので簡単。
参考リンク
- MicroProfile メトリックを使用したモニタリング
- MicroProfile Metrics REST API
- MicroProfile Metrics 1.1 ベンダー・メトリック
- New connection pool metrics (Monitor 1.0 with MicroProfile Metrics 1.1 updates)
ローカルLibertyの準備
はじめにローカルでLibertyを動かして確認する。以下のようなserver.xml
でLibertyを起動する。
mpMetrics-1.1
フィーチャーを追加することで/metrics
からメトリクスが取得できるようになり、monitor-1.0
を追加することで、venderスコープの追加のメトリクスが取得できるようになる。<mpMetrics authentication="false"/>
で認証を無効にすることができる。
<server description="new server">
<!-- Enable features -->
<featureManager>
<feature>webProfile-8.0</feature>
<feature>monitor-1.0</feature>
<feature>mpMetrics-1.1</feature>
</featureManager>
<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint host="*" httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint"/>
<mpMetrics authentication="false"/>
<!-- Automatically expand WAR files and EAR files -->
<applicationManager autoExpand="true"/>
<applicationMonitor updateTrigger="mbean"/>
<webApplication id="sample" location="sample.war" name="sample"/>
</server>
curl
curl
でどのようなメトリクスが取得できるのかを確認する。
デフォルトではPrometheus Textで返ってくる。
$ curl -s http://localhost:9080/metrics
# TYPE base:classloader_total_loaded_class_count counter
# HELP base:classloader_total_loaded_class_count Java 仮想マシンが実行を開始した以降にロードされたクラスの総数を表示します。
base:classloader_total_loaded_class_count 9596
# TYPE base:cpu_system_load_average gauge
# HELP base:cpu_system_load_average 直前の 1 分間におけるシステム負荷の平均を表示します。システム負荷の平均は、使用可能なプロセッサーのキューに入れられている実行可能なエンティティーの数と、使用可能なプロセッサーで実行されている実行可能なエンティティーの数の合計の一定期間の平均値です。負荷平均を計算する方法はオペレーティング・システムに固有ですが、通常は、ダンプ時間依存の平均です。負荷平均が使用できない場合には、負の値が表示されます。この属性は、システム負荷に関するヒントを提供する目的で用意されたもので、頻繁に照会できます。このメソッドを実装するにはコストがかかる場合、一部のプラットフォームでは負荷平均を使用できない場合があります。
base:cpu_system_load_average 3.51025390625
# TYPE base:gc_ps_scavenge_time_seconds gauge
# HELP base:gc_ps_scavenge_time_seconds 概算累積コレクション経過時間 (ミリ秒) を表示します。このコレクターのコレクション経過時間が定義されていない場合、この属性は -1 を表示します。Java 仮想マシンの実装では、経過時間の測定に高解像度タイマーを使用する場合があります。コレクション経過時間が非常に短い場合、コレクション・カウントが増分されても、この属性は同じ値を表示することがあります。
base:gc_ps_scavenge_time_seconds 0.125
# TYPE base:thread_count counter
# HELP base:thread_count デーモン・スレッドと非デーモン・スレッドの両方を含めて、現在のライブ・スレッドの数を表示します。
base:thread_count 42
# TYPE base:classloader_current_loaded_class_count counter
# HELP base:classloader_current_loaded_class_count Java 仮想マシンに現在ロードされているクラスの数を表示します。
base:classloader_current_loaded_class_count 9564
# TYPE base:jvm_uptime_seconds gauge
# HELP base:jvm_uptime_seconds Java 仮想マシンの開始時刻 (ミリ秒) を表示します。この属性は、Java 仮想マシンが開始された概算時刻を表示します。
base:jvm_uptime_seconds 15.264000000000001
# TYPE base:memory_committed_heap_bytes gauge
# HELP base:memory_committed_heap_bytes Java 仮想マシンで使用するためにコミットされているメモリー量 (バイト) を表示します。このメモリー量は、Java 仮想マシンでの使用が保証されています。
base:memory_committed_heap_bytes 8.24180736E8
# TYPE base:thread_max_count counter
# HELP base:thread_max_count Java 仮想マシンが開始された以降またはピークがリセットされた以降のピーク・ライブ・スレッド・カウントを表示します。これには、デーモン・スレッドと非デーモン・スレッドが含まれます。
base:thread_max_count 50
# TYPE base:cpu_available_processors gauge
# HELP base:cpu_available_processors Java 仮想マシンが使用可能なプロセッサーの数を表示します。この値は、仮想マシンの特定の呼び出し中に変更されることがあります。
base:cpu_available_processors 8
# TYPE base:gc_ps_mark_sweep_count counter
# HELP base:gc_ps_mark_sweep_count 発生したコレクションの総数を表示します。このコレクターのコレクション・カウントが定義されていない場合、この属性は -1 をリストします。
base:gc_ps_mark_sweep_count 3
# TYPE base:thread_daemon_count counter
# HELP base:thread_daemon_count 現在のライブ・デーモン・スレッドの数を表示します。
base:thread_daemon_count 38
# TYPE base:classloader_total_unloaded_class_count counter
# HELP base:classloader_total_unloaded_class_count Java 仮想マシンが実行を開始した以降にアンロードされたクラスの総数を表示します。
base:classloader_total_unloaded_class_count 33
# TYPE base:gc_ps_scavenge_count counter
# HELP base:gc_ps_scavenge_count 発生したコレクションの総数を表示します。このコレクターのコレクション・カウントが定義されていない場合、この属性は -1 をリストします。
base:gc_ps_scavenge_count 7
# TYPE base:memory_max_heap_bytes gauge
# HELP base:memory_max_heap_bytes メモリー管理のために使用できるヒープ・メモリーの最大量 (バイト) を表示します。ヒープ・メモリーの最大サイズが定義されていない場合、この属性は -1 を表示します。このメモリー量がコミット済みメモリー量より多い場合、このメモリー量をメモリー管理のために使用できるとは保証されません。使用済みメモリー量がこの最大サイズを超えていない場合でも、Java 仮想マシンはメモリー割り振りに失敗することがあります。
base:memory_max_heap_bytes 3.817865216E9
# TYPE base:cpu_process_cpu_load_percent gauge
# HELP base:cpu_process_cpu_load_percent Java 仮想マシン・プロセスの「最近の CPU 使用量」を表示します。
base:cpu_process_cpu_load_percent 0.0
# TYPE base:memory_used_heap_bytes gauge
# HELP base:memory_used_heap_bytes 使用済みヒープ・メモリー量 (バイト) を表示します。
base:memory_used_heap_bytes 1.04625672E8
# TYPE base:gc_ps_mark_sweep_time_seconds gauge
# HELP base:gc_ps_mark_sweep_time_seconds 概算累積コレクション経過時間 (ミリ秒) を表示します。このコレクターのコレクション経過時間が定義されていない場合、この属性は -1 を表示します。Java 仮想マシンの実装では、経過時間の測定に高解像度タイマーを使用する場合があります。コレクション経過時間が非常に短い場合、コレクション・カウントが増分されても、この属性は同じ値を表示することがあります。
base:gc_ps_mark_sweep_time_seconds 0.27
# TYPE vendor:session_default_host_metrics_invalidated_total counter
# HELP vendor:session_default_host_metrics_invalidated_total このメトリックが有効になって以降にログアウトしたセッションの数。
vendor:session_default_host_metrics_invalidated_total 0
# TYPE vendor:session_default_host_metrics_invalidatedby_timeout_total counter
# HELP vendor:session_default_host_metrics_invalidatedby_timeout_total このメトリックが有効になって以降にタイムアウトによってログアウトしたセッションの数。
vendor:session_default_host_metrics_invalidatedby_timeout_total 0
# TYPE vendor:session_default_host_metrics_live_sessions gauge
# HELP vendor:session_default_host_metrics_live_sessions 現在ログインしているユーザーの数。
vendor:session_default_host_metrics_live_sessions 1
# TYPE vendor:threadpool_default_executor_active_threads gauge
# HELP vendor:threadpool_default_executor_active_threads タスクを実行しているスレッドの数。
vendor:threadpool_default_executor_active_threads 1
# TYPE vendor:session_default_host_metrics_create_total gauge
# HELP vendor:session_default_host_metrics_create_total このメトリックが有効になって以降にログインしたセッションの数。
vendor:session_default_host_metrics_create_total 1
# TYPE vendor:threadpool_default_executor_size gauge
# HELP vendor:threadpool_default_executor_size スレッド・プールのサイズ。
vendor:threadpool_default_executor_size 16
# TYPE vendor:session_default_host_metrics_active_sessions gauge
# HELP vendor:session_default_host_metrics_active_sessions 同時にアクティブなセッションの数。(あるセッションがアクティブであるとは、製品がそのユーザー・セッションを使用する要求を現在処理している場合をいいます。)
vendor:session_default_host_metrics_active_sessions 1
$
Accept: application/json
ヘッダを渡すと結果がjsonで返ってくる。
$ curl -s -H "Accept: application/json" http://localhost:9080/metrics | jq .
{
"vendor": {
"servlet.com.ibm.ws.microprofile.metrics.public.PublicMetricsRESTProxyServlet.request.total": 1,
"session.default_host_metrics.invalidated.total": 0,
"session.default_host_metrics.invalidatedbyTimeout.total": 0,
"session.default_host_metrics.liveSessions": 1,
"threadpool.Default_Executor.activeThreads": 1,
"session.default_host_metrics.create.total": 1,
"threadpool.Default_Executor.size": 16,
"session.default_host_metrics.activeSessions": 0,
"servlet.com.ibm.ws.microprofile.metrics.public.PublicMetricsRESTProxyServlet.responseTime.total": 43263305
},
"base": {
"classloader.totalLoadedClass.count": 9624,
"cpu.systemLoadAverage": 3.64404296875,
"gc.PSScavenge.time": 125,
"thread.count": 40,
"classloader.currentLoadedClass.count": 9592,
"jvm.uptime": 66339,
"memory.committedHeap": 824180736,
"thread.max.count": 50,
"cpu.availableProcessors": 8,
"gc.PSMarkSweep.count": 3,
"thread.daemon.count": 36,
"classloader.totalUnloadedClass.count": 33,
"gc.PSScavenge.count": 7,
"memory.maxHeap": 3817865216,
"cpu.processCpuLoad": 0.005175639772178392,
"memory.usedHeap": 116114704,
"gc.PSMarkSweep.time": 270
}
}
$
http://localhost:9080/metrics/base
のようにしてスコープを限定したり、http://localhost:9080/metrics/base/memory.usedHeap
のようにして指定のメトリックのみを取得することもできる。
一般的に取得しそうなものは以下。名前を指定するものは、アプリケーションにアクセスが発生してそのMBeanが作成されないと取得できない。
項目 | スコープ | フィールド名 | Prometheus Text |
---|---|---|---|
現在のヒープサイズ | base | memory.committedHeap |
base:memory_committed_heap_bytes |
現在のヒープ使用量 | base | memory.usedHeap |
base:memory_used_heap_bytes |
スレッドプールサイズ | vendor |
threadpool.<スレッドプール名>.size (例) threadpool.Default_Executor.size
|
vendor:threadpool_<スレッドプール名>_size (例) vendor:threadpool_default_executor_size
|
アクティブなスレッド数 | vendor |
threadpool.<スレッドプール名>.activeThreads (例) threadpool.Default_Executor.activeThreads
|
vendor:threadpool_<スレッドプール名>active_threads (例) vendor:threadpool_default_executor_active_threads
|
セッション数 | vendor |
session.<仮想ホスト名>_<アプリケーション名>.liveSessions (例) session.default_host_metrics.liveSessions
|
vendor:session_<仮想ホスト名>_<アプリケーション>_live_sessions (例) vendor:session_default_host_metrics_live_sessions
|
アクティブなセッション数 | vendor |
session.<仮想ホスト名>_<アプリケーション名>.activeSessions (例) session.default_host_metrics.activeSessions
|
vendor:session_<仮想ホスト名>_<アプリケーション>_active_sessions (例) vendor:session_default_host_metrics_active_sessions
|
接続プール数 | vendor |
connectionpool.<JNDI名>.managedConnections (例) connectionpool.jdbc_mydb.managedConnections
|
connectionpool_<JNDI名>_managed_connections (例) connectionpool_jdbc_mydb_managed_connections
|
空きプール数 | vendor |
connectionpool.<JNDI名>.freeConnections (例) connectionpool.jdbc_mydb.freeConnections
|
vendor:connectionpool_<JNDI名>_free_connections (例) vendor:connectionpool_jdbc_mydb_free_connections
|
Python
コード作成
Pythonスクリプトは以下の通り作成。→GitHub
セッションは取得しているが、metricsアプリケーションのものを取得しており、実際は取得したいアプリケーションを指定する必要がある。
#!/usr/bin/env python3
import argparse
import csv
import datetime
import logging
import os
import sys
import time
import requests
import urllib3
formatter = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
logging.basicConfig(level=logging.WARNING, format=formatter)
logger = logging.getLogger(__name__)
def request_with_retry(url, timeout, retry):
"""HTTPリクエストを行い、失敗した場合はリトライを行う。
:param url:
:param timeout:
:param retry:
:return: jsonデータ
"""
session = requests.Session()
# backoff_factorが1だとリトライの間のスリープ時間は[0s, 2s, 4s, 8s, ...]となる
retries = urllib3.util.retry.Retry(total=retry, backoff_factor=1)
session.mount('https://', requests.adapters.HTTPAdapter(max_retries=retries))
session.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
headers = {'Accept': 'application/json'}
logger.info('GET {}'.format(url))
response = session.get(url, headers=headers, timeout=timeout)
return response.json()
def write_header(filepath):
"""ヘッダー書き込む。
filepathが指定され、ファイルが存在しなかった場合はファイルを作成してヘッダーを書く。
filepathが指定され、ファイルが空の場合もヘッダーを書く。
filepathが指定されなかった場合は標準出力にヘッダーを書く。
:param filepath:
:return:
"""
fieldnames = [
'Time',
'Heap', 'UsedMemory',
'PoolSize', 'ActiveThreads',
'LiveCount', 'ActiveCount']
# filepathが指定された場合
if filepath:
# ファイルが存在しない場合はファイルを作成してヘッダーを書く
if not os.path.isfile(filepath):
with open(filepath, 'w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
# ファイルが存在する場合
else:
with open(filepath, 'r+') as csv_file:
# ファイルが空の場合はヘッダーを書く
if not csv_file.read():
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
# filepathが指定されなかった場合は標準出力にヘッダーを書く
else:
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
writer.writeheader()
def append_data(filepath, metrics):
"""性能情報の値を追記する。
filepathが渡された場合はcsvに書き込み、渡されなかった場合は標準出力に書き込む。
:param filepath:
:param metrics:
:return:
"""
fieldnames = [
'Time',
'Heap', 'UsedMemory',
'PoolSize', 'ActiveThreads',
'LiveCount', 'ActiveCount']
item = {
'Time': datetime.datetime.now(),
'Heap': metrics['base']['memory.committedHeap'],
'UsedMemory': metrics['base']['memory.usedHeap'],
'PoolSize': metrics['vendor']['threadpool.Default_Executor.size'],
'ActiveThreads': metrics['vendor']['threadpool.Default_Executor.activeThreads'],
'LiveCount': metrics['vendor']['session.default_host_metrics.liveSessions'],
'ActiveCount': metrics['vendor']['session.default_host_metrics.activeSessions']
}
if filepath:
# データをファイルに追記する
with open(filepath, 'a') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writerow(item)
else:
# 標準出力に出力する
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
writer.writerow(item)
def main():
# コマンド引数の処理
parser = argparse.ArgumentParser()
parser.add_argument('--host',
action='store',
type=str,
default='localhost',
help='接続先のホスト名を指定します default:localhost')
parser.add_argument('--port',
action='store',
type=int,
default=9080,
help='接続先のポートを指定します default:9080')
parser.add_argument('--interval',
action='store',
type=int,
default=60,
help='データの取得間隔(秒)を指定します default:60')
parser.add_argument('--delay',
action='store',
type=int,
default=30,
help='モニターを開始するまでの待機時間(秒)を指定します default:30')
parser.add_argument('--timeout',
action='store',
type=int,
default=2,
help='モニターリクエストのタイムアウト時間(秒)を指定します default:2')
parser.add_argument('--retry',
action='store',
type=int,
default=10,
help='モニターのリトライ回数を指定します default:10')
parser.add_argument('-f', '--filename',
action='store',
type=str,
help='出力先のファイル名を指定します default:未指定(標準出力)')
args = parser.parse_args()
host = args.host
port = args.port
timeout = args.timeout
retry = args.retry
filepath = args.filename
# 待機時間
time.sleep(args.delay)
# ヘッダーを書く
write_header(filepath)
# MicroProfile MetricsのURL
url = 'http://{}:{}/metrics'.format(host, port)
# モニター
while True:
metrics = request_with_retry(url, timeout, retry)
append_data(filepath, metrics)
time.sleep(args.interval)
if __name__ == '__main__':
main()
実行例
usage: monitor_mpMetrics.py [-h] [--host HOST] [--port PORT]
[--interval INTERVAL] [--delay DELAY]
[--timeout TIMEOUT] [--retry RETRY] [-f FILENAME]
optional arguments:
-h, --help show this help message and exit
--host HOST 接続先のホスト名を指定します default:localhost
--port PORT 接続先のポートを指定します default:9080
--interval INTERVAL データの取得間隔(秒)を指定します default:60
--delay DELAY モニターを開始するまでの待機時間(秒)を指定します default:30
--timeout TIMEOUT モニターリクエストのタイムアウト時間(秒)を指定します default:2
--retry RETRY モニターのリトライ回数を指定します default:10
-f FILENAME, --filename FILENAME
出力先のファイル名を指定します default:未指定(標準出力)
sotoiwa@SotonoMacBook-Pro:~/workspace/liberty_monitor/monitor_mpMetrics (master)
$ ./monitor_mpMetrics.py --interval 10 --delay 0
Time,Heap,UsedMemory,PoolSize,ActiveThreads,LiveCount,ActiveCount
2019-01-13 15:03:11.891491,592445440,233771544,16,1,0,0
2019-01-13 15:03:21.906834,592445440,235929560,16,1,0,0
2019-01-13 15:03:31.920073,592445440,236361128,16,1,0,0
2019-01-13 15:03:41.934451,592445440,236361128,16,1,0,0
サイドカーとして動かす
restConnectorの場合と基本的に同じはず。
Libertyコンテナのビルド
上に記載したserver.xml
とアプリを含めてLibertyコンテナをビルドする。mpMetrics-1.1
はwebProfile8
には含まれていないので、フィーチャーのインストールが必要。
FROM websphere-liberty:18.0.0.4-webProfile8
COPY --chown=1001:0 server.xml /config/
COPY --chown=1001:0 sample.war /config/apps/
RUN installUtility install --acceptLicense defaultServer
ファイル配置は以下。
liberty-mpmetrics
├── Dockerfile
├── sample.war
└── server.xml
ビルドする。
docker build -t sotoiwa540/liberty-mpmetrics:1.0 .
docker push sotoiwa540/liberty-mpmetrics:1.0
Pythonサイドカーコンテナのビルド
Pythonのコンテナをビルドする。
FROM python:3-alpine
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY monitor_mpMetrics.py ./
ENV PYTHONUNBUFFERED TRUE
ENTRYPOINT [ "python", "./monitor_mpMetrics.py" ]
ファイル配置は以下。
monitor-mpmetrics
├── Dockerfile
├── monitor_mpMetrics.py
└── requirements.txt
docker build -t sotoiwa540/monitor-mpmetrics:1.0 .
docker push sotoiwa540/monitor-mpmetrics:1.0
Kubernetesへのデプロイ
以下のようにLiberty(with サイドカー)のマニフェストを作成。
apiVersion: apps/v1
kind: Deployment
metadata:
name: liberty-mpmetrics
spec:
selector:
matchLabels:
app: liberty
replicas: 1
template:
metadata:
labels:
app: liberty
spec:
containers:
- name: liberty
image: sotoiwa540/liberty-mpmetrics:1.0
imagePullPolicy: Always
ports:
- containerPort: 9080
- name: monitor
image: sotoiwa540/monitor-mpmetrics:1.0
imagePullPolicy: Always
command:
- python
- ./monitor_mpMetrics.py
args:
- --host
- localhost
- --port
- "9080"
- --interval
- "60"
- --timeout
- "2"
- --delay
- "30"
デプロイする。
kubectl apply -f liberty-mpmetrics-deployment.yaml
稼働確認
LibertyのPodが稼働していることを確認。コンテナの数が2となっている。
$ kubectl get po
NAME READY STATUS RESTARTS AGE
liberty-mpmetrics-575d67dff8-9sx86 2/2 Running 0 46s
$
サイドカーの方のログを確認。csvが出力できた。
$ kubectl logs -f liberty-mpmetrics-575d67dff8-9sx86 -c monitor
Time,Heap,UsedMemory,PoolSize,ActiveThreads,LiveCount,ActiveCount
2019-01-13 07:30:35.752988,70123520,51206664,4,1,1,1
2019-01-13 07:31:35.820176,70123520,59221272,4,1,1,0
TODO
取得するメトリクスを引数か環境変数で渡せるようにしたほうがよい。