ZabbixでMySQLスレーブの Slave_IO_RunningとSlave_SQL_Runningを監視する

表題の件に関するメモ。

Zabbixは便利だけどTempalete_App_MySQLでは、レプリケーションしたときに一番知りたいSlave_IO_RunningとSlave_SQL_Runningの値を監視できない。

なのでUserParameterを設定して監視することにした。


Zabbixのインストール方法などはZabbix2.2をyumでインストール(Linux CentOS6.4)を参照。

UserParameter

Slaveが走るホストの/etc/zabbix/zabbix_agentd.confに以下のUserParameterを設定する。
長いので改行するけども、設定するときは一行で。特にパイプ"|"の前後に空白をいれないこと。

UserParameter=mysql.slave,mysql --defaults-extra-file=/var/lib/zabbix/.my.cnf 
-e "SHOW SLAVE STATUS\G"|awk 'BEGIN{io="None";sql="None"}
{if($1 ~/^Slave_IO_Running:$/){io=$2}
else if($1 ~/^Slave_SQL_Running:$/){sql=$2}}
END{if(io=="Yes" && sql=="Yes"){ret=0}
else if(io=="No" && sql=="Yes"){ret=2}
else if(io=="Yes" && sql=="No"){ret=3}
else if(io=="No" && sql=="No"){ret=4}
else if(io=="Connecting"){ret=1}
else{ret=5};
print ret}'

Keyは"mysql.slave"。

返り値は:

  • 0なら正常、
  • 1ならSlave_IO_Running="Connecting"、
  • 2ならSlave_IO_Running="No"、
  • 3ならSlave_SQL_Running="No"、
  • 4ならどちらも"No"、
  • 5ならCHANGE MASTERで値が設定されていないか、サーバがダウンしているか


zabbix_agentを稼働させるユーザ(zabbix)がmysqlコマンドを実行できるように、/var/lib/zabbix/.my.cnfを作成し、MySQLのユーザ名(ここではroot)とパスワードを設定する*1

root> chown zabbix:zabbix /var/lib/zabbix/.my.cnf
root> chmod 600 /var/lib/zabbix/.my.cnf
root> cat /var/lib/zabbix/.my.cnf
[mysql]
host = localhost
user=root
password=XXXXX
socket = /var/lib/mysql/mysql.sock

Zabbixサーバ側

Zabbixの設定はブラウザで行う。
すでにスレーブは登録されているとする。

スレーブ選択

「設定」ー>「ホスト」でスレーブを選ぶ。

アイテム作成

スレーブの「アイテム」部分をクリックし、全アイテムを表示させる。
右上の「アイテムの作成」をクリックする。

  • 名前:適当に。"MySQL Slave IO/SQL threads check"とか
  • タイプ:Zabbixエージェント
  • キー:mysql.slave

あとは適当に。

トリガの作成

もう一度、スレーブを選択し、今度は「トリガ」部分をクリック。
右上の「トリガの作成」をクリックする。

名前は適当に。"Slave IO thread is down"とか。


条件式は以下のように:

  • アイテム: さっきつけた名前"MySQL Slave IO/SQL threads check"
  • 条件式:"最新の値=N"
  • T = 0
  • N = 2

これは、「ret = N = 2」==> 「Slave_IO_Running="No"」をチェックするトリガ。

以下、同様に計5つのトリガを仕掛ける。たとえばトリガ名"Slave SQL thread is down"でN=3などなど。

*1:AllowRoot=1ならば、rootユーザで実行できるので、普通にやる/root/.my.cnfにすればよい。なお、AllowRootを使う場合はSELinux=disabled必須。

何かするスクリプト

なにかするスクリプトのひな形

XXXX.NNNNNNみたいなファイルの処理で使う。

#!/usr/bin/perl
#
# Usage: 
#
# Remark: Don't use "RESET MASTER"
use warnings;
use strict;
use Sys::Syslog;

my $hist = '/usr/local/mysql/data/hist.dat'; # Save the file number that was last processed.
my $basedir = '/usr/local/mysql/data/';
my $binlog_prefix = "binlog";

##----------------------------
## Global variables
##----------------------------
my $MIN;
my $MAX;
my %BINLOG;
##----------------------------
## Functions
##----------------------------

# usage: get_start_num(hist_file)
# return: a value which is described in hist_file.
sub get_start_num {
    my @args = @_;
    my $fh;
    my $num = 0;
    if (open($fh, '<', $args[0])) {
	while (my $line = <$fh>) {
	    chop($line);
	    $num = $line;
	}
	close $fh;
    }
    else {
	open($fh, '>', $args[0]) or die;
	print $fh "$num\n";
	close $fh;
    }
    return $num;
}

# usage: get_max_num()
# return: 
sub get_max_num {
    my @args = @_;
    my $max = 0;

    opendir  my $dh, $args[0] or die "$args[0]:$!";
    while (my $file = readdir $dh) {
        next if $file =~ /^\.{1,2}$/; # skip '.', '..'                                              
        if ($file =~ /^$binlog_prefix\.\d+/) {
            my $tmp;
            my $key = $file;
            $key =~ s/^$binlog_prefix\.//g;
            $BINLOG{$key} = $file;
            $tmp = $key + 0; # string to number                                                     
            $max = $tmp if ($max < $tmp);
        }
    }
    closedir $dh;

    return $max;
}

# usage: update_hist(num, hist_file)
# 
sub update_hist {
    my $MAX = $_[0];
    my $hist = $_[1];
    my $fh;
    open($fh, '>', $hist) or die;
    print $fh $MAX . "\n";
    close($fh);
}

sub  message {
    openlog('test', 'pid', 'local0');
    syslog('info', $_[0]);
    closelog();
}

##----------------------------
## Main process
##----------------------------
&message("backup started.");

$MIN = &get_start_num($hist);
$MAX = &get_max_num($basedir); 

###
## FLUSH LOGS
###

