古民家でビジュアライズのハッカソン
きのう(11/07)は【裏鎌倉×野良IT】感性ビジュアライズの世界に参加しました。
場所は由比ヶ浜にある蕾の家という古民家。
イベントの趣旨は、NORA ITをして鎌倉の自然をコンピュータで可視化しようというもの。
NORA ITとは、屋外でIT仕事をしてインスピを得まくろうというものらしいです。
イベントの説明を受けた後、由比ヶ浜周辺をみんなで散策しました。
きのう(11/07)は【裏鎌倉×野良IT】感性ビジュアライズの世界に参加しました。
場所は由比ヶ浜にある蕾の家という古民家。
イベントの趣旨は、NORA ITをして鎌倉の自然をコンピュータで可視化しようというもの。
NORA ITとは、屋外でIT仕事をしてインスピを得まくろうというものらしいです。
イベントの説明を受けた後、由比ヶ浜周辺をみんなで散策しました。
サーバのログを可視化するSaaSは沢山あり、手軽にBusiness Intelligenceを施行できます。
DataDogとかKeen IOとかlibrato、Logglyなどなど。
とても便利そうですね。でも価格が高い!
なんでもかんでもSaaSに頼ってたら毎月数十万とかになりそうです。貧乏にはつらいです。
だから、OSSで無料でやりましょう。
もちろんインフラ代は最低限かかります。
でも、クラウドリソースはいずれ水みたいになりますから、そこはしばらくの辛抱です。
今回はApacheのアクセスログみたいな一般的なものではなく、独自のアプリケーションログを集積して可視化します。
ログデータのフォーマットは以下のようなイメージです:
{
"logid": "Log ID",
"level": "Log level (DBG/MSG/WRN/ERR)",
"message": "Message",
"module": "Name of the application module",
"hostname": "Hostname sent the log",
"time": "timestamp of the log",
<Further data..>
}
このフォーマットは昔に設計したもので、解析するのを意識していなかった事情があります。
上記フォーマットに書いてある<further data..>
の部分は、アプリケーション特化の情報です。
エラーログならコールスタックとか、ユーザ登録ならユーザIDとかです。
そういう特別な情報は、可視化までしなくとも個別に見られるようにしておきたい。
また、今回の構成が将来的に変更になっても対応しやすいように、フォーマットをシステム特化で縛りたくない。
Graphiteとかすごく良いんですが、細かい調整がやりにくい。
タイムスケールをもうちょっと細かくみたいな、とか、ヒストグラムの分割単位を変えたいとか、そこはパイグラフで見たいとか、トレンドで見たいとか、このシリーズだけ抜き出したいとか。
あと、上記でも挙げた特化データを個別にすぐ表示できると便利です。
GrowthForecastもいいんだけど、イマドキRRDtoolかよ・・JSでグリグリ動くのがいい。
上記の要件を満たす構成は、以下のようなイメージです。
タイトルの通り、fluentd + MongoDB + Elasticsearch + Kibanaとなりました。
以下、それぞれ概要と用途を説明します。
ログ集積のためのツール。
PHP/node.js/Ruby/Python製などのさまざまなプログラムと連携させられます。
syslogやApache/nginxログにも対応。
それらのログを集めて、フィルタリングしたり整形した後、集積先へと転送してくれます。
NOSQLのDBMSです。
構造体のデータを格納できます。
スキーマレスなので、アプリケーション特化の情報をどんどん追加格納できます。
Capped collectionで高速にログを蓄積できます。
今回は、ログの一次格納先として使用します。
のちのち可視化部分のシステム構成が変更になった場合に、元のデータをオリジナルで保持しておけば対応がいくらか容易になるからです。
Luceneベースの全文検索サーバ。
MongoDBと同じく、スキーマレスで構造体が扱えます。
MongoDBからデータを流し込んで、可視化用にインデックスを作成してくれます。
タイムスタンプがついたデータならなんでも可視化できるというツール。
Elasticsearchと密結合になっています。
UIがよくできていて、柔軟に設定を変えられるので、今回の用途にもってこいです。
基本的にサーバはChefで管理したいので、Cookbookを拾ってきて使います。
以下を前提に進めます
手順内にsspe
と出てきますが、プロジェクトのコードネームです。
ご自身のプロジェクト名と読み替えてください。
アプリケーションは、td-agent(fluentd agent)に対してhttp経由でログを投げます。
td-agentは、受け取ったログを指定のmongodbに転送します。
mongodbはreplicasetなので、typeをmongo_replset
としています。
<source>
type http
port 8888
</source>
<match sspe.log.*>
type mongo_replset
database fluent
collection log
# Following attibutes are optional
nodes mongodb-server-1:27017,mongodb-server-2:27017
# Set 'capped' if you want to use capped collection
capped
capped_size 1000m
# Other buffer configurations here
flush_interval 10s
</match>
1つのサーバにElasticsearchとKibanaの両方を入れます。
Elasticsearchには、以下の4つのプラグインもインストールします。
Knifeコマンドでサクっと立てます。
$ knife ec2 server create
-I ami-a1124fa0
-f t2.small
--node-name server-name
--security-group-ids sg-hogehoge
--subnet subnet-111111111
--associate-public-ip
--ebs-size 50
--ssh-key ssh-key-name
--identity-file ~/.ssh/id_rsa
--ssh-user ubuntu
--ssh-gateway ubuntu@hogehuga
--verbose
Berksfile
に以下の行を追記します。
cookbook 'elasticsearch'
cookbook 'java'
cookbook 'kibana', git: 'git@github.com:lusis/chef-kibana.git'
なぜかKibanaだけ同名の想定とは別のものが入ってしまったので、repos uriを指定します。
インストールしてアップロードします。
$ berks install
$ berks upload
ノードの設定を編集します。
$ knife node edit server-name
{
"name": "server-name",
"chef_environment": "_default",
"normal": {
"tags": [
],
"java": {
"install_flavor": "openjdk",
"jdk_version": "7"
},
"elasticsearch": {
"version": "1.2.2",
"cluster": {
"name": "sspe"
},
"plugins": {
"elasticsearch/elasticsearch-mapper-attachments": {
"version": "2.3.0"
},
"com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb": {
"version": "2.0.1"
},
"mobz/elasticsearch-head": {
},
"royrusso/elasticsearch-HQ": {
},
"elasticsearch/elasticsearch-lang-javascript": {
"version": "2.1.0"
}
}
},
"kibana": {
"config": {
"elasticsearch": "window.location.protocol+"//"+window.location.hostname+":"+9200",
"default_route": "/dashboard/file/guided.json"
},
"kibana": {
"web_dir": "/opt/kibana/current"
}
},
},
"run_list": [
"recipe[java]",
"recipe[elasticsearch]",
"recipe[elasticsearch::plugins]",
"recipe[kibana::install]"
]
}
設定出来たら、chef-client
を実行します。
以下のように結果が返ってきたら成功です。
$ curl "http://localhost:9200/_cluster/health?pretty"
{
"cluster_name" : "sspe",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 0,
"active_shards" : 0,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0
}
Elasticsearchのインデックスとは、RDBMSでいうデータベースみたいなもんです。
下記コマンドで作成します。
curl -XPUT 'http://localhost:9200/sspe/'
マッピングとは、RDBMSにおけるスキーマ定義みたいなもんです。
明示しなくても自動で作成させる事もできますが、今回は勉強も兼ねて指定します。
$ curl -XPUT 'http://localhost:9200/sspe/log/_mapping' -d '
{
"log" : {
"properties" : {
"logid" : {"type" : "string" },
"level" : {"type" : "string" },
"message" : {"type" : "string", "store" : true },
"module" : {"type" : "string" },
"hostname" : {"type" : "string" },
"time" : {"type" : "date" },
"userinfo": { "type": "object", "enabled": false, "include_in_all": true }
}
}
}
'
riverとは、Elasticsearchにデータを流し込む経路設定のことです。
elasticsearch-river-mongodb
をインストールしてあるので、これを使う設定をします。
$ curl -XPUT "localhost:9200/_river/mongodb/_meta" -d '
{
"type": "mongodb",
"mongodb": {
"servers":
[
{ "host": "mongodb-server-1", "port": 27017 },
{ "host": "mongodb-server-2", "port": 27017 }
],
"options": {
"secondary_read_preference" : true
},
"db": "fluent",
"collection": "log"
},
"index": {
"name": "sspe",
"type": "log"
}
}'
実行すると、データの抽出が開始されるはずです。
下記URLから、headプラグインの管理画面を開きます。
http://<elasticsearch-server>:9200/_plugin/head/
ドキュメント数が増えていれば成功です。
もし設定がうまく動かなくて、Elasticsearchを再起動してもログに上記が吐かれて止まってしまう場合。
Riverのステータスをリセットしてやれば再稼働します。
$ curl -XDELETE 'http://localhost:9200/_river/mongodb/_riverstatus'
実行後、Elasticsearchを再起動します。
ESとKibanaをインストールしたサーバにブラウザからアクセスします。
http://<elasticsearch-server>/#/dashboard/file/guided.json
すると、下記のような画面が表示されるはずです。
以下にアクセスすると、空っぽのダッシュボードにアクセスできます。
http://<elasticsearch-server>/#/dashboard/file/blank.json
ADD A ROW
というボタンをクリックして、グラフを表示する領域の行を追加します。
Rowの名前を入力後、Create Row
します。
次に、パネルを追加します。
time
を指定すると・・・
やった!出ましたね!
エラーログのみを表示したい場合は、QUERYの欄に level:ERR
と打ちます。
クエリ構文はLuceneと基本同じものが使えるようです。以下が参考になります。