テーマ:
こんばんは。社内ニートの佐野と申します。所属はいちおAmebaのインフラチームです。Twitterもやっていますがフォローする価値はないです。つぶやいているのはメシと酒の話と下ネタだけです。
最近、組織改編とかもろもろありましてこのたびは雑用からニートになりました:(;゙゚'ω゚'): あまり目立たないように生きてたんですが、何気にこのブログへの登場は3回目になります...。前回は2012年12月に「QCon San Francisco 2012の参加レポ」を書かせていただきました。その前は2012年5月に「Virident FlashMAXの検証」として名前だけ登場しました。なんか、ニートだけど会社に貢献してる気がしてきた(^ρ^)

そんなわけで以前WebSocketを使った監視ツールもどきを作ったので、それについての簡単な説明と、各種技術要素(Python, Jolokia)のTipsを紹介させていただきます。
WebSocketというと特にフロントエンドを生業とするエンジニアは早い段階から触れているかと思います。なので、エンドユーザ同士のチャットや通知機能の実装に使われるものという印象を持たれる方も多いんじゃあないかと思いますが、これを監視に応用します。
自分のブログに書いたエントリの焼き増し記事になってしまうのですが、WebSocketを監視ツールに使うのアリだな...ってことが伝わればうれしいです。


作ったもの

こんなの↓


Cassandraの全ノードのPendingTask値(※)をリアルタイムでチェックすることができます。値が一定以上を超えると色が変わります。

※ CassandraのPendingTask値は処理の滞留数を表します。PendingTask値の高いノードは処理が詰まっているということになります。Cassandraのアーキテクチャや挙動については弊社のCassandra芸人達がきっと解説してくれるハズ(チラッチラッ)。


なんで作ったのか

Cassandraの全ノードのPendingTask値の一覧を見たいよね って話から始まりました。
もちろんnagiosなりで監視してはいるのですが、調査に入ったときには既に値が落ち着いてしまっていたり、Cactiやmuninなどのツールではグラフ描画されるまでに少し時間がかかってしまいます。そこで

・今まさに何が起きているかがリアルタイムに見たい
・Cassandra90ノード分を1ビューで見たい
・グラフ描画よりは数値表示が良い(ノード数が多すぎるのでグラフだと見づらい。また、スパイクがあるとグラフが潰れてしまう。)
・数値が高くなったら色を変わると良い。

という要件を満たすものを作ってみよっか、という感じで作りました。実は最初はAjaxのポーリングだったのですが、「ツールにアクセスするとMacBook Proのファンがッー!ブラウザがッー!」とクレームを受けたのでやめました。90ノードを数秒間隔でポーリングにしたらそらそうなるよね...wこれもWebSocketにした理由でもあります。


ツールの構成

こんな感じ↓


ツールの動作なのですが、ブラウザでServerにアクセスしてWebSocketを張ります。
別プロセスで動くCollectorがnode(※)を巡回してPendingTask値を収集し、データをHTTPでServerに送ります。
Serverはデータを受信すると値をブラウザにプッシュします。

※ nodeはCassandraノードのことです。PendingTask値収集のためにJolokiaというライブラリ(後述)を仕込んでいます。

ここまでは自分のブログの焼き増し...(;-公-)


Tornado

ここからは主にTipsになります。サーバの実装にはTornadoを使いました。
TornadoはFriendFeedが開発したWebフレームワークで、シングルスレッド/ノンブロッキングIOで動作するのが特徴です。現在はFriendfeedを買収したFacebookがオープンソースとして公開しています。
なぜTornadoを採択したかというと...僕がTornadoの使い方をある程度知っていたという理由を筆頭に、WebSocketハンドラが標準で付いていること、ツール用のサーバが低スペックな仮想マシンのため省リソースであること、が挙げられます。あと、最近はPythonを書くときは最新の3.3系を使うようにしているのですが、Tornadoはちゃんと対応してます^^
ソースコードは次のとおり、シンプルにWebアプリケーションが書けます。

------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import tornado
from tornado import websocket, web, ioloop

ws_client = []

class IndexHandler(tornado.web.RequestHandler):

    def get(self):
        self.render('index.tpl')

class SocketHandler(tornado.websocket.WebSocketHandler):

    def open(self):
        if self not in ws_client:
            ws_client.append(self)

    def on_close(self):
        if self in ws_client:
            ws_client.remove(self)

class ApiHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    def get(self, *args):
        self.finish()
        id = self.get_argument('id')
        value = self.get_argument('value')
        data = {"id": id, "value": value}
        data = json.dumps(data)
        for cl in ws_client:
            cl.write_message(data)

