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

今回はここまで。

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

前回は同じサーバー内でコマンドからファイル出力を行いました。
内容としては以下のような流れです。

  +--------------------------------------+
  | server                               |
  +--------------------------------------+
  |                fluentd               |
  |                                      |
  | +-------------+       +------------+ |
  | |    input    |       |   output   | |
  | |-------------| +---> |------------| |
  | |command(tcp) |       |    file    | |
  | +-------------+       +------------+ |
  +--------------------------------------+

今回はApacheのログを別のサーバーにファイル転送してみたいと思います。
以下のようなイメージです。
  +--------------------------------------+   +--------------------------------------+
  | web server (10.0.0.8)                |   | log server (10.0.0.16)               |
  +--------------------------------------+   |--------------------------------------|
  |                fluentd               |   |               fluentd                |
  |                                      |   |                                      |
  | +-------------+       +------------+ |   | +------------+       +-------------+ |
  | |    input    |       |   output   | |   | |   input    |       |    output   | |
  | |-------------| +---> |------------| +---> |------------| +---> |-------------| |
  | |    tail     |       |    tcp     | |   | |   tcp      |       |    file     | |
  | +-------------+       +------------+ |   | +------------+       +-------------+ |
  +--------------------------------------+   +--------------------------------------+


まず、送信側のwebサーバーでは、以下のようにアパッチのログをapache.accessというタグでtailし、tcp送信するように設定します。

source

 ログのフォーマット指定でapacheを指定することでapacheのログ構造をJSON化できるようになります。
そのほかにはsyslogのフォーマットがプリセットで指定できますが、その他アプリのログなどは名前付きキャプチャの正規表現で指定することにより、任意のプロパティ名でJSON化できます。

match

 matchディレクティブで設定するforwardは、Bufferd Outputプラグインというタイプで、デフォルトではメモリにデータをバッファし60秒でflushします。
つまり60秒に一回まとめて送信を行います。今回はわかりやすくリアルタイムで行うよう、flush_intervalを0sとし、0秒でflushするように設定してみます。
また、serverディレクティブで送信先のサーバーのIPを指定します。 特に指定しない場合、ポートは24224番が使用されます。

# 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 forward
  flush_interval 0s
  <server>
    host 10.0.0.16
  </server>
</match>
---

# /etc/init.d/td-agent restart


受信側のサーバーにもtd-agentをインストールし、以下のように、tcpを受信しファイルに出力するように設定します。

source
 webサーバーのtcpから受信するためforward Input Pluginを指定します。

match
 apache.accessタグのデータを/var/log/fluent/apache_access_log***に書き出します。

# mkdir /var/log/fluent
# chown td-agent:td-agent /var/log/fluent
# vim /etc/td-agent/td-agent.conf
---
<source>
  type forward  
</source>

<match apache.access>
  type file
  path /var/log/fluent/apache_access_log
</match>
---

# /etc/init.d/td-agent start

ここで、webサーバーとlogサーバーの間にFWなどがある場合、fluentdのポートを開放する必要があります。 今回はEC2を使用しているので、Security Groupの設定でTCPの24224番を開放します。また、データ転送にはTCPを使用しますが、死活監視にはUDPが使われるので、両方を開放しておきます。



webサーバーのコンテンツにブラウザからアクセスしてみます。



すると、logサーバー側の/var/log/fluent/apache_access_log.xxxというファイルにJSONが記録されているのがわかります。
tail -1000f /var/log/fluent/apache_access_log.20121105.b4cdb099dbbaa1e42
---
2012-11-05T05:20:11+09:00 apache.access {"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"}
2012-11-05T05:20:11+09:00 apache.access {"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"}
2012-11-05T05:20:11+09:00 apache.access {"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"}
2012-11-05T05:20:11+09:00 apache.access {"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"}

これで、リモートのサーバーにログを送ることができました。 また、このようにApacheのログがオブジェクト化されることで、後々集計集約しやすくなりました。

次回は、ログをDBに送り、抽出してみたいと思います。
以上です。

Fluentdってなんじゃ?

今回はfluentdについて触れてみたいと思います。

普段サーバーの運用をしていて、規模が大きくなりサーバーが分散されるとWebサーバーやアプリケーションのログが各サーバーに散在することになり、ユーザーからの問い合わせや集計などのためにログをかき集めたりする必要が出てきます。
fluentdはイベントログをJSONフォーマットで転送して一箇所に集約するためのツールです。

集計などを行う場合、Apacheなどの生ログはテキストデータなのですが、fluentdを経由しJSON化することで、ログのデータをオブジェクトとして扱うことが出来るので、DBに入れてフィルタをかけて集計なども簡単に行えます。

また、fluentdは入力元となるログの種類や出力先の指定をプラグイン形式で様々なフォーマットに対応して、それぞれを組み合わせることで数多くの用途に答えることができます。
例えば以下のような入出力ができます。

入力元:
  • fluent apiを利用してアプリケーションから直接出力
  • 既存ログのtail
  • TCPソケットからのデータ
  • コマンドの実行結果
出力先:
  • ファイルに書き出み
  • 各種DBへ保存
  • SQSでキューにメッセージ送信
  • S3へ保存
  • Growlで通知
  • メール送信
  • Twitterへ投稿

こうみるとPlaggerを思い出しますね。

fluentdはruby環境が必要ですが、TreasureDataというデータウェアハウスがtd-agentというruby環境込みのstableパッケージを提供しています。td-agentを使用すると、ruby環境がない場合や既存のruby環境を壊したくない場合にとても有用です。今回はtd-agentを利用してみます。

まずはtd-agentをインストールします。
TreasureDataのyumリポジトリを登録してから、yumでインストールを行います。
# vi /etc/yum.repos.d/td.repo
---
[treasuredata]
name=TreasureData
baseurl=http://packages.treasure-data.com/redhat/$basearch
gpgcheck=0

---

# yum install td-agent -y
インストールはこれで完了です。

次に、設定ファイルを見てみます。

設定ファイルはシンプルで、source定義で指定した入力を、マッチしたmatch定義の設定で出力します。
まずは、flluentdのQuickStartにあるように付属のflluent-catを使用して、擬似的なTCPからの入力をローカルのファイルに出力するように設定します。
#vi /etc/td-agent/td-agent.conf

---
<source>
  type forward
  tag test
</source>

<match test>
  type file
  path /var/log/fluent/test
</match>
---

これは、

source
 forward Input Pluginを使用してtcpからの入力にtestというタグをつける

match test 
 file Output Pluginを使用して、タグが「test」にマッチした入力があれば、/var/log/fluent/test*** というファイルに受け取ったデータを出力する

という意味の設定になります。 設定を記述したら起動します。
#/etc/init.d/td-agent start
そしてfluent-catで以下のようにコマンドからJSONを送ってみます。
その際、引数として、タグとして設定した「test」を渡します。
# echo '{"msg":"Hello World!"}' | /usr/lib/fluent/ruby/bin/fluent-cat test
すると、以下のようなファイルが生成され、時間+タグ+JSONの形式で出力されていることがわかります。
# tail -1000f /var/log/fluent/test.20121105.b4cdae0705726cb91
2012-11-05T01:40:32+09:00 test {"msg":"Hello World!"}


ここまででは、ただファイルに入力したのと変わらないので、便利さがわかりづらいと思います。
次回はログをリモートのサーバーに転送してみたいと思います。
以上です。