##
##
foreach my $num (sort keys %BINLOG) {
    my $file = $BINLOG{$num};
    $num = $num + 0; # string to integer
    if ($MIN < $num && $num <= $MAX) {
       #
       # do something  ; error -> exit(-1)
       # 
       my @command = ('ls', $basedir."/".$file);
       my $ret = system @command;
       if ($ret != 0) {
            &message("Error: backup stopped.");
            exit(-1);
        }
        &update_hist($num, $hist);
    }
}

##
##
&message("backup completed.");

exit(0);

なにかするスクリプト

#!/bin/bash
#
# usage: XXX hostname days
#
MYSQL=/usr/local/mysql/bin/mysql
USER=root
CONF=/root/.my.cnf
HOST=localhost
COMMAND=""
DAYS=3

if [ $# -ne 2 ]; then
    exit -1
else
    HOST=$1;
    if [ $2 -gt $DAYS ]; then
        DAYS=$2
    fi
    COMMAND="PURGE MASTER LOGS BEFORE timestamp(date_sub(current_date, interval '$DAYS' day));"
fi

$MYSQL --defaults-extra-file=$CONF -h $HOST -u $USER -e "$COMMAND"                                   

if [ $? -ne 0 ]; then
    exit -1
fi

exit 0
スクリプトの準備
$ cat /usr/local/bin/logpurge.pl
#!/usr/bin/perl
#
# usage: XXX basedir log_prefix ndays
#
#
use strict;
use warnings;
use Sys::Syslog;

##--------------------------
## Global Variables
##--------------------------
my @NDate;
my $min_date;

my $basedir = '/usr/local/mysql/data/';
my $log_prefix = "secure";
my $ndays = 3;
##--------------------------
## Functions
##--------------------------
sub get_date {    
    my ($time) = $_[0];
    my @date;
    my ($sec,$min,$hour,$mday,$month,$year,$wday,$stime) 
	= localtime($time+0);
    $date[0] = $year + 1900;
    $date[1] = $month + 1;
    $date[2] = $mday;
    
    return @date;
}

sub ndays_ago {
    my $ndays = $_[0];
    $ndays += 0; # string to number
    return &get_date(time - ($ndays * 24 * 3600));
}

sub  message {
    openlog(‘logpurge’, 'pid', 'local0');
    syslog('info', $_[0]);
    closelog();
}

##--------------------------
## Main Process
##--------------------------
&message("purge log(". $log_prefix . ") started");

if ($#ARGV != 2) {
    &message("Error: purge log(". $log_prefix . ")");
    exit(-1);
}

$basedir = $ARGV[0];
$log_prefix = $ARGV[1];
$ndays = $ARGV[2] if ($ndays < $ARGV[2]);

@NDate = &ndays_ago($ndays);

$min_date = $NDate[0] . $NDate[1] . $NDate[2];
$min_date+=0; # string to number


opendir my $dh, $basedir or die "$basedir:$!";
while (my $file = readdir $dh) {
    next if $file =~ /^\.{1,2}$/; # skip '.', '..'
    if ($file =~ /^$log_prefix\.\d+\_\d+\.log/) {
	my $date = $file;
	$date =~ s/$log_prefix\.//g;
	$date =~ s/_\d+\.log//g;
	$date+=0; # string to number

	if ($date < $min_date) {
	    #
	    # do something
	    #
            my @command = ('/bin/rm', $basedir . "/" . $file);
            my $ret = system @command;
            if ($ret != 0) {
		&message("Error: purge log(". $log_prefix . ") " . $file . " remove f\
ailed.");
            }
#           printf ("\tfile = %s  (%d)\n", $file, $date);                             

            &message("purge log(". $log_prefix . ") " . $file . " removed.");
	}
    }
}
closedir $dh;

&message("purge log(". $log_prefix . ") completed");

exit(0);
$ chmod +x /usr/local/bin/purgelog.pl
crontab
$cat /usr/local/bin/purgelog
#!/bin/bash

PURGELOG=/usr/local/bin/purgelog.pl
BASEDIR=/var/log/fluentd
NDAYS=4

# host2
$PURGELOG $BASEDIR/host2 secure  10  2>&1 > /dev/null

## for other hosts……
##
##

exit 0
$ chmod +x /usr/local/bin/purgelog
$ whoami
root
$ crontab -e
18 21 * * *  /usr/local/bin/purgelog 2>&1 > /dev/null

keepalived ソースコードリーディング 1

使うならソースくらい読まないと。

今回の目標は「MISC_CHECKがどのように行われるか(timeout関連をきちっと知るため)」

ダウンロード

ここからダウンロードする。

構造

ソースを展開して構造をみる。

$ pwd
/usr/local/src/keepalived-1.2.9
$ du
0	./bin
8	./doc/man/man1
32	./doc/man/man5
8	./doc/man/man8
48	./doc/man
200	./doc/samples
408	./doc
224	./genhash
424	./keepalived/check
120	./keepalived/core
32	./keepalived/etc/init.d
8	./keepalived/etc/keepalived
40	./keepalived/etc
352	./keepalived/include
32	./keepalived/libipvs-2.4
120	./keepalived/libipvs-2.6
536	./keepalived/vrrp
1632	./keepalived
624	./lib


中心はkeepalivedとlib。genhashは今回は無視。
目標からすると、keepalived/core、keepalived/checkあたりを眺めれば良さげ。


軽ーく眺めただけだが、内部はマルチスレッド構成で、VRRP関連の処理や各種チェックを管理するスレッドが走る。

check関連も、詳しくみればチェックタスクをcheckers_queueに溜めて、それをスケジューラーが適宜起動するなど、本気で辿るにはそれなりの時間がかかりそうな作りになっている。が、今知りたいのは実際のMISC_CHECKがどうなっているかなので細かいことは気にしないことにする。

MISC_CHECK

MISC_CHECKの起動シーケンスをしめす。

  1. daley_loopで指定した周期で、misc_check_thread()@keepalived_check/check_misc.cが起動される。
  2. 起動したmisc_check_thread()は子プロセスをforkする。
  3. forkされた子プロセスはsystem_call()@lib/notify.c => システムコールsystem()でMISC_PATHのスクリプトを実行する。
  4. 親プロセス側=keepalived本体ではmisc_timeoutのタイマーがセットされる。


子プロセスでスクリプトを実行するので、もしもスクリプトが致命的な動作をしても、keepalived本体には影響がない(はず)。

int
misc_check_thread(thread_t * thread) @ keepalived_check/check_misc.c
{
… 略 …
        thread_add_timer(thread->master, misc_check_thread, checker,
                         checker->vs->delay_loop);

	/*
	 * 子プロセスのフォーク
	*/	
        pid = fork();

… 略 …
        /* In case of this is parent process */
        if (pid) {
 	        long timeout;
		/*
		 *  親プロセス側では、misc_timeoutのタイマー設定
		 */
                timeout = (misck_checker->timeout) ? misck_checker->timeout : checker->vs->delay_loop;

	        thread_add_child(thread->master, misc_check_child_thread,
                                 checker, pid, timeout);
                return 0;
        }

        /* Child part */
… 略 …

	/*
	 * 子プロセス側では、misc_pathのスクリプト実行
	 */
        status = system_call(misck_checker->path);

        if (status < 0 || !WIFEXITED(status))
                status = 0; /* Script errors aren't server errors */
        else
                status = WEXITSTATUS(status);
        exit(status);
}

int
system_call(char *cmdline) @ lib/notify.c
{
        int retval;

        retval = system(cmdline);

	if (retval == 127) {
                /* couldn't exec command */
          log_message(LOG_ALERT, "Couldn't exec command: %s  (ret=%d)", cmdline, retval);
	} else if (retval == -1) {
                /* other error */
          log_message(LOG_ALERT, "Error exec-ing command: %s   (ret=%d)", cmdline, retval);
        }

        return retval;
}

ここで、misc_pathで実行したスクリプトがmisc_timeoutよりも長い時間がかかってしまった場合、misc_check_child_thread()@keepalived/check/check_misc.c スレッドが子プロセスをkillする。

int
misc_check_child_thread(thread_t * thread) @ keepalived/check/check_misc.c
{
… 略 …

	if (thread->type == THREAD_CHILD_TIMEOUT) {
	        pid_t pid;

                pid = THREAD_CHILD_PID(thread);

		if (svr_checker_up(checker->id, checker->rs)) {
                        log_message(LOG_INFO, "Misc check to [%s] for [%s] timed out"
                                            , inet_sockaddrtos(&checker->rs->addr)
                                            , misck_checker->path);
                }

		/*
		 * 起動した子プロセスをkill
		 */
		kill(pid, SIGTERM);
                thread_add_child(thread->master, misc_check_child_timeout_thread,
                                 checker, pid, 2);
                return 0;
        }

… 略 …


ただし、MISC_CHECKの注意点に書かれているとおり、子プロセスが(system()で)起動したスクリプトは終了しない。

例えば、“”LVS + keepalived の設定 4で作ったmysql-check.shで”SELECT 100”を”SELECT sleep(1000)”にすると、mysql-check.shを起動した子プロセスはmisc_timeout秒後にkillされるが、mysql-check.shスクリプトは(1000秒間)動き続ける。


というわけで、misc_pathに書くヘルスチェックスクリプトは、確実に終了するように書かなければならない。



今回はここまで。気が向いたらvrrpによるMASTER electionを辿ってみるつもり。

LVS+keepalived の設定 4

備忘録として、DSRによるLVSkeepalivedの設定を段階的に書く。

前提は以下とする。

  • LVSの転送方式はDSR
  • ネットワークは単一セグメントで超簡単なもの
  • OSはCentOS6.5


今回はMySQLスレーブの死活監視+管理について。システム構成はLVS+keepalivedの設定 3を参照。

TCPポートの監視:TCP_CHECK

もっとも簡単な方法。LVS+keepalivedの設定 2で使った。

keepalived.confの一部を示す。

… 略 …

    real_server 192.168.1.201 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: TCPポートにアクセスするだけの超簡易的方法
        TCP_CHECK {
            connect_port 3306
            connect_timeout 30
            nb_get_retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.1.202 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: TCPポートにアクセスするだけの超簡易的方法
        TCP_CHECK {
            connect_port 3306
            connect_timeout 30
            nb_get_retry 3
            delay_before_retry 3
        }
    }

この方法の根本的な欠点は、障害検出が間接的ということである。
「ポートにアクセスできない=サービス停止」とは限らない訳で、サーバは稼働しているが一時的に通信ができなくて障害と判断されてしまう可能性がある。しかしもっとマズいのは、すでに過負荷でサービスは停止していたり、max_connection超えで接続できないにも関らず、3306ポートが生きていれば、LVSはサーバの障害発生を検出できないことである。

クライアントはアクセスできない状態なのに、それを認識できないのはバランサとして存在価値がないに等しい。

自前のプログラムで監視:MISC_CHECK

TCP_CHECKはあまりに低機能なので、MISC_CHECKを利用するためのチェックプログラムを自前で実装してみる。

最初にMISC_CHECKを使うためのkeepalived.confの一部を示す。

… 略 …

    real_server 192.168.1.201 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: 
           MISC_CHECK {
		# 自前のチェックスクリプト。 Usage: mysql-check.sh host timeout
               misc_path "/usr/local/bin/mysql-check.sh 192.168.1.201 10"
		# チェックスクリプトのタイムアウト
               misc_timeout 15
            }
    }

    real_server 192.168.1.202 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: 
           MISC_CHECK {
               misc_path "/usr/local/bin/mysql-check.sh 192.168.1.202 10"
               misc_timeout 15
    }
}

