Spread Toolkit で分散ログ (1)

以前 LVS + Ultra Monkey で負荷分散 (設定編) - 酒日記 はてな支店 で書いたように、ApacheLVS で負荷分散したのですが、この際にアクセスログを一つに (時系列を保って) まとめるにはどうすればいいか、ちょっと悩みました。

結局 Apache からパイプ経由で logger コマンドを起動して syslogd でログ取得ホストに飛ばす、ということをしたのですが、いくつか問題が。

  • ログの先頭に syslogd の日付 (Jan 24 18:33:30 www1 logger: ) が付く → 集計の邪魔
  • syslog は 1メッセージ 1024 byte までしか記録できないので、尻切れになる行が (稀にだけど) 発生する
  • Linuxsyslogd は書き込みの際に頻繁に sync するので、DISK IO が大量に発生して重い

特に最後のは、秒間数百リクエストを超えるような場合に結構負荷として効いてくるようで、なんとかしたいなと。

まず思いついたのは、syslogd から syslog-ng に乗り換えること。

ただ、運用中のサーバの syslog を入れ替えるのはちょっと億劫。現在 syslog で取っているログをすべて切り替えるのは面倒だし、Apache 以外のログはそれほど大量に発生するわけではないし。

で、スケーラブルWEBサイト を読んでいたら、The Spread toolkit を使うといいよ! みたいなことが書いてあったので実験してみた。

しかし、最近出てきた技術でもないのに、ググっても日本語の情報がさっぱり見つからないのは何故なんだろう。

おおざっぱに言うとこんな感じの構成になります。

Apache
web server
spread
messaging daemon
spreadlogd
log writer daemon
  • すべてのホストに spread デーモンが起動。一つのクラスタを構成する
  • Apache は mod_log_spread (またはパイプ経由で外部プログラム) を使用し、localhost の spread にログをメッセージとして出力
  • spread はマルチキャスト (またはブロードキャスト) で、クラスタにメッセージを流す
  • spreadlogd (デーモン) は localhost の spread からメッセージを受信し、ファイルに書き込む

上記「localhost の」の部分は実は localhost でなくても構わないのだけど、話を単純にするためそうします。

クラスタ内に何台のホストがあっても、その間の spread のメッセージはマルチ(ブロード)キャストで流れるため、通信量が少なくて済む、というメリットがあるそうな。

Spread Toolkit で分散ログ (2) インストールと設定

OS は fedora core 5 / 6 で、以下のような構成で。

www (192.168.0.10)
Apache, spread
loghost (192.168.0.11)
spread, spreadlogd

spread-src-3.17.4.tar.gz をダウンロードし、展開。

$ ./configure
$ make
# make install

/usr/local/lib にライブラリがインストールされるので、ld.so.conf に /usr/local/lib を追加し、/sbin/ldconfig しておく。
Perl モジュールもインストール。

$ cd perl/Spread
$ perl Makefile.PL
$ make
$ make test
# make install

Spread.pm はインターフェースが Perl っぽくなくて、いまいち使いづらいので、Perl 使いの方は Spread::Session も入れておくと吉かも。

/usr/local/etc/spread.conf

# ブロードキャストを使う場合
Spread_Segment  192.168.0.255:4803 {
    www        192.168.0.10
    loghost    192.168.0.11
}
# マルチキャストを使う場合
Spread_Segment  225.0.0.1:4803 {
    www        192.168.0.10
    loghost    192.168.0.11
}
RuntimeDir = /var/run/spread
DaemonUser  = nobody
DaemonGroup = nobody

最初、閉じたネットワーク (default gateway がない) でマルチキャストを使おうとしたら

DL_init_channel: problem in setsockopt to multicast address

というエラーが出て spread が起動せず。かなりはまった。

# route add -net 224.0.0.0/4 dev eth0

として、マルチキャストの経路を設定してやる必要があった。

DaemonUser, DaemonGroup を指定した場合は RuntimeDir に chroot して動作するため、あらかじめ /var/run/spread を作成しておく。

起動。

# /usr/local/sbin/spread
(中略)
Conf_init: using file: /usr/local/etc/spread.conf
Successfully configured Segment 0 [225.0.0.1:4803] with 2 procs:
                         www: 192.168.0.10
                         loghost: 192.168.0.11
Set user name to 'nobody'
Set group name to 'nobody'
Membership id is ( -1062731627, 1169641071)
--------------------
Configuration at fc5 is:
Num Segments 1
        2       225.0.0.1         4803
                www                     192.168.0.10
                loghost                 192.168.0.11
====================
++++++++++++++++++++++
Num of groups: 0

www, loghost 両方で起動すると、数秒でこんな感じに。

