目次
FreeBSDでHAST/NFS Serverを立てる(Old)
Mail Serverなどに供給するFilesystemは安定性と冗長性が重要である。 しかし、一般にFilesystemの冗長化は非常に難しく、なかなかFreeに利用できる良い実装はない。
Linuxを利用する場合は、DRBDやlsyncd/rsyncを用いた同期が可能である。
- DRBD: Block DeviceとしてのHDDを同期する。今回のHASTと考え方はよく似ている。
- lsyncd/rsync: Linux kernelのinotifyを利用した、ファイルの書き換え検出(lsyncd)と、検出されたファイルの同期(rsync)の組み合わせで実装する、ファイル単位の同期
しかし、FreeBSDを利用する場合、そのために用いる手法としては、実質HASTしかない。また、このHASTはNetBSDやOpenBSDには存在しない。
今時、MailServerを構築する際には、Lock問題が発生しないように、Maildirを利用しHome Directoryにファイル単位で受信したメールを保存するのが一般的である。この時、SMTP Serverは冗長性を確保したいので、Home DirectoryはNFSでshareしたいという要望がある。
以上より、FreeBSD 10.1を用いてHASTでHome Directoryを冗長化することにした。
当初 NAS4Free で行こうと思ったのだが、以下の理由で FreeBSD + HAST を用いてNFS Server を組むことにした。
- NFS「しか」使わないこと
- CentOS7でNFSで謎の挙動に出会ってはまったこと
- *BSDではそのような謎の挙動を観測したことがないこと
- XenServer 上で GuestVM として構築すること
- つまりはxe-guest-utilitiesを入れたいということ。
- NAS4Freeは現在の段階でも、まだFreeBSD 9系統なので、Full Virtual でしか動作しない
- NAS4Free の HAST は Still Experimental であって、それなら FreeBSD 10.1 でも同じであること
仮想基盤の Storage が NAS4Free で RAID-Z2 であることと、HAST で筐体間冗長を構成することを考慮し、NFS Server 単体では冗長構成は構築しない。もし、冗長構成を取るなら
- 仮想 HDD を 2 個にし、ZFS Mirror/snapshot を利用
- 仮想 HDD を 3 個にし、RAID-Z1/snapshot を利用
する手も考えられるが、今回は容量も50G程度なので、UFS(FFS)を利用する。
なお、当然のことだが、HASTの冗長は「片側を書き換えたら即逆側も書き換わる」冗長なので、いわゆるBackupではない。冗長である。なので、当然Backupは定期的に取るに越したことはない。NAS4Free側でsnapshotを取るのも悪くないが、やはりちゃんとbackupを取るべきであろう。
なお、UFSでのsnapshotの取得については、ここを参照のこと
設定
今回は、以下の構成で構築する
- /dev/ada1 を HAST で冗長化する
- hast の resource 名は home とする
- hast で共有する Filesystem は /hast/[resource] (今回は /hast/home) に mount する
- NFSはv3を利用する
- これは、NFSv4で頑張るために必要な account 情報をまだ一元管理していないため
- NFSv2での接続は禁止する。そのために、vfs.nfsd.server_min_nfsvers=3 を設定する
- NFSv3ではTCP/UDP共に許可する
- NFS Serverとしてのアドレスは CARP のものを利用する
- NFS で export するDirectoryは /hast 下のもののみとする
とにかく FreeBSD を Install
諸元
OS | FreeBSD 10.1 | |
vCPU | 1 | |
Memory | 1024MB | |
HDD | Boot | 20GB |
HDD | Export | 50GB |
NIC | 2 |
とりあえず、普通にInstall
- ports/src のみを Installする (srcは不要かもしれない)
- Disk は以下のように設定(自動で作成し、HDDはada0のみを選択する)
- ada1も同時に構築しようとすると、正しくDiskが構築されない。これはおそらくFreeBSDの不具合
ada0 | GPT | 備考 | |
---|---|---|---|
ada0p1 | Freebsd-boot | 512KB | FreeBSD Boot code上の制限 |
ada0p2 | Freebsd-ufs | 19GB | |
ada0p3 | Freebsd-swap | 1GB |
- sshd/ntpd/powerd/dumpdev を設定
- 追加 Account を作成
初期設定
- ntp Server関連の設定
- sshd関連の設定
- 追加NICの設定 (/etc/rc.conf)
- fstabの修正(procfsを追加)
proc /proc procfs rw 0 0
- OSのUpdate
freebsd-update fetch
freebsd-update install
portsnap fetch
portsnap update
- XenServer 上で PV Guest にする
pkg install xe-guest-utilities
echo 'xenguest_enable=“YES”' » /etc/rc.conf.local
- /etc/sysctl.confに以下を追加
- vfs.nfsd.server_min_nfsvers=3
- net.inet.carp.allow=1
- net.inet.carp.preempt=1
ここまで出来たら、shutdownして、VMをCopyし、2台目の設定を実施する。
HAST 設定
HASTが動作するためには、GEOM_GATE(geom_gate.ko)がkernelにlinkされている必要がある。 通常は、hastdが起動されるタイミングでLKMとして読み込まれる(kernelにlinkされる)が、staticにkernelにlinkするならkernelを再構築する。その際 OPTIONS GEOM_GATE を追加すること
HASTの設定は、/etc/hast.confに記載する。
# # HAST: Highly Available STorage configuration. # # General configuration listen tcp4://0.0.0.0:8457 replication fullsync compression lzf timeout 10 on nfs001 { listen tcp4://10.1.101.3:8457 } on nfs002 { listen tcp4://10.1.101.4:8457 } # node configuration resource home { local /dev/ada1 on nfs001 { remote tcp4://10.1.101.4:8457 } on nfs002 { remote tcp4://10.1.101.3:8457 } }
- HASTで同期する両サーバーで以下を実行
- /etc/hast.conf を作成する
hastctl create home
を実行service hastd onestart
を実行
- HASTのPrimary側で以下を実行
hastctl role primary home
- HASTのSecondary側で以下を実行
hastctl role secondary home
- 両方で以下を実行
hastctl status home
- この結果のstatusが complete であるなら、同期は完了している
- この結果のstatusが degraded であるなら、設定の何かが間違っているので確認する
hastctl list -d home
を実行すると、詳細な情報が出力される。この時dirtyが0であることを確認する
- HASTのPrimary側で以下を実行
- ファイルシステムを構築する :
newfs -O2 -U -j /dev/hast/home
- 今回は、UFS/Soft-update(-U)/soft-update journaling(SUJ)(-j)を生かす設定にする。
- gjournalを利用する場合、-jを-Jに変える。
- snapshotを取得できるようにするため、-nは指定しない。
mkdir /home
mount /dev/hast/home /home
- これで、HASTで同期しているFilesystemがPrimary側でmountされた。
echo 'hastd_enable=“YES”' » /etc/rc.conf.local
- これで再起動時に自動的にHASTdが動作する
- /etc/fstabに必要な情報を記載する
HASTの制御コマンドは以下
hastctl status [resource]
: HAST resource [resource] の状態を見るhastctl status all
: HASTに登録されている全ての[resource]の状態を見るhastctl role primary [resource]
[resource]をPrimaryにする- このタイミングで、/dev/hast/[resource]が生える
hastctl role secondary [resource]
[resource]をSecondaryにする- このタイミングで、/dev/hast/[resource]が消える
hastctl list [resource]
: [resource]の詳細情報を表示する
hastdがSplit-Brainを通知する場合
hastで共有しているDiskの状態がおかしくなったので、以下を実施する
- まず、secondary側でhastctl create [resource]してみる。
- これでエラーが出なければ、primary側でdirtyを確認し、dirtyが0になればOK
- これでダメな場合、以下を実行する
- とにかくhastの同期を切り離す (片方(仮にBとする)のI/Fをdownするのが手っ取り早い)
- Bで、以下の作業を実施する
- hastctl role primary して、FSをmountする
- DataのBackupを取得する
- Bをhastctl role init [resource] → hastctl create [resource] → hastctl role secondary [resource] して初期化する
- HASTの同期を再開する (I/Fをあげるのが良い)
- primary側がDegradedになり、同期が開始される。
- primary側のdirtyが0になり完全に同期するのを待つ
- primary側にBから取得したバックアップから必要なファイルを書き込む
とりあえず、これが一番手っ取り早い。
split-brain状態になっていることを検出し、勝手にhastctl createするようにする方策を考えなければならない
CARPの設定
ここまでで、HAST deviceは作成できた。
ここからは、CARPとdevdを利用して、Failoverの設定を行う
echo 'carp_load=“YES”' » /boot/loader.conf
を実行- 初期設定する場合、rebootするのは面倒なので、
kldload carp
を実行する - kernelにstatic linkしておくには、
devide CARP
を追加する
- /etc/rc.confに以下を追加
- Primaryマシンに
ifconfig_xn1_alias0=“vhid 2 advskew 10 pass UltraSecret alias xxx.xxx.xxx.xxx/32”
- Secondaryマシンに
ifconfig_xn1_alias0=“vhid 2 advskew 10 pass UltraSecret alias xxx.xxx.xxx.xxx/32”
これでCARPの設定は終了。
CARPのI/FをBACKUPに切り替えるには、ifconfig xn1 vhid 2 state backup
でOK
ifconfig state backup で CARP の state を変化させた場合、相手側の I/F の state は変化しない。
従って、host-1 でifconfig xn1 vhid 2 state backup を実行した場合、host-2 で ifconfig xn1 vhid 2 state master を実行すること
VHIDは、KeepalivedやVRRPなどのVHIDと衝突しないように設定する必要があるので、注意
FreeBSDのCARPはVRRPと異なり、自分がBACKUPの時でも共有IPアドレスが自身に割り当てられているものとして扱われる。つまり、host-1がCARP MASTER、host-2がCARP BACKUPであって、共有IP AddressがAddr(s)であるとすると、Addr(s)への通信に返事を返すのはhost-1のみであるが、host-2のInterface Address TableにもAddr(s)が載る。
devdの設定
前項のCARPまで設定が終了すれば、HASTで同期しているFilesystemを公開する準備が整っている。 この時利用できるProtocolとしては、SAMBAやNFSがある。iSCSIは動作するか微妙。
そこで、Interfaceの状態が変化した場合にhastのroleを変化させる設定を行う。 この設定は、devdを利用する。
まず、/etc/dev.confの末尾に、以下を追記する。
notify 100 { match "system" "CARP"; match "subsystem" "[0-9]+@[0-9a-z]+"; match "type" "(MASTER|BACKUP|INIT)"; action "/usr/local/sbin/carp-hast-switch $subsystem $type"; };
その後、service devd restart
を実行する
次に、/usr/local/sbin/carp-hast-switchを作成する。
- 2015/04/16 Release版
- 2015/11/22 更新: Boot時の問題を解決。
- carp-hast-switch
#! /bin/sh # # carp-hast-switch: shell script for change hast role when carp # status is changed. # # Original script by Freddie Cash <fjwcash@gmail.com> # Modified by Michael W. Lucas <mwlucas@BlackHelicopters.org> # and Viktor Petersson <vpetersson@wireload.net> # and HEO SeonMeyong <seirios@seirios.org> # Last modified 2015/11/10 HEO SeonMeyong <seirios@seirios.org> # ***WARNINGS*** # Need net.inet.carp.preempt=1 and same of advskew on carp. # Currently HAST device must formatted by UFS # ZFS code is impremented but not checked. # This script is assumed to match the ZFS pool name and HAST resource name ############################################################################## # Setting Variables and parse Arguments. #DEBUG=1 SYSLOG_FACILITY="user.notice" SYSLOG_TAG="carp-hast" IF=${1%@*} VHID=${1#*@} ACTION=$2 # Work around for boot time. devd execute this script before start hastd. [ ! `/bin/pgrep hastd` ] && exit case "${ACTION}" in MASTER|BACKUP|INIT) /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "State Changed. I/F: ${IF} VHID: ${VHID} state: ${ACTION}" ;; *) /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: ${ACTION} is not yet implemented" exit 1 ;; esac ############################################################################## # Get resources. HASTDEV=`/sbin/hastctl dump all | /usr/bin/grep resource | /usr/bin/sed -e 's/^.*:\ *//'` [ "x"$DEBUG != "x" ] && echo "HASTDEV = ${HASTDEV}" [ -z "${HASTDEV}" ] && exit 0 # no hast device. # get all carp interfaces ifs=`/sbin/ifconfig -l` for i in ${ifs}; do no_of_carp=`/sbin/ifconfig $i | /usr/bin/grep -c carp` [ "x"$DEBUG != "x" ] && echo "Interface $i has ${no_of_carp} CARP configuration" [ ${no_of_carp} != "0" ] && carps="${carps} $i" done [ "x"$DEBUG != "x" ] && echo "CARP I/F = ${carps}" [ -z "${carps}" ] && exit 0 # no carp I/F. PREEMPTION=`/sbin/sysctl net.inet.carp.preempt | /usr/bin/awk '{print $2}'` [ "x"$DEBUG != "x" ] && echo "CARP preemption = ${PREEMPTION}" [ ${PREEMPTION} != "1" ] && exit 0 # No carp preemption. May cause failure. ############################################################################## # Main. case "${ACTION}" in "MASTER") # make sure all carp is master. if [ -n "${carps}" ]; then for if in ${carps}; do vhid=`/sbin/ifconfig ${if} | /usr/bin/grep carp | /usr/bin/awk '{print $3 " " $4}'` /sbin/ifconfig ${if} ${vhid} state master done fi for disk in ${HASTDEV}; do # If there is secondary worker process, it means that remote primary process is # still running. We have to wait for it to terminate. for i in `jot 30`; do /bin/pgrep -f "hastd: ${disk} \(secondary\)" >/dev/null 2>&1 || break sleep 1 done if pgrep -f "hastd: ${disk} \(secondary\)" >/dev/null 2>&1; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: Secondary process for resource ${disk} is still running after 30 seconds." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "Role for HAST resources ${disk} switched to primary." /sbin/hastctl role primary ${disk} if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: Unable to change role to primary for resource ${disk}." exit 1 fi done # Wait for the /dev/hast/* devices to appear for disk in ${HASTDEV}; do for loop in $( jot 120 ); do [ -c "/dev/hast/${disk}" ] && break sleep 0.5 done if [ ! -c "/dev/hast/${disk}" ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: GEOM provider /dev/hast/${disk} did not appear." exit 1 fi FSFMT=`file -bs /dev/hast/${disk}` if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: /dev/hast/${disk} cannot define FS format." exit 1 fi FSFMT=`echo ${FSFMT} | /usr/bin/awk '{print $1 " " $2}'` case ${FSFMT} in "Unix Fast") /sbin/fsck -y -t ufs /dev/hast/${disk} >/dev/null 2>&1 if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: UFS fsck /dev/hast/${disk} failed." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "fsck /dev/hast/${disk} finished." e_code=`/sbin/mount /dev/hast/${disk} /hast/${disk} 2>&1` if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: UFS mount for resource ${disk} failed: ${e_code}." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "UFS /dev/hast/${disk} is mounted." ;; *) # If not UFS, Assume that filesystem is ZFS. e_code=`/sbin/zpool import -f ${disk} 2>&1` if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: ZFS import for resource ${disk} failed: ${e_code}." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "ZFS ${disk} is imported." ;; esac done # NFS Service ( run nfsd and mountd. ) /usr/sbin/service rpcbind restart /usr/sbin/service statd restart /usr/sbin/service lockd restart /usr/sbin/service nfsd restart /usr/sbin/service mountd restart /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "NFS started." # iSCSI service # /etc/rc.d/iscsi_target start ;; "BACKUP"|"INIT") # make sure all carp is backup if [ -n "${carps}" ]; then for if in ${carps}; do vhid=`/sbin/ifconfig ${if} | /usr/bin/grep carp | /usr/bin/awk '{print $3 " " $4}'` /sbin/ifconfig ${if} ${vhid} state backup done fi # stop iSCSI service # /etc/rc.d/iscsi_target forcestop # NFS Service ( stop nfsd and mountd. ) /usr/sbin/service mountd forcestop /usr/sbin/service nfsd forcestop /usr/sbin/service lockd forcestop /usr/sbin/service statd forcestop /usr/sbin/service rpcbind forcestop /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "NFS stoped." # Switch roles for the HAST resources for disk in ${HASTDEV}; do sleep 1 # First of all, set hast status to secondary. # Do not touch device before hast status become secondary. # This work protects to become split-brain status. /sbin/hastctl role secondary ${disk} 2>&1 if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: Unable to switch role to secondary for resource ${disk}." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "Role switched to secondary for resource ${disk}." # Unmount UFS for i in `/sbin/mount | /usr/bin/awk '{print $1 ":" substr($4,2)}' | /usr/bin/grep hast`; do if [ ${i%:*} = "/dev/hast/${disk}" ]; then if [ ${i#*:} = "ufs," ]; then e_code=`/sbin/umount /hast/${disk} 2>&1` if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: UFS unmount of resource ${disk} failed: ${e_code}" exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "UFS /dev/hast/${disk} is unmounted" fi fi done # Export ZFS zpool list | egrep -q "^${disk} " if [ $? -eq 0 ]; then # Force export ZFS pool. e_code=`/sbin/zpool export -f ${disk} 2>&1` if [ $? -ne 0 ]; then /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "FATAL: ZFS export of resource ${disk} failed: ${e_code}." exit 1 fi /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "ZFS ${disk} is exported." fi done ;; *) /usr/bin/logger -p ${SYSLOG_FACILITY} -t ${SYSLOG_TAG} \ "Unknown CARP state. ${STATE}" ;; esac exit 0 ############################################################################## # Local Variables: # coding: utf-8 # mode: sh # sh-basic-offset: 4 # sh-indentation: 4 # End:
このファイルは「shell script」なので、chmod 750 carp-hast-switch
とchown root:wheel carp-hast-switch
を実行しておくこと。
iscsi target関連の設定はコメントアウトしている。(2015/04/14)
FilesystemをmountするDirectoryが/hastにhardcodeされている。(2015/04/14)
ZFSのcodeは組み込んであるが試験をしていない。また、zfsのpool名がhastのresource名と同じであることを仮定している
最後に、/hastを作成する。mkdir /hast
これで、hastがprimaryの時には、/hast/homeが作成される。
NFS Server 設定
NFSサーバーの設定を行う。
- ここにも詳細な情報がある。特にNFSv4関連は必読
- /etc/rc.conf.localに以下を記述
rpc_lockd_enable="YES" rpc_statd_enable="YES" rpcbind_enable="YES" portmap_enable="YES" mountd_enable="YES" mountd_flags="-r" nfs_server_enable="YES" nfs_client_enable="YES"
- mountd_flagsに関してはmanを読むこと。Diskless workstationを利用する場合には“-r”が必要
- /etc/exportsを記述する
/hast/home -network xxx.xxx.xxx.xxx/xx -maproot=root
今回はHome Directoryをshareすることを前提としているので、-maproot=root とか -maproot=0:0を記載しているが、root権限のファイルがNFSでshareされること自体は十分に検討する必要があるので注意すること
ここまで設定すれば NFS Server が Filesystem を export できるようになっている。 そこで以下のコマンドを実行する
# kill -HUP `cat /var/run/mountd.pid` # showmount -e /hast/home xxx.xxx.xxx.xxx/xx #
NFS client 設定
NFS Client側では、/etc/fstabに必要な設定を投入すれば、FilesystemをNFSでmountできる。
host-carp:/hast/home /home nfs rw,noinet6,tcp,soft,noatime,nfsv3,bg,wsize=32768,rsize=32768 0 0 #host-carp:/hast/home /home nfs rw,noinet6,tcp,hard,intr,noatime,nfsv3,bg,wsize=32768,rsize=32768 0 0
/usrなどの「必須filesystem」をmountする場合には、hard,intrを、/homeのような「システムに必須ではない」ファイルシステムはsoftを指定すると良い。
hard,intr : NFSサーバーが落ちている時にファイルシステムにアクセスすると、アクセスができるまで停止する。signalを送ることで割り込み(intr)がかかりエラーが帰る
soft : NFSサーバーが落ちている時にファイルシステムにアクセスすると即時エラーが帰る
読み込み・書き込みの最大転送サイズを変更する場合には、wsize=32768,rsize=32768
をつける。
非同期書き込みで良い場合にはasyncをつける
起動時の処理について
本節の問題は、carp-hast-switchを書き換えることで、とりあえず解決したはず。 以下は、過去の記録として残すのみで、考慮する必要は(現時点では)ないはず
サービスでHASTを利用する場合、事前に考えておくべきことが多い。 特に起動時の問題が大きい。定常状態まで行けば、あとはHASTdがSplit-Brainになった場合の対処だけとも言える。
起動時のhastの問題は、
- hastdは起動されるとresourceをinit状態にする
- 従って、CARPのstatusなどを鑑みて、自身のroleを設定しなければならない
- しかし、下手するとsplit brainになるからlocalcntとremotecntを確認しなければならない
ことである。むしろ、勝手に判断されるよりはましであるとも言える。
上記の問題を考えると、HASTdを起動するタイミングが問題になる。
本記事中に掲載した “carp-hast-switch” はCARPのstateの変化をdevd経由で取得しHASTdのroleを設定している。この時、NFSの挙動も制御しているため、定常状態では十分なのだが、OSの起動時には問題が発生する。
FreeBSDの起動順序では、
- devdを起動
- NICが設定される
- ここでCARPも設定される
- rpc関連を起動
- NFS Client関連の起動
- hastd 起動
- NFS Server関連の起動
となっている。
この起動順序では、以下の問題が発生する
- devdがNIC設定より前に起動されているため、NIC設定時点でcarp-hast-switchが呼ばれることがある
- hastdが起動された時には、すでにNICが設定されているため、先のcarp-hast-switchは呼び出されない
- そのため、hastdが起動されたのちにhastdのroleを設定する方策がない
- hastd起動時点でNFS関連の設定をしても直後のNFS Serverの設定時点で元の木阿弥になる
この問題は、要するに、「hastdの制御」と「hastdを用いたファイルシステムのexport」を混同しているから発生する。従って、この問題に対処するには、結局以下の方策のいずれかを採用するしかない。
- /etc/rc.localにhast関連の設定を行うscriptを記述する
- 結局devからhastdが起動される前にcarp-hast-switchを呼び出す問題は解決しない
- /usr/local/etc/rc.dになんらかのscriptを設置し、起動時に一度だけ読みだすようにする
- hastdのscriptを修正し、hastdのscriptで処理を行う
- この場合、nfs関連のscriptもおそらく修正が必要
- 最終的には本案で解決した。(2015/11/22)
上記案のいずれを採用するか決める前に、方針の決定と、問題点を抽出する。
方針は
- system関連のscriptはいじらない
- OSのUpdateなどで更新が入った場合に、問題が読めなくなる
- Update時に気を使わなければならないシステムはミスの温床になる。
- できる限り単純に実装する
- 将来なんらかの問題が発生した時に、scriptがprimitiveであることが可読性を上げる
- 対象をNFSに絞る
- おそらくiSCSIもSAMBAも制御可能だと思うが、今はそこまで考えない
- pfによる制御(filteroutなど)は実行しない
- このサーバーは、あくまでFile Serverとしてしか動作させない
ものとする
現時点で把握している問題をいかに挙げる。
- hastdは、hastdを起動してからresourceが/dev/hastに現れる他での時間が長い
- (下手すると3sec近くかかる)
- 従って、単純にNFSでexportして、ファイルシステムが公開されるまでの間にNFSでアクセスされるとfile not foundになる。これは、Mail ServerのSpoolなどでは致命的な問題を引き起こす可能性がある
- 従って、nfsサービスを停止すれば、アクセスできないからwaitがかかるので都合がいい
- ただし、HARD mountすることが前提になる。softだと、NFSでの接続ができない段階でerrorになるため
- hastdが起動し、INIT状態である段階でNFSが一度上がってしまう
- この問題の回避だけならば、nfs_server_enableやmountd_enableをNOにする手がある
- この手を使うと、rc.conf.localの設定の一貫性を壊す
- 他人がscriptを見て何してるかわからなくなる
- 他のscriptから判断することも困難になる
- 従って、この手法は採用できない。
- hastdが立ち上がり、nfsd/mountdが起動した後、nfsを停止するまでの短時間は、どうしてもNFSによるアクセスによってエラーになる可能性がある。
- この問題は、この問題が発生する頻度が低いこと(両NFS Serverが同時に再起動している時にのみ発生する)と問題になる時間が短いこと(nfsdが起動されてからnfsdを停止するまでの時間概ね1秒未満)から、許容範囲のリスクであると考えるしかない
- Daemonの監視・復旧が少し複雑になる
- localcntとremotecntを比較して、split-brain出ないことを確認しなければならない
- split-brainである場合、即座にCARPをBACKUPにし、hastdを停止しなければならない
- CARP StatusとHAST stateを監視し、必要に応じて同期しなければならない
- この問題は、devdが動作していない時に発生する可能性がある
- 従って、devdの死活の監視も必須になる
以上から、実装としては、
- carp-hast-nfs-boot を作成し、/usr/local/etcに設置
* /etc/rc.confにcarp-hast-nfs-boot_enable=“YES”を記述
- /etc/rc.localに直に記載する
の2案が考えられるが、現時点では、後者のrc.localによる実装を採用する。 (2015/11/22)
結論としては、carp-hast-switchで
- hastdが起動していない場合には「何もしない」
ように実装を修正することで、問題を解決した。
この手法で問題が解決できる理由は、CARPのステータスが「MASTER」になった際に、必ずcarp-hast-switchが呼ばれるため、最終的に正しく設定がなされることが期待出来るからである。
- rc.local
# Obsolete. Do not use this.(2015/11/22) #----- HAST initiate (Only boot time needed) if checkyesno hastd_enable; then echo "hast initializing" for if in `/sbin/ifconfig -l`; do if [ "0" != `/sbin/ifconfig ${if}|/usr/bin/grep -c carp` ]; then if [ -n "${if}" ]; then state=`/sbin/ifconfig ${if}|/usr/bin/grep carp|/usr/bin/awk '{print $2 " " $4}'` [ -x /usr/local/sbin/carp-hast-switch ] && \ /usr/local/sbin/carp-hast-switch ${state##* }@${if} ${state%% *} break else echo -n " no carp I/F" fi fi done unset if state echo " ... done" fi
注意点
FreeBSD 10.1 の段階では、以下の挙動が確認されている。
- host-1 と host-2 が /dev/ada1 を HAST で共有し、/hast/home に mount されているとする。
- 初期状態では、hast-1 が CARP MASTER/HAST Primary であるとする。
この状況において、
- host-1 がなんらかの事情(OSのUpdateなど)で停止した(もしくは通信不能な状況になった)とする
- host-1 では、secondaryになったことを受け、filesystemをunmountした
- これを受けて、host-2が CARP MASTER/HAST Primary に変化した
- なんらかの作業の結果、/hast/home でファイルの変更(fsckをかける、fileを削除など)が発生したとする
- これは、/homeとしてNFS Exportし、かつMailを受信したなどの作業を実施した状況と同様
- host-1が起動(もしくは、通信が復活)し、CARP BACKUP/HAST Secondaryとして復活した
という流れが発生したとする。
この時、HASTは、Dirtyが小さい状況であっても「Split-Brain状態になった」と認識し、同期が外れた状況となってしまう。
状況 | host-1 | host-2 | |||||
---|---|---|---|---|---|---|---|
HAST | localcnt | remotecnt | HAST | localcnt | remotecnt | ||
0 | 初期状態 | primary | 0 | 0 | secondary | 0 | 0 |
1 | host-1 切断 | primary | 0 | 0 | secondary | 0 | 0 |
2 | host-1 unmount | primary | 1 | 0 | secondary | 0 | 0 |
3 | host-2 primary | secondary | 1 | 0 | primary | 0 | 0 |
4 | host-2でfsck実行 | secondary | 1 | 0 | primary | 1 | 0 |
5 | host-2 mount | secondary | 1 | 0 | primary | 1 | 0 |
6 | host-1 復帰 | secondary | 1 | 0 | primary | 1 | 0 |
7 | split-brain | secondary | 1 | 0 | primary | 1 | 0 |
8 | hastctl create home | secondary | 0 | 0 | primary | 1 | 0 |
9 | 最後 | secondary | 0 | 0 | primary | 1 | 0 |
これを見ると、host-1でroleがprimaryの状況のままでunmountしたことが、Degrade、ひいてはSplit-brainの引き金になっているように見える。当初のcarp-hast-switchは、このような実装であったため、ちょっとした試験で頻繁にSplit-Brainとなってしまっていた。なぜumountでsplit-brainが発生するかは不明だが、MemoryとDiskのSyncに起因すると推測している。
この問題は、要するにsecondaryになる前にumountしているのが問題なのだと考えられることから、シナリオを変更して、以下のようにする。
状況 | host-1 | host-2 | |||||
---|---|---|---|---|---|---|---|
HAST | localcnt | remotecnt | HAST | localcnt | remotecnt | ||
0 | 初期状態 | primary | 0 | 0 | secondary | 0 | 0 |
1 | host-1 切断 | primary | 0 | 0 | secondary | 0 | 0 |
2 | host-2 primary | secondary | 1 | 0 | primary | 0 | 0 |
3 | host-1 unmount | secondary | 0 | 0 | primary | 0 | 0 |
4 | host-2でfsck実行 | secondary | 0 | 0 | primary | 1 | 0 |
5 | host-2 mount | secondary | 0 | 0 | primary | 1 | 0 |
6 | host-1 復帰 | secondary | 0 | 0 | primary | 0 | 0 |
7 | 最後 | secondary | 0 | 0 | primary | 1 | 0 |
要するに、当初シナリオの2と3をひっくり返した。その結果、Split-Brainが発生しなくなった。
というわけで、carp-hast-switchを書き換えて問題は解決した。(現在掲示しているcarp-hast-switchはこの問題を修正済みのものである)