JMX で快適モニタリング環境を作ろう

こんにちは、アプリケーション基盤チームの青木(@a_o_k_i_n_g)です。

今回は Java アプリケーションのモニタリング等で活躍する JMX について記します。JMX を用いれば、きめ細やかでかつ手軽にモニタリングすることができます。もちろんサイボウズが提供するクラウドサービス cybozu.com でも JMX を活用して日々モニタリングを行っています。

モニタリングの重要性については今さら言うまでもありません。一方で現実のモニタリングは何かと手間がかかったり属人化してしまったりすることがありがちです。JMX は銀の弾丸ではありませんが、少なくともメトリクスの収集・提供部分に関しては利便性を提供してくれます。JMX ユーザーが少しでも増えるよう、この記事で紹介させていただきます。

JMX とは

JMX とは Java Management Extensions の略称で、Java アプリケーションを管理するための仕様です。この JMX を用いることで Java アプリケーションの任意の項目を外部から取得したり設定できたりします。

特別な設定をしなくとも JMX を用いてヒープや GC の情報は取得できますし、また、既存の数多くのアプリケーションが JMX に対応しています。例えば Jetty や Tomcat、 Solr、 Elasticsearch、 Hadoop、 Cassandra 等々、それぞれがアプリケーションに応じた細やかなメトリクスを収集できるようになってます。同様に多くのモニタリングツールやサービスが JMX に対応しており、 Zabbix や Prometheus、 Datadog や Mackerel 等を使えばスムーズに JMX によるメトリクス収集を行えます。

今回は、任意のメトリクスを外部から取得するケース、つまり一般的なモニタリング用途での使い方について紹介します。例として、サーバーにやってきたリクエストの数を取得する JMX の例を作ってみます。

手順は下記の通りで、小さなコードでモニタリング出来ることを示したいと思います。

  1. 取得したいメトリクスに沿った MBean インターフェースを作る
  2. MBean を実装したクラスを作る
  3. 2. のクラスを MBeanServer に登録する
  4. メトリクスを取得可能な場所で値を設定する
  5. モニタリングシステムでモニタリング項目を取得する

MBean インターフェースを作ろう

まず始めに、MBean というインターフェースを作る必要があります。MBean(Management Bean) とは管理された Bean を意味し、ここではモニタリングに用いるオブジェクトということを表します。外部からアプリケーションの情報を取得する際はこの MBean を介します。

作成したインターフェース名のサフィックスの MBean は省略できないので注意してください。今回は RequestMonitorMBean という名前でインターフェースを作ります。インターフェースには、JMX で取得したい項目を記述します。

public interface RequestMonitorMBean {
    long getRequestCount(); // リクエスト数を取得する
}

MBean を実装したクラスを作ろう

では RequestMonitorMBean を実装したクラスを作りましょう。サーバーのリクエストは並列に来るものなので、内部カウンタは AtomicLong を利用します。

public class RequestMonitor implements RequestMonitorMBean {

    // リクエスト数を保持するカウンタ
    private final AtomicLong requestCount = new AtomicLong();

    @Override
    public long getRequestCount() {
        return requestCount.get();
    }

    // リクエスト数をインクリメントする
    public void incrementRequestCount() {
        requestCount.incrementAndGet();
    }
}

MBeanServer に登録しよう

RequestMonitor クラスを作成したら、そのインスタンスを MBeanServer という MBean を管理するためのクラスに登録する必要があります。オブジェクト登録時にオブジェクト名を任意に決められるので、わかりやすい名前を付与しましょう。オブジェクト名には様々な属性値を付与することができますが今回は省略します。下記コードはモニタリングを行う前の部分(通常はアプリケーション起動時)で処理されるようにします。

RequestMonitor monitor = new RequestMonitor(); // MBean オブジェクトの生成
String name = "com.cybozu.server:type=RequestMonitor"; // MBean 名
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); // MBeanServer を取得
mBeanServer.registerMBean(monitor, new ObjectName(name)); // 登録

メトリクスを収集しよう

ここまで準備が整ったら、あとは RequestMonitor クラスのインスタンスに、適切な箇所でリクエスト数をインクリメントしていくだけです。イメージ的には下記のようなリクエストを受け付ける部分にコードを差しこめば OK です。

