PXE、DHCP、TFTPのオサライ

PCのBIOSからPXEを利用してPXEサーバからtftpで「pxelinux.0」という2次ローダをダウンロードして、次にpxelinux.0が本体のプログラムをダウンロードして起動する。pxelinux.0 は syslinuxパッケージをインストールすると /usr/lib/syslinux/pxelinux.0にあるので /tftpbootの下の任意のディレクトリにコピーしておく。「任意のディレクトリ」はDHCPサーバがPXEでブートするクライアントPCに対して「/tftpbootの下の×××というディレクトリにPXEローダがある」と教えている。つまり /etc/dhcp.confでクライアントごと、もしくはクライアントのグループごとに指定する(PXE用DHCPサーバの設定)。「/ftfpboot」も変更することができる。これはtftpdのルートディレクトリとなるので、/etc/xinetd.d/tftp で指定する。tftpのルートは任意に変えることは可能だが、?SELinuxのポリシーを追加する必要がある、?(後述するように)他のアプリケーションなどが /tftpbootというパスに依存しているので、/tftpbootを使う方が無難だ。

ディスクレスを実装してみる。

先ずは、「PXEでディスクレス・クライアントを実現するには」を参考にディスクレスを実装してみる。この記事は比較的新しい(2007年4月11日)だが、Fedora Core 6をベースとしているので、2ヵ所Fedora 8と違うところがあったが、大体、この通りやっていけばディスクレス・クライアントを「ブートするところまで」は何とかできる。大変役立った。(が、問題は、その先。ログインできるところまで持っていくのに手間取った。この記事にはそこまでの手順は書いていない。)

DHCP、TFTP、PXEについては前回の実装のまま。ただ、tfptdのルートディレクトリを/tftpbootから/home/TFTPbootへ変えていたが、最初これでは上手くいかなかったので、一旦、/tftpbootを使って実験した。また、/tftpbootにクライアントのルートファイルシステムを格納するが、サブディレクトリは「f8」という名前にした。(上記、記事では「fc6」である。)

最初、気付かなかったが、/tftpbootの下には2つのサブディレクトリが出来ることになる。一つは、tftpdを使ってPXEローダ(pxelinux.0)やLinuxカーネル+initrdをダウンロードするためのもので、素直に「linux-install」という名前にした。もう一つはクライアントのファイルシステムを格納するディレクトリで、今回はクライアントのOSもFedora 8を使ったので「f8」とした。最初この2つのディレクトリを混同してしまって失敗した。

あと、クライアント用のファイルシステムを /tftpboot/f8/root/の下に格納するわけだが、当然、3GBとか4GBののサイズになる。サーバ用のOSがやはり3GB以上は使っているので、ルートファイルに余裕がないと苦しい。(後述するように一通りブートまで確認したところで、クライアントのファイルシステム、すなわち「f8」は/home/TFTPbootへ引っ越した。)

ちなみに、/tftpboot/f8/rootはFedora 8を使う複数のクライアントで共通に使われる。しかし、中には共有できないファイルやディレクトリがある。例えばホスト名を格納しているファイルとか。それらはそれぞれのクライアント毎に「/tftpboot/f8/snapshot/クライアント名/」というディレクトリに格納される。PXEサーバでは共用部分はrootの下、個別部分は"shapshot/クライアント名"の下と分けているが、クライアントからは、まずrootをローカルの / にRead OnlyでNFSマウントし、次に、個別のファイルやディレクトリを"その上に"Read/WriteでNFSマウントするので、一つのファイルシステムと見える。その代わり、クライアントでmountコマンドを使って見るとわかるが、もの凄い数のNFSマウントを行なっている。(正直、余り美しくない。クライアントの数が少ない場合は、2GBくらいだったら、各クライアント毎に専用のファイルシステムを割当てて構わないと思った。)

