solr
grafana
prometheus
solrcloud
solr-exporter
SolrDay 8

Solr の Prometheus Exporter を作ってみた

お知らせ

私が開発した solr-exporter (https://github.com/mosuka/solr-exporter) は Apache Lucene/Solr プロジェクト (http://lucene.apache.org/solr/) へコントリビュートしました。
今後は Apache Solr に同梱される solr-exporter をご利用ください。
詳細は下記 URL を参照してください。
http://lucene.apache.org/solr/guide/monitoring-solr-with-prometheus-and-grafana.html


Solr のモニタリングに使えるソフトウェア で、Apache Solr (以下「Solr」) のモニタリングに使えるソフトウェアを紹介しました。
それらは、基本的に JMX の機能を利用して Solr のメトリクスをモニタリングできるようにしているものです。
Solr でインデックスしているデータをモニタリングすることはできません。

例えば、

  • アプリケーションログを保存していたら、エラーがどれくらい、どのシステムで発生しているか
  • EC であれば、商品カテゴリー毎の商品数の推移
  • ユーザー投稿型のサービスであれば、問題のあるキーワードを含むコンテンツがどれだけあるか

Solr もインデックスされたデータの可視化であれば、Lucidworks が OSS で公開している Banana (Kibana3 相当) で行うことができます。これは Elastic が提供する Kibana にあたるソフトウェアです。
しかし、アラーティングまで行うとなると、X-Pack や、Yelp が OSS で公開している ElastAlert のようなソフトウェアが必要ですが、良さそうなのがなかなかありません。それに、監視のためにいろいろツールが増えてしまっても運用が大変になってきます。

そこで、モニタリングソフトウェアを PrometheusGrafana に一本化し、Solr のメトリクスとインデックスをモニタリングできるようにする solr-exporter を作ってみました。

solr-exporter

solr-exporter を使うことで、Solr メトリクスと Solr がインデックスしているデータのモニタリングとアラーティングが可能となります。
下の図は、スタンドアローンの Solr へ接続する際のダイアグラムです。もちろん、SolrCloud へ接続することも可能です。

sample

solr-exporter は Java で開発されています。
Java を採用した理由は、Solr プロジェクトが提供する SolrJ (Solr クライアントライブラリ) を使用するためです。
Solr は REST ライクなインターフェースを備えていて、HTTP でアクセスできるのですが、フェールオーバーなどを考慮すると実装が大変です。
SolrJ は スタンドアローンの Solr への接続だけでなく、SolrCloud を使用したクラスターの場合、ZooKeeper から Node Discovery を行なって、稼働中の Solr へ接続してくれます。
また、Solr に同梱されるクライアントライブラリなので、メンテンスも十分にされていて安心して使えます。

Solr に接続した solr-exporterSolr のモニタリングに使える API で紹介した Metrics API や、Solr に対して全文検索を行い、それらのレスポンスを Prometheus のメトリクスとして公開 (expose) します。

設定は YAML フォーマットのファイルで管理されます。
返却される API の JSON レスポンスのスクレイピングには jq コマンドのクエリーを採用しています。

開発した経緯

solr-exporter を開発するに至った経緯は次のようになります。

  • 検索エンジンとして Solr の運用ノウハウを持っている・監視システムとして Prometheus に統一・集約したい
    • 想定する実行環境が Kubernetes で、PrometheusKubernetes との相性もよさそう
    • Prometheus は、元 Google のエンジニアが Google 社内監視システムの Borgmon にインスパイアされて開発したもので筋がよさそう
    • 監視システムとして、Solr と同じ Lucene をベースにした検索エンジンである Elasticsearch や、可視化・モニタリングのために KibanaX-Pack を導入して運用コストを上げたくない
  • Solr でインデックスしたドキュメント (データ) のモニタリングをしたい
    • 「このキーワードにマッチするドキュメントがどれくらいある」などをモニタリングしたい
    • Solr の全文検索クエリーを実行したい
    • BananaZeppelin では可視化はできても、アラーティングまでできない
    • ElastAlert のような、Solr 向けのアラーティングのソフトウェアが欲しい
  • メトリクスのような時系列データを Lucene の転置インデックスで扱うのに抵抗がある
    • 時系列データには時系列データベースを使用した方がいいのではないか
  • Solr の Exporter 作ったら、上記の課題を解決できる

というところから、「無いなら作ったらいいじゃない」ということで個人的に作ってみました。

インストール

solr-exporterリポジトリのリリースページ から コンパイル済みのアーカイブをダウンロードします。
適当なディレクトリで解答してインストールは完了です。

$ cd ~/
$ unzip solr-exporter-0.3.8-bin.zip
$ cd solr-exporter-0.3.8

実行

インストールしたディレクトリ以下の ./bin/solr-exporter を実行してください。

$ ./bin/solr-exporter -p 9983 -b http://localhost:8983/solr -f ./conf/config.yml

Windows 環境の人は、同じディレクトリの .\bin\solr-exporter.bat を使用します。

> .\bin\solr-exporter.bat -p 9983 -b http://localhost:8983/solr -f .\conf\config.yml

SolrCloud モードでクラスタを組んでいる場合は、次のように ZooKeeper の接続文字列を指定します。

$ ./bin/solr-exporter -p 9983 -z localhost:2181/solr -f ./conf/config.yml

他のコマンドラインオプションについてはヘルプを確認してください。

$ ./bin/solr-exporter -h
usage: SolrCollector [-h] [-v] [-p PORT] [-b BASE_URL] [-z ZK_HOST] [-f CONFIG]
                     [-n NUM_THREADS]

Prometheus exporter for Apache Solr.

optional arguments:
  -h, --help             show this help message and exit
  -v, --version          show version
  -p PORT, --port PORT   solr-exporter listen port
  -b BASE_URL, --baseurl BASE_URL
                         specify Solr base URL when connecting  to Solr in standalone mode (for
                         example 'http://localhost:8983/solr')
  -z ZK_HOST, --zkhost ZK_HOST
                         specify  ZooKeeper  connection  string  when  connecting  to  Solr  in
                         SolrCloud mode (for example 'localhost:2181/solr')
  -f CONFIG, --config-file CONFIG
                         specify configuration file
  -n NUM_THREADS, --num-thread NUM_THREADS
                         specify number of threads

設定ファイル例

solr-exporter の設定ファイルは次のような YAML ファイルになっています。

query で Solr へのリクエストを設定し、jsonQueries でそのレスポンスをパースする jq クエリーを設定します。
これは、Solr の solrconfig.xml でリクエストハンドラのパスをユーザーが自由に変更できることに対応するのと、ユーザーが自由にメトリクス名、ラベル名を変更できるようにするためです。
メトリクス名などのルールについては Prometheus の Metric and label naming にガイドラインがありますので参考にしてみてください。

ping:
  query:
    path: /admin/ping
  jsonQueries:
    - |-
      . as $object | $object |
      (if $object.status == "OK" then 1.0 else 0.0 end) as $value |
      {
        name         : "solr_ping",
        type         : "GAUGE",
        help         : "See following URL: http://lucene.apache.org/solr/guide/7_1/ping.html",
        label_names  : [],
        label_values : [],
        value        : $value
      }

metrics:
  query:
    path: /admin/metrics
    params:
      - group: 'all'
      - type: 'all'
      - prefix: ''
      - property: ''
  jsonQueries:
    ##############################
    # jetty
    ##############################
    # solr_metrics_jetty_response_total
    - |-
      .metrics["solr.jetty"] | to_entries | .[] | select(.key | startswith("org.eclipse.jetty.server.handler.DefaultHandler")) | select(.key | endswith("xx-responses")) as $object |
      $object.key | split(".") | last | split("-") | first as $status |
      $object.value.count as $value |
      {
        name         : "solr_metrics_jetty_response_total",
        type         : "counter",
        help         : "See following URL: https://lucene.apache.org/solr/guide/7_1/metrics-reporting.html",
        label_names  : ["status"],
        label_values : [$status],
        value        : $value
      }

... [中略] ...

collections:
  query:
    path: /admin/collections
    params:
      - action: 'CLUSTERSTATUS'
  jsonQueries:
    # solr_collections_live_nodes
    - |-
      .cluster.live_nodes | length as $value|
      {
        name         : "solr_collections_live_nodes",
        type         : "gauge",
        help         : "See following URL: https://lucene.apache.org/solr/guide/7_1/collections-api.html#clusterstatus",
        label_names  : [],
        label_values : [],
        value        : $value
      }

... [中略] ...

queries:
  - query:
      collection: collection1
      path: /select
      params:
        - q: "*:*"
        - start: 0
        - rows: 0
        - json.facet: |-
            {
              category: {
                type: terms,
                field: cat
              }
            }
    jsonQueries:
      # solr_facets_category
      - |-
        .facets.category.buckets[] as $object |
        $object.val as $term |
        $object.count as $value |
        {
          name         : "solr_facets_category",
          type         : "gauge",
          help         : "Category facets",
          label_names  : ["collection", "term"],
          label_values : ["collection1", $term],
          value        : $value
        }

solr-exporter では、pingmetricscollectionsqueries の設定を持っています。

  • ping - Solr の Ping を利用し、コア、コレクションが利用可能かのメトリクスを取得
  • metrics - Solr の Metrics API を利用して、メトリクスを取得
  • collections - Solr の Collections API を利用して、クラスターの情報をメトリクスとして取得
  • queries - Solr の検索を利用して、検索結果をメトリクスとして取得

Solr の返す JSON レスポンスを、次のフォーマットでメトリクスとして JSON を組み立てる jq のクエリー設定すると、Prometheus 形式のデータに変換して公開 (expose) します。

{
  "name": "some_metric_name",
  "type": "gauge", 
  "help": "describe metric.",
  "label_names": ["label_name1", "label_name2"],
  "label_values": ["label_value1", "label_value2"],
  "value": 1.0
}

例えば、上記 JSON は、下記のような Prometheus フォーマットに変換されます。

# HELP some_metric_name describe metric.
# TYPE some_metric_name gauge
some_metric_name{label_name1 ="label_value1", label_name2 ="label_value2",} 1.0

solr-exporter を実行したら、Solr のメトリクスが expose できることを、次の URL へアクセスして確認してください。

$ curl -s 'http://localhost:9983/metrics'
# HELP solr_metrics_jetty_response_total See following URL: https://lucene.apache.org/solr/guide/7_1/metrics-reporting.html
# TYPE solr_metrics_jetty_response_total counter
solr_metrics_jetty_response_total{base_url="http://localhost:8983/solr",status="1xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8983/solr",status="2xx",} 516.0
solr_metrics_jetty_response_total{base_url="http://localhost:8983/solr",status="3xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8983/solr",status="4xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8983/solr",status="5xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8984/solr",status="1xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8984/solr",status="2xx",} 517.0
solr_metrics_jetty_response_total{base_url="http://localhost:8984/solr",status="3xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8984/solr",status="4xx",} 0.0
solr_metrics_jetty_response_total{base_url="http://localhost:8984/solr",status="5xx",} 0.0

... [中略] ...

# TYPE solr_collections_shard_leader gauge
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="banana-int",shard="shard1",replica="core_node3",core="banana-int_shard1_replica_n1",base_url="http://localhost:8983/solr",node_name="localhost:8983_solr",type="NRT",} 0.0
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="banana-int",shard="shard1",replica="core_node4",core="banana-int_shard1_replica_n2",base_url="http://localhost:8984/solr",node_name="localhost:8984_solr",type="NRT",} 1.0
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="apache-log",shard="shard1",replica="core_node3",core="apache-log_shard1_replica_n1",base_url="http://localhost:8983/solr",node_name="localhost:8983_solr",type="NRT",} 0.0
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="apache-log",shard="shard1",replica="core_node4",core="apache-log_shard1_replica_n2",base_url="http://localhost:8984/solr",node_name="localhost:8984_solr",type="NRT",} 1.0
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="solr-log",shard="shard1",replica="core_node3",core="solr-log_shard1_replica_n1",base_url="http://localhost:8983/solr",node_name="localhost:8983_solr",type="NRT",} 0.0
solr_collections_shard_leader{zk_host="localhost:2181/solr",collection="solr-log",shard="shard1",replica="core_node4",core="solr-log_shard1_replica_n2",base_url="http://localhost:8984/solr",node_name="localhost:8984_solr",type="NRT",} 1.0
# HELP solr_scrape_duration_seconds Time this Solr scrape took, in seconds.
# TYPE solr_scrape_duration_seconds gauge
solr_scrape_duration_seconds 1.584429789