パラメータについて*1


ここでタイムアウトは大事。delay_loopで指定した周期でmisc_pathのプログラムが実行される。もしもそのプログラムの実行がdelay_loopよりもかかると、チェックプロセスがどんどん立ち上がってしまう。

よって、可能ならばチェックプログラムにもタイムアウトを指定し、次の関係が成り立つように値を調整する。

 misc_pathプログラムのタイムアウト < misc_timeout  < delay_loop

以下、順に説明する。

mysql-check.sh

以下のプログラムをLVS1とLVS2の/usr/local/bin以下に作成する。

cat /usr/local/bin/mysql-check.sh
#!/bin/bash
#
# mysql server check script
#
# Usage: mysql-check.sh host timeout

MYSQL=/usr/bin/mysql
USER=root
CONF=/root/.my.cnf
HOST=localhost
TIMEOUT=10

if [ $# -ne 2 ]; then
    exit -1
else
    HOST=$1;
    TIMEOUT=$2
fi

$MYSQL --defaults-extra-file=$CONF -h $HOST -u $USER --connect-timeout=$TIMEOUT -e 'SELECT 100' 2>&1 > /dev/null

if [ $? -eq 0 ]; then
    # check:OK
    exit 0
else
    # check:NG
    exit 1
fi


mysqlクライアントでHOSTに接続し、”SELECT 100”を実行する。これにより、少なくともTCPポート監視よりはマシな死活監視が可能である。
通信路がおかしいと接続に時間がかかる場合があるので、--connect-timeoutで明示的にタイムアウトを設定する。パスワードなどは(後述する)/root/.my.cnfに設定する。


作成したらパーミッションを設定する。

# chmod 700 /usr/local/bin/mysql-check.sh
LVS1, LVS2にmysqlパッケージインストール

mysqlクライアント(と後述するmysqladmin)を使うため。


LVS1, LVS2に/root/.my.cnfを準備

スレーブにアクセスするためのパスワードを/root/.my.cnfに設定する。

# cat /root/.my.cnf
[client]
user=root
password=XXXX

[mysqladmin]
user=root
password=XXXX

作成したらパーミッションを変更し、root以外は読み書きできないようにする。

# chmod 600 /root/.my.cnf
Slave側でユーザ登録

LVS1,LVS2からアクセスできるようにユーザを登録する。

mysql> GRANT ALL PRIVILEGES on *.* to root@192.168.1.100 identified by PASWORD(‘XXXX’);
mysql> GRANT ALL PRIVILEGES on *.* to root@192.168.1.101 identified by PASWORD(‘XXXX’);
mysql> FLUSH PRIVILEGES;

これは権限を与え過ぎだが、後で説明する”mysqladmin shutdown”のためにこうしてある。

動作確認

これでうまくいくはず。うまく行かない場合はパーミッション関係を見直すこと。

スクリプトの問題点

ところでこのmysql-check.shは、スレーブとの接続前ならconnect-timeoutでタイムアウトするが、接続後にスレーブがとても重くなるとそのまま”SELECT 100”の実行が待たされる可能性がある。
よって以下のような関係になる場合がある。

 misc_pathプログラムのタイムアウト > misc_timeout

この場合、keepalivedは接続失敗とみなすが実はスレーブは稼働しているわけで、とにかく障害検出はとてもとても難しい。


これはレアケースだから考慮する必要がないかといえば、そんなことはない。
本当にサーバが不調になった発端を検出したのかもしれないし、単に一時的な過負荷かもしれないし、ネットワーク機器の不調やリコンフィグレーションによってレスポンスが遅れているだけかもしれない。


大抵のシステムでは、何回かリトライしてN回連続失敗したら障害発生とみなすようになっている。実用上、数回ダメならそのノードは切り離した方がいいし、障害発生と見なすのは合理的だろう。


しかし、「keepalivedのMISC_CHECKではretry指定ができないから、上記レアケースを受け入れるしか無い」というのは、かなり不満が残る。
せいぜいmisc_timeoutとloop_delayの値を大きく取って、障害検出ミスを避ける=レアケースがほとんど起きる可能性がないような設定を選ぶしかない。

課題

上の問題に限らず、障害検出はとても難しい(非同期分散システムにおいて完全な障害検出は原理的に不可能)。
でもそこは1億歩譲って、上記の障害検出スクリプトが1(異常値)を返し、”何かおかしなこと”が起きたとしよう。


実は、障害検出後の処理も難しい。”何かおかしなこと”が起きた結果としてmysqlクライアントが"SELECT 100"を時間内に実行できなかったわけだが、その原因である”何かおかしなこと”はそれこそ無数に考えられるし、その原因の結果が"SELECT 100"の遅れだけとは限らないから、どんな対処をすべきかなんて、とてもじゃないが網羅できるはずがないのだ。

MySQLサーバはもちろん玩具みたいに脆いが、同様にHDDをはじめとするサーバのハードウエア、スイッチやケーブルなどのネットワーク機器もちょっとした不調は起こり得る。そもそも不調ではなく、ネットワーク管理者が他のノードの設定中に誤って通信不可になってしまうとか、設定をリセットするために軽い気持ちでスイッチやルータの電源をOFF/ONするとか、keepalivedMySQLにとっての異常事態はわりと簡単に起きる。このようなちょっとしたことが原因でどんな結果になってしまうのか、予め全てを想定するのは不可能。


ただ一つ確実なのは、なんらかの障害が検出されたということは、システムのどこかが正常でない挙動を示したということで、とにかく”何かおかしなことが少なくとも一度は起きた”のである。
すると、マスターとスレーブのデータの不一致が起きてしまった可能性も否定はできない。特にレプリケーション*2はちょっとしたことがキッカケで不調になるので油断ならない。

フェールセーフ

では、サーバの状態が障害検出されたりまた正常に戻ったりしたらどうだろう? その都度、LVSがサーバを切り離したり繋いだりしては困る。障害検出が完全でない以上、例え障害後に正常と判断しても、マスターとスレーブでデータの不一致等、不具合が発生している可能性は否定できない。

ということで、障害検出だけでは冗長化したDBクラスタを護れない。障害検出後の処理もとても重要だ。


DBを管理するLVSは「障害検出したスレーブを積極的に停止させる=安全側に倒した構成(フェールセーフ)」にすべきだろう。もちろん、過度に安全側に設定を倒しすぎると、障害に対してあまりにセンシティブになって、ちょっとしたことでサービス全停止なんて事態に行き着くのであるが。


難しいことはさておき、とりあえず障害検出したら”mysqladmin shutdown”を実行する設定とスクリプトの例を以下に示す。

shutdownスクリプト

以下のスクリプトを作成する。

cat /usr/local/bin/mysql-shutdown.sh
#!/bin/bash
#
# mysql server shutdown script
#
# Usage: mysql-shutdown.sh host

MYSQLADMIN=/usr/bin/mysqladmin
USER=root
CONF=/root/.my.cnf
HOST=localhost

if [ $# -ne 1 ]; then
    exit -1
else
    HOST=$1;
fi

$MYSQLADMIN --defaults-extra-file=$CONF -h $HOST -u $USER shutdown

exit 0


作成したらパーミッションを設定する。

# chown root:root /usr/local/bin/mysql-shutdown.sh
# chmod 700 /usr/local/bin/mysql-shutdown.sh
keepalived.confの設定

real_serverにnotify_downの設定を追加する。


… 略 …

    real_server 192.168.1.201 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: 
           MISC_CHECK {
		# 自前のチェックスクリプト。 Usage: mysql-check.sh host timeout
               misc_path "/usr/local/bin/mysql-check.sh 192.168.1.201 10"
		# チェックスクリプトのタイムアウト
               misc_timeout 15
    	    }
    # 実サーバの障害を検出したら、mysql-shutdown.shを実行。スレーブを積極的にshutdownする
	notify_down “/usr/local/bin/mysql-shutdown.sh 192.168.1.201”    
   }

    real_server 192.168.1.202 3306 {
        weight 1
        inhibit_on_failure
    # サーバ監視: 
           MISC_CHECK {
               misc_path "/usr/local/bin/mysql-check.sh 192.168.1.202 10"
               misc_timeout 15
          }
	notify_down “/usr/local/bin/mysql-shutdown.sh 192.168.1.202”  

       }
動作確認

これで障害検出したらスレーブサーバを強制的に(mysqladminで)shutdownする。

またまた課題

そもそもの話だがmysqlクライアントで接続できないサーバに、mysqladminで接続できるのかという根本的な問題がある。


より確実にサーバを停止させるには、sshで”/etc/init.d/mysqld stop”を実行するとか、”kill -9 mysqld.pid”を実行するほうがましだろう。それでも確実ではないが。
しかし、スクリプトsshを実行させるにはパスワード入力をさけるためにssh-agentを動かさねばならない等、システム全体のセキュリティがどんどん低下してしまう。


しかも、それでもダメな場合だってある。LVS1とLVS2の障害が重なるイヤらしいシナリオとして「LVS1がスレーブ1の障害を検出した直後にダウンしたのでスレーブ1を停止できなかった。その後LVS2がマスターに昇格したときには、なぜかスレーブ1も動作していたが、実はデータがおかしくなっていた」とか。悪い状況はいくらでも想定できる*3


どこまでやるかは仕様次第というか、決めの問題。確実さを求めるほどどんどん手間が増えるし、どこまでいっても終わりはない。

参考URL

*1:今回はmisc_dynamicは無視する。misc_dynamicを有効にすると、チェックプログラムの2以上の返り値の場合、その値から2を引いた値を重みにする。例えば5を返したら3が重みになる。

*2:特にバイナリーログなんていうとんでもないものを基盤としているMySQLのレブリケーションの場合は。

*3:そして本当にマズいタイミングでそれは発生する。

LVS+keepalived の設定 3

備忘録として、DSRによるLVSkeepalivedの設定を段階的に書く。

前提は以下とする。

  • LVSの転送方式はDSR
  • ネットワークは単一セグメントで超簡単なもの
  • OSはCentOS6.5

今回はkeepalived冗長化(2台構成)にする。

ネットワーク構成

今回は以下の構成。


  • LVS1はeth0 = 192.168.1.100、LVS2はeth0 = 192.168.1.101。VirtualIPはeth0:0 = 192.168.1.10を設定。クライアントはこのアドレス(VIP)にアクセスする。
  • WEB1はeth0 = 192.168.1.201、WEB2はeth0 = 192.168.1.202
  • 全サーバ(LVS + WEB1 + WEB2)は NetworkManagerとSELinuxをoffにする。

LVSの設定

基本的な設定は「LVS + Keepalivedの設定 2」と同じ。違いは3つ

  1. keepalivedの起動オプションの変更
  2. keepalived.confの変更
  3. iptablesの設定

なので以下、差分のみ書く。

起動オプションの変更

パッケージでインストールすると起動オプションは空である。運用上、VRRPと監視プロセスを起動しなければならないのと、ログを取った方がよいのでオプションを変更する。

/etc/init.d/keepalivedに以下の一行追加する。

KEEPALIVED_OPTIONS="-S 0 -vrrp -check"

これで、keepalivedのログが/var/log/messagesに書き込まれる。

iptablesの設定

LVS同士はVRRPで互いに監視しあうので、iptablesVRRPパケットの出入りを可能にしておく。

# iptables -A INPUT -p vrrp -j ACCEPT
# iptables -A OUTPUT -p vrrp -j ACCEPT 

LVS1、LVS2でどちらも実行する。確認方法はこちら:

# iptables -L --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED 
2    ACCEPT     icmp --  anywhere             anywhere            
… 略 …

9    ACCEPT     vrrp --  anywhere             anywhere            

… 略 …

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     vrrp --  anywhere             anywhere            

削除するなら「iptables -D INPUT 9」「iptables -D OUTPUT 1」など。番号は”-L —line-numbers”で表示されたもの。


CentOSではiptablesの設定ファイルは/etc/sysconfig/iptablesで、これはiptablesを実行しても書き変わらない。よって設定を永続的にするには以下のようにする。

root> services iptables save

keepalived.conf

どちらをマスターとするか、3つのパラメータで制御する。

  • preempt or nopreempt
  • priority
  • state

これらの場合分け別挙動はこちらによくまとまっている。

今回は以下のポリシーで設定する。

  • LVS1が常にマスタ
  • LVS1がダウンしたらLVS2が引き継ぐ
  • LVS1が復活したら、マスタはLVS2からLVS1に戻る

よって、

  • LVS1
    • preempt
    • priority 100
    • state MASTER
  • LVS2
    • preempt
    • priority 100
    • state BACKUP

とする。

LVS1のkeepalived.conf 抜粋
# cat /etc/keepalived/keepalived.conf

… 略 …

# VirtualIP管理+LVS相互監視のための設定
vrrp_instance VI_1 {
    state MASTER
    interface eth0
     garp_master_delay 3
    virtual_router_id 1
    priority 100
     preemp
    advert_int 5
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.10/24 dev eth0
    }

… 略 …
LVS2のkeepalived.conf 抜粋
# cat /etc/keepalived/keepalived.conf

… 略 …

# VirtualIP管理+LVS相互監視のための設定
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
     garp_master_delay 3
    virtual_router_id 1
    priority 100
     preemp
    advert_int 5
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.10/24 dev eth0
    }

