FreeBSDのネットワークブート with qemu on FreeBSD

FreeBSDPXEブートについて、仮想マシンを用いたテスト環境の作成した記録です。
ホストOSがFreeBSD、ブートの対象マシンはqemu仮想マシンです。そして、ブートするOSはFreeBSDです。

こんな感じ。

+---------+              +----------+
|  Host   |     pxe      | VM(qemu) |
|         |---> boot --> |          |
| FreeBSD |              | FreeBSD  |
+---------+              +----------+

まず始めに。

PXEの仕組みは、簡単に説明すると以下の通りとなっている。

  1. NICの中にBIOSが入っており、マザーのBIOSからNICBIOSに移る
  2. NICBIOSDHCPサーバにリクエストを出す
  3. DHCPサーバがTFTPサーバのIPアドレスを投げる
  4. TFTPのサーバからブートローダを読み込み、実行する

今回、FreeBSDをブートするに当たっては

  1. TFTPで公開するファイルとしてBSDのpxebootというファイルを用いる
    • これは、通常のbsdloaderのpxe対応版である
  2. pxebootはNFSのクライアントとしてもう一度サーバにアクセスする
  3. NFSで/boot以下が読めるので、ここにあるカーネルとモジュールを読み込み、実行する
    • この時mfsrootという、ディスクイメージもダウンロードしてくる
  4. カーネル起動後、カーネルはHDDをマウントする代わりに先にダウンロードしたmfsrootをルートファイルシステムとしてマウントする

という流れとなります。

qemuの設定

今回実験環境をFreeBSDとしたため、qemuを利用して実験した。

ブートイメージをゲットする

qemupxeブートを実現するために、http://rom-o-matic.net/からブートイメージを生成した。

  1. qemuがエミュレーションできるnicがrtl8139なので、"rtl8139:rtl8139"を選択。
  2. imageはisoに。
  3. 特にconfigureせず。
  4. Get ROMをポチっとな。
ネットワーク周りの設定
+-----+   +---------+   +------+
| tap |---| 仮想lan |---| qemu |
+-----+   +---------+   +------+
            (vlan)

上図のような構成にする。
tapが使えるように、カーネルモジュールをロード。デバイスを作る為に、適当にデバイスにリードを発生させる(catなりfileなり何なり)。

# kldload if_tap
# file /dev/tap0

ifconfigでtap0が出来ている事が確認出来たら、tapに適当なipアドレスを振る。今回は192.168.64.1とした。

# ifconfig tap0 192.168.64.1

qemu動作確認

qemuにはカーネルモジュールaioが必要なので、読み込む。

# kldload aio

という事で、ブートするするか確かめる。尚、-cdromの引数には、先に生成したisoイメージの名前を渡す。今回はeb-5.4.4-rtl8139.isoとした。

# qemu -cdrom eb-5.4.4-rtl8139.iso -boot d -net nic,vlan=1,model=rtl8139 -net tap,ifname=tap0,vlan=1,model=rtl8139


tapの設定

以上の手動でtapデバイスを作成する事は可能であるが、nfsサーバの起動時(つまり、OSのブート時)にtapデバイスが無いとnfsdは起動しない。
よって、起動時にtapデバイスが作成されるように以下の設定を行う。

  • /boot/loader.confに追記
if_tap_load="YES"
aio_load="YES"

tapデバイスを作成するのに必要なカーネルモジュールと、qemuを起動するのに必要なカーネルモジュールが起動時に読み込まれるようになる。

  • /etc/start_if.tap0 を作成、以下を記述
file /dev/tap0

tap0の初期化処理が行われる。
これが実行される事により、起動時にtap0が作成される。

  • /etc/rc.confに以下を追加。
network_interfaces="lo0 em0 tap0" 
ifconfig_lo0="inet 127.0.0.1"
ifconfig_tap0="inet 192.168.64.1 netmask 255.255.255.0 broadcast 192.168.64.255"

network_interfacesにネットワークインタフェースを羅列する事により、マシンが持っているインタフェースを明示的に示す事が出来る。
これが指定されていない場合は自動検出モードになっているが、実在しない"tap0"インタフェースは検出されないので自動検出モードを使わず、インタフェースを明示する。
この例の場合は左から順に

  • lo0: ループバック
  • em0: 実在するnic(ここは適宜それぞれの環境によって書き換える)
  • tap0: 仮想インタフェース

となっている。
しかし、インタフェースが存在している事を示すだけでは不十分である。ループバックインタフェースのipアドレス、tap0のipアドレスも明示することも必要である。それが"ifconfig_lo0"及び"ifconfig_tap0"の行である。特にループバックインタフェースの事を忘れやすいので注意する。
尚、実在するnic(この場合em0)もdhcpを使わずにipを固定している場合も同様に書かなければならない。

