良くあるDiskless Clientの構成法だと思われるが,あまり情報がないので自分用メモ.
- NFSROOT でまず NFS サーバの export を
/
に mount - NFS 越しに squashfs を read-only で mount
- local で system に書き込む部分は tmpfs を利用
ということを overlayfs で squashfs に tmpfs を overlay して実現する,という話.
ざっくりと設定手順は以下のとおり.サーバ・クライアントは Debian GNU/Linux stretch を想定.
- DHCP Server の設定
- TFTP Server の設定
- NFS の設定
- root (
/
) イメージ作成 - initrd の細工
- NFSROOT の設置
DHCP Server の設定
isc-dhcp-server をインストールして,/etc/dhcp/dhcpd.conf
を以下のような感じで設定.group
を使うと記述が少なくて済む(global parameter として next-server と filename を書いても良い).
group {
next-server 10.0.0.2;
filename "/client/pxelinux.0";
host megane {
hardware ethernet xx:xx:xx:xx:xx:xx;
fixed-address 10.0.0.71;
option host-name "megane";
}
### 他のホストの設定多数
}
10.0.0.2
はTFTP ServerのIP Address,filename
でTFTPのパスを指定.
TFTP Server の設定
tftpd-hpa をインストール(tftpd は Tftpd is not suitable for use with the PXE bootloader (https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=401288) とのことで PXE boot 向けではないとのこと).
/etc/default/tftpd-hpa
にTFTP_DIRECTORY="/srv/tftp"
とあって,つまりサーバの/srv/tftp
がTFTPの/
になる.
PXE netboot 用のファイルを /srv/tftp/client
(dhcpd.conf
で指定したパス)に配置する.network boot の Debian installerを細工する形で置くが簡単.
/srv/tftp/client/pxelinux.cfg/default
に設定ファイルを置く.私の場合は以下.
default syslinux/vesamenu.c32
prompt 0
menu title Diskless clients
MENU AUTOBOOT Starting the default kernel in # seconds
include syslinux/stdmenu.cfg
label Linux 4.11.7-diskless-01+
menu label Linux 4.11.7-diskless-01+ (stretch)
menu default
timeout 100
kernel boot/vmlinuz-4.11.7-diskless-01+
append initrd=boot/initrd.img-4.11.7-diskless-01+ root=/dev/nfs nfsroot=10.0.0.2:/export/diskless,ro ip=:::::eth0:dhcp
label Linux 4.11.6-diskless-01+
menu label Linux 4.11.6-diskless-01+ (stretch)
kernel boot/vmlinuz-4.11.6-diskless-01+
append initrd=boot/initrd.img-4.11.6-diskless-01+ root=/dev/nfs nfsroot=10.0.0.2:/export/diskless,ro ip=:::::eth0:dhcp
boot/
は実際には/srv/tftp/client/boot/
が対応し,ここに後の手順で作成する root のイメージから,vmlinuz と initrd を copy する.10.0.0.2:/export/diskless
はNFSで mount する際の source.
syslinux/
以下のファイルはDebian installerから流用(若干調整が必要).
NFS Server の設定
nfs-kernel-server をインストール./export/diskless
がNFSでクライアントからマウント可能なように/etc/exports
を設定する.See man exportfs.
root (/
) イメージ作成
debootstrap
を利用する.debootstrapをインストールしておく.
sudo debootstrap --verbose --arch amd64 stretch stable http://ftp.jp.debian.org/debian/
で,stable/
以下に root (/
) のファイルが配置される.あとは
sudo chroot stable /bin/sh
で stable/
を /
とみなした shell が起動するので,そこで apt-get
で必要なパッケージを入れまくる(/etc
以下や password 等も設定).
ただし /proc
等が存在しないので,chroot の前に以下で mount しておくのが無難.
sudo mount -t devpts devpts stable/dev/pts
sudo mount -t proc proc stable/proc
sudo mount -t binfmt_misc binfmt_misc stable/proc/sys/fs/binfmt_misc
sudo mount -t sysfs sysfs stable/sys
sudo mount -t tmpfs tmpfs stable/sys/fs/cgroup
この段階で,別途 make-kpkg でカスタム linux-image package を作成し,それを chroot 環境で dpkg -i する.kernel 再構築の際,使う NIC の Driver や NFS 等は module (m)ではなくkernelに組み込んでおく(y)必要がある.また,initrd に細工する必要があるが,これはまた後ほど.
あと,ホスト毎に処理を変えたい部分については /etc/rc.local
に書いておく.ssh server の鍵をホスト毎に変えたくなる(かつ永続性も確保したい)が,これについてはたとえば以下のようなファイルを用意しておき,
[/etc/init.d/ssh-pre]
#! /bin/sh
### BEGIN INIT INFO
# Provides: ssh-pre
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# X-Start-Before: sshd
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Prepare ssh host keys
### END INIT INFO
# `hostname` に応じてどこかから鍵を /etc/ssh/ に copy する
chroot環境でinsserv ssh-pre
を実行して,起動時に実行されるようにしておく.
好みの感じに仕上がった stable/
を squashfs 化する.
sudo rsync -avzL --exclude=... stable/ stable-tmp/
sudo mksquashfs stable-tmp stable.img
上のように無駄なファイルを squashfs に入れても仕方がないので rsync で一旦フィルタする.mksquashfs
の -ef option でも exclude できるが,容量が大きくテストが煩雑になるので,rsync で exclude するようにした.手元の script では例えば以下のように exclude している.
--exclude='/tmp/*' \
--exclude='/opt/*' \
--exclude='/proc/*' \
--exclude='/sys/*' \
--exclude='/dev/*' \
--exclude='/var/cache/apt/archives/*' \
--exclude='/var/tmp/*' \
--include='/var/log/*/' \
--exclude='/var/log/*' \
--exclude='/var/log/*/*' \
initrd の細工
initrd から squashfs を mount (ro)して,それに対して tmpfs (rw) を overlay して diskless client の /
とする,という設定を行う.今から思えばhttps://github.com/chesty/overlayrootとほぼ同じ.
以下の2つのファイルを用意する.
[/usr/share/initramfs-tools/hooks/overlayroot]
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
manual_add_modules overlay
manual_add_modules squashfs
manual_add_modules loop
hooks/*
は kernel module や initrd で必要な実行ファイルを initrd に含める設定を書く箇所.overlay や squashfs が initrd に含まれるようにする.
$1
で処理を分岐している部分は,hook ファイルの起動順を決めるための部分で,もし他のhookファイルに依存する場合はPREREQ
変数にその名前を記載しておく (see man initramfs-tools
).
[/usr/share/initramfs-tools/scripts/init-bottom/overlayroot]
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
modprobe overlay
modprobe squashfs
modprobe loop
mkdir -p /over
mount -t tmpfs -o size=1g tmpfs /over
mkdir -p /over/rw
mkdir -p /over/ro
mkdir -p /over/sq
mkdir -p /over/work
mount -o remount,ro /root
ro=/over/ro
file=`ls /root/*.img | tail -1`
if test -f $file ; then
mount -t squashfs $file /over/sq
ro=/over/sq
fi
mount --move /root /over/ro
mount -t overlay -o upperdir=/over/rw,lowerdir=$ro,workdir=/over/work overlay /root
# Create files for /var/log/ .
# See /var/lib/dpkg/info/base-files.postinst .
touch /root/var/log/wtmp /root/var/log/btmp /root/var/log/lastlog
mkdir -p /root/over
mount --move /over /root/over
if command -v nuke >/dev/null; then
nuke /over
fi
scripts/init-bottom/overlayroot
は /usr/share/initramfs-tools/init
(/init
)から起動されることになるファイル.scripts/init-bottom/udev
も存在すると思われるが,それより先に実行される(ファイル名で sort した順っぽい).PREREQ
についてはhookと同じ.
やっていることは以下のとおり.
- 必要な kernel module の load (modprobe)
- tmpfs を
/over/
に mount - squashfs
/root/*.img
の1つ(最新のものになるように細工したりする)を/over/sq/
に mount.ここで/root/
は NFS でマウントしている10.0.0.2:/export/diskless/
の mount point /root/
を/over/ro/
に移動- overlayfs を使って squashfs (ro)の上に tmpfs (rw)を重ねて,
/root/
に mount - 起動時に必要なファイルを準備 (touch の箇所)
- 最終的に
/root/
が/
になるが,その際に/over/
が参照可能なように,/over/
を/root/over/
に移動 nuke
(rm -rf
) で/over/
(mount point)を削除
あとは chroot 環境で以下で initrd を作り直す.initrd は /boot/initrd*
に生成される.
# update-initramfs -u -k all
NFSROOT の設置
NFS server の /export/diskless
に squashfs のイメージ stable.img
を置く.また,/export/diskless/dev/
等は一時的に mount point となるので,以下を mkdir しておく(中身は空).
/export/diskless/dev/
/export/diskless/run/
/export/diskless/tmp/
テスト
いちいち実機でテストするのは面倒なので同一ネットワーク内の別ホストで kvm を用いてテストすると簡便.たとえば以下(DHCP サーバに接続するため要ブリッジの設定).
% qemu-img create -f qcow2 debian.qcow 20G
% kvm -vnc :0 -hda debian.qcow -smp 8 -net bridge -net nic,model=e1000 -m 8000 &
% vncviewer :0 &
追記 (2017/08/03)
上記作業だと
- debootstrap で作った /
- そこから無駄なファイルを除外するための rsync した / (これは削除可能)
- そこから作った squashfs
と,かなり容量が必要で,もとの / は upgrade 等のために必要で保存しておく必要もあるので,いっそのこと作った squashfs + overlayfs で 1. の代用にしようとしたのだが,
- 通常の filesystem の timestamp は nanosecond 精度(たぶん)
- Squashfs の timestamp は second 精度 (ctime のみ保持)
ということで,1. を 3. +overlayfsに rsync -a しようとしたら,大量の同一ファイルの copy が発生してしまった.
% ls -l --full-time stable/etc/hosts stable-20170731-over/etc/hosts
-rw-r--r-- 1 root root 109 2017-06-20 13:05:10.000000000 +0900 stable-overlay/etc/hosts
-rw-r--r-- 1 root root 109 2017-06-20 13:05:10.714742658 +0900 stable/etc/hosts
% stat stable/etc/hosts stable-20170731-over/etc/hosts
File: stable/etc/hosts
Size: 109 Blocks: 8 IO Block: 4096 regular file
Device: 2ch/44d Inode: 11835294 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-08-04 10:32:23.151773243 +0900
Modify: 2017-06-20 13:05:10.714742658 +0900
Change: 2017-06-20 13:05:10.714742658 +0900
Birth: -
File: stable-20170731-over/etc/hosts
Size: 109 Blocks: 1 IO Block: 1024 regular file
Device: 702h/1794d Inode: 1613 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-06-20 13:05:10.000000000 +0900
Modify: 2017-06-20 13:05:10.000000000 +0900
Change: 2017-06-20 13:05:10.000000000 +0900
Birth: -
みたいな感じで,stable/ は通常の 1. filesystem 上のファイル,stable-overlay/ は 3. のファイル.
rsync で dry-run してみると,以下のとおり一応同じ timestamp だとはみなしてもらえる.
% rsync -avz -n stable/etc/hosts stable-20170731-over/etc/hosts
sending incremental file list
sent 44 bytes received 12 bytes 112.00 bytes/sec
total size is 109 speedup is 1.95 (DRY RUN)
で,実際に rsync しようとすると以下のように怒られる.
% rsync -avz stable/etc/hosts stable-20170731-over/etc/hosts
sending incremental file list
rsync: failed to set times on "/somewhere/stable-20170731-over/etc/hosts": Operation not permitted (1)
sent 44 bytes received 141 bytes 370.00 bytes/sec
total size is 109 speedup is 0.59
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2]
実際に copy はしないが,timestamp の update を試みていることが分かる(user権限でrsyncしたので,permission が deny された).実際に timestamp が update されると,overlayfs の upper layer 側にファイルが作成されることになる.
ということで 1. を完全に削除(つまり 3. のうえに rsync しない)しようか思案中.