Elasticsearch の JVM ヒープ使用量を Graphite と Grafana を利用して可視化したいと思ったので Cluster API と Python を利用して実現してみた。
Cluster API
Cluster APIs の Nodes Stats を利用するので curl
を利用して取得してみる。
実際に叩いてみる。
$ curl -XGET 'http://localhost:9200/_nodes/stats/jvm?pretty=true'
{
"cluster_name" : "kappa-elasticsearch",
"nodes" : {
"RiQNuftXTIO2fiX4GJjO4w" : {
"timestamp" : 1435845867712,
"name" : "node2",
"transport_address" : "inet[/xxx.xxx.xx.11:9300]",
"host" : "localhost.localdomain",
"ip" : [ "inet[/xxx.xxx.xx.11:9300]", "NONE" ],
"jvm" : {
"timestamp" : 1435845867712,
"uptime_in_millis" : 44858656,
"mem" : {
"heap_used_in_bytes" : 105512584,
"heap_used_percent" : 9,
"heap_committed_in_bytes" : 259719168,
"heap_max_in_bytes" : 1065025536,
"non_heap_used_in_bytes" : 49661032,
"non_heap_committed_in_bytes" : 49938432,
"pools" : {
"young" : {
"used_in_bytes" : 64778368,
"max_in_bytes" : 69795840,
"peak_used_in_bytes" : 69795840,
"peak_max_in_bytes" : 69795840
},
"survivor" : {
"used_in_bytes" : 2255592,
"max_in_bytes" : 8716288,
"peak_used_in_bytes" : 8716288,
"peak_max_in_bytes" : 8716288
},
"old" : {
"used_in_bytes" : 38478624,
"max_in_bytes" : 986513408,
"peak_used_in_bytes" : 38478624,
"peak_max_in_bytes" : 986513408
}
}
},
"threads" : {
"count" : 37,
"peak_count" : 42
},
"gc" : {
"collectors" : {
"young" : {
"collection_count" : 41,
"collection_time_in_millis" : 369
},
"old" : {
"collection_count" : 0,
"collection_time_in_millis" : 0
}
}
},
"buffer_pools" : {
"direct" : {
"count" : 198,
"used_in_bytes" : 6273408,
"total_capacity_in_bytes" : 6273408
},
"mapped" : {
"count" : 10,
"used_in_bytes" : 205985,
"total_capacity_in_bytes" : 205985
}
}
}
},
"BPARRUhjSMuQlJBBxrp4Mg" : {
"timestamp" : 1435845867715,
"name" : "node1",
"transport_address" : "inet[/xxx.xxx.xx.10:9300]",
"host" : "localhost.localdomain",
"ip" : [ "inet[/xxx.xxx.xx.10:9300]", "NONE" ],
"jvm" : {
"timestamp" : 1435845867715,
"uptime_in_millis" : 44867169,
"mem" : {
"heap_used_in_bytes" : 79838632,
"heap_used_percent" : 7,
"heap_committed_in_bytes" : 259719168,
"heap_max_in_bytes" : 1065025536,
"non_heap_used_in_bytes" : 48005728,
"non_heap_committed_in_bytes" : 48300032,
"pools" : {
"young" : {
"used_in_bytes" : 40145808,
"max_in_bytes" : 69795840,
"peak_used_in_bytes" : 69795840,
"peak_max_in_bytes" : 69795840
},
"survivor" : {
"used_in_bytes" : 2416792,
"max_in_bytes" : 8716288,
"peak_used_in_bytes" : 8716288,
"peak_max_in_bytes" : 8716288
},
"old" : {
"used_in_bytes" : 37276032,
"max_in_bytes" : 986513408,
"peak_used_in_bytes" : 37276032,
"peak_max_in_bytes" : 986513408
}
}
},
"threads" : {
"count" : 39,
"peak_count" : 44
},
"gc" : {
"collectors" : {
"young" : {
"collection_count" : 40,
"collection_time_in_millis" : 355
},
"old" : {
"collection_count" : 0,
"collection_time_in_millis" : 0
}
}
},
"buffer_pools" : {
"direct" : {
"count" : 68,
"used_in_bytes" : 4085453,
"total_capacity_in_bytes" : 4085453
},
"mapped" : {
"count" : 10,
"used_in_bytes" : 206859,
"total_capacity_in_bytes" : 206859
}
}
}
}
}
}
上記のように Elasticsaerch のクラスタを構成している全てのノード JVM に関する各種値を取得することが出来るので、これらの値から jvm
キーに含まれる mem
キーの heap_used_in_bytes
を取得するスクリプトを作成してみたい。スクリプトの言語は基本的に問わないが、CentOS ですぐに利用出来る Python を利用して作成する。
スクリプトと Sensu からの利用
以下のようなスクリプトを作ってみた。
import json, urllib2, sys, time, random
argvs = sys.argv
hosts = argvs[1].split(",")
url = 'http://' + random.choice(hosts) + ':9200/_nodes/stats/jvm'
r = urllib2.urlopen(url)
j = json.loads(r.read())
nodes = j['nodes']
for node in nodes:
timestamp = int(time.time())
label = argvs[2] + '.' + j['nodes'][node]['name'] + '.' + 'elasticsearch_jvm.heap_used'
print '%s %s %d\n' % (label, j['nodes'][node]['jvm']['mem']['heap_used_in_bytes'], timestamp)
r.close()
Python も Hello World レベルの為、謎や無駄が多くなってしまっているかもしれない...
で、実際に利用する場合には、以下のように利用する。
$ ./script.py node1,node2 foo
第一引数にはクラスタに含まれるノードをカンマ区切りで渡す。また、第二引数には Graphite で利用するトップレベルネームを指定する。
実行結果は以下のようになる。
foo.node2.elasticsearch_jvm.heap_used 84739064 1435879508
foo.node1.elasticsearch_jvm.heap_used 51600232 1435879508
Sensu からの利用
既に Sensu プラグインでは sensu-plugins-elasticsearch というプラグインが配布されており、実際に metrics-es-node-graphite.rb を利用してみるとプラグインを動かしているホストの CPU 使用率が高くなってしまう現象が見られた(自分の環境だけかもしれないが)ので、今回はシンプルにヒープサイズのみを取得するようにしてみた。
以下のような利用シーンをイメージ。
スクリプトは /etc/sensu/plugins
以下に実行権限を付けて設置する。
sudo mv script.py /etc/sensu/plugins/
Sensu Check の設定は以下のように。今回は Standalone Check を有効にしている。
{
"checks": {
"metrics-elasticsearch-jvm-heap": {
"type": "metric",
"handlers": ["graphite", "file"],
"command": "script.py node1,node2 foo",
"interval": 30,
"standalone": true
}
}
}
設定すると以下のように Grafana で確認することが出来るようになる。
おお、Grafana キレイ。
おまけ
Cluster API のエンドポイントをランダムに選択する実装
上記例の node1 と node2 のどちらのノードでも Cluster API が同様に結果を返してくれるのであれば、スクリプトでもどちらかのノードにアクセスさせることで、負荷の偏りを防ぐ、片方のノードが落ちてもメトリクスの収集が出来る(その前に復旧すれば良いが...)というメリットがありそうなので、アクセスするエンドポイントをランダムに選択するようにしてみたのが以下。
(略)
hosts = argvs[1].split(",")
url = 'http://' + random.choice(hosts) + ':9200/_nodes/stats/jvm'
スクリプトの第一引数で対象のノードをカンマ区切りで指定するところと random.choice
を利用しているところがミソ。カンマ区切りの第一引数は配列になって変数 hosts
に代入されて、random.choice
により配列からランダムに一つのノードが取り出されてエンドポイントにアクセスする。
$ ./script.py node1,node2 foo
http://node1:9200/_nodes/stats/jvm
foo.node2.elasticsearch_jvm.heap_used 65309008 1435882093
foo.node1.elasticsearch_jvm.heap_used 106770624 1435882093
$ ./script.py node1,node2 foo
http://node2:9200/_nodes/stats/jvm
foo.node2.elasticsearch_jvm.heap_used 65323128 1435882095
foo.node1.elasticsearch_jvm.heap_used 106774232 1435882095
$ ./script.py node1,node2 foo
http://node2:9200/_nodes/stats/jvm
foo.node2.elasticsearch_jvm.heap_used 65328928 1435882099
foo.node1.elasticsearch_jvm.heap_used 106774232 1435882099
$ ./script.py node1,node2 foo
http://node1:9200/_nodes/stats/jvm
foo.node2.elasticsearch_jvm.heap_used 66125096 1435882100
foo.node1.elasticsearch_jvm.heap_used 107118056 1435882100
実際にアクセスさせてみると、上記のように node1 と node2 がランダムに利用されていることが判る。
EC2 の meta-data を利用してインスタンス ID を取得する場合
以下のように urllib2
を利用して簡単に取得することが出来る。
meta_url = 'http://169.254.169.254/latest/meta-data/instance-id'
i = urllib2.urlopen(meta_url)
instance_id = i.read()
i.close()
ということで
も少しちゃんとスクリプトを作れるようになりたいのと、ヒープ使用量以外の値も同時に取得出来るように少し修正しなければ。