FreeBSDのいにしえのRoot on ZFSをBoot Environmentに変換する
FreeBSD 11あたりで、Solaris由来のBoot Environmentという仕組みがシステムに取り込まれた。ZFSのスナップショットとクローンを使って、起動システムを簡単に複製したり切り替えたりするための仕組みで、OS更新時やカーネル関連の実験の時に有用である。
Boot Environmentを使うにはZFSのデータセットを、Boot Environment準拠の構造にする必要がある1)。bsdinstall以後のインストーラによるRoot on ZFS環境は、当然ながらBoot Environment準拠した構成となる。
大昔にRoot on ZFS環境を手動で作っている場合、例によってBoot Environment構成とは異なるため変換の必要がある。変換といっても、Boot Environmentなデータセットを作り、既存のファイルを全コピーで行けるハズ……ということでやってみた。
ちなみにBoot Environmentだろうとそうでなかろうと、システムの動作には一切影響しないので無理にBE化する必要もなかったりする。
試した環境
- FreeBSD 12.2-RELEASE
ファイルシステム構成の確認
FreeBSD 8時代のいにしえのRoot on ZFSなデータセット構成と、現在のBoot Environmentな構成を下表にまとめる。
従来のRoot on ZFS構成 | Boot Environment構成 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
NAME | mount point | can mount | comp- ression | NAME | mount point | can mount | comp- ression | atime | exec | set uid |
zroot | none | on | off | zroot | /zroot | on | lz4 (L) | off (L) | on | on |
zroot/ROOT | / | on | lz4 | zroot/ROOT | none | on | lz4 (i) | off (i) | on | on |
zroot/ROOT/default | / | noauto (L) | lz4 (i) | off (i) | on | on | ||||
zroot/ROOT/tmp | /tmp | on | lz4 | zroot/tmp | /tmp | on | lz4 (i) | off (i) | on (L) | off (L) |
zroot/ROOT/usr | /usr | on | lz4 | zroot/usr | /usr | off (L) | lz4 (i) | off (i) | on | on |
zroot/ROOT/usr/include | /usr/include | on | gzip-9 | |||||||
(home用の別プールをマウント) | /usr/home | zroot/usr/home | /usr/home | on | lz4 (i) | off (i) | on | on | ||
zroot/ROOT/usr/ports | /usr/ports | on | gzip-9 | zroot/usr/ports | /usr/ports | on | lz4 (i) | off (i) | on | off (L) |
zroot/ROOT/usr/ports/distfiles | /usr/ports/distfiles | on | off | |||||||
zroot/ROOT/usr/ports/packages | /usr/ports/packages | on | off | |||||||
zroot/ROOT/usr/src | /usr/src | on | gzip-9 | zroot/usr/src | /usr/src | on | lz4 (i) | off (i) | on | on |
zroot/ROOT/var | /var | on | off | zroot/var | /var | off (L) | lz4 (i) | off (i) | on | on |
zroot/var/audit | /var/audit | on | lz4 (i) | off (i) | off (L) | off (L) | ||||
zroot/ROOT/var/crash | /var/crash | on | gzip-9 | zroot/var/crash | /var/crash | on | lz4 (i) | off (i) | off (L) | off (L) |
zroot/ROOT/var/db | /var/db | on | off | |||||||
zroot/ROOT/var/db/pkg | /var/db/pkg | on | gzip-9 | |||||||
zroot/ROOT/var/empty | /var/empty | on | off | |||||||
zroot/ROOT/var/log | /var/log | on | gzip-9 | zroot/var/log | /var/log | on | lz4 (i) | off (i) | off (L) | off (L) |
zroot/ROOT/var/mail | /var/mail | on | gzip-9 | zroot/var/mail | /var/mail | on | lz4 (i) | on (L) | on | on |
zroot/ROOT/var/run | /var/run | on | off | |||||||
zroot/ROOT/var/tmp | /var/tmp | on | off | zroot/var/tmp | /var/tmp | on | lz4 (i) | off (i) | on | off (L) |
- プロパティの凡例
- (L) … SOURCEがlocal(自ファイルシステムで明示的に設定された値)
- (i) … SOURCEがinherited from … (親ファイルシステムから継承した値)
- 無印 … SOURCEがdefault
- “従来のRoot on ZFS構成”の方は省略
- 灰色の項目は当方の独自構成
いにしえ構成とBoot Environment構成のzroot/ROOT
は名前が一緒なだけで、意味するところは明確に違うので混同に注意。前者は“ルートディレクトリのルート”、後者は“Boot Environmentでルートディレクトリ(/
)となるデータセット置き場”のニュアンスである。
なお、いにしえ構成でROOT
を噛ませているのは「トップレベルにデータセットを1つ作り、その下に他のデータセットを置くべし」という教えに従ったため。トップレベルに直接ファイルを置いたり、複数のファイルシステムがあると、スナップショットやzfs send/redvの取り扱いが少し面倒なため、こうした言い伝えがある。
今回のBoot Environemt化にあたっては、教えを無視して公式構成に準拠することとする。
Boot Environmentでは、zroot/ROOT
以下に複数のシステム用データセットが格納でき、起動スプラッシュで選択した環境がファイルシステムの/
となってシステムが起動する。標準のシステムはzroot/ROOT/default
となる。カーネル起動後、zroot/usr
やzroot/tmp
などのzroot/ROOT
と同列にあるデータセットがmountpointプロパティに従いマウントされることで、OSが想定するファイルシステム構造が出来上がるという仕掛けになっている。
注意すべきはcanmount
プロパティがno
のデータセットたち。FreeBSD 12.2-RELEASE時点で該当するのは、zroot/usr
とzroot/var
である。
子データセットのcanmount
はon
なのに親がoff
というのは一見不思議だが、なんて事はない、親は子供のための単なる“コンテナ”としての存在に過ぎない。逆に言うと、/usr
の実体は各々のBoot Environmentに存在するため、zroot/usr
がマウントされると都合が悪い。下表のように、同じ/usr
でもパスによって格納先のデータセットが変わってくるため、移行作業では気を付ける必要がある。
ファイルパス | ZFS的格納場所 |
---|---|
/usr/bin/sh | zroot/ROOT/defaultデータセット直下のusrディレクトリ内のbinディレクトリのshファイル |
/usr/ports/UPDATING | zroot/usr/portsデータセット直下に配置されているUPDATINGファイル |
以上をふまえ、下記の手順で移行を試みる。
- 独自データセットの解消
- Boot Environment環境にないデータセットの解消
- Boot Environment環境の構築
- 既存環境とBE環境を差し替える
要はzpoolのデータセット構成をBoot Environment構成に合わせ、適切なデータセットに適切にファイルを移動すれば晴れてBE環境になる、ハズ。
手順
独自データセットの解消
Boot Environment構成になく、かつ旧Root on ZFSの公式構成にもない、自分が勝手に切り分けたデータセットを解消する。上表でいう所のzroot/ROOT/usr/include
である。
解消と大層な言葉を使ってみたものの、単にincludeがzroot/usrの1フォルダとなるようファイルをコピーするだけ。
# rsync -aX /usr/include/ /usr/include2 # diff -r /usr/include /usr/include2 # zfs destroy zroot/ROOT/usr/include # mv /usr/include2 /usr/include
rsyncを使っているのは念のため拡張属性もコピーしときたいから。特に気にしなければcpでおkだし、基本的には気にする必要もない。
/usr/homeも独自構成ではあるが、BE環境には影響しないので特に何もしない。
Boot Environment環境にないデータセットの解消
Boot Environment構成にない旧Root on ZFS構成のデータセットを解消する。作業内容は独自データセットの解消と同じで、対象は次のとおり。
- zroot/ROOT/usr/ports/distfiles
- zroot/ROOT/usr/ports/packages
- zroot/ROOT/var/db
- zroot/ROOT/var/db/pkg
- zroot/ROOT/var/empty
- zroot/ROOT/var/run
稼働中のシステムで/var
をこねくり回すのは怖いので、FreeBSDのインストーラのシェル(Live環境)で作業する方が安全だろう。というわけで、今後のコマンド例はLive環境を想定している。
普通にzpool importするとLive環境の/
に上書きマウントされてしまうので、-Rオプションでルートを指定してマウントすべし。
# mkdir /tmp/altroot # zpool import -R /tmp/altroot zroot # LD_LIBRARY_PATH=/tmp/altroot/usr/local/lib /tmp/altroot/usr/local/bin/rsync -aX ... # zfs destroy ...
この作業を終えた時点で、zrootのデータセット構成がBoot Environemtの構成と同一になっていることが重要。
旧Root on ZFS環境のマウントポイントの補正
後続の処理のため、旧Root on ZFS環境の各マウントポイントをzroot/ROOT
から継承した設定に変更する。
具体的にはmountpointのSOURCEがinherited from zroot/ROOT/~
となるようにする。
# zfs inherit mountpoint zroot/ROOT/tmp zroot/ROOT/usr zroot/ROOT/var
旧Root on ZFS環境はzroot/ROOT以下に全てが収まっているので、これをいったんROOT2として退避する。こうすれば旧環境を温存したままBE環境を構築できる算段。
一旦プールをエクスポートし、自動マウントはせずに再度インポートする。
# zpool export zroot # zpool import -N -R /tmp/altroot zroot
既存環境をリネーム
# zfs rename zroot/ROOT zroot/ROOT2
既存環境のルートのマウントポイントを変更しマウント。ここで先に行った補正が活きてくる。始祖データセットのマウントポイントの変更だけで、子データセットのマウントポイントを一気に変えることができる。
# zfs set mountpoint=/ROOT2 zroot/ROOT2 # zfs mount -a
Boot Environment環境の構築
Boot Environment用のデータセットを作る。
# zfs create -o mountpoint=none zroot/ROOT # zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT/default # zfs mount zroot/ROOT/default # zfs create -o mountpoint=/tmp -o exec=on -o setuid=off zroot/tmp # zfs create -o mountpoint=/usr -o canmount=off zroot/usr # zfs create -o mountpoint=/usr/ports -o setuid=off zroot/usr/ports # zfs create -o mountpoint=/usr/src zroot/usr/src # zfs create -o mountpoint=/var -o canmount=off zroot/var # zfs create -o mountpoint=/var/audit -o exec=off -o setuid=off zroot/var/audit # zfs create -o mountpoint=/var/crash -o exec=off -o setuid=off zroot/var/crash # zfs create -o mountpoint=/var/log -o exec=off -o setuid=off zroot/var/log # zfs create -o mountpoint=/var/mail -o atime=on zroot/var/mail # zfs create -o mountpoint=/var/tmp -o setuid=off zroot/var/tmp # chmod 1777 /tmp/altroot/tmp # chmod 1777 /tmp/altroot/var/tmp
zroot/ROOT/defaultを作った直後に手動でマウントすることをお忘れなく。
データセット的には親子関係になく、ファイルシステム的には親子関係がある場合、データセットのマウント順に気を付ける必要がある。
現環境のファイルをBoot Environmentなデータセットにコピー。
# LD_LIBRARY_PATH=/tmp/altroot/usr/local/lib /tmp/altroot/usr/local/bin/rsync -aX /tmp/altroot/ROOT2/ /tmp/altroot/
起動データセットの変更
起動するデータセットをBoot Environmentにする。
# zpool set bootfs=zroot/ROOT/default zroot
ついでに、いにしえのRoot on ZFS環境をマウントしないようにしておく。
# zfs set mountpoint=none zroot/ROOT2
Boot Environmentの起動に失敗するようなら、bootfsをzroot/ROOT2、zroot/ROOT2のマウントポイントを/にすれば、以前の環境がそのまま起動する。