やるきなし

2017/06/28 11:30 / nfsroot + squashfs + overlayfs

良くあるDiskless Clientの構成法だと思われるが,あまり情報がないので自分用メモ.

ということを overlayfs で squashfs に tmpfs を overlay して実現する,という話.

ざっくりと設定手順は以下のとおり.サーバ・クライアントは Debian GNU/Linux stretch を想定.

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-hpaTFTP_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と同じ.

やっていることは以下のとおり.

あとは chroot 環境で以下で initrd を作り直す.initrd は /boot/initrd*に生成される.

# update-initramfs -u -k all

NFSROOT の設置

NFS server の /export/diskless に squashfs のイメージ stable.img を置く.また,/export/diskless/dev/ 等は一時的に mount point となるので,以下を mkdir しておく(中身は空).

テスト

いちいち実機でテストするのは面倒なので同一ネットワーク内の別ホストで 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)

上記作業だと

と,かなり容量が必要で,もとの / は upgrade 等のために必要で保存しておく必要もあるので,いっそのこと作った squashfs + overlayfs で (1) の代用にしようとしたのだが,

ということで,(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 しない)しようか思案中.

Related articles