上記のようにメトリクスを出力できていれば solr-exporter の準備は完了です。

デフォルトで、主要なメトリクスや、Solr の検索クエリーのサンプルが設定されています。
不要なメトリクスや、別のメトリクス、検索結果を取得したい場合は適宜修正をしてください。

Prometheus

ここから solr-exporter の公開するメトリクスを Pull してくるため、Prometheus の準備を行います。

Prometheus のインストールと設定

Prometheus のインストールについては、[Installation | Prometheus](https://prometheus.io/docs/prometheus/latest/installation/ を参考にして、環境に合わせて行なってください。

インストール後、prometheus.yamlscrape_configs に solr-exporter の情報を追加します。

$ vi ./prometheus.yaml
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

... [中略] ...

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'solr'
    static_configs:
      - targets: ['localhost:9983']

以上で、solr-exporter からメトリクスを Pull することができます。

Promethues の実行

次のコマンドで Prometheus を起動します。

$ ./prometheus --config.file ./prometheus.yml

Prometheus を起動すると、次の URL で Prometheus の UI にアクセスできます。

http://localhost:9090/graph

Status メニューから Targets をクリックすると、Prometheus が収集しているターゲットの一覧を確認できます。
次のように、先ほど登録した solr-exporter がターゲットに存在することを確認してください。一覧にない場合、または、エラーが発生する場合は、設定ファイルの内容が間違っていないか確認してください。

Screen Shot 2017-12-01 at 17.52.23.png

Prometheus が収集したメトリクスは、次のようにグラフで可視化できます。

Screen Shot 2017-12-01 at 17.54.08.png

が、やっぱりちょっと寂しいです。Grafana のサンプルダッシュボードを用意しているのでそちらを表示してみます。

Grafana

ここから Prometheus に保存されたメトリクスをより高度に可視化するため、Grafana の準備を行います。

Grafana のインストール

Grafana のインストールについては、Installing Grafana を参考にして、環境に合わせて行なってください。

Grafana ダッシュボード

Grafana を起動すると、次の URL で UI にアクセスできます。

http://localhost:3000

Screen Shot 2017-12-03 at 23.11.00.png

初期の管理者アカウントは次のようになります。

User : admin
Password : admin

今回、サンプルのダッシュボードを用意しました。次の URL で公開しています。
https://grafana.com/dashboards/3888

サンプルのダッシュボードは、次のようにしてインポートできます。
DashboardsImportImport Dashboard ダイアログを表示します。

Screen Shot 2017-12-03 at 23.14.48.png

Grafana.com Dashboard3888 (サンプルのダシュボードの ID) を入力してください。

Screen Shot 2017-12-03 at 23.17.10.png

インポートに成功すると、次のようなダッシュボードが表示できます。

solr-dashboard.png

サンプルのダッシュボードなので、自由に編集して、好みのダッシュボードを作成してください。

まとめ

Solr の Metrics API の API のレスポンスに一貫性がなく、機械的に一発で Prometheus フォーマットへ変換することが困難でしたが、jq のクエリを使うことで、不揃いなフォーマットを統一したフォーマットに直すことができるようになりました。

Solr は、全文検索エンジンとして豊富な機能を提供し、稼働実績もあるソフトウェアですが、モニタリングやアラーティングについてはエコシステムが整っていないため、弱い部分です。
solr-exporter は、そんな Solr の弱点を、モニタリングやアラーティングで強力な機能を提供する Prometheus を繋ぐソフトウェアです。

実験的な試みなので、不具合もあるかもしれませんが、興味あれば使ってみてください。
また、もっとこうした方がいいなどあれば、ぜひ Pull Request ください。

Solr コミュニティでも Metrics API のレスポンスを Prometheus フォーマットで公開 (expose) する [SOLR-10654] Expose Metrics in Prometheus format という Issue も上がっています。
また、OpenMetrics という Prometheus のフォーマットをベースに、メトリクスのフォーマットの標準化をしようという試みもされています。
Prometheus Advent Calendar 2017 もあります。Prometheus にも興味がでてきた方は、こちらもご覧ください。

TH160_9784774189307.jpg

Solr のインデックスしたデータを抽出するには Facet や Stats API、検索クエリーを利用することになると思います。[改訂第3版]Apache Solr入門 に検索クエリーの書き方や各種 API が解説されているので、興味ある方は是非読んでみてください。