更に注意しなければならないのは、用意するクライアント用のファイルシステムである。上記記事のなかでは、サラリと「稼働しているLinux(ここでは、FC6)を用意して、そこから全ファイルをコピーするのが一番簡単な方法だ」と書いているが、(確かにその通りなのだが)クライアント用のOSとなるので、この段階で設定を注意した方がいい。例えばSELinuxはdisableにしておく(最低でもpermissiveにしておく)必要がある。また、xorg.confも出来るだけ汎用性のある記述にしておいた方がいい。これらの点に注意しておかないと、カーネルはブートできても、途中で(例えば /dev/consoleが見当たらないとかで)パニックになったり、Xウィンドウが立ち上がらない、コマンドラインでもログインできない、という障害が待ち受けている。ちなみに、私はVMware Serverで一つ仮想マシンを作って、そのマシンでクライアント用のLinuxを作成した。

それと、上記記事で取り扱ったFC6と私が使ったF8とで若干手順がことなる。一つ目はクライアントのカーネルを起動する際のパラメータを記述する「/tftpboot/install-linux/pxelinux.cfg/クライアントごとの記述ファイル」に対して「このファイルのappendの行に『ramdisk_blocksize=1024』を追加する。この設定を行わないと、クライアントの起動時にKernel Panicが発生する。」とあるが、これは追加しなくても問題なかった(というか「ramdisk_size=20920」という設定でデフォルトでされていた)。もう一つは、「クライアント起動用のイメージファイルにmount.nfsを追加する。この作業を行わないと、クライアントがNFSでのマウントに失敗する。」とあるが、これも必要ない。F8のディスクレスクライアント用のinitrd.imgにはNFSマウントのコマンドは入っていた。(ちなみに、F8(確かFC6でも同じだったと思うが)のinitrd.imgはファイルシステム・イメージではなくcpioのアーカイブ形式になているのでmount -o loopではマウントできない。cpioコマンドを使って展開する必要がある。(例えば「cpioフォーマットのinitrdイメージの展開と作成」を参照。)

カスタマイズ

一応、ディスクレス・クライアントのカーネルが立ち上がるところまで確認したので、先ずはカスタマイズをした。

/home/TFTPbootへの引越し

/ftfpboot はルートファイルシステムにある。勿論、どこかのパーティションをマウントすれば別のファイルシステムになるが、/tftpbootの為にパーティションを用意するのも行き過ぎだし、今のマシンには余ったパーティションはない*1。そこで、/tftpbootの下のディレクトリの一部を別のファイルシステムの中のディレクトリへシンボリックすることになる。例えば、/tftpboot/f8を/home/TFTPboot/f8へリンクするような感じだ。

しかし、(tftpdのソースコードを見ていないので確認はしていないが)tftpdは"-s"オプションでルート・ディレクトリを変更できるが、これはアプリケーション(tftpd)で意識的に管理しているのではなくchrootでプロセスのルートディレクトリ自身を変えていると思われる。そのため/tftpbootの下に上位のディレクトリ(この場合 /)まで遡らないとたどり着けないパスへのシンボリックリンクは実現できない。つまり、シンボリックリンクのリンク先も/tftpbootの下でないとリンクをたどれないことになる。これではルート・ファイルシステムから外へは抜け出せない。と、あきらめかけたが。

PXEのブート手順は、①BIOSがTFTPを使ってPXEローダ(pxelinux.0)をダウンロードし起動する、②PXEローダがTFTPを使って(←推測)LinuxカーネルとRAMディスク・イメージをダウンロードし、起動する、③LinuxカーネルNFSを使って必要なファイルをマウントする、ということになる。ここで①と②はTFTPプロトコルを使っている(推測)ので、必要なファイルは/tftpbootの下に置いておかなければならないが、③のNFSでマウントするファイル、つまりクライアントのルート・ファイルシステムは/tftpbootの下に置かなくても構わない。そこで次のような構成にした。

# ls -l /tftpboot
total 12
lrwxrwxrwx 1 root root   17 2008-01-22 23:39 f8 -> /home/TFTPboot/f8
drwxr-xr-x 5 root root 4096 2008-01-24 01:24 linux-install

# ls -l /home/TFTPboot
total 8
drwxr-xr-x 4 root root 4096 2008-01-22 23:40 f8

つまり、Linuxカーネルのロード&ブートまでに必要なファイルを格納いしてる「linux-install」は/tftpbootの下に実体を置き、NFSでマウントするクライアントのルート・ファイルシステムを格納する「f8」は/home/TFTPbootに実体を置き、/tftpbootからはシンボリックシンクを張っておいた。

f8に何故シンボリックリンクを張ったかというと、「PXEでディスクレス・クライアントを実現するには」にも書いてあるが、「クライアントのファイルを格納するディレクトリ名は、最初が/tftpbootで、最後がrootでなければならない」という奇妙の性質を持っているためである。つまり、/tftpboot/f8/rootはOKだが、/home/TFTPboot/f8/rootはNGになる。(最初、それを知らずにやっていて、設定が出来ずにハマっていた。しかし、何でこんな制限があるのか???)

ちなみに、NFSの設定ファイル /etc/exportsではシンボリックリンクを使ったパスで記述してある。

# cat /etc/exports
/tftpboot/f8/root       192.168.0.10/24(ro,sync,no_root_squash)
/tftpboot/f8/snapshot   192.168.0.10/24(rw,sync,no_root_squash)

クライアント用ファイルシステムの最適化

今回、ディスクレスクライアントを実現しようとしたのは古くなってHDDが使えなくなったPCをVMware Serverのコンソールとして使おう、と思ったからである。従って、クライアントで動くWindowsなりLinuxなりのOSは最小限の機能があれば良く、OSが立ち上がった状態でvmware-server-consoleが動けばいい。また、HDDが無いのでスワップも使えず、全てメモリ上で収まる範囲内のサービスしか動かせない。(今回の場合、メモリが192MBなので、その範囲内で動く必要がある) 「PXEでディスクレス・クライアントを実現するには」では「稼働しているLinux(ここでは、FC6)を用意して、そこから全ファイルをコピーするのが一番簡単な方法だ」とあるが、確かに簡単だが、既に動いている標準的な環境では色々なサービスが動いてメモリは400MB近くを占領しているのでNG。また、ファイルシステムも4GB近くもある。

既にあるマシンでディスクレス用にOSをカスタマイズすると「既にあるマシン」が使い物にならなくなってしまう。こうした時に便利なのが仮想マシンだ。仮想マシンを1つ作って、そこにFedora 8をインストールし、不必要なパッケージを削り、さらに不必要なサービスを起動しないようにした。Xウィンドウはtwmでも良かったちょっと寂しいのでGNOMEの最小限の機能がけを残した。

ファイルシステムの大きさは大体、1.9GBだった。(仮想ディスクのスペースとしては少々余裕をみて2.2GBとってある。)

# df
Filesystem           1K-blocks      Used Available Use% Mounted on
rootfs                 2269184   1892352    261632  88% /

ログイン直後のメモリの使用状況は以下の通りである。

# free
              total       used       free     shared    buffers     cached
 Mem:        190168     175776      14392          0          0     119664
 -/+ buffers/cache:      56112     134056
 Swap:            0          0          0

56MB程度しか使っていない。vmware-server-consoleを起動しても80MB程度の消費である。

クライアント用ファイルシステム「イメージ」の作成

さて、仮想マシンで作ったファイルシステムを「PXEでディスクレス・クライアントを実現するには」に紹介されているようにrsyncを使って(私の場合は)/home/TFTPboot/f8/root の下にコピーして来てもいいが、私の環境ではちょっと問題がある。/homeの下は仮想マシン用の仮想ディスクを数多く格納することを主目的としているため、比較的大きいファイル(数GB〜数十GB)を数多く置くことを前提に4MBブロックでmkfsしている。ディスクアクセスの回数を減らして高速化を計るためである。そのため、たとえ1バイトの大きさのファイルでも4MBのエリアを喰ってしまう。小さいファイルを数多く置くには効率が悪い。つまりルートファイルシステムなどのOSを格納するには不向きである。

そのため、ディスクレス・クライアントのファイルシステムは「ファイル」として/home/TFTPboot/f8/rootの下に格納するのではなく、2.2BGのディスクイメージとして適当な場所(今回は/home/TFTPboot/f8)において置き、として/home/TFTPboot/f8/rootにマウントする方法にした。一回ループデバイスを通してのアクセスになるので、ちょっと遅くなるかも知れないが、今回のケースでは一旦クライアントでvmware-server-consoleが動き出せばメモリ上だけで動作し、殆どディスクへのアクセスは無くなるので問題はないだろう。

そのために仮想マシンで、ディスクイメージを作る作業を行なった。
① 最適化したクライアントのファイルシステムを格納している仮想ディスク(A)の他に、作業の仮想ディスク(B)を追加する。実はディスクAは一旦、Fedora 8をインストールして、要らないサービスやパッケージを削るために10GBとちょっと大きめに作ってあった。作業終了後、ファイルシステムの大きさは1.9GB程度と分かったので、少々余裕を持った2.2GBの作業用ディスクBを追加して、FedoraのLiveCDでブートした。以下はLiveCDのターミナルからイメージファイルを切り出すための作業である。

# mkdir /a /b
# mount /dev/sda1 /a
# mkfs -t ext3 /dev/sdb1
# mount /dev/sdb1 /b

# cd /a
# dump -0f - . | ( cd /b; restore -rf - )

# umount /dev/sdb1
# dd if=/dev/sdb1 of=/a/tmp/Fedora-8-Diskless.img bs=512M

# ssh cat \> /home/TFTPboot/f8/Fedora-8-Diskless.img < /a/tmp/Fedora-8-Diskless.img
# rm /a/tmp/Fedora-8-Diskless.img

dump & restoreで一旦、AにあるファイルをBへ移している。cpやtarを使わなかったのはSELinuxのコンテキストがコピーされないからだ。(-Zフラグととかも付けてみたが。rsyncでもコピーできないようだ。)

sshはサーバへイメージファイルを転送するの使っているだけで、sshでなくても構わない。

これをPXIサーバでは/home/TFTPboot/f8/rootにマウントして使う。(以下はPXEサーバでの操作)

# cd /home/TFTPboot/f8
# ls root/
# mount -o loop Fedora-8-Diskless.img root
# ls root/
#    boot  etc   lib         media  net  proc  sbin     srv  tmp  var
bin  dev   home  lost+found  mnt    opt  root  selinux  sys  usr

*1:と書いていて、シンボリックリンクなんかを使わないで/tftpbootごとディスクイメージファイルにまとめてマウントしてしまえばいいじゃん、と気がついた。今度やっていみよう。

トラブルシュート

本来、カスタマイズの前にトラブルシュートをするべきだが、クライアント用OSの設定を変える必要があったので、先に最適化などのカスタマイズを行なった。

先ずはカーネルのブート途中でPanicで止まってしまうところから修正を始めた。

ブートの途中で止まる

Linuxカーネルがブートする途中で幾つかエラーメッセージを出してくる。例えば/dev/consoleがないというメッセージを出しpanicで止まったりする。原因は2つ。

  1. NFSがサーバ側のファイヤーウォールでブロックされてしまう。
  2. SELinuxのコンテクストが正しく読めていない。

NFSに関しては、標準ではmountdの持ちうけポート番号が動的に代わるので、ファイヤーウォールを越えられないらしい。対応策としてはファイヤーウォールを止めるというのが一般的らしいが、それもいかがなものかと。もう一つの方法はポート番号を固定して使う方法だ。これによってファイヤーウォールの例外ポートして指定できる。GNOMENFS管理ツールなどで簡単に指定できる。私の場合は次のようにした。

rpc.lockd(TCP) 44928
rpc.lockd(UDP) 32770
rpc.mountd(TCP) 59385
rpc.statd(TCP) 54011

これが正しいポート番号かは怪しいが、一応、動いている。

NFSが出来るようになれば、ログインのところまでたどり着くことができた。ただし、SELinuxのコンテキストが正しく認識されないために、エラーメッセージが大量にでる。

コマンドラインでもログインできない

最初Xが立ち上がらなかったためコマンドラインのログイン・プロンプトが出たが、ユーザ名とパスワードを入れると一瞬(0.3秒くらい?)メッセージが出て、またログインプロンプトに戻る。メッセージが読めない。

これもSELinuxの影響だったようだ。(メッセージが読めないので推測だが。)クライアントのSELinuxの設定をPXEサーバ側でpermissive以下にしたら直った。

原因はどうやらSELinuxのコンテキストが正しく認識されていないようだ。ベースとなるマシン(VMware仮想マシン)からファイルをコピーする際にはdump & restoreを使ってコンテキストもコピーしているし、PXEサーバ側でイメージファイルをマウントしてから各ファイルのタイプを確認しても正しかった。

 # ls -aZ /home/TFTPboot/f8/root/
 drwxr-xr-x  root root system_u:object_r:root_t:s0      .
 drwxr-xr-x  root root system_u:object_r:tftpdir_t:s0   ..
 drwxrwxr-x  root root system_u:object_r:default_t:s0   #
 -rw-r--r--  root root system_u:object_r:root_t:s0      .autorelabel
 drwxr-xr-x  root root system_u:object_r:bin_t:s0       bin
 drwxr-xr-x  root root system_u:object_r:boot_t:s0      boot
 drwxr-xr-x  root root system_u:object_r:root_t:s0      dev
 drwxr-xr-x  root root system_u:object_r:etc_t:s0       etc
 drwxr-xr-x  root root system_u:object_r:home_root_t:s0 home
 drwxr-xr-x  root root system_u:object_r:lib_t:s0       lib
 drwx------  root root system_u:object_r:lost_found_t:s0 lost+found
 drwxr-xr-x  root root system_u:object_r:mnt_t:s0       media
 drwxr-xr-x  root root system_u:object_r:mnt_t:s0       mnt
 drwxrwxr-x  root root system_u:object_r:default_t:s0   net
 drwxrwxr-x  root root system_u:object_r:default_t:s0   .oldroot
 drwxr-xr-x  root root system_u:object_r:usr_t:s0       opt
 drwxr-xr-x  root root system_u:object_r:root_t:s0      proc
 drwxr-x---  root root system_u:object_r:user_home_dir_t:s0 root
 drwxr-xr-x  root root system_u:object_r:bin_t:s0       sbin
 drwxr-xr-x  root root system_u:object_r:root_t:s0      selinux
 drwxrwxr-x  root root system_u:object_r:default_t:s0   .snapshot
 drwxr-xr-x  root root system_u:object_r:var_t:s0       srv
 drwxr-xr-x  root root system_u:object_r:root_t:s0      sys
 drwxrwxrwt  root root system_u:object_r:tmp_t:s0       tmp
 drwxr-xr-x  root root system_u:object_r:usr_t:s0       usr
 drwxr-xr-x  root root system_u:object_r:var_t:s0       var

ところがNFSを通してクライアントが読むとタイプが変わってしまっている。SELinuxをpermissiveにして立ち上げてみると次のようになっている。

 # ls -aZ /
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       .
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       ..
 drwxrwxr-x  root root system_u:object_r:nfs_t:s0       #
 -rw-r--r--  root root system_u:object_r:nfs_t:s0       .autofsck
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       bin
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       boot
 drwxr-xr-x  root root system_u:object_r:tmpfs_t:s0     dev
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       etc
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       home
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       lib
 drwx------  root root system_u:object_r:nfs_t:s0       lost+found
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       media
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       mnt
 drwxr-xr-x  root root system_u:object_r:autofs_t:s0    net
 drwxrwxr-x  root root system_u:object_r:nfs_t:s0       .oldroot
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       opt
 dr-xr-xr-x  root root system_u:object_r:proc_t:s0      proc
 drwxr-x---  root root system_u:object_r:nfs_t:s0       root
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       sbin
 drwxr-xr-x  root root system_u:object_r:security_t:s0  selinux
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       .snapshot
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       srv
 drwxr-xr-x  root root system_u:object_r:sysfs_t:s0     sys
 drwxrwxrwt  root root system_u:object_r:tmp_t:s0       tmp
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       usr
 drwxr-xr-x  root root system_u:object_r:nfs_t:s0       var

さて、どうしたものか。一番安直な方法はSELinuxをdisableにすることだ。(permissiveでも動作はするが、エラーメッセージの数が異様に多くなる。)クライアントの環境でSELinuxをdisableにするには、PXEサーバ(というかファイルサーバ)側で/home/TFTPboot/f8/root/etc/selinux/configファイルを変更してから、クライアントを立ち上げる。

今のところNFSサーバのファイルのコンテキストを透過的にクライアントで見る方法が分からない。NSAの2005年のシンポジューム資料では、

Interoperability with Normal NFS

Client needs to specify whether to use SELinux or normal NFS.
Modified mount to add “selinux” option and NFS_MOUNT_SELINUX flag

Server must recognize the difference between a SELinux and normal NFS request.
SELinux NFS uses a different RPC program number (100006 instead of 100003)
Modified RPC layer to handle more than one program number

とあるが、selinuxオプションを持ったmountコマンドもなければプログラム番号100006のRPCもない。2005年から随分経っているけど。NFSではSELinuxは使えない、ってことか?

それから、クライアント側のSELinuxをpermissive以下にしただけでは解決しない。サーバ側で、ftfpdやnfsdなどのデーモンがアクセスできないファイルがあるからだ。これを修正するためにはローカル・ポリシーを追加しなければならない。現在、サーバ側で追加しているローカル・ポリシーは以下の通り。

# cat vmserver.te
module vmserver 1.0;

require {
        type home_root_t;
        type lvm_control_t;
        type tftpd_t;
        type nfsd_t;
        type nfs_t;
        class chr_file write;
        class file {getattr read};
        class dir search;
}

#============= tftpd_t ==============
allow tftpd_t home_root_t:dir search;

#============= nfsd_t ==============
allow nfsd_t lvm_control_t:chr_file write;

#============= tftpd_t ==============
allow tftpd_t nfs_t:file {getattr read};

これを「tftpのルートディレクトリが変更できない?」にあるようにシステムに登録する。

Xウィンドウが立ち上がらない

これは単純な話し。/etc/X11/xorg.confに書かれていた"ビデオカード"が「VMware」だったからだ。一旦、クライアントでログインして、/etc/X11/xorg.confをクライアントが使っている"ビデオカード"に変更すれば無事にXウィンドウが立ち上がる。

/etc/X11はsnapshotの対象ディレクトリなので、その下のファイルに変更があればクライアントごとに保存される。

その他

automountデーモンの起動のところで止まってしまうが、キーボードを叩けば次に進む。
調べてみると/etc/auto.masterの中で標準で/net、/miscがマウントの対象になるような設定になっていた。しかし、ディスククライアントの環境では/はread onlyになっているため、ディレクトリを作れずに固まっていたようだ。/etc/auto.masterを変える方法もあるが、ディスククライアントではautomount自身を使う必要がないのでサービスを停止する。

ただし、クライアントからGNOMEのサービス設定ツールなどを使っては変更できないので、PXEサーバ側の/etc/rc[0-6].dの下のンボリックリンクの名前を変える。具体的には /tftpboot/f8/root/etc/rc[0-6].dの各ディレクトリにおいて、

# mv S28autofs K72autofs

と名前を変える。

PXEによるディスクレス・クライアント

前回PXE(ピクシー)を利用したカーネルのブートまでは成功した。これをベースにディスクレス・クライアントを起動できるようにするが、その前にちょっとPXEブートについてオサライをしておく。