… 略 …
動作確認

keepalivedを起動させたら、ログファイルを確認する。

こちらはLVS1のログ:

grep Keepalived /var/log/messages

… 略 …
Dec 19 23:00:41 localhost Keepalived[4153]: Stopping Keepalived v1.2.7 (02/21,2013)
Dec 19 23:00:41 localhost Keepalived_vrrp[4156]: VRRP_Instance(VI_1) sending 0 priority
Dec 19 23:00:42 localhost Keepalived[4228]: Starting Keepalived v1.2.7 (02/21,2013)
Dec 19 23:00:42 localhost Keepalived[4229]: Starting Healthcheck child process, pid=4230
Dec 19 23:00:42 localhost Keepalived[4229]: Starting VRRP child process, pid=4231

  ##  これらを気にする必要は無い。
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: Interface queue is empty
Dec 19 23:00:42 localhost Keepalived_healthcheckers[4230]: Interface queue is empty
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: No such interface, eth3
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: No such interface, pan0
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: Registering Kernel netlink reflector
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: Registering Kernel netlink command channel
Dec 19 23:00:42 localhost Keepalived_vrrp[4231]: Registering gratuitous ARP shared channel
Dec 19 23:00:42 localhost Keepalived_healthcheckers[4230]: No such interface, eth3
Dec 19 23:00:42 localhost Keepalived_healthcheckers[4230]: No such interface, pan0
Dec 19 23:00:42 localhost Keepalived_healthcheckers[4230]: Registering Kernel netlink reflector
Dec 19 23:00:42 localhost Keepalived_healthcheckers[4230]: Registering Kernel netlink command channel