app = tornado.web.Application([
    (r'/', IndexHandler),
    (r'/ws', SocketHandler),
    (r'/api', ApiHandler),
])

if __name__ == '__main__':
    app.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
------------------------------------------------------------

簡単にソースコードを説明します。
ブラウザがhttp://xxx.xxx.xxx.xxx:8888/にアクセスするとindex.tpl(※)を返します(IndexHandler)。
SocketHandlerはWebSocketコネクションを管理します。
ApiHandlerは/api?id=xxx&value=xxxを受け取ると、接続しているWebSocketクライアントにメッセージを配信します。

※ Tornadoにはテンプレートエンジンも備わっています。拡張子が.tplなのは適当で特に理由はないです。index.tplには90ノード分のIPやら何やらをtableタグに記述するのですが、

------------------------------------------------------------
<tr id="cas1">
<td> 1 </td><td> xxx.xxx.xxx.1 </td><td id="ptask1"> 0 </td>
</tr>

・・・

<tr id="cas10">
<td> 10 </td><td> xxx.xxx.xxx.10 </td><td id="ptask10"> 0 </td>
</tr>
------------------------------------------------------------

などと律儀に書いているわけもなく、
 
------------------------------------------------------------
{%for n in range(1,11)%}
<tr id="cas{{n}}">
<td>{{n}} </td><td> xxx.xxx.xxx.{{n}} </td><td id="ptask{{n}}"> 0 </td>
</tr>
{%end%}
------------------------------------------------------------

などとすることで、スッキリ短く記述できます。


Jolokia

CassandraからPendingTask値を抜くにはJolokiaを使っています。JolokiaはMBeanにHTTPでアクセスできるようにしてくれるJavaのライブラリです。レスポンスはJSONで返ってきます。PendingTask値はCassandra付属のnodetoolコマンドや、MBeanから取得できるのですが、やっぱRESTが楽ですよね。例えば次のようにJVM起動オプションに渡してあげます。下記の例の場合、8778ポートでMBeanにHTTPアクセスできるようになります。

JVM_OPTS="$JVM_OPTS -javaagent:/usr/local/lib/jolokia-jvm-1.0.6-agent.jar=port=8778,host=xx.xx.xx.xx"

8778ポートにHTTPリクエストを投げてやることでMBeanに格納された各種パラメータを取得したり、FullGCを発生させたりすることができるようになります。Cassandraに限らずJVMベースのミドルウェアやアプリケーションを使っている場合は有効活用できます。

・例1:JVMのメモリ使用量を取得する場合
 http://xxx.xxx.xxx.xxx:8778/jolokia/read/java.lang:type=Memory
・例2:ReadStageのPendingTask値を取得する場合
 http://xxx.xxx.xxx.xxx:8778/jolokia/read/org.apache.cassandra.request:type=ReadStage


Collector

これはOSSでもなんでもなくて、単に情報を収集するための独立したプロセスです。これがjolokia経由でCassandraのPendingTask値を抜いて、TornadoのHTTPの口(/api)にPendingTask値を送りつけます。HTTPが投げられればいいので言語はなんでも良いです。また、各CassandraノードにAgentとして仕込んでも良いし、tornadoが稼働するサーバに同居させても良いし、どっか別のサーバにあっても良い。
今回はPendingTask値を収集していますが、CPU使用率などのOSの情報を収集するようにすればそれを表示できるようになります。



以上がツールの紹介と各種技術要素のTipsになります。
また、冒頭でも述べた通り、WebSocketというとフロントエンドのエンジニアの技術と思いがちなのですが、むしろ監視やモニタリングなどシステム管理を担当しているような運用寄りのソフトウェアエンジニアも習得しとくといいんじゃないかと思います。


最後に

弊社は個人個人が自由な言語を使っています(逆に言うとまとまりがない)。そんな中、僕はPython推しなので、社内でPython勉強会を催してみたり地味に布教活動にいそしんでおります。そして、ようやく弊社にもGitHub Enterpriseが導入されました!GHE!GHE!GHE!GHE!
導入されてからはクローズドな自作ツールやソースコードの断片はGHEに載せるようにしてます。これで社内の開発力が活性化するとうれしいですね。

開発も運用もできるエッジの効いたエンジニア募集中!!!
いいね!した人  |  リブログ(0)

サイバーエージェント 公式エンジニアブログさんの読者になろう

ブログの更新情報が受け取れて、アクセスが簡単になります