一つ注意点があります。ここで使う monitorMBeanServer に登録したオブジェクトと同じオブジェクトを使うようにしましょう。このオブジェクトの受け渡しが手間になることもしばしばあるので、RequestMonitor のようなクラスはシングルトンにするという手もあります。

public void doGet(Request req, Response res) {
    monitor.incrementRequestCount(); // リクエストが来たらカウンタをインクリメント
    ...
}

モニタリングしよう

いよいよモニタリング出来る状態になりました。ここで、正しく外部からメトリクスを取得出来るか試してみます。JMX に対応していればツールは何でも良いのですが、手元で気軽に試すには jconsole が簡単です。jconsole は JDK に同梱されているのでターミナルで jconsole と打てば起動するはずです。

jconsole 起動

自身で起動したアプリケーションを選択して [Connect] を選択、その後 [MBeans] タブを開くと左ペインに MBean オブジェクトがあるはずなので、そのオブジェクトのツリーを展開すると今回のメトリクスが表示されていることが確認できるかと思います。今回は実験でサーバーに 5 回アクセスしたので、[Attribute value] に RequestCount が 5 と表示されています。

メトリクス表示

実際に運用環境でモニタリングする際は RMI を用いてリモートから値を取得できるようにしましょう。RMI (Remote Method Invocation) とはその名の通りリモート環境の Java のメソッド呼び出しを行える仕組みです。起動オプションでプロパティを指定すれば RMI が有効になり、リモートで JMX メトリクスを収集できるようになります。簡単に動かすための例を示します。

  • -Dcom.sun.management.jmxremote
  • -Dcom.sun.management.jmxremote.port=18686
  • -Dcom.sun.management.jmxremote.rmi.port=18686
  • -Dcom.sun.management.jmxremote.authenticate=false
  • -Dcom.sun.management.jmxremote.ssl=false

ポート番号は適当な空いているポートを使いましょう。RMI は外部から接続できるとは言えインターネット経由で誰でも接続できる状態にしてしまうとセキュリティホールになるので注意してください。RMI を呼び出す際に認証することもできるので、環境に応じてセキュリティ設定をしましょう。仕様はこの辺りにあります。

ポートを開けたり、MBean 周りのメソッドの実行などは Java Security Manager の権限を必要とします。必要に応じて権限も修正しましょう。

以上で、無事 JMX を用いてモニタリングを行えるようになりました。

サイボウズの JMX 活用例

サイボウズでの JMX の活用例を紹介します。

まずひとつ目は定番である JVM メモリ量です。単にプロセスの使用メモリ量を表示するのではなく、コードキャッシュ容量や OLD 領域、EDEN 領域などに区分けして可視化しています。メモリ状況はモニタリング用の特別なコードを書かずとも取得できるので、このようなグラフを作るのは非常に簡単です。

Datadog-Memory

こちらはとある Java プログラムの内部キャッシュ状況です。キャッシュのサイズや、キャッシュにエントリを挿入した回数、ルックアップ回数、ヒット率などを表示しています。

Datadog-Cache

最後に、ある非同期ジョブ実行サービスの各種ジョブの実行状況のグラフです。ジョブ実行キューが滞留してしまった際などはこのようなグラフを見ると異常な形をしていることが多く、ひと目で状況が理解できます。

Datadog-JobCount

プロセスの使用メモリ量などは JMX を使わずとも取得できますが、内部キャッシュのルックアップ回数のような細やかな値も取得できるのは JMX の面目躍如といったところだと思います。

終わりに

モニタリングはシステムの中でも無限に追求できてしまう部分です。アラートのしきい値をどれくらいにすれば良いのか、どのメトリクスを収集すれば良いのか、どうやって収集するか、モニタリングシステムのスケーラビリティ問題、等々追求しようとする限り終わりがありません。

個人的には、モニタリング環境がどれだけ整っているか、という指標はわりとシステムの成熟度を表しているのではないかと思います。アプリケーションがダウンした際に即座に対応できたり、負荷状況を確認できたりすることはアプリケーションをより良い方向に進化させるきっかけとなるからです。アプリケーション開発とモニタリング、この両輪が揃うことでシステムが成熟する方向に進むことができ、結果、開発者やユーザーの幸福度を増大してくれるものだと信じています。きっと、 JMX はその一端を担ってくれることでしょう。

それでは、良いモニタリングライフを!

参考