… 略 …

 ## ここで、狙い通り MASTERになっているかどうか確認。
Dec 19 23:00:45 localhost Keepalived_vrrp[4231]: VRRP_Instance(VI_1) Transition to MASTER STATE

…

こちらはLVS2のログ:

grep Keepalived /var/log/messages

…
Dec 19 23:01:49 localhost Keepalived[15170]: Starting Keepalived v1.2.7 (02/21,2013)
Dec 19 23:01:49 localhost Keepalived[15171]: Starting Healthcheck child process, pid=15172## ここで狙い通りBACKUPになっているかどうか確認。
Dec 19 23:01:49 localhost Keepalived_vrrp[15174]: VRRP_Instance(VI_1) Entering BACKUP STATE
Dec 19 23:01:49 localhost Keepalived_healthcheckers[15172]: Using LinkWatch kernel netlink reflector...
Dec 19 23:01:49 localhost Keepalived_healthcheckers[15172]: Activating healthchecker for service [192.168.1.201]:80
Dec 19 23:01:49 localhost Keepalived_healthcheckers[15172]: Activating healthchecker for service [192.168.1.202]:80


もしもどちらもMASTERになっている場合、VRRPが通っていない可能性が99.9999%である。よくiptablesの設定を見直すこと。

MySQL:Slaveの設定

