Please feel free to link to this page.
TOP PDF ver.
Powered by SmartDoc

Diskless Linux by PXELinux or GRUB

初版 2002/04/16 修正 2006/01/17
Shohei NOBUHARA
DisklessなLinuxマシンをつくる.起動スクリプトなど,何カ所かdebianであることに依存している点があるかもしれない.今のところ動作実績はクライアント35台・3年弱(2002/04〜2005/02).

目次

1 目的

DisklessなLinuxマシンをつくる.ここでdisklessであるとは,

ことを指すことが多いが,その目的・メリットは

  1. ネットワークブート型:多数のPCを集中管理できる(thin client用)
  2. CD / FDブート型:手軽にOSを入れ替えることができる(gateway, router,あるいはデモやレスキュー用ディスクなど)

のように,

ハードディスクに収まっていたはずのOSをどこから用意するか

ということの実現方法に応じて異なる.

ここでは前者の"集中管理"を目的として,ネットワークブート型のdiskless linuxを実現することにし,以降では

ホスト
Linuxシステムに必要なファイルを提供する
クライアント
ホストが提供するファイルをマウントし,ブートする

と呼ぶことにする.

なお後者のCD / FDブートの選択肢としては,Knoppix(日本語版)が

  1. ハードウェアの自動認識
  2. 日本語化

などの点で優れている.

2 手段

いくつか選択肢がある.

今回は,以下の手順で起動させる方法を説明する.

  1. カーネルのダウンロード
  2. カーネルブート時にnftrootとip=dhcpのオプションを与え,NFS rootマウントを行うと同時に,IPアドレス関係の設定を(カーネルが)再度DHCPで取得し自動設定させる

以下では,

tftp/nfs/dhcpサーバ
IP=192.168.0.1, exportするディレクトリは/tftpboot/Linux
クライアント
MAC=12:34:56:78:9a:bc, IP=192.168.0.100

として説明を行う(tftp/nfs/dhcpサーバは別々でもよい).

3 nfsroot 用ディレクトリの用意

基本的方針は,

  1. 既に動いているLinuxシステムのルート以下をコピー
  2. ホスト固有の設定部分に対する対策

という流れ.この節ではサーバのファイルではなく,クライアント用に用意したファイル・ディレクトリを操作している点に注意!(chrootして作業すると,ミスの心配が無くていいかもしれない)

3.1 ルート以下のコピー

今回は/tftpboot/LinuxをクライアントがNFSでマウントするので,ここにホストの/以下をコピーする(1)

# cd /tftpboot/Linux
# cp -a /bin /dev /etc /lib /sbin /usr /var .

次に空のディレクトリを作る

# cd /tftpboot/Linux
# mkdir home mnt proc ram root boot sys

ramは,後にramディスクをマウントするために作っておく.

なおディスク容量の都合で/usr/local/tftpbootなど,どこか別の場所を使いたい場合も,mountのbindオプションを使用して/tftpboot以下にマップしておくと何かと間違いが少なくてすむだろう.コマンドラインからは

# mount -o bind /usr/local/tftpboot /tftpboot

とし,起動時に自動的にマウントさせるためにはfstabに以下のように書く.

リスト 1 /etc/fstab
/usr/local/tftpboot /tftpboot auto bind 0 0
  1. devfs, udevは使わないものとする.

3.2 nfsroot向け設定

debianの場合,基本的に

/etc
静的な設定ファイル
/var, /tmp
実行時に変化するファイル

となっている.

3.2.1 /etc

DHCPでカーネル自身にIP関係の設定をさせるので,一番やっかいなネットワーク関係のファイルは必要ない.よって,

hostname
消去
hosts
localhostのみ残す
リスト 2 hosts
127.0.0.1       localhost

