Raspberry PiとFreeBSDで高可用性サーバの構築
もくじ
- はじめに
- FreeBSD/ARMのインストール
- CARPの設定
- HASTの準備
- DNSサーバ(BIND)のインストールとHASTの設定
- フェイルオーバーの設定
- 障害対応シナリオ
- まとめ
- [付録] RPi用のクロスコンパイル環境と独自リポジトリの構築
更新履歴
- 2015/05/03 公開
- 2015/05/04 「フェイルオーバーの設定」にカーネルパニックへの対応を追加
はじめに
シングルボードコンピュータのRaspberry Pi(以下、RPi)。低価格、名刺サイズ、低消費電力といった特徴が受けて当初の教育用という目的以外にも様々な分野で活躍していますが、本稿ではRPi 2台にFreeBSDをインストールして高可用性(HA)サーバを構築してみます。例として、次のホスト構成で、仮想IPアドレス"192.168.100.10"のDNSサーバを運用することを考えます。
役割 | ホスト名 | IPアドレス |
---|---|---|
Master | hadns1 | 192.168.100.101 |
Slave | hadns2 | 192.168.100.102 |
利用する主な技術は以下の通りです。
- CARP(Common Adress Redundancy Protocol))
- 複数のサーバでIPアドレスを共有し、冗長化を実現するためのプロトコル
- HAST(Highly Available Storage)
- 2台のサーバ間でファイルシステムを同期して、高可用性ストレージを実現する技術。
- Rgular file Filesystem(正式名称不明)
- 単一ファイルをファイルシステムとして使う技術。昔はswapを増設するために使われていたと書くと一部の
年寄り管理者にはピンと来るはず。
これらの技術を使って、以下のような機能の実現を目指します。
- HASTによるサービスに必要な設定ファイルの共有
- CARPによるMaster障害発生時の自動切り替え
- 障害復旧後の再同期
本稿執筆時点でFreeBSDはまだRPi2には対応していませんので、以下ではRPi B+を使って進めていきます。
FreeBSD/ARMのインストール
RPi用FreeBSD/ARMのインストールについては、他にたくさん情報があるのでここでは簡単に述べます。まず、FreeBSDのFTPサイト(あるいは日本のミラー)にアクセスしてイメージをダウンロードします。本稿執筆時点での最新版は、"FreeBSD-10.1-STABLE-arm-armv6-ZEDBOARD-20150417-r281609.img.bz2"です。
イメージをダウンロードしたら、bunzip2コマンドで展開し、ddコマンド等でメモリカードにイメージを書き込みます。
% bunzip2 FreeBSD-10.1-STABLE-arm-armv6-ZEDBOARD-20150417-r281609.img.bz2 % sudo dd if=FreeBSD-10.1-STABLE-arm-armv6-ZEDBOARD-20150417-r281609.img of=[メモリカードのパス] bs=1m
書き込みに成功したら、メモリーカードをRPiに挿して電源を入れます。ブートしてログインプロンプトが表示されたら、rootでログインします。初期状態ではパスワードが空なので、まず設定します。
# passwd Changing local password for root New Password: [パスワード入力] Retype New Password: [パスワード入力]
ディスクイメージの初期状態ではメモリカードの1GB分の領域しか使っていません(/etc/rc.confには「growfs_enable="YES"」という記述があるのですが、本稿執筆時点ではうまく動いていないようです)。
# df -m Filesystem 1M-blocks Used Avail Capacity Mounted on /dev/mmcsd0s2a 906 394 439 47% / devfs 0 0 0 100% /dev /dev/mmcsd0s1 16 3 13 21% /boot/msdos /dev/md0 28 0 26 0% /tmp /dev/md1 14 0 12 0% /var/log /dev/md2 4 0 4 0% /var/tmp # gpart show => 63 15523777 mmcsd0 MBR (7.4G) 63 34776 1 !12 [active] (17M) 34839 1918224 2 freebsd (937M) 1953063 13570777 - free - (6.5G) => 0 1918224 mmcsd0s2 BSD (937M) 0 105 - free - (53K) 105 1918080 1 freebsd-ufs (937M) 1918185 39 - free - (20K)
これを、残りの領域も使うように拡張します。まずはFreeBSDのスライスから。
# gpart resize -i 2 mmcsd0 # gpart show => 63 15523777 mmcsd0 MBR (7.4G) 63 34776 1 !12 [active] (17M) 34839 15488991 2 freebsd (7.4G) 15523830 10 - free - (5.0K) => 0 15488991 mmcsd0s2 BSD (7.4G) 0 105 - free - (53K) 105 1918080 1 freebsd-ufs (937M) 1918185 13570806 - free - (6.5G)
次にスライス2上のFreeBSD領域を拡張します。
# gpart resize -i 1 mmcsd0s2 # gpart show => 63 15523777 mmcsd0 MBR (7.4G) 63 34776 1 !12 [active] (17M) 34839 15488991 2 freebsd (7.4G) 15523830 10 - free - (5.0K) => 0 15488991 mmcsd0s2 BSD (7.4G) 0 105 - free - (53K) 105 15480704 1 freebsd-ufs (7.4G) 15480809 8182 - free - (4.0M)
最後にgrowfsコマンドを使って/パーティションを拡張します。
# growfs / Device is mounted read-write; resizing will result in temporary write suspension for /. It's strongly recommended to make a backup before growing the file system. OK to grow filesystem on /dev/mmcsd0s2a, mounted on /, from 937MB to 7.4GB? [Yes/No] Yes super-block backups (for fsck_ffs -b #) at: 1918400, 2397952, 2877504, 3357056, 3836608, 4316160, 4795712, 5275264, 5754816, 6234368, 6713920, 7193472, 7673024, 8152576, 8632128, 9111680, 9591232, 10070784, 10550336, 11029888, 11509440, 11988992, 12468544, 12948096, 13427648, 13907200, 14386752, 14866304, 15345856 # df -m Filesystem 1M-blocks Used Avail Capacity Mounted on /dev/mmcsd0s2a 7314 394 6334 6% / devfs 0 0 0 100% /dev /dev/mmcsd0s1 16 3 13 21% /boot/msdos /dev/md0 28 0 26 0% /tmp /dev/md1 14 0 12 0% /var/log /dev/md2 4 0 4 0% /var/tmp
ここで念のため一旦再起動します。
# reboot
CARPの設定
次にCARPの設定を行います。
まず、carpカーネルモジュールの読み込み設定を両サーバで記述します。
- /boot/loader.conf
carp_load="YES"
次に/etc/rc.confにIPアドレスの設定を追記します。
- Master
ifconfig_ue0="inet 192.168.100.101 netmask 255.255.255.0" ifconfig_ue0_alias0="vhid 1 pass [パスフレーズ] 192.168.100.10/32"
- Slave
ifconfig_ue0="inet 192.168.100.102 netmask 255.255.255.0" ifconfig_ue0_alias0="vhid 1 advskew 50 pass [パスフレーズ] 192.168.100.10/32"
ifconfig_ue0_alias0がCARPの設定になります。各パラメータの意味は次の通りです。
- vhid [id]
- バーチャルホストID。CARPで冗長化するIPアドレスごとの識別子
- advskew [n]
- 死活監視への応答遅延(n/256秒)。結果、値が小さいほどそのサーバの優先度が高くなる。省略時のデフォルトは"0"
- pass [phrase]
- パスフレーズ。冗長化するIF間で合わせる
- IPアドレス/マスク
- ネットマスクは必ず32にする
最後に、/etc/hostsに両方のサーバのIPアドレスを追記します。
- /etc/hosts
192.168.100.101 hadns1 192.168.100.102 hadns2
hadns1,hadns2を順に再起動してCARPがうまく動作しているか確認します。
- hadns1
# ifconfig ue0 ue0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=80001<RXCSUM,LINKSTATE> ether b8:27:eb:72:b0:b8 inet 192.168.100.101 netmask 0xffffff00 broadcast 192.168.100.255 inet 192.168.100.10 netmask 0xffffffff broadcast 192.168.100.10 vhid 1 carp: MASTER vhid 1 advbase 1 advskew 0 media: Ethernet autoselect (100baseTX <full-duplex>) status: active nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
- hadns2
# ifconfig ue0 ue0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=80001<RXCSUM,LINKSTATE> ether b8:27:eb:d1:82:5c inet 192.168.100.102 netmask 0xffffff00 broadcast 192.168.100.255 inet 192.168.100.10 netmask 0xffffffff broadcast 192.168.100.10 vhid 1 carp: BACKUP vhid 1 advbase 1 advskew 50 media: Ethernet autoselect (100baseTX <full-duplex>) status: active nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
この状態でhadns1を再起動して、hadns2のcarpが'MASTER'に変わることを確認します。 hadns1の再起動完了後もhadns2が'MASTER'のままとなるのが正常な動作です。
hadns1を'MASTER'に戻すには、hadns2で以下のコマンドを実行します。
# ifconfig ue0 vhid 1 state backup
HASTの準備
CARPの準備と動作確認がうまくいったら、HASTの準備を行います。
# mkdir /var/hast # dd if=/dev/zero of=/var/hast/disk1.img bs=1m count=64 64+0 records in 64+0 records out 67108864 bytes transferred in 9.181383 secs (7309233 bytes/sec)
/etc/hast.confを作成し、以下を記述します。
replication fullsync resource disk1 { on hadns1 { local /var/hast/disk1.img remote hadns2 } on hadns2 { local /var/hast/disk1.img remote hadns1 } }
disk1を初期化します。
- hadns1
# hastctl create disk1 # /etc/rc.d/hastd onestart # hastctl role primary disk1
- hadns2
# hastctl create disk1 # /etc/rc.d/hastd onestart # hastctl role secondary disk1
hastctlで状態を確認します。
- hadns1
# hastctl status Name Status Role Components disk1 complete primary /var/hast/disk1.img hadns2
- hadns2
# hastctl status Name Status Role Components disk1 complete secondary /var/hast/disk1.img hadns1
Statusが"complete"になったら、hadns1でファイルシステムを初期化します。
# newfs -U -J /dev/hast/disk1 /dev/hast/disk1: 64.0MB (131056 sectors) block size 32768, fragment size 4096 using 4 cylinder groups of 16.00MB, 512 blks, 2048 inodes. with soft updates super-block backups (for fsck_ffs -b #) at: 192, 32960, 65728, 98496
最後に/etc/rc.confにHAST起動の設定を追加します。
hastd_enable="YES"
DNSサーバ(BIND)のインストールとHASTの設定
FreeBSDのバージョン10以降ではBINDが取り除かれてしまいましたので、インストールします。ここではRPi上でコンパイルする正攻法を説明しますが、かなり時間がかかる作業となるので、付録に示すクロスコンパイル環境で独自リポジトリを構築する方法がおすすめです。
RPiにログインし、root権限でPorts treeを展開します。
portsnap fetch extract
BINDをインストールします。
cd /usr/ports/dns/bind910/ make && make install
インストールが完了したら、/usr/local/etc/namedの下に設定ファイルができるので、HASTのディスクにコピーします。
mkdir -p /hast/disk1/etc/namedb cp -rp /usr/local/etc/namedb/ /hast/disk1/etc/namedb
コピー先のHASTのディレクトリが/usr/local/etc/namedbにマウントされるように、/etc/fstabに以下の記述を追加します。後述するcarpctlコマンドでマウントを制御するため、fstabではnoautoオプションを指定します。
/hast/disk1/etc/namedb /usr/local/etc/namedb nullfs rw,noauto 0 0
HASTのPrimaryとなっているホストでマウントします。
mount /hast/disk1/etc/namedb
/usr/local/etc/namedb/named.confを編集してDNSの設定を行います。本稿ではnamed.confの説明は省略します。
フェイルオーバーの設定
CARPの状態によるファイルオーバーの設定を行います。ここで重要な役割を果たすのがdevdというデーモンです。devdはカーネルイベントが発生した時にユーザーランドのプログラムを実行するデーモンで、CARPの状態の切り替わりをトリガとしてHASTとサービスの切り替え設定を自動化することができます。
まず、carpctlを/usr/local/sbin/の下に置き、パーミッションをセットます。
# ls -l /usr/local/sbin/carpctl -rwxr-x--- 1 root wheel 4784 Jan 1 00:00 /usr/local/sbin/carpctl
このファイルにより、NICの状態が変化した時にHASTの切り替えやマウント、サービスの起動/停止を実行します。ファイルの冒頭に設定があるので、必要に応じて変更します。
- hast_if
- HASTの制御に使うNICを指定します。Raspberry Piの場合は"ue0"です。
- resources
- HASTで切り替えるリソース(ディスク)をスペース区切りで列挙します。リソース名はhast.confで定義したものです。
- services
- CARPの状況に応じて制御するサービスをスペース区切りで列挙します。本稿では"named"のみ制御します。
次に、devdの設定ファイルを設置します。標準のdevdの設定では/usr/local/etc/devd以下のファイルを読み込んでくれますので、ディレクトリを作成してその下に設置します。
mkdir /usr/local/etc/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 "/usr/local/sbin/carpctl $type $subsystem"; };
簡単に説明すると、notifyステートメントでmatch行で定義されるカーネルイベントに対してaction行で指定したコマンドを実行します。
- system
- システム名。"CARP"になります。
- subsystem
- サブシステム名。CARPの場合は [vhid]@[if名] という書式になります。
- type
- 通知の種別。CARPの場合は"INIT", "MASTER", "BACKUP"のいずれかになります。
- action
- 実行するコマンド。carpctlに対してtypeとsubsystemを引数として渡すようにします。
ここまでの設定が終わったら、CARPのMASTER側のRPiでifconfigコマンドを使って切り替え試験を行います。
# ifconfig ue0 vhid 1 state backup
しばらくすると、BACKUPだったRPiでディスクがマウントされ、namedが起動します。
# df Filesystem 1K-blocks Used Avail Capacity Mounted on (略) /dev/hast/disk1 63124 244 57832 0% /hast/disk1 /hast/disk1/etc/namedb 63124 244 57832 0% /usr/local/etc/namedb # /usr/local/etc/rc.d/named onestatus named is running as pid ####.
基本的には以上なのですが、このままでは起動時にMASTERとなるRPiでディスクのマウントやサービスの起動に失敗します。これは、標準のブート手順ではdevdの方がhastdより先に起動してしまうためです。
これを回避するため、/etc/rc.d/devdの
# REQUIRE: netif
という行を、
# REQUIRE: netif hastd
と書き換えます。
さらに、条件がよくわかっていないのですが、MASTER側をshutdownやrebootするとたまにカーネルがパニックしてデバッガが起動し、遠隔地から操作できなくなってしまうので、これに対応します。
まず、/etc/sysctl.confに以下の行を追加します。
debug.debugger_on_panic=0
そして、/usr/local/etc/rc.d/ddb という名前で以下のスクリプトを設置します。
#!/bin/sh # PROVIDE: ddb # BEFORE: sysctl . /etc/rc.subr name="ddb" start_cmd="ddb_start" stop_cmd=":" rcvar="ddb_enable" ddb_start () { /sbin/ddb script kdb.enter.panic=reset } load_rc_config $name run_rc_command "$1"
スクリプトの実行ビットを立てます。
# chmod 750 /usr/local/etc/rc.d/ddb # ls -l /usr/local/etc/rc.d/ddb -rwxr-x--- 1 root wheel 228 May 3 18:40 /usr/local/etc/rc.d/ddb
最後に、/etc/rc.confに以下の行を追加します。
ddb_enable="YES"
shutdownやrebootしてデバッガで止まらずに「15秒後に再起動」メッセージが表示されて再起動すれば正しく設定できています。本来であればpanicの原因解明と対策をした方がいいのですが、本稿の運用方式であればMASTER側をリブートすると必ずBACKUPとなり、もう一方のサーバからHASTの同期をやり直すので、致命的なデータ喪失等は起きないはずです。
障害対応シナリオ
本稿で説明した設定では、障害発生時には以下のような動作となります。
- 初期状態
- hadns1がCARP MASTER/HAST Primaryでnamedが起動
- hadns2がCARP BACKUP/HAST Secondaryでnamedは起動しない
- hadns1で障害発生
- hadns2が自動的にCARP MASTERに切り替わる
- hadns2がHAST Primary(degrated)となる
- hadns2でnamedが起動する
- hadns1が復旧
- hadns2はCARP MASTERのままで、namedサービス継続
- hadns1はCARP BACKUPとなる
- hadns1はHAST Secondaryとなり、hadns2(Primary)と再同期する
つまり、本稿で説明している設定ではCARPのadvskew設定は使用しておらず、一旦MASTERになると、RPiの再起動やifconfigコマンドで明示的にBACKUPに切り替えない限りはMASTERであり続けます。これは、hadns1で障害が発生してMASTERが切り替わってから復旧するまでの間に、hadns2の方で設定が変更されたりしていると、その修正が失われてしまうからです。
hadns1の復旧後、再度MASTERに戻したい場合HASTの状態がsecondaryでcompleteになっているのを確認してから、hadns2の方のCARPを手動でBACKUPに切り替えるようにします。
まとめ
本稿ではRaspberry Piを使った高可用なFreeBSDサーバの構築手順を説明しました。今回はDNSサービスのみ取り上げましたが、carpctlは汎用性を考えて作ってありますので、同ファイル内のservice設定や/etc/fstabを適宜修正すれば、他のサービスでも使えると思います。総額でも1.5万円(UPS除く)程で冗長化サーバが構築できるというのは、なかなか魅力的ではないでしょうか。
[付録] RPi用のクロスコンパイル環境と独自リポジトリの構築
RPiは非力なのでPortsのコンパイルにはかなりの時間を要します。そこで、他のアーキテクチャのFreeBSD環境でRPi用のパッケージを構築する方法を説明します。以下の説明はRPiと同じ10.1の環境がすでにあるという前提で進めます。amd64の環境で確認しています。
まず、ARMプロセッサのエミュレータをインストールします。
pkg install emulators/qemu-sbruno pkg install emulators/qemu-user-static
binmiscctlコマンドを使ってARM用のバイナリイメージを認識できるようにします。
binmiscctl add armv6 --interpreter "/usr/local/bin/qemu-arm-static" --magic "\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00" --mask "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff" --size 20 --set-enabled
# binmiscctl list name: armv6 interpreter: /usr/local/bin/qemu-arm-static flags: ENABLED USE_MASK magic size: 20 magic offset: 0 magic: 0x7f 0x45 0x4c 0x46 0x01 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x28 0x00 mask: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xfe 0xff 0xff 0xff
package管理ツール(Poudriere)をインストールします。クロスコンパイル環境のためにはdevelバージョンが必要なようです。
pkg install poudriere-devel
必要に応じて/usr/local/etc/poudriere.confを編集します。ここではデフォルトで進めます。
Ports treeを展開します。
poudriere ports -c
ARMアーキテクチャ用のjailを構築します。
poudriere jail -c -j 10armv6 -m svn -a arm.armv6 -v stable/10
パッケージを作ります。例: bind910
poudriere options -j 10armv6 dns/bind910 poudriere bulk -j 10armv6 dns/bind910
これで、/usr/local/poudriere/data/packages/10armv6-default以下にリポジトリができるので、Webサーバを使って公開します。例えばApache 2.2.xの場合は、Aliasを使って以下のように定義します。
Alias /10armv6 /usr/local/poudriere/data/packages/10armv6-default <Directory /usr/local/poudriere/data/packages/10armv6-default> Order allow,deny Allow from all </Directory>
RPiの方でこのリポジトリを使うように設定します。まずディレクトリを作成します。
mkdir -p /usr/local/etc/pkg/repos
ここにリポジトリの設定ファイル(MyREPO.conf)を設置します。
FreeBSD: { enabled: no } MyREPO: { url: "http://[リポジトリサーバ]/10armv6", mirror_type: "http", enabled: yes }
- コメント:
Keyword(s):
References:[SideMenu]