2012年11月11日日曜日

redisってなんじゃ?(pub/sub編)


redisにはpub/subという機能があります。

これはpublish/subscribeパターンという仕組みの実装で、発行者と購読者という役割でメッセージの配信を行うものです。

では、redisのコンソールを使って試してみたいと思います。

redis1とredis2という2つのホストを使用します。
redis1ではローカルホストでredis-cliに接続します。
redis2ではredis1のredis-clに接続します。

redis1
# redis-cliredis 127.0.0.1:6379>

redis2
# redis-cli -h 10.0.0.200redis 10.0.0.200:6379>

メッセージの購読はチャンネル名を指定します。
redis2でgreetingというチャンネル名でsubscribeコマンドを実行します。

redis2
redis 10.0.0.200:6379> subscribe greeting
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "greeting"
3) (integer) 1

こうすることによって、redis2はredis1のgreetingというチャンネルのメッセージの待ち受けが始まります。

続いて、redis1でメッセージを発行します。
greetingというチャンネルで「Hello World!」という内容のメッセージを発行します。

redis1
redis 127.0.0.1:6379> publish greeting "Hello World!"
(integer) 1
redis 127.0.0.1:6379>

すると即座にredis2のクライアントにHello Worldというメッセージが受信されたことがわかります。

redis2
redis 10.0.0.200:6379> subscribe greeting
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "greeting"
3) (integer) 1
1) "message"
2) "greeting"
3) "Hello World!"

このように、redisでは他のサーバーのredisクライアントとメッセージのやりとりが可能です。
クライアントコンソールでは効能がわかりにくいので、次回はアプリケーションレベルで試してみたいと思います。

以上です。

2012年11月10日土曜日

Node.jsってなんじゃ?(redisでSocket.IOをスケール)


前回は、redisをインストールして生でつかってみました。
今回はnode.jsでredisを利用してみたいと思います。

マルチユーザーのサーバーでのプッシュ配信はSocket.IOが定番ですが、
サーバーが増えた時にある問題が生じます。
例えばサーバーを2つに増やして、サーバーAでブロードキャストしても
サーバーBのクライアントでは受信できないのです。

以前の記事で作成したチャットプログラムを例にしてみます。

サーバー側のjs
$ cat /home/appadmin/chat/node/chat.js
var server = require('http').createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('server connected');
});
server.listen(3001);

var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
  socket.emit('info', { msg: 'welcome' });
  socket.on('msg', function (msg) {
    io.sockets.emit('msg', {msg: msg});
  });
  socket.on('disconnect', function(){
    socket.emit('info', {msg: 'bye'});
  });
});


クライアント側のjs

$ cat /home/appadmin/chat/public/assets/js/client.js
$(function(){
    var socket = io.connect('http://'+location.hostname+':3001/');
    socket.on('connect', function() {
      $("#log").html($("#log").html() + "<br />" + 'connected');
      socket.on('info', function (data) {
        $("#log").html($("#log").html() + "<br />" + data.msg);
      });
      socket.on('msg', function(data){
        $("#log").html($("#log").html() + "<br />" + "<b>" + data.msg + "</b>");
      });
      $("#send").click(function(){
        var msg = $("#msg").val();
        if(!msg){
          alert("input your message");
          return;
        }
        socket.emit('msg', msg);
      });
    });
  });


画面
$ cat /home/appadmin/chat/public/index.html
<!DOCTYPE html>
<html>
<head>
     <meta charset="UTF-8">
     <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
     <script type="text/javascript">
     $(function(){
         function load(){
             $.getScript("assets/js/client.js");
         }
         $.getScript("http://" + location.hostname + ":3001/socket.io/socket.io.js", function(){
             load();
         });
     });
     </script>
     <title>Node A</title>
</head>
<body>
     <input id="msg" type="text" style="width:400px;"></input>
     <input id="send" type="button" value="send" /></br >
     <div id="log" style="width:400px;height:400px;overflow:auto;border:1px solid #000000;"></div>
</html>


サーバー側のjsをforeverで起動します。
$ forever start chat.js