::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
network/interfaces
loのみ残す
リスト 3 network/interfaces
# iface lo inet loopback
auto lo
iface lo inet loopback
fstab
全てnfsrootでまかなうので,ほぼ空になる
リスト 4 fstab
# <file system> <mount point> <type> <options> <dump> <pass>
proc                            /proc   proc    defaults        0       0
mtab

mtabには,現在のマウント状況が保存される.つまり,ホスト毎のマウント状況に応じて異なる内容となるので,本来は共有できない.そこでホントはやってはいけないかもしれないが,取り合えず

# ln -sf /proc/mounts mtab

としてしまう.

network/ifstate

ifstateには,ネットワークの状況が保存される[7]./dev/nullへのリンクでもOKらしいが,

# ln -sf /ram/ifstate network/ifstate

としてしまう.

3.2.2 /var

varには2種類のデータがある.

共有させたいファイル
パッケージ情報など
個別に用意すべきファイル
local lock log mail run spool tmp yp lib/nfsなど

この判断に基づいて,先にコピーしたうちの「個別にすべきもの」を退避させる.

# mkdir /tftpboot/Linux/var.ro
# mkdir /tftpboot/Linux/var.ro/lib
# cd /tftpboot/Linux/var
# mv local lock log mail run spool tmp yp ../var.ro/
# mv lib/nfs ../var.ro/lib/nfs

これらは後にramディスクに書き込むようにするので,リンクしておく

# cd /tftpboot/Linux/var
# for i in local lock log mail run spool tmp yp; do ln -s \
    /ram/var/${i}; done
# ln -s /ram/var/lib/nfs lib

ここで,"/ram"とする点に注意./tftpboot/Linuxにchrootしたと想定したときに,ちゃんとリンクをたどれるようにするため.

3.2.3 /tmp

/ram/tmpへのリンクとする.

# cd /tftpboot/Linux
# ln -s /ram/tmp

別に/tmp自身をramディスクとしてもいいが,/tmpは起動時にクリーンアップされるので注意.

3.2.4 /ram

ここにramディスクをマウントし,個別に用意すべきものを入れる.このためのスクリプトとして

リスト 5 etc/init.d/NFSROOT
## tmpfs を使用する場合
mount -t tmpfs tmpfs /ram -o size=128m

## 従来の /dev/ram を使用する場合
# /sbin/mke2fs /dev/ram0
# /bin/mount /dev/ram0 /ram

