FreeBSD
AnyEvent

FreeBSDでAnyEventを利用したサーバ間リアルタイム双方向同期

linuxのlsyncd(inotify)が使えないので苦労した話。

環境・前提条件など

  • 2台のWEBサーバで冗長化構成をとっているCMS。
  • LBでクライアントIPアドレス毎にアクセスを振り分ける。
  • 異なるクライアントIPアドレスで共有される同期対象ファイルは無い。
  • OS:FreeBSD 11.1
  • Perl 5.24
  • AnyEvent-Filesys-Notify-1.21

準備

双方向同期を行う2台のサーバでそれぞれ以下の設定を行う。

rsyncの設定

rsyncをPortsからインストールする。

$ cd /usr/ports/net/rsync
$ sudo make
$ sudo make install

rsyncdの設定ファイルを作成する。

$ cd /usr/local/etc/rsync
$ sudo vi rsyncd.conf
rsyncd.conf
log file = /[ログファイル出力先のパス]/rsyncd.log
pid file = /[PIDファイル出力先のパス]/rsyncd.pid
use chroot = false

[名称1]
path=[同期対象ディレクトリ1]
hosts allow = [同期先サーバIPアドレス]
read only = no

[名称2]
path=[同期対象ディレクトリ2]
hosts allow = [同期先サーバIPアドレス]
read only = no
.
.
.

例)以下は/usr/hoge/dataと/usr/hoge/etcの2つのディレクトリを同期する場合。

rsyncd.conf
log file = /usr/hoge/log/rsyncd.log
pid file = /usr/hoge/spool/rsyncd.pid
use chroot = false

[data]
path=/usr/hoge/data
hosts allow = 172.16.xxx.xxx
read only = no

[etc]
path=/usr/hoge/etc
hosts allow = 172.16.xxx.xxx
read only = no

SSHの設定

同期実行アカウントで鍵を生成する。

$ ssh-keygen -t rsa -N ""

生成された公開鍵id_rsa.pubを同期先サーバに設置する。

(id_rsa.pubを同期先サーバに持っていって)
$ cat id_rsa.pub >> ~/.ssh/authorized_keys
$ rm id_rea.pub

~/.ssh/authorized_keysの先頭に以下を追加する(鍵の前に記述する)。

command="/usr/local/bin/rsync --server --daemon --config=/usr/local/etc/rsync/rsyncd.conf .",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa ...

rsyncの動作確認

rsync.confに記述した同期ディレクトリを作成して、手動で以下のコマンドを実行する。

$ rsync -avh --delete -e ssh [同期ディレクトリ] [同期実行アカウント]@[同期先サーバIPアドレス]::[名称]

例)

$ rsync -avh --delete -e ssh /usr/hoge/data hoge@172.16.xxx.xxx::data

リアルタイム同期設定

AnyEventのインストール

CPANからPerlモジュールをインストールする。

$ perl -MCPAN -e shell
cpan[1]> install AnyEvent::Filesys::Notify

同期スクリプトの作成

AnyEventを用いてファイルを監視する常駐スクリプトを作成する。

例)

evsyncd.pl
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent::Filesys::Notify;

my $target_ip = '172.16.xx.xx';
my $retry = 5;

my %watch_module = (
    '/usr/hoge/data/' => 'data',
    '/usr/hoge/etc/'  => 'etc',
    );

my $cv = AnyEvent->condvar;

my $notifier = AnyEvent::Filesys::Notify->new(
    dirs     => [ qw( /usr/hoge/data /usr/hoge/etc ) ],
    backend  => 'Fallback',
    interval => 0.5,
    cb       => sub {
        my (@events) = @_;
        foreach my $event (@events) {
            my $type =  $event->type();
            my $path =  $event->path();
            foreach my $watch_pass (keys %watch_module){
                if($path =~ /^$watch_pass/){
                    for ( my $i = 1; $i <= $retry; $i++ ) {
                        system("/usr/local/bin/rsync -auvh --bwlimit=8192 --timeout=10 -e ssh $watch_pass hoge\@$target_ip::$watch_module{$watch_pass}");
                        my $err = $? >> 8;
                        last if($err == 0);
                    }
                }
            }
        }
    },
    parse_events => 1,
);

$cv->recv();

exit 0;
  • rsyncで時々やからす--deleteオプションは今回要件の中で不要であったため付けないことにした(不要になった過去ファイルの消去等は個々のWEBサーバ内で独立して実行する)。
  • backendにはKQueueとFallbackを指定可能だが、KQueueの場合監視対象の全ディレクトリとファイルのファイルハンドルを掴みっぱなしになり食い潰す恐れがあったため、今回はFallbackを指定した。
  • Fallbackを指定するとCPU負荷が上がるため、interval(秒)で調整した。
  • 実際には\$typeや\$pathをログに書き出しているが上記では省略している。
  • 詳しいモジュールの説明はこちら(CPAN)

サーバ起動時の逆方向同期

サーバダウンからの復帰時に逆方向の同期を行う。サーバ起動時に1回だけ実行されるようにする。

evsyncd_init.pl
#!/usr/bin/perl
use strict;
use warnings;

my $target_ip = '172.16.xxx.xxx';
my $retry = 5;

my %watch_module = (
    '/usr/hoge/data/' => 'data',
    '/usr/hoge/etc/'  => 'etc',
    );

foreach my $watch_pass (keys %watch_module){
    for ( my $i = 1; $i <= $retry; $i++ ) {
        system("/usr/local/bin/rsync -auvh --bwlimit=8192 --timeout=60 -e ssh hoge\@$target_ip::$watch_module{$watch_pass} $watch_pass");
        my $err = $? >> 8;
        last if($err == 0);
    }
}

exit 0;

同期スクリプトの起動

あとは適当に起動スクリプトを書く。

/usr/local/etc/rc.dに設置する。

evsyncd.sh
#!/bin/sh
killproc() {
            pid=`/bin/ps -ax | /usr/bin/grep -w "$1" | /usr/bin/grep -v /usr/bin/grep | sed -e 's/^  *//' -e 's/ .*//'`
            [ "$pid" != "" ] && kill $pid
}
case "$1" in
    start)
        /usr/bin/su - hoge -c /usr/hoge/bin/evsyncd.pl &
        echo "start"
        ;;
    stop)
        killproc '/usr/bin/perl /usr/hoge/bin/evsyncd.pl'
        echo "stop"
        ;;
    *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac
exit 0

起動時(サーバダウンからの復帰時)逆方向同期

同じく/usr/local/etc/rc.dに設置する。

evsyncd_init.sh
#!/bin/sh
if [ -x /usr/hoge/bin/evsyncd_init.pl ]; then
    echo -n ' evsyncd_init.pl'
    /usr/bin/su - hoge -c /usr/hoge/bin/evsyncd_init.pl
fi

以上