デーモンの設定

dhcpサーバの設定

portsより、isc-dhcp30-serverをインストール。
dhcpd.confは以下の通り。
"option root-path"の項目部分は、bootloaderが必要とするファイルを置く場所を指定。これはnfsで提供する。今回は/net-boot/pxerootとした。

ddns-update-style none;
default-lease-time 7200;
max-lease-time 7200;

subnet 192.168.64.0 netmask 255.255.255.0 {
	filename "pxeboot";
	next-server 192.168.64.1; 	# tftp server
	option broadcast-address 192.168.64.255;
	range 192.168.64.2 192.168.64.32;
	option root-path "192.168.64.1:/net-boot/pxeroot";
}

以上、dhcpd.confとして適当な場所に置いた後、起動する事を確認をする。今回、このスクリプトは/net-bootに置いた。

# dhcpd -cf dhcpd.conf -lf tmp tap0
# ps aux | grep dhcpd

tftpサーバの設定

ここら辺を参考に、コンパイル
勿論portsにも入っているが、inetdを使うバージョンがコンパイルされるので非常に使いづらい。
因みに、FreeBSDはデフォルトがbsdmakeなのでgmakeでコンパイル

$ wget http://www.kernel.org/pub/software/network/tftp/tftp-hpa-0.48.tar.bz2
$ tar jxvf tftp-hpa-0.48.tar.bz2
$ cd tftp-hpa-0.48
$ ./configure --without-tcpwrappers
$ gmake
$ cp tftpd/tftpd ../
$ cd ..

makeが終わったら、起動する事を確認する。

# ./tftpd/tftpd -l -s
# ps aux | grep tftpd

今回、出来たバイナリは/net-boot/tftpdに置いた。

nfsサーバの設定
  • rc.conf

nfsサーバをudpで提供する。

# network boot
portmap_enable="YES"
rpcbind_enable="YES"
nfs_server_enable="YES"
nfs_server_flags="-u -n 4 -h 192.168.64.1"
mountd_enable="YES"
mountd_flags="-r"
nfsd_enable="YES"
  • /etc/exports

nfsで提供するディレクトリを指定。先に書いたdhcp.confの"option root-path"項と同じものにする。

/net-boot/pxeroot -network 192.168.64.0 -mask 255.255.255.0

ここまでの設定を反映するために、一旦リブートをする。

ブート最終準備

ブートに必要なファイル類を揃える

FreeBSDのインストールディスクから、pxeboot及び/bootを持ってくる。
pxebootは任意のディレクトリを作成しそこの中へコピーする。
/bootについては、先に書いたdhcp.confの"option root-path"項に書いたディレクトリにコピーする。

# mdconfig -a -t vnode -f 7.0-RELEASE-i386-bootonly.iso
# mount -t cd9660 md0 /mnt
# cp /mnt/boot/pxeboot /net-boot/pxeboot/
# cp -Rp /mnt/boot /net-boot/pxeroot

デーモン起動

dhcpdとtftpdを立ち上げる。
尚、tftpdの引数には、'pxeboot'をコピーしたディレクトリを指定する。

# dhcpd -cf dhcpd.conf -lf tmp tap0
# ./tftpd/tftpd -l -s ./pxeboot

レッツブート!

# qemu -cdrom eb-5.4.4-rtl8139.iso -boot d -net nic,vlan=1,model=rtl8139 -net tap,ifname=tap0,vlan=1,model=rtl8139

便利に。

毎回デーモン起動の手順を踏むのは非常に面倒なので、以下のようなスクリプトを準備すると非常にお得感が溢れる。
OSの初回起動時にsettings.shを実行し、それ以後はstartqemu.shのみ実行すれば良い。

  • settings.sh
#!/bin/sh
sudo dhcpd -cf dhcpd.conf -lf tmp tap0
sudo ./tftpd/tftpd -l -s ./pxeboot
  • startqemu.sh
#!/bin/sh
sudo ifconfig tap0 192.168.64.1
sudo qemu -cdrom eb-5.4.4-rtl8139.iso -boot d -net nic,vlan=1,model=rtl8139 -net tap,ifname=tap0,vlan=1,model=rtl8139

startqemu.shでtap0のipを指定しているが、これはqemuが終了と同時にtap0のipアドレスを初期化してしまうからである。

そんで。

今回、このままブートするとsysinstallが立ち上がってきます。
これは、通常/sbinにあるはずのinitが存在しないためです。つまり、initを作成してごにょごにょすると、インストーラは立ち上がらずに色々おもしろい事が出来るという事です。いやっほう!
という事で、以上「とりあえずqemupxeブートするの巻でした。つづ......く?

参考URL