Elasticsearch の JVM ヒープ使用量を Graphite と Grafana を利用して可視化したいと思ったので Cluster API と Python を利用して実現してみた。

Cluster API

Cluster APIsNodes Stats を利用するので curl を利用して取得してみる。

20150703081941

実際に叩いてみる。

$ 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 からの利用

スクリプト

以下のようなスクリプトを作ってみた。

#!/usr/bin/env python

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 %dn' % (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 使用率が高くなってしまう現象が見られた(自分の環境だけかもしれないが)ので、今回はシンプルにヒープサイズのみを取得するようにしてみた。

以下のような利用シーンをイメージ。

20150703084012

スクリプトは /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 で確認することが出来るようになる。

20150703085508

おお、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()

ということで

も少しちゃんとスクリプトを作れるようになりたいのと、ヒープ使用量以外の値も同時に取得出来るように少し修正しなければ。

元記事はこちら

Elasticsearch の JVM のヒープ使用量を Graphite / Grafana で可視化するちょっとしたスクリプト