MySQL:Slave側の設定は「LVS + Keepalivedの設定 2」と変わらず。

動作確認

LVS1とLVS2を交互に落として、狙い通りにMASTERが切り替わっているか、ログで確認するとよい。

LVS+keepalived の設定 2

備忘録として、DSRによるLVSkeepalivedの設定を段階的に書く。

前提は以下とする。

  • LVSの転送方式はDSR
  • ネットワークは単一セグメントで超簡単なもの
  • OSはCentOS6.5

今回はkeepalivedを導入する。

ネットワーク構成

今回は前回と同じ、以下の構成。

  • LVSはeth0 = 192.168.1.100。さらに後々を考えてVirtualIPとしてeth0:0 = 192.168.1.10を設定。クライアントはこのアドレス(VIP)にアクセスする。
  • MySQL:Masterはeth0 = 192.168.1.200、ただしLVSとは関係ない。
  • MySQL:Slave1はeth0 = 192.168.1.201、MySQL:Slave2はeth0 = 192.168.1.202
  • 全サーバ(LVS + Slave1 + Slave2)は NetworkManagerとSELinuxをoffにする。

(LVS上の)keepalivedの設定

もしもLVSの設定をしていれば、それらを削除する。
具体的には:

  • ipvsadm -Cで設定をクリア
  • ifcfg-eth0:0など、VIP用の設定ファイルを削除。当然、ネットワークはrestart。
Firewall

3306番ポートを開けておく。

インストール

keepalivedパッケージをインストール。

# yum install keepalived
システムパラメータの設定

パケットフォワーディングを行うため、net.ipv4.ip_forwardを有効にする*1
具体的には/etc/sysctl.confを編集し、sysctl -pを実行する。

# vi /etc/sysctl.conf
net.ipv4.ip_forward = 1
# /sbin/sysctl -p
# cat /proc/sys/net/ipv4/ip_forward 
1
keepalived.confの設定

/etc/keepalived/keepalived.confを編集する。

# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived

# 障害通知関係の設定
global_defs {
   notification_email {
	# 障害通知メールの宛先
   	mail@example.com
   }
   # 障害通知メールの送信元
   notification_email_from mail@example.com
   # 障害通知メールを送信するSMTPサーバ名
   smtp_server 10.10.0.1
   smtp_connect_timeout 30
   # 障害発生したLVSを特定するための、一意な名前
   router_id LVS_1
}

# VirtualIP管理+LVS相互監視のための設定
vrrp_instance VI_1 {
    # “MASTER” or “”BACKUP
    state BACKUP
    # 監視するネットワークデバイス
    interface eth0
    #
    virtual_router_id 1
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    # Virtual IPの指定
    virtual_ipaddress {
        192.168.1.10/24 dev eth0
    }
}

# 仮想サーバの設定
virtual_server 192.168.1.10 3306 {
    # 監視周期。単位は秒。
    delay_loop 30
    # 負荷分散方式。rr = ラウンドロビン
    lvs_sched rr
    # パケット転送方式。 DR = Direct Return
    lvs_method DR

   # rrなら不要。
 #    persistence_timeout 50

    # 監視するプロトコル
    protocol TCP
    # 全real serversがダウンした場合、Sorryサーバに向ける。
	# この構成の場合はMasterでもよいが、過負荷でマスターまでダウンさせるのも….
    #    sorry_server XXX.XXX.XXX.XXX YY

    # 実サーバ(Slave1)の設定
    real_server 192.168.1.201 3306 {
    # rrの重み付け
        weight 1
    # 設定すると、実サーバがダウンした場合、重みを0にする。
    # 後述するが、サーバが復活すると重みは設定値(ここでは1)に戻る。
        inhibit_on_failure

    # サーバ監視: TCPポートにアクセスするだけの超簡易的方法
        TCP_CHECK {
            connect_port 3306
            connect_timeout 30
            nb_get_retry 3
            delay_before_retry 3
        }
	# ダウンと判断した場合の実行スクリプトを書く。今回はパス。後でじっくり検討する。
        # notify_down "shell script" 
    }

    # 実サーバ(Slave2)の設定
    real_server 192.168.1.202 3306 {
        weight 1
        inhibit_on_failure
        TCP_CHECK {
            connect_port 3306
            connect_timeout 30
            nb_get_retry 3
            delay_before_retry 3
        }
        # notify_down "shell script" 
    }
}

設定が終ったらサービスを開始する。

# service keepalived start
設定確認

設定内容はipvsadmで確認できる。

# ipvsadm -S
-A -t 192.168.1.10:mysql -s wlc
-a -t 192.168.1.10:mysql -r 192.168.1.201:mysql -g -w 1
-a -t 192.168.1.10:mysql -r 192.168.1.202:mysql -g -w 1

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.10:3306 rr
  -> 192.168.1.201:3306           Route   1      0          0         
  -> 192.168.1.202:3306           Route   1      0          0 

ネットワークはifconfigでは確認できないので、ipコマンドで以下のようにする。

# ip addr show eth0
0: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:c3:3a:be brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0
    inet 192.168.1.10/24 scope global secondary eth0
    inet6 fe80::20c:29ff:fec3:3abe/64 scope link 
       valid_lft forever preferred_lft forever

inetで(実と仮想の)2つのIPaddressが設定されていれば成功。

MySQL:Slaveの設定

MySQL:Slaveサーバ側の設定は前回のLVS単体のときと変わらず。
しかし情報がバラバラになると理解し難いので、ここにも書く。

Firewall

3306番ポートを開けておく。

MySQL

MySQLを初期化し、起動する。

# mysql_install_db

# mysqld_safe &

次に、Client:192.168.1.5 からアクセスできるように、ユーザ(ここではroot)の権限設定を行う。

# mysql
mysql> GRANT ALL ON *.* TO root@'192.168.1.5’
システムパラメータの変更

以下のパラメータを/etc/sysctl.confに追記する。

net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2

編集後、設定を反映させる。

# /sbin/sysvtl -p
仮想loopbackデバイス作成

LVSのVIPと同じIPをlo:0に登録。

# cd /etc/sysconfig/network-scripts
# cp ifcfg-lo ifcfg-lo:0
# vi ifcfg-lo:0
DEVICE=lo:0
IPADDR=192.168.1.10
# NETMASKはこれでなければならない。
NETMASK=255.255.255.255 
NETWORK=192.168.1.0
# If you're having problems with gated making 127.0.0.0/8 a martian,            
# you can change this to something else (255.255.255.255, for example)          
BROADCAST=192.168.1.255
ONBOOT=yes
NAME=loopback0

作成したら再起動。

# service network restart

動作確認

ClientからLVSのVIP(192.168.1.10)にアクセスすると、どちらかのスレーブに飛ぶ。

# mysql -h 192.168.1.10

LVS上で以下のコマンドを実行すると動作状況がみえる。

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.10:3306 rr
  -> 192.168.1.201:3306           Route   1      6          0         
  -> 192.168.1.202:3306           Route   1      5          0         