# var
mkdir /ram/var/
cp -a /var.ro/* /ram/var

# tmp
mkdir /ram/tmp/
chmod 1777 /ram/tmp

を/tftpboot/Linux/etc/init.d/NFSROOTとして作成する(2)

# cd /tftpboot/Linux/etc/init.d/
# vi NFSROOT
# chmod +x NFSROOT

次にetc/rcS.dを眺めると,S35mountall.shでfstabの内容をマウントしているようなので,この直前にNFSROOTスクリプトを実行するようにする.

# cd /tftpboot/Linux/etc/rcS.d
# ln -s ../init.d/NFSROOT S34NFSROOT

そしてmountall.shの内容を若干変更する(3)

リスト 6 diff etc/init.d/mountall.sh etc/init.d/mountall.sh.old
--- mountall.sh.old     Thu Nov  1 18:05:41 2001
+++ mountall.sh         Mon Apr 15 22:33:38 2002
@@ -11,8 +11,9 @@
 # about this. So we mount "proc" filesystems without -v.
 #
 [ "$VERBOSE" != no ] && echo "Mounting local filesystems..."
-mount -avt nonfs,nosmbfs,noncpfs,noproc
-mount -at proc
+#mount -avt nonfs,nosmbfs,noncpfs,noproc
+#mount -at proc
+mount -av
 
 #
 # We might have mounted something over /dev, see if /dev/initctl is there.
  1. 従来の/dev/ramを使用しても問題はないが,kernel 2.4以降を使用するのならメモリの使用効率,使い勝手など,多くの面でtmpfsのほうが便利.なお,/dev/ramを使用する場合,通常は/dev/ram0をinitrdが使用し,ユーザは/dev/ram1以降を使用することを考えて,あえて/dev/ram0を使用し,/dev/ram1以降を空けておいたほうがいいと思う.
  2. Debian(sarge)では不要

3.2.5 カーネル

クライアントがブートするためのカーネルを用意する.後にクライアント自身で再コンパイルすることも考えて,/tftpboot/Linux/usr/srcにソースを展開して作業する.

# cd /tftpboot/Linux/usr/src
# rm linux
# wget \
    http://ring.astem.or.jp/archives/linux/kernel.org/kernel/v2.4/linux-2.4.25.tar.bz2 \
# tar jxvf linux-2.4.25.tar.bz2
# ln -s linux-2.4.25 linux
# cd linux

次にカーネル設定をする.

# make xconfig

クライアント自身に必要なものの他に,

リスト 7 .config (一部)
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_IP_PNP_BOOTP=y
CONFIG_IP_PNP_RARP=y
CONFIG_TMPFS=y
CONFIG_RAMFS=y
CONFIG_ROOT_NFS=y

は必要.基本的にモジュールとしてコンパイルすることは避けること(4).起動時に必要なものをモジュールにしてしまうと,「鶏と卵」状態になるので.こうするとbzImageがフロッピーに入らないくらい大きくなるが,bzImageはtftpでネットワークからダウンロードするので気にしなくてよい.

コンパイルし,必要なものをコピー

# make-kpkg --revision nfsroot.20020415.0 kernel_image
# cp arch/i386/boot/bzImage /tftpboot/Linux/boot/vmlinuz-2.4.25
# cp System.map /tftpboot/Linux/boot/System.map-2.4.25
# cd /tftpboot/Linux
# ln -s /boot/vmlinuz-2.4.25 vmlinuz

ここでも,最後のシンボリックリンクは/tftpboot/Linuxにchrootしたときにつじつまが合うように注意すること.また,モジュールも作ったのならそれもコピーしておくこと.(5)

  1. 特にネットワークとファイルシステム関係
  2. make-kpkgはdebianの場合.ふつうはmake dep; make bzImage.

4 サーバの設定

用意したnfsroot用のディレクトリをexportするためのサーバを設定する.

まずtftpサーバ.使用したのはtftpd-hpaパッケージのもの.特にPXEの場合,tftpパッケージのものでは不具合があるらしい.

リスト 8 /etc/inetd.conf (一部)
tftp dgram udp wait root /usr/sbin/in.tftpd /usr/sbin/in.tftpd -s /tftpboot

次にnfsサーバ.使用したのはnfs-kernel-serverパッケージのもの.

リスト 9 /etc/exports (一部)
/tftpboot 192.168.0.0/255.255.255.0(rw,no_root_squash)

最後にdhcpサーバ.使用したのはdhcp3-serverパッケージのもの.

リスト 10 /etc/dhcp3/dhcpd.conf (一部)
# global params
option domain-name-servers 192.168.0.1;
option domain-name "yourdomain";
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.0.255;
option routers 192.168.0.1;

# for static assign
group {
  use-host-decl-names on;

  host client1 {
    hardware ethernet 12:34:56:78:9a:bc;
    fixed-address client1.yourdomain;
  }
}

ただし,"client1.yourdomain"がDNSで参照できるようにしておくこと.

あとは各デーモンのリスタートorリロード(6).面倒なときはinitスクリプトで.

# /etc/init.d/inetd reload
# /etc/init.d/nfs-kernel-server reload
# /etc/init.d/dhcp3-serve restart
  1. ISC製のdhcpd(ver 3)はSIGHUPでリロードしてくれない. manに理由が書いてある

5 PXEブート環境の作成

基本的にsyslinuxに含まれる/usr/share/doc/syslinux/pxelinux.docに沿って作業を行う.

5.1 tftpサーバの設定

まずtftpサーバとなるマシンに,syslinuxパッケージをインストールする(7)

あとは/tftpboot以下に必要なものを用意する.

# cp /usr/lib/syslinux/pxelinux.0 /tftpboot
# mkdir /tftpboot/pxelinux.cfg
# vi /tftpboot/pxelinux.cfg/default
リスト 11 /tftpboot/pxelinux.cfg/default
DEFAULT linux

LABEL linux
  KERNEL Linux/boot/vmlinuz-2.4.25
  APPEND root=/dev/nfs nfsroot=192.168.0.1:/tftpboot/Linux ip=dhcp
  IPAPPEND 0

ここで,KERNELで指定するファイル名は,tftpdが/tftpbootにchrootしていることに注意して,そこからの相対パスとする.どうにもうまくいかなければ,/tftpboot直下にコピーして,これを指定すればよい.

また,pxelinuxにはIPAPPENDという便利な機能があるが,ここではホスト名を自動設定するためにip=dhcpとした.

  1. pxelinuxはsyslinuxパッケージに含まれている

5.2 DHCPサーバの設定

dhcpd.conf (ISC dhcpd ver 3用)を以下のように修正し,再起動させる.

リスト 12 dhcpd.conf
# global params
option domain-name-servers 192.168.0.1;
option domain-name "yourdomain";
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.0.255;
option routers 192.168.0.1;

allow booting;
allow bootp;

# for static assign

group {
  # for hostname
  use-host-decl-names on;

  option vendor-class-identifier "PXEClient";

  # この行は不要かも
option vendor-encapsulated-options \
    09:0f:80:00:0c:4e:65:74:77:6f:72:6b:20:62:6f:6f:74:0a:07:00:50:72:6f:6d:70:74:06:01:02:08:03:80:00:00:47:04:80:00:00:00:ff; \

  next-server 192.168.0.1;
  filename "pxelinux.0";

  host client1 {
    hardware ethernet 12:34:56:78:9a:bc;
    fixed-address client1.yourdomain;
  }
}

vendor-encapsulated-optionsの値が非常に長い文字列になっているが,改行せずに1行で指定すること.

6 PXEが使えない場合のブートフロッピーの作成

クライアントがブートするためのフロッピーをつくる.

6.1 GRUBのインストールとブート可能イメージの作成

GNU GRUBをダウンロード,コンパイル,インストール.このとき,ネットワークブート対応のフロッピーを作る点に注意.

debianの場合(作業時のgrubのバージョンは0.91-4),

$ apt-get source grub
$ cd grub-0.91
$ dpkg-buildpackage -us -uc -rfakeroot
$ su
# dpkg -i ../grub_0.91-4_i386.deb

でgrub自身をインストールした後,

$ make clean
$ ./configure --enable-diskless --enable-rtl8139

でディスクレスブート可能なstage1とstage2を作る.たいていのカードには対応しているので,詳しくはnetboot/README.netbootを読むこと.

6.1.1 注意

以下の作業では,コマンドとして使用するgrubと,実際にフロッピーに書き込むstage1とstage2をコンパイルしたgrubのバージョンをそろえること.要するに,ソースツリーの./grub/grubを直接起動する.

コマンドはパッケージからインストールしたものを使用し,stage1とstage2は別ソースから作ったりすると,起動できない可能性がある.

6.1.2 1000Base-TのNICへの対応

残念ながらgrubのバージョン1系列では,1000Base-TのNICには対応していない.しかし最近はオンボードのNICがIntelのEtherExpress Pro/1000アダプタであることも多い.このような場合はオリジナルのgrubにresumo [8]パッチをあてるとconfigureで

が使えるようになる.他にもいくつかパッチがgrubのメーリングリストなどに流れたことがあるが,結局このパッチが一番安定している.

6.2 ブートフロッピーの作成

まず,空のフロッピーをフォーマット(この場合はmtoolsを使ってvfat形式).

# superformat -B /dev/fd0 hd

これをマウントし,必要なファイルをコピー.

# mount /dev/fd0 /mnt
# cd /mnt
# mkdir -p boot/grub
# cd boot/grub
# cp ${YOUR_GRUB_SRC_PATH}/stage1/stage1 .
# cp ${YOUR_GRUB_SRC_PATH}/stage2/stage2 .
# cp ${YOUR_GRUB_SRC_PATH}/docs/menu.lst .

このmenu.lstはサンプルなので,これを以下のように編集

リスト 13 menu.lst
# Boot automatically after 30 secs.
timeout 30

# By default, boot the first entry.
default 0

# Fallback to the second entry.
fallback 1

# For installing GRUB into the hard disk
title Linux {tftp,nfsroot,dhcp}@yourhost
dhcp
# override tftp server address if DHCP server is not tftp server
# tftpserver 192.168.0.2
root   (nd)
kernel /Linux/vmlinuz root=/dev/nfs nfsroot=192.168.0.1:/tftpboot/Linux \
    ip=dhcp

tftpをdhcpとは別のサーバでやっているのなら,

# override tftp server address if DHCP server is not tftp server
tftpserver 192.168.0.2

のように指定しておく.inetd.confでtftpサーバからアクセスできる範囲を/tftpboot以下にしたので,パス名が/Linuxから始まることに注意.

最後にブートできるようにフロッピーに書き込む.

# grub
grub> root (fd0)
grub> setup (fd0)
grub> quit

あとは

# fdflush
# umount /mnt

で,取り出して終了.

7 クライアントの起動

以上で準備は整ったはずなので,PXEあるいはフロッピーから起動してみる.起動しないときは,フロッピーから起動してメニューが表示された段階で「c」キーを押し,コンソールに切り替えて,

  1. まず

    >dhcp
    

    としたときに,DHCPでIPが取得できているかどうか.

  2. 続いて

    >root (nd)
    >kernel /Linux/vmlinuz root=/dev/nfs \
        nfsroot=192.168.0.1:/tftpboot/Linux ip=dhcp
    

    としたときに,カーネルがダウンロードされるかどうか.ただしtftpサーバがdhcpサーバと別なら,

    >tftpserver 192.168.0.2
    >root (nd)
    >kernel /Linux/vmlinuz root=/dev/nfs \
        nfsroot=192.168.0.1:/tftpboot/Linux ip=dhcp
    

    のようにアドレスを指定すること.

  3. 最後に

    >boot
    

    としてブートが始まるかどうか.

をチェックする.これらは順に

  1. GRUBがNICを認識できていないor DHCPサーバの設定ミス
  2. tftpサーバの設定ミス
  3. クライアント用に用意したLinux環境の設定ミスor NFSサーバの設定ミス
    1. 起動時に「Permission denied」が表示されるようなら,no_root_squashが機能していない./etc/exportsで,/tftpbootを複数のhostやnetgroup向けに異なるオプションを混在させて設定していると,こういう症状が起きた.シンプルに/tftpbootを1つのnetgroupに対してno_root_squashでエクスポートすると解決した.

が原因として考えられる.カーネルの起動メッセージをよく観察しましょう.

7.1 起動後

起動した後は,

作っておいたカーネルパッケージのインストール
さっきはコピーしただけだったので,正式にインストールする.
# dpkg -i \
    /usr/src/kernel-image-2.4.25_nfsroot.20020415.0_i386.deb
必要な追加モジュールの作成・インストール
起動してしまえば(NFSルートが正しく得られれば)カーネルモジュールも自由に使える.よってALSAも動く

など適宜必要なことを.

8 おまけ

8.1 Wake On LAN

Wake on LAN (WoL)対応のマザーを使用している場合,LAN上の別のマシン,例えば今回の場合はdhcpサーバなどからマジックパケットを送信することで,リモートから電源を入れることが可能になる.これは特に,PCクラスタを運用しているときに便利.

マジックパケットの送信には,wakeonlanというコマンド(debianパッケージ名もwakeonlan)を使用する.使用方法はシンプルで,

# wakeonlan 01:02:03:04:05:06 ...

のように,起動したいマシンのMACアドレスを必要な数だけ指定すればよい.

8.2 X端末として使うには

クライアントを,ブート後はただのX端末として使用するようにするには,xdmcp接続を設定すればよい.

まず接続先となるホストのxdmが,xdmcp接続を受け付けるようにする.

リスト 14 接続先の/etc/X11/xdm/xdm-config
! コメントアウトする
! DisplayManager.requestPort:   0
リスト 15 接続先の/etc/X11/xdm/Xaccess
*                                       #any host can get a login window

つぎにクライアントのxdmの起動スクリプトを以下のようにする(8)

リスト 16 クライアントの/etc/init.d/xdm
#!/bin/sh

X=/usr/X11R6/bin/X
H=接続先のホスト名
D=":0 vt7"
PID=/var/run/xdm-remote.pid
EXEC="$X -- $D -query $H"

if [ ! -x $X ]; then
        exit 0
fi

case "$1" in
    start | restart | force-reload | reload)
        start-stop-daemon --start -b -m --pid $PID --exec ${EXEC}
        ;;
    stop)
         start-stop-daemon --stop --pid $PID
        ;;

    *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac

これで「X端末もどき」ができあがる.

  1. debianの場合.かなり適当なスクリプト.

8.3 dnsmasqを使う場合

ISCのdhcpdではなく,dnsmasqを使う場合は,/etc/dnsmasq.confに

dhcp-boot=pxelinux.0,tftp-server-name,192.168.0.1

という行を追加する.「tftp-server-name」は,tftpサーバのドメイン名.

8.4 パフォーマンスの改善

まずクライアントがnfsrootマウントする際のマウントオプションを以下のようにして設定してみる.

... root=/dev/nfs nfsroot=192.168.0.1:/tftpboot/Linux,rsize=8192,wsize=8192 \
    ip=dhcp

もちろん,これにあわせてカーネルをコンパイルしておく必要がある(9)

またクライアントの数が多い場合,サーバ側のnfsデーモンの数を以下のようにして増やしておく.

リスト 17 /etc/default/nfs-kernel-server
# Number of servers to start up
RPCNFSDCOUNT=16

# Options for rpc.mountd
RPCMOUNTDOPTS=

ただし[6]によると,やみくもに増やせばいいというものではないらしい.

  1. NFSv3やTCPオプションを指定することは可能だが,運用してみると不安定だった.nfsrootの場合と,普通にマウントする場合で差異があるのだろうか?

9 既知の不具合

現在判明している不具合について.

9.1 too many symlinks?

[症状]

[回避策]

ただし,デーモンをリスタートなんかしたりすると,nfsホスト側のプロセスが普通にchrootで再起動してしまうので,とんでもないことになる可能性が高い.要注意.

参考文献

[1]H. Peter Anvin. SYSLINUX - The Easy-to-use Linux Bootloader. http://syslinux.zytor.com/
[2]Yoshinori K. Okuji. GNU GRUB - GNU Project - Free Software Foundation (FSF). http://www.gnu.org/software/grub/
[3]James McKenzie and Chris Lightfoot. RPLD - an RPL/RIPL remote boot server. http://gimel.esc.cam.ac.uk/james/rpld/index.html
[4]Ken Yap and Markus Gutschke. EtherBoot.org. http://etherboot.sourceforge.net/
[5]Gero Kuhlmann. Netboot. http://netboot.sourceforge.net/
[6]Tavis Barr et al. Linux NFS-HOWTO. http://www.linux.or.jp/JF/JFdocs/NFS-HOWTO/
[7]Debian project. Parts of Debian needing help. http://www.nl.debian.org/devel/todo/index.html
[8]NTT Corporation. Resumo. http://resumo.sourceforge.net/