これをサーバーAとします。
同じ内容を別のサーバーBに配置し、同じようにnodeを起動します。

2つのサーバーにアクセスすると、以下のように同じ画面が表示されます。




サーバーAで「a」と入力します。
サーバー側では接続した全ユーザーに投稿内容をブロードキャストし、ユーザーの画面に「a」が表示されます。
しかし、サーバーBには何も表示されません。
おなじようにサーバーBで、「b」と投稿してもサーバーAに接続した画面にはなにも表示されません。
接続がサーバーAとサーバーBで共有されていないためです。

ここで登場するのがredisです。
Socket.IOではデフォルトで接続情報をローカルメモリに保存しています。
これをMemoryStoreと呼びますが、Socket.IOにはRedisStoreというredisに接続情報を保存するオプションも存在します。
このオプションを選択することで、分散されたnodeサーバーが同じredisサーバーを参照し
各nodeの接続情報を共有することができます。


それではサーバー側のjsを修正してRedisStoreを使ってみます。
接続するredisは前回設定したredisサーバーにします。

var server = require('http').createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('server connected');
});
server.listen(3001);

var io = require('socket.io').listen(server);

//RedisStoreを読み込みます
var RedisStore = require('socket.io/lib/stores/redis');
//redisサーバーの接続先情報を定義します
opts = {host:'10.0.0.200', port:6379};
//storeをRedisStoreにし、redisPub, redisSub, redisClientをredisサーバーに向けます
io.set('store', new RedisStore({redisPub:opts, redisSub:opts, redisClient:opts}));

io.sockets.on('connection', function (socket) {
  socket.emit('info', { msg: 'welcome' });
  socket.on('msg', function (msg) {
    io.sockets.emit('msg', {msg: msg});
  });

  socket.on('disconnect', function(){
    socket.emit('info', {msg: 'bye'});
  });
});


これで再起動します。
$ forever restart chat.js


再度2つの画面をリロードして、サーバーAの画面に「a」サーバーBの画面に「b」と入力してみます。




おお、両方の画面に「a」「b」が表示されました!
これで台数が増えてもすべてのユーザーが同じ空間でコミュニケーションすることができます。

以上です。

2012年11月9日金曜日

redisってなんじゃ?


redisはCで書かれたインメモリKVSでで、データセットすべてをメモリ内にもつため動作は爆速です。

また、指定回数更新が発生すると、データのスナップショットをディスクに書き込むため、プロセスが落ちてもデータを保持することができ永続化も可能です。

今回はredisをちょっと触ってみたいと思います。
さっそくインストールしてみます。
$ cd /usr/local/src
$ wget http://redis.googlecode.com/files/redis-2.6.3.tar.gz
$ tar xzf redis-2.6.3.tar.gz
$ cd redis-2.6.3
# make
# make install

インストールはこれで完了です。/usr/local/binにredisのコマンド群がインストールされました。