spread が起動した状態で、spuser という対話コマンドを使うと、メッセージの送受信がテストできます。

Spread Toolkit で分散ログ (3) インストールと設定 mod_log_spread / spreadlogd

mod_log_spread
Apache は 1.3.37 を使用。Apache2 には対応していないので、Apache2 ではパイプ経由で外部プログラムを起動します(後述)。

mod_log_spread.tar.gz をダウンロード、展開して apxs でコンパイルとインストール。

$ tar zxvf mod_log_spread.tar.gz
$ cd mod_log_spread-1.0.4/
$ /usr/local/apache/bin/apxs -c \
      -I/usr/local/include -L/usr/local/lib \
      -lspread -ltspread mod_log_spread.c
# /usr/local/apache/bin/apxs -i mod_log_spread.so

-lspread -ltspread を忘れると、コンパイルは通るのに Apache 起動時に

Cannot load /usr/local/apache/libexec/mod_log_spread.so into server: /usr/local/apache/libexec/mod_log_spread.so: undefined symbol: SP_connect

とかなるので注意。

httpd.conf の設定。

LoadModule log_spread_module libexec/mod_log_spread.so
AddModule mod_log_spread.c
SpreadDaemon 4803
CustomLog $apache combined

mod_log_spread-1.0.4 に含まれる spread.html には "LoadModule spread_log_module libexec/mod_log_spread.so" という記述があるが、これだとこれまた Apache 起動時に

Can't locate API module structure `spread_log_module' in file /usr/local/apache/libexec/mod_log_spread.so: /usr/local/apache/libexec/mod_log_spread.so: undefined symbol: spread_log_module
LoadModule spread_log_module libexec/mod_log_spread.so

となって起動しないので、これも注意。

Apache を起動すると、"[notice] set_spread_daemon(4803) for index 0" のようなメッセージが error_log に出ます。

spreadlogd

spreadlogd.tar.gz (1.4.2)
spreadlogd.tar.gz (2.0.0)
[追記] 2007-2-7
初出時、spreadlogd.tar.gz へのリンクが version-1.4.2 のものになっていました。以下は 2.0.0 での記述です。(fc5 だと 1.4.2 は make でコケル)

make には libevent (1.1以上?) が必要です。

$ ./configure
$ make
# make install

/usr/local/etc/spreadlogd.conf

Spread {
        Port = 4803
        Log {
                RewriteTimestamp = CommonLogFormat
                Group = apache
                File = /var/log/spread_log
        }
}

Group には httpd.conf で CustomLog $xxx とした場合の xxx を指定。

/usr/local/sbin/spreadlogd で起動します。オプション -v で verbose mode、-D で daemon にならずに起動 (デバッグモード) です。

Spread Toolkit で分散ログ (4) Spread::Session

ああ長かった。

上記設定を済ませて Apache, spread, spreadlogd を起動し Apache にアクセスすると、ログが loghost:/var/log/spread_log に出力されるはず。

spreadlogd.conf で

RewriteTimestamp = CommonLogFormat

とするとログのタイムスタンプ部分を spreadlogd の現在時刻で上書きしてくれるので、複数の時刻のずれた Apache からログを集約しても時系列が前後しません。

Apache2 では mod_log_spread が使えないので (mod-log-spread2 があるけど、alpha 版らしい)、外部プログラムをパイプ経由で起動することでログを spread に流します。

 CustomLog "| /path/to/spread_log.pl" combined

もちろん Apache1 でこの手を使っても OK.

mod_log_spread にも error_log_spread.pl というのが同梱されているのだけど、せっかくなので Spread::Session を使って書いてみた。

#!/usr/bin/perl

use strict;
use Spread::Session;

Log::Channel::disable('Spread::Session');

my $group             = 'apache';
my $retry_interval    = 3;
my $retry_max_attempt = 10;
my $session           = connect_to_spread();

while ( my $log = <> ) {
    eval {
        my $r = $session->publish( $group => $log );
    };
    if ( $@ ) {
        warn "$@ disconnect to spread.";
        undef $session;
        $session = connect_to_spread();
    }
}


sub connect_to_spread {
    my $retry_count = 0;
    while ( 1 ) {
        my $s = eval { Spread::Session->new() };
        return $s if $s;

        warn "$@ retry to connect after $retry_interval sec.\n";
        if ( ++$retry_count > $retry_max_attempt ) {
            die "Aborted.";
        }
        sleep $retry_interval;
    }
}

spread が落ちていた時にリトライするように、とかもやっているけど、流れとしては

  • Spread::Session を new() して
  • publish() でメッセージを送る

だけ。結構簡単。