特徴など
- FreeBSDで標準で利用可能(おそらく10.x以降)
- VIPの切替えを利用してActive/Standbyを構成する
- クラウド上で利用する場合は注意が必要になる可能性が高い
- carpのステータスが変わったのをdevdで検知し、制御ツールを起動する
- マニュアルなど
- https://www.freebsd.org/doc/en/books/handbook/carp.html
- http://www.jp.freebsd.org/cgi/mroff.cgi?subdir=man&lc=1&cmd=&man=carp&dir=jpman-11.0.2%2Fman§=0
- http://www.jp.freebsd.org/cgi/mroff.cgi?subdir=man&lc=1&cmd=&man=devd&dir=jpman-11.0.2%2Fman§=0
- http://www.jp.freebsd.org/cgi/mroff.cgi?sect=5&cmd=&lc=1&subdir=man&dir=jpman-11.0.2%2Fman&subdir=man&man=devd.conf
インストール・設定
- インストールは特に必要なし(FreeBSD 11.0、おそらく10.xも必要なし)
- /boot/loader.conf
- carpモジュールをロード
# sysrc -f /boot/loader.conf carp_load="YES"
- /etc/rc.conf
- ネットワーク設定にCARPで使うvipの設定を追加
- vhid {id} -- バーチャルホストID。CARPで冗長化するIPアドレスごとの識別子
- advskew {n} -- 死活監視への応答遅延(n/256秒)
- この値をいくつにするのが適切かは未検証
- pass {phrase} -- パスフレーズ。冗長化するIF間で合わせる
- IPアドレス/マスク -- ネットマスクは必ず32にする
- ネットワーク設定にCARPで使うvipの設定を追加
host1
# tee -a /etc/rc.conf <<EOF
ifconfig_vtnet0="inet 192.168.0.101 netmask 255.255.255.0"
ifconfig_vtnet0_alias0="inet vhid 1 pass {パスフレーズ} alias 192.168.0.100/32"
EOF
host2
# tee -a /etc/rc.conf <<EOF
ifconfig_vtnet0="inet 192.168.0.102 netmask 255.255.255.0"
ifconfig_vtnet0_alias0="inet vhid 1 advskew 50 pass {パスフレーズ} alias 192.168.0.100/32"
EOF
- /usr/local/etc/devd/carp.conf
- devdの設定
- ※CARPの状態変更を全て受け取る場合
- system -- システム名。"CARP"
- subsystem -- サブシステム名。CARPの場合は {vhid}@{if名}
- type -- 通知の種別。CARPの場合は"INIT", "MASTER", "BACKUP"のいずれか
- action -- 実行するコマンド
- 後述の制御スクリプトを利用している
- devdの設定
/usr/local/etc/devd/carp.conf
notify 0 {
match "system" "CARP";
match "subsystem" "[0-9]+@[0-9a-z]+";
match "type" "(INIT|MASTER|BACKUP)";
action "/path/to/carphast.sh $type $subsystem";
};
- /etc/rc.local
- devdが起動する前に制御が必要な場合
# tee -a /etc/rc.local <<EOF
/path/to/carphast.sh AUTO 1@vtnet0
EOF
状態・操作について
- 状態は INIT, MASTER, BACKUP の3種類
- 基本的にはMASTER/BACKUPを切り替える
- 実行したサーバの状態をBACKUP(Standby)に変更する
- 反対側サーバのstateがMASTER(Active)に変わる
# ifconfig vtnet0 vhid 1 state backup
- 実行したサーバの状態をMASTER(Active)に変更する
- 反対側サーバのstateがBACKUP(Standby)に変わる
- advskew値が小さいほうでのみ可能(同じなら両方可能)
# ifconfig vtnet0 vhid 1 state master
- 実行したサーバの状態をINITにする
- インターフェイスを停止した時にINITになる模様
- 反対側サーバのstateがMASTER(Active)に変わる
# ifconfig vtnet0 down
- shutdownやrebootした時は残っているほうがMASTERになる
- 起動時は先にbootしたほうがMASTERになる
- 中途半端な死活状態になった時にどうなるかは未確認
- pingが通っていれば生きている扱い?
状態変更時・起動時の処理について
- HAProxyのようなネットワークのみで完結するものであれば特に処理は必要ないと思われる
- devdが状態変更時に制御スクリプトを呼び出す
- サーバ起動時はdevdが開始されてないため、/etc/rc.local で制御スクリプトを呼び出す
- MySQLをfailoverする場合はおおよそ下記のようになると思われる
- CARPがtype:INITになった時:
- ※この時は何もしない?
- CARPがtype:BACKUPになった時:
service mysql-server stop
- ファイルシステムのアンマウント(ufsとzfsで違う)
hastctl role seconday all
- CARPがtype:MASTERになった時:
hastctl role primary all
- ファイルシステムのマウント(ufsとzfsで違う)
service mysql-server start
- CARPがtype:INITになった時:
CARPを利用したHAST制御スクリプト
- CARPの状態を利用してHASTを管理する
- 第1引数にCARPの状態を渡す
- 第1引数がAUTOの時はCARPのstateから判断する
- 第2引数にvhidとIF名が渡されるが、現在は未使用
- vhidが複数ある場合には制御したほうがよさそう
- 制御スクリプトと同じディレクトリに carphast.conf があればその設定を読み込む
- services は起動したいサービス名。複数可
- zpools は importしたいZFSのプール名。複数可
- resources は 利用するHASTのリソース名。複数可。allの時は全て利用する
- /dev/hast/* に現れるデバイスは /etc/fstab の設定に応じてmountする
- ZFSはimportしたあとに/etc/fstabの内容でmountしている
carphast.conf
services="mysql-server"
zpools="hast-zfs"
resources="all"
#resources="shared1 shared2"
carphast.sh
#!/bin/sh
conffile=${0%.sh}".conf"
if [ -f $conffile ]; then
. $conffile
fi
resources=${resources:-all}
action=$1
vhid=${2%@*}
ifname=${2#*@}
syslog_facility="user.notice"
syslog_tag="carp-hast"
maxwait=60
delay=3
logger="/usr/bin/logger -p $syslog_facility -t $syslog_tag"
if [ "$resources" = "all" ]; then
hastdevs=$(/sbin/hastctl dump | /usr/bin/awk '/^[[:space:]]*resource:[[:space:]]/ {print $2}')
else
hastdevs="$resources"
fi
#
case "$action" in
MASTER|BACKUP|INIT)
$logger "State Changed. I/F: $ifname VHID: $vhid state: $action"
;;
AUTO)
action=$(/sbin/ifconfig $ifname | /usr/bin/awk '/[[:space:]]*carp:[[:space:]]+([A-Z]+)[[:space:]]vhid[[:space:]]'"$vhid"'[[:space:]]?/ {print $2; exit}' )
if [ "$action" ]; then
$logger "State Changed. I/F: $ifname VHID: $vhid state: $action"
else
die "carp state not found"
fi
;;
*)
die "$action is not yet implemented"
;;
esac
reverse_list()
{
_revlist=
for _revfile in $*; do
_revlist="$_revfile $_revlist"
done
echo $_revlist
}
die()
{
$logger "FATAL: "$*
exit 1
}
# check hastd enabled
if ! /bin/pgrep -q hastd; then
$logger "hastd not running"
exit
fi
stop_services()
{
for service in $( reverse_list $* ); do
if /usr/sbin/service ${service} onestatus | /usr/bin/grep -q "running as" ; then
/usr/sbin/service ${service} onestop \
|| $logger "Unable to stop service: ${service}."
fi
done
}
change_role()
{
roletype=$1
shift 1
for hdev in $*; do
/sbin/hastctl role $roletype $hdev \
|| $logger "Unable to change role to $roletype for resource: $hdev"
done
}
# main
case "$action" in
BACKUP|INIT)
# stop services
stop_services $services
# unmount zfs
for pool in $zpools; do
if /sbin/zpool status | /usr/bin/grep -q "pool: $pool" ; then
/sbin/zpool export -f $pool \
|| $logger "Unable to export zpool: ${pool}."
fi
done
# unmount ufs
for mdev in $(/sbin/mount -p | /usr/bin/awk '/^\/dev\/hast\// {print $1}'); do
for hdev in $hastdevs; do
if [ "$mdev" = "/dev/hast/$hdev" ]; then
/sbin/umount -f $mdev \
|| $logger "Unable to unmount: ${mdev}."
fi
done
done
# change role
if [ "$action" = "BACKUP" ]; then
roletype="secondary"
else
roletype="init"
fi
change_role $roletype $resources
$logger "Change role $roletype completed."
;;
MASTER)
# stop services
stop_services $services
# wait for not running secondary
for hdev in $hastdevs; do
for i in $(/usr/bin/jot $maxwait); do
/bin/pgrep -fq "hastd: ${hdev} \(secondary\)" || break
sleep 1
done
if /bin/pgrep -fq "hastd: ${hdev} \(secondary\)" ; then
die "Secondary process for resource ${hdev} is still running after $maxwait seconds."
fi
done
# change role primary
change_role primary $resources
sleep $delay
# wait for the /dev/hast/* devices to appear
for hdev in $hastdevs; do
for i in $(/usr/bin/jot $maxwait); do
[ -c /dev/hast/$hdev ] && break
sleep 1
done
if [ ! -c /dev/hast/$hdev ]; then
die "GEOM provider /dev/hast/$hdev did not appear."
fi
done
# mount zfs
if [ "$zpools" ]; then
for pool in $zpools; do
/sbin/zpool status | /usr/bin/grep -q -E "[[:space:]]*$pool[[:space:]]+ONLINE[[:space:]]" && break;
/sbin/zpool import -d /dev/hast/ -f -N $pool \
|| die "Unable to import zpool: ${pool}."
done
/sbin/zpool scrub $zpools
for pool in $zpools; do
for mdev in $(/usr/bin/awk '/^'"$pool"'\// {print $1}' /etc/fstab); do
$logger "mount $mdev"
/sbin/mount -p | /usr/bin/grep -q -e "^${mdev}[[:space:]]" && break;
/sbin/mount ${mdev} \
|| die "Unable to mount: ${mdev}."
done
done
fi
# mount ufs
for mdev in $(/usr/bin/awk '/^\/dev\/hast\// {print $1}' /etc/fstab); do
for hdev in $hastdevs; do
if [ "$mdev" = "/dev/hast/$hdev" ]; then
$logger "mount $mdev"
/sbin/mount -p | /usr/bin/grep -q -e "^${mdev}[[:space:]]" && break;
/sbin/fsck -y -t ufs ${mdev} \
|| die "Failed to fsck: ${mdev}."
/sbin/mount ${mdev} \
|| die "Unable to mount: ${mdev}."
fi
done
done
# start services
for service in ${services}; do
/usr/sbin/service ${service} onestart \
|| $logger "Failed to start service: ${service}."
done
$logger "Change role primary completed."
;;
esac