続いてredisを起動してみます。
# redis-server
[3042] 08 Nov 16:32:25.609 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
[3042] 08 Nov 16:32:25.609 # Unable to set the max number of files limit to 10032 (Operation not permitted), setting the max clients configuration to 992.
[3042] 08 Nov 16:32:25.610 # Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.
                _._                                              
           _.-``__ ''-._                                          
      _.-``    `.  `_.  ''-._           Redis 2.6.3 (00000000/0) 32 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 3042
  `-._    `-._  `-./  _.-'    _.-'                                
 |`-._`-._    `-.__.-'    _.-'_.-'|                              
 |    `-._`-._        _.-'_.-'    |           http://redis.io    
  `-._    `-._`-.__.-'_.-'    _.-'                                
 |`-._`-._    `-.__.-'    _.-'_.-'|                              
 |    `-._`-._        _.-'_.-'    |                              
  `-._    `-._`-.__.-'_.-'    _.-'                                
      `-._    `-.__.-'    _.-'                                    
          `-._        _.-'                                        
              `-.__.-'                                            

[3042] 08 Nov 16:32:25.610 # Server started, Redis version 2.6.3
[3042] 08 Nov 16:32:25.610 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[3042] 08 Nov 16:32:25.610 * The server is now ready to accept connections on port 6379

このようなコンソールログが出力され、起動されたことを示します。
リッスンポートは6379がデフォルトのようです。


また、ログの内容をみると、confファイルを指定して起動する方法も記載されているので、その様にしてみます。
まずCtrl+Cでサーバーをストップします。
そして、/etc/redis/ディレクトリを作成し、解凍したredis-2.6.3にあるredis.confを、その下に配置します。
また、データファイルの置き場として/var/lib/redisを作成しておきます。
# mkdir /etc/redis /var/lib/redis
$ cd /usr/local/src/redis-2.6.3/
$ cp redis.conf /etc/redis/

そして設定ファイルの場所を指定して再起動します。
# redis-server /etc/redis/redis.conf


ここで、同じサーバーの別のコンソールからこのサーバーへクライアント接続してみます。
$ /usr/local/src/redis-2.6.3/src
$ redis-cli
redis 127.0.0.1:6379>


このようにredisのクライアントが立上がり、コマンドを待ち受けます。

値の保持と取得はset,getで行うようです。
redis 127.0.0.1:6379> set greeting "Hello World!"
OK
redis 127.0.0.1:6379> get greeting
"Hello World!"
問題なくセットされているようです。


次に、redisサーバーを落としてみます。
[3042] 08 Nov 16:32:25.610 * The server is now ready to accept connections on port 6379
^C


再起動します。
# redis-server /etc/redis/redis.conf
redis 127.0.0.1:6379> get greeting
(nil)

永続化されていないようです。
redisはディスクに書き込む条件を設定でき、さきほどの設定ファイルをみると
save 900 1
save 300 10
save 60 10000
となっています。

ドキュメントの日本語訳をみると
このような設定がされると、次のようなタイミングで保存します:
もし最低1回、キーの変更が発生すると、900秒(15分)後
もし最低10回、キーの変更が発生すると、300秒(5分)後
もし最低10,000回、キーの変更が発生すると、60秒後

となっています。
ここではわかりやすいように、変更があったら1秒後に保存してみます。
また、ログファイル出力やデータファイルの場所も指定しておきます。
# vim /etc/redis/redis.conf
---
~略~
#logfile stdout
logfile /var/log/redis.log
~略~
save 1 1
#save 900 1
#save 300 10
#save 60 10000
~略~
---


そして再起動します。
^C
# redis-server /etc/redis/redis.conf


再びコンソールで値をセットします。
redis 127.0.0.1:6379> set greeting "Hello Word!"
OK
redis 127.0.0.1:6379> get greeting
"Hello World!"


そして、サーバーを一度落としてから再度起動してみます。
^C
# redis-server /etc/redis/redis.conf


再度コンソールで値を取得すると、、、
redis 127.0.0.1:6379> get greeting
"Hello World!"

今度は取得することができました。
データがメモリからディスクに保存されていたことがわかります。
また、起動してしまえばデータはメモリ上に再配置されているので、パフォーマンスが落ちることはありません。

ここまででも良いのですが、redisには起動スクリプトがありません。
起動スクリプトをつくって起動してみます。
あらかじめredis-serverは落としておきます。
まず、redisをデーモン化できるように設定します。
# vim /etc/redis/redis.conf
---
~略~
#daemonize no
daemonize yes
~略~
---


スクリプトファイルをgist公開している人がいたので、流用させていただきました。
ただし、redisの場所は/usr/local/sbinではなく/usr/local/binなのでその部分は変更しています。
#vim /etc/init.d/redis-server
---

#!/bin/sh
#
# redis - this script starts and stops the redis-server daemon
#
# chkconfig: - 85 15
# description: Redis is a persistent key-value database
# processname: redis-server
# config: /etc/redis/redis.conf
# config: /etc/sysconfig/redis
# pidfile: /var/run/redis.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

redis="/usr/local/bin/redis-server"
prog=$(basename $redis)

REDIS_CONF_FILE="/etc/redis/redis.conf"

[ -f /etc/sysconfig/redis ] && . /etc/sysconfig/redis

lockfile=/var/lock/subsys/redis

start() {
    [ -x $redis ] || exit 5
    [ -f $REDIS_CONF_FILE ] || exit 6
    echo -n $"Starting $prog: "
    daemon $redis $REDIS_CONF_FILE
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

reload() {
    echo -n $"Reloading $prog: "
    killproc $redis -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac
---


続いて、自動起動設定します。
# chkconfig --add redis-server
# chkconfig redis-server on
# /etc/init.d/redis-server start
redis-server を起動中:                                     [  OK  ]
# ps -ax | grep redis
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
 5494 ?        Ssl    0:00 /usr/local/bin/redis-server /etc/redis/redis.conf
 5499 pts/2    S+     0:00 grep redis


うまく起動しているようです。
ここで別コンソールのredis-cliを起動してみます。
$ redis-cli
redis 127.0.0.1:6379> get greeting
"Hello World!"

成功しました。
これで起動時にredisを立ち上げることができました。

次回はこのredisをアプリケーションから使ってみたいと思います。
以上です。

2012年11月7日水曜日

Fluentdってなんじゃ?(Mongo&Node編 ブラウザでtail)

前回までで、fluentdを利用してApacheのログをmongoDBに保存することができました。

構成はこのようになっていました。
  +----------------------------+   +------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16)|
  |----------------------------|   |------------------------|
  |          fluentd           |   |                        |
  |                            |   |                        |
  | +---------+     +--------+ |   |      +----------+      |
  | | input   |     | output | |   |      |  fluent  |      |
  | |---------|+--> |--------| +--------->|----------|      |
  | |  tail   |     |  mongo | |   |      |   test   |      |
  | +---------+     +--------+ |   |      +----------+      |
  +----------------------------+   +------------------------+

今回はnode.jsを使用して、mongoDBに保存されたログをブラウザでリアルタイムにtailしてみます。
構成は以下のとおりです。
  +----------------------------+   +-------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16) |
  |----------------------------|   |-------------------------|
  |          fluentd           |   |         mongod          |
  |                            |   |                         |
  | +---------+     +--------+ |   |      +----------+       |
  | | input   |     | output | |   |      |  fluent  |       |
  | |---------|+--> |--------|+---------->|----------|       |
  | |  tail   |     |  mongo | |   |      |   logs   |       |
  | +---------+     +--------+ |   |      +-----+----+       |
  +----------------------------+   +------------|------------+
                                                |
                                 +--------------+
                                 | +-------------------------+
                                 | | node server(10.0.0.100) |
                                 | |-------------------------|
                                 | |         node.js         |
                                 | |                         |
                                 | |      +-----------+      |
                                 +------->| mongoose  |      |
                                   |      |-----------|      |
                                   |      | socket.io +----------> Brower
                                   |      +-----------+      |
                                   +-------------------------+


今回、web serverとmongo serverは、ほとんど前回の設定のままですが、
新規nodeサーバーでmongodbにアクセスするmongooseはデフォルトで複数系の名前のコレクションにアクセスするようになっています。
そこで、webサーバーのtd-agentのmongo outputプラグインの書き出し先をlogsという名前に変更しておきます。
また、リアルタイムに表示させたいのでflush_intervalを0sに設定しておきます。
<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access="apache.access">
  type mongo
  flush_interval 0s
  database fluent
  collection logs

  host 10.0.0.16
  user memorycraft
  password *******
</match>


それでは、nodeサーバーを設定します。
新規にEC2インスタンスをたちあげてnodeとhttpdをインストールします。
# yum install httpd -y
# /etc/init.d/httpd start

# yum install -y wget
# cd /usr/local/src/
# wget http://nodejs.org/dist/v0.8.14/node-v0.8.14.tar.gz
# tar xzvf node-v0.8.14.tar.gz 
# cd node-v0.8.14
# ./configure
# make
# make install


次にnpmをインストールしてからsocket.io, log, forever, mongooseをnpmインストールします。
# curl https://npmjs.org/install.sh | sh
# npm install -g socket.io
# npm install -g forever


nodeを動かすためのユーザーを作成します
# useradd appadmin
# passwd appadmin
# chmod 755 /home/appadmin
# su - appadmin


ノードモジュールへのパスを通すために環境変数を設定します。
$ vi ~/.bash_profile 
$ source ~/.bash_profile 
---
export NODE_PATH=/usr/local/lib/node_modules
---

$ source ~/.bash_profile


ここで、nodeサーバーとHTML用のコンテンツ置き場を用意します。
ディレクトリ構成は以下のとおりです。
cd ~/nodetest
tree
.
|-- node
|   |-- logs
|   |   `-- app.log
|   `-- server.js (nodeサーバー)
`-- public
    |-- assets
    |   |-- css
    |   |-- img
    |   `-- js
    |       `-- client.js (nodeクライアント)
    `-- index.html (tail用の画面)


また、publicをhttpdのDocumentRootにするため、以下のように設定します。
$ su -
# cd /var/www
# mv html html.org
# ln -s /home/appadmin/nodetest/public html
# vi /etc/httpd/conf/httpd/conf
---
~略~
<directory html="html" var="var" www="www">
    Options FollowSymLinks
    AllowOverride All
~略~
---
</directory>

# /etc/init.d/httpd restart


そして、httpdとnodeに接続させるため、SecurityGroupを以下のように設定します。



次にサーバーとコンテンツを実装してみます。 それぞれの内容は以下の通りです。

node/server.js
//3001番でlistenします
var server = require('http').createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('server connected');
});
server.listen(3001);

//logsコレクションのスキーマ定義をしておきます。
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AccessSchema = new Schema({
    host: String,
    user: String,
    method: String,
    path: String,
    code: Number,
    size: Number,
    referer: String,
    agent: String,
    time : Date
});
mongoose.model('log', AccessSchema);

//fluentデータベースに接続します。
mongoose.connect('mongodb://10.0.0.16/fluent');
var Access = mongoose.model('log');

//socket.ioを起動します。
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
    /**
     * クライアント接続時に、初期データとして、
     * 保存されているlogsコレクションをすべてemitします。
     */
    Access.find({}).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            socket.emit('init', docs);
        }
    });
});

/** 
 * 1.5秒おきに最終取得ログの日時以降のログを取得して
 * 全員にブロードキャストし、最終日時を保存します。
 */
var last = new Date();
setInterval(function(){
    Access.find({}).where('time').gt(last).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            for(var i=0; i<docs.length; i++){
                if(last < docs[i].time){
                    last = docs[i].time;
                }
            }
            io.sockets.emit('log', docs);
        }
    });
}, 1500);


public/assets/js/client.js
$(function(){
  //接続します
  var socket = io.connect('http://' + location.hostname + ':3001/');
  socket.on('connect', function() {
   /**
     * 接続成功時に初期データを時系列降順で表示させます
     */
    socket.on('init', function(data){
        $("#log").empty();
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

    /**
     * 随時送られてくるデータは一番上に表示させます
     */
    socket.on('log', function(data){
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

  });
});


public/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
    function load(){
        $.getScript("assets/js/client.js");
    }
    $.getScript("http://" + location.hostname + ":3001/socket.io/socket.io.js", function(){
        load();
    });
});
</script>
<body>
    <div id="content">
        <h1>ログ</h1>
        <div id="log" width="500px" height="100%">
        </div>
    </div>
</body>
</html>


早速動かしてみます。
$ cd ~/nodetest/node
$ forever start server.js

これでnodeサーバーが起動しました
それでは、ブラウザを見てみます。



初期ログが表示されています。うまく動いているようです。
そこで、fluentdのtail対象サーバーであるwebサーバーへアクセスをしてみます。
わかりやすくするために、何度かリロードしておきます。


そして、さきほどのnodeサーバーの画面をみると、、、


おお!ログが追加で現れました!
これであるサーバーのログデータを別のサーバーでリアルタイムに表示させることができました。

以上です。

2012年11月5日月曜日

Fluentdってなんじゃ?(MongoDB編)


今回は、fluentdを利用して、Apacheのログを別サーバーのMongoDBに保存してみようと思います。

前回のlogサーバー(10.0.0.16)にmongoDBをホストし、webサーバー(10.0.0.8)のtd-agentから直接mongoに保存します。

まずはlogサーバーにmongoDBをインストールします。
yumリポジトリとして以下を追加してからyumインストールを行います。
# vi /etc/yum.repos.d/10gen.repo]
---
[10gen]
name=10gen Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/i686
gpgcheck=0
---

# yum install mongo-10gen mongo-10gen-server -y

mongoのインストールは完了しました。
起動しておきます。
# /etc/init.d/mongod start


続いてwebサーバー側にmongoプラグインを設定します。
mongo Output Pluginはビルトインではなく、td-agent用のrubygemでインストールして使用するため、以下のようにインストールを行います。
# /usr/lib/fluent/ruby/bin/fluent-gem install fluent-plugin-mongo
Fetching: fluent-plugin-mongo-0.6.10.gem (100%)
Successfully installed fluent-plugin-mongo-0.6.10
1 gem installed
Installing ri documentation for fluent-plugin-mongo-0.6.10...
Installing RDoc documentation for fluent-plugin-mongo-0.6.10...


設定ファイルには、以下のように出力先をtype mongoを設定します。これは、

source
 apacheフォーマットの/var/log/httpd/access_logファイルをtailし、apache.accessというタグをつける

match
  apache.accessタグの入力があったら、10.0.0.16のmongoDBのfluentデータベースへmemorycraftユーザーで接続し、hogeコレクションとしてデータを登録する

という意味になります。
# vim /etc/td-agent/td-agent.conf

---

<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type mongo
  database fluent
  collection hoge
  host 10.0.0.16
  user memorycraft
  password ******
</match>

---


この時点ではwebサーバーからlogサーバーへmongoの接続ポートが閉じているので、まだ接続できません。
EC2で、mongoのデフォルトポートの27017番を開放します。




ここで一度webサーバー側のtd-agentを再起動しておきます。
# /etc/init.d/td-agent restart


この時点でmongoに接続すると、fluentデータベースはできているようです。
ここで、認証用にユーザーを作成します。
# mongo
MongoDB shell version: 2.2.1
connecting to: test

> use fluent
switched to db fluent
> db.addUser("memorycraft", "*****")
{
 "_id" : ObjectId("5096e46f1b4da85be896fb1e"),
 "user" : "memorycraft",
 "readOnly" : false,
 "pwd" : "2922d9ce24a959b1f359f7ba2d044017"
}

これで認証が設定されたので、webサーバー側のtd-agentを改めて再起動します。
# /etc/init.d/td-agent restart


ここで、mongoシェルに接続してみるとfluentというDBにhogeというコレクションができていることがわかります。
# mongo
MongoDB shell version: 2.2.1
connecting to: test
> use fluent
switched to db fluent
> show collections
system.indexes
system.users
hoge


中身を見てみると、、、
> db.hoge.find()
{ "_id" : ObjectId("5096e4e5a3441113d4000001"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/", "code" : "403", "size" : "5039", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:09Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000002"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/icons/poweredby.png", "code" : "304", "size" : "-", "referer" : "http://54.249.23.246/", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000003"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/icons/apache_pb.gif", "code" : "304", "size" : "-", "referer" : "http://54.249.23.246/", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }
{ "_id" : ObjectId("5096e4e5a3441113d4000004"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/favicon.ico", "code" : "404", "size" : "288", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:10Z") }



おおー。データが入っています。
たとえば、pathが"/"のデータのみのログも簡単に抽出できるようになりました!
> db.hoge.find({path:"/"})
{ "_id" : ObjectId("5096e4e5a3441113d4000001"), "host" : "219.117.233.241", "user" : "-", "method" : "GET", "path" : "/", "code" : "403", "size" : "5039", "referer" : "-", "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.1 Safari/537.17", "time" : ISODate("2012-11-04T21:57:09Z") }


このようにfluentdを用いると、ログの集約や集計がデイリーやマンスリーではなく準リアルタイムで行えるようになります。とても便利ですね。

今回はここまで。