Linux 方面では pacemaker と corosync 、そして drbd で MySQL サーバの冗長構成を構築する。なんてのは日常茶飯事のようですが、同様な構成を FreeBSD で組んでみたい。
と、いうのも Linux 方面では pacemaker の管理用コマンドがややこしい。以前の CentOS7 辺りでは crm_mon を利用しているかと思っていたが、 AlmaLinux8 では どうやら crm_mon が利用できない。あと、 pacemaker のバージョンが 1 系から 2 系になり、設定ファイルに互換性がなくなり、もう一度、設定方法について勉強し直し。みたいな雰囲気で、そろそろ FreeBSD で MySQL サーバの冗長を組みたくなってきた。と、いう雰囲気です。
と、いうことで、今回は FreeBSD で carp と hast を利用して、二台のマシンで冗長構成な MySQL サーバを構築してみましょう。
そもそも、二台の FreeBSD を利用した冗長構成な MySQL サーバを構築するには何を利用したら良いの? Linux で言うところの pacemaker と drbd みたいなものって、何かあるの?などと、調査からはじめました。
carp が pacemaker 部分。 hast のほうが drbd を受け持っている。と、いう認識で良いかと思われます。
まずは順番に見ていきます。
1. 今回の構成
二台の FreeBSD を用意するわけですが、今回は VMware ESXi 上で動作する FreeBSD/amd64 14.2-RELEASE と bhyve 上で動作する FreeBSD/amd64 14.2-RELEASE を用意しました。二台のサーバ共、同一の ESXi 上に載せていても冗長構成になりませんからねf(^^;;。
ESXi 側の FreeBSD のホスト名は freebsd-esxi (192.168.202.53) 、bhyve上で動作している FreeBSD のホスト名は freebsd-bhyve (192.168.202.202) として運用します。あと、 VIP が必要ですが、これは 192.168.202.251 とします。
図に書くとこんな感じになります。

それぞれの仮想環境上に冗長構成として利用する 2 台のサーバを設置。各サーバは OS をインストールしたディスク (da0) と、hast 用 (Linux で言うところの drbd) にもう一個 da1 を用意します。それが、各ホストの下の「hast disk」です。
プライマリ側で hast で利用しているディスクに更新があると、チョロチョロチョロっと、対向のセカンダリ側の hast のディスクに書き込まれて同期が保たれます。で、セカンダリ側がマスタになると、そのまま hast disk をマウントしてサービスを開始します。
carp はプライマリとセカンダリを切り替える機能、 Linux で言うところの pacemaker と corosync に相当します。
それでは実際に hast と carp の設定について見ていくことにしましょう。
2. carp の設定
まずはネットワーク系に相当する carp の設定から見ていきます。
とは言いつつ、簡単です。
まずはカーネルモジュールのロード設定ですね。
o. /boot/loader.conf
続いて VIP の IP アドレスの設定を /etc/rc.conf に記載します。
プライマリ側とセカンダリ側では多少設定が違います。
o. プライマリ側 /etc/rc.conf
ifconfig_em0_alias0="inet vhid 1 pass mysql00 alias 192.168.202.251/32"
|
o. セカンダリ側 /etc/rc.conf
ifconfig_em0_alias0="inet vhid 1 pass mysql00 advskew 50 alias 192.168.202.251/32"
|
まぁ、実際に、セカンダリの設定が入っているサーバがプライマリで動作しているときにプライマリの設定が入っているサーバが再起動すると、セカンダリとして動作するので、設定自体にあまり意味はないかもですね。
この設定を入れると em0 には VIPが付加されるのと、マルチキャストアドレスが付加されます。まずは上記設定を入れて再起動してみましょう。
$ ifconfig em0
em0: flags=1008943 metric 0 mtu 1500
options=4e524bb
ether 00:0c:29:75:28:84
inet 192.168.202.211 netmask 0xffffff00 broadcast 192.168.202.255
inet 192.168.202.251 netmask 0xffffffff broadcast 192.168.202.252 vhid 1
inet6 fe80::20c:29ff:fe75:2884%em0 prefixlen 64 scopeid 0x1
inet6 2001:470:fe36:feed:0:202:211:1 prefixlen 64
carp: MASTER vhid 1 advbase 1 advskew 50
peer 224.0.0.18 peer6 ff02::12
media: Ethernet autoselect (1000baseT )
status: active
nd6 options=21
|
192.168.202.251 が alias で付加され、あと、carp: MASTER vhid 1 advbase 1 advskew 50 というのが付加されます。マルチキャストアドレスで、同期判断をしています。多分、Linux でいうところの drbd に相当する差分データも流れていると思います。
そして、一点注意点があります。VMwareESXi 上で動作している仮想マシンの em0 が接続する vSwitch はプロミスキャス・モードを有効にしてあげる必要があります。 ESXi 的に言うと『無差別モード』と、いうヤツですね。これを許可していないと二台サーバの同期ができません。
bhyve 側にはこの設定がありません。まぁ、bhyve の vSwitch に相当する部分はそもそも bridge インターフェイスなので、プロミスキャス・モードは既にオンになっていますね。
と、いうことでネットワークの設定は終了。
続いて hast 側、HDD の作成や、設定について見ていきましょう。
3. hast 用ディスクイメージの用意
上にも書いたとおり、ディスクイメージは二つ必要で、da0 側に OS イメージをインストールし、da1 側はフツーに ufs でフォーマットします。あ。多分 zfs でも行けると思いますが、僕は ufs でフォーマットしました。
両方のサーバで実施してください。
# newfs -U /dev/da1
# mount /dev/da1 /mnt/
/dev/da1 20308252 8 18683584 0% /mnt
# umount /mnt
|
gpart で、パーティションを分けるようなことはせず、ダイレクトに /dev/da0 に対して newfs を実施します。その後、マウントして、サイズを確認します。2 台のサーバで同一容量になっている必要があります。
4. hast 用環境設定
続いて各種設定を見ていきます。
o. /etc/rc.conf
o. /etc/hast.conf (新規に作成)
replication memsync
resource disk0 {
on freebsd-esxi {
local /dev/ada0
remote 192.168.202.202
}
on freebsd-bhyve {
local /dev/da1
remote 192.168.202.53
}
}
|
/etc/hast.conf ファイルは一個のディクスを用意する場合は上記の設定で、二個目の場合は resource disk1 { } みたいになります。
両方のサーバで同一の内容を記載します。
on freebsd-esxi { } の設定のところで remote の IP アドレスを記載します。対向のサーバの IP アドレスになります。
また、ホスト名は名前解決ができている必要があります。 DNS 登録がない場合は /etc/hosts に記載しましょう。
o. /etc/fstab
/dev/hast/disk0 /var/db/mysql ufs noauto,rw,noatime 0 0
|
とりあえず /var/db/mysql にマウントする設定内容で記載します。
設定内容はこんな感じですかね。 Linux 方面 の pacemaker と corosync、そして drbd の設定よりは格段に楽ちんです。
5. /dev/hast/disk0 の準備
まずは hastd を起動します。
# service hastd start
# ps -ax | grep hast
1053 - Ss 0:00.01 /sbin/hastd
|
続いてディスクの初期化を実施します。
まずは、プライマリサーバ側から。今回は、プライマリ側サーバは IP アドレスの若版である、freebsd-esxi にします。
# hastctl create disk0
# hastctl role primary disk0
|
続いてセカンダリ側を。こちらは freebsd-bhyve になります。
# hastctl create disk0
# hastctl role secondary disk0
|
そしたら次に HDD 初期化とマウントを実施します。これは両方のサーバで実施してください。
# newfs -U /dev/hast/disk0
# fsck -fy -t ufs /dev/hast/disk0
# mount -o noatime -o rw /dev/hast/disk0 /var/db/mysql
|
ここまで来たら作業は完了。 hastctl でステータスを確認します。
# hastctl status disk0
Name Status Role Components
disk0 degraded primary /dev/ada0 192.168.202.51
# hastctl list
disk0:
role: primary
provname: disk0
localpath: /dev/ada0
extentsize: 2097152 (2.0MB)
keepdirty: 64
remoteaddr: 192.168.202.51
replication: memsync
status: degraded
workerpid: 1747
dirty: 75497472 (72MB)
statistics:
reads: 211
writes: 150
deletes: 0
flushes: 0
activemap updates: 36
local errors: read: 0, write: 0, delete: 0, flush: 0
queues: local: 0, send: 0, recv: 0, done: 0, idle: 255
|
こちらはプライマリ側の状態です。hastctl status disk0 で確認すると、primary と表示され、IP アドレスも合わせて表示されます。
セカンダリ側でも同様のコマンドで確認することができます。今回は割愛します。
hastctl list はディスク同期の詳細が表示されます。まぁ、簡単に言うと、この二つのコマンドは Linux でいうところの cat /proc/drbd みたいな感じでしょうか。
6. hast の状態の遷移
HDD イメージを二台のサーバ間で操るのですが、hastctl コマンドで制御します。
- プライマリとして利用: hastctl role primary disk0
- セカンダリとして利用: hastctl role secondary disk0
- どちらでもない状態: hastctl role init disk0
実際に運用し始めるとある程度解ってくると思います。
とまぁ、ここまでで carp と hast の設定が全て完了しました。これで、さぁてっ!! 冗長構成にするぜいっ!! と、は、実はまだなりません。
carp はサーバ障害発生時に IP アドレスを付け替えてくれます。hast は HDD 周りの同期を取ってくれます。がっ!! では、誰が /dev/hast/disk0 を /var/db/mysql にマウントしてくれるの?誰が切り替わった後に mysqld を起動してくれるの?
そーなのです。 carp+hast はこの部分に未対応です。なので、スクリプトを書く必要があるのであります。ありゃまっ!! orz
ここからはスクリプトについて見ていきましょう。
7. ホストダウンの検知
対向のホストがダウンした場合、検知は carp がしてくれます。カーネルモジュールを kldload しているので、実質的にはカーネルでの検知と、いうことになりますね。
ですので、イベントは devd で拾うことができます。まずは devd の carp.conf を作成します。設置場所はお好きなところに。僕は /usr/local/etc/devd/carp.conf を設置しました。
中身はこんな感じです。が、今回は以下のサイトを参考にさせて頂きました。ありがとうございました。
https://qiita.com/asakura_titems/7c117f2d7870afa76994
o. carp.conf
notify 0 {
match "system" "CARP";
match "subsystem" "[0-9]+@[0-9a-z]+";
match "type" "(INIT|MASTER|BACKUP)";
action "/usr/local/bin/carphast.sh $type $subsystem";
};
|
system が CARP で、subsystem が ifconfig em0 したときの MASTER vhid で、type が ステータスですね。このイベントを拾ってから action で記載されたスクリプトが動作します。
スクリプトは以下になります。
o. carphast.sh
#!/bin/sh
services="mysql-server"
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 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 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
|
基本的に、MASTER の場合は hastctl role primary disk0 して /var/db/mysql をマウントして mysqld をスタートする感じ。
それ以外は hastctl role secondary disk0 する。それをもう少し複雑に色々している雰囲気でしょうか。
まぁ、切り替わったときにやる一連の作業を devd 経由で検知して、スクリプトを実行するようにした。と、いう感じです。
この辺り、の管理用スクリプトとか、ports になってないのかな?
僕の場合、もう一個、 /etc/rc.local から呼び出して実行するスクリプトを用意しています。サーバが再起動したとき hastctl status disk0 すると init になっているので、対向のホストに ssh してステータスを確認して hastctl role secondary disk0 を打つようにしました。
o. /usr/local/bin/hast_primary_check.sh
#!/bin/sh
REMOTE=`/usr/local/bin/sudo hastctl list | grep remoteaddr |awk '{print $2}'`
HBSTATUS=`/usr/bin/ssh -i /home/takachan/.ssh/id_rsa takachan@${REMOTE} /usr/local/bin/sudo hastctl status disk0 | grep ^disk0 | awk '{print $3}'`
if [ ${HBSTATUS} = 'primary' ];then
# echo "sudo hastctl role secondary disk0"
/usr/local/bin/sudo hastctl role secondary disk0
fi
|
再起動直後はセカンダリで良いので、起動時にステータスを更新してしまう。と、いう感じです。
さてと、これで carp+hast の冗長構成完了です。
Linux 方面の pacemaker と corosync、そして drbd の場合はスクリプト書かないんだけど、設定が面倒。 FreeBSD の場合は設定は簡単なんだけど、アプリ側に仕掛けとか何もなし。
どちらが良いかは自分で決めてくだされ。
ただ、これで冗長構成が完了したので、ヨシヨシ。と、いう感じかな。