ここで意図的にSlave2をダウンさせる。
すると監視ルールに則って、Slave2の重みが0になる。

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.10:3306 rr
  -> 192.168.1.201:3306           Route   1      6          0         
  -> 192.168.1.202:3306           Route   0      5          0         

そして、Slave2を復旧(再起動)させると、重みが元に戻る。

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.10:3306 rr
  -> 192.168.1.201:3306           Route   1      9          0         
  -> 192.168.1.202:3306           Route   1      5          0         

WEBサーバならいいけども、DBの場合は一旦ダウンしたものを再起動した瞬間にサービス提供してはマズいはず*2


これが大変参考になった。

*1:net.ipv4.conf.default.rp_filterなどは有効のままでよさげ

*2:データの不一致が起きているかもしれないし、復旧作業のために起動したかもしれない

LVS+keepalived の設定 1

備忘録として、DSRによるLVSkeepalivedの設定を段階的に書く。


前提は以下のとおり:

  • LVSの転送方式はDSR
  • ネットワークは単一セグメントで超簡単なもの
  • OSはCentOS6.5

今回はDSRのLVSMySQL:Slaveの負荷分散の練習。
以降の予定は:

  1. keepalived導入
  2. LVS+keepalivedの2重化
  3. keepalivedによる厳格なMySQL:Slave死活監視+死活管理

ネットワーク構成

今回は以下の構成。


  • LVSはeth0 = 192.168.1.100。さらに後々を考えてVirtualIPとしてeth0:0 = 192.168.1.10を設定。クライアントはこのアドレス(VIP)にアクセスする。
  • MySQL:Masterはeth0 = 192.168.1.200、ただしLVSとは関係ない。
  • MySQL:Slave1はeth0 = 192.168.1.201、MySQL:Slave2はeth0 = 192.168.1.202
  • 全サーバ(LVS + Slave1 + Slave2)は NetworkManagerとSELinuxをoffにする。

LVSの設定

Firewall

3306番ポートを開けておく。

インストール

ipvsadmパッケージをインストール。

# yum install ipvsadm
システムパラメータの設定

パケットフォワーディングを行うため、net.ipv4.ip_forwardを有効にする*1
具体的には/etc/sysctl.confを編集し、sysctl -pを実行する。

# vi /etc/sysctl.conf
net.ipv4.ip_forward = 1
# /sbin/sysctl -p
# cat /proc/sys/net/ipv4/ip_forward 
1
VirtualIPの設定

eth0にVirtualIPを設定する。

# cd /etc/sysconfig/network-scripts/
# cp ifcfg-eth0 ifcfg-eth0:0

次のような内容を書く。

# cat ifcfg-eth0:0
DEVICE="eth0:0”
ONBOOT=yes
IPADDR=192.168.1.10
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
NETWORK=192.168.1.0
BROADCAST=192.168.1.255
HWADDR="00:0C:29:C3:3A:BE"
MTU="1500"
NM_CONTROLLED="no"
ONBOOT="yes"

作成したらネットワークを再起動し、ifconfigで確認する。

# service network restart# /sbin/ifconfig 
eth0      Link encap:Ethernet  HWaddr 00:0C:29:C3:3A:BE  
          inet addr:192.168.1.100  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:748 errors:0 dropped:0 overruns:0 frame:0
          TX packets:924 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:77893 (76.0 KiB)  TX bytes:91746 (89.5 KiB)

eth0:0    Link encap:Ethernet  HWaddr 00:0C:29:C3:3A:BE  
          inet addr:192.168.1.10  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:246 errors:0 dropped:0 overruns:0 frame:0
          TX packets:246 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:15828 (15.4 KiB)  TX bytes:15828 (15.4 KiB)
ipvsadmの設定

まず、-Cオプションで内容をクリアし、確認する。

# ipvsadm -C
# ipvsadm -S

以下のコマンドを実行する。

# ipvsadm -A -t 192.168.1.10:3306
# ipvsadm -a -t 192.168.1.10:3306 -r 192.168.1.201:3306 -g
# ipvsadm -a -t 192.168.1.10:3306 -r 192.168.1.202:3306 -g

確認は以下。

# ipvsadm -S
-A -t 192.168.1.10:3306 -s wlc
-a -t 192.168.1.10:3306 -r 192.168.1.201:3306 -g -w 1
-a -t 192.168.1.10:3306 -r 192.168.1.202:3306 -g -w 1

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.1.10:3306 wlc
  -> 192.168.1.201:3306           Route   1      0          0         
  -> 192.168.1.202:3306           Route   1      0          0 

MySQL:Slaveの設定

Firewall

3306番ポートを開けておく。

MySQL

MySQLを初期化し、起動する。

# mysql_install_db

# mysqld_safe &

次に、Client:192.168.1.5 からアクセスできるように、ユーザ(ここではroot)の権限設定を行う。

# mysql
mysql> GRANT ALL ON *.* TO root@'192.168.1.5’
システムパラメータの変更

以下のパラメータを/etc/sysctl.confに追記する。

net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2

編集後、設定を反映させる。

# /sbin/sysvtl -p
仮想loopbackデバイス作成

LVSのVIPと同じIPをlo:0に登録。

# cd /etc/sysconfig/network-scripts
# cp ifcfg-lo ifcfg-lo:0
# vi ifcfg-lo:0
DEVICE=lo:0
IPADDR=192.168.1.10
# NETMASKはこれでなければならない。
NETMASK=255.255.255.255 
NETWORK=192.168.1.0
# If you're having problems with gated making 127.0.0.0/8 a martian,            
# you can change this to something else (255.255.255.255, for example)          
BROADCAST=192.168.1.255
ONBOOT=yes
NAME=loopback0

作成したら再起動。

# service network restart

動作確認

ClientからLVSのVIP(192.168.1.10)にアクセスすると、どちらかのスレーブに飛ぶ。

# mysql -h 192.168.1.10

*1:net.ipv4.conf.default.rp_filterなどは有効のままでよさげ