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/usrzroot/tmpなどのzroot/ROOTと同列にあるデータセットがmountpointプロパティに従いマウントされることで、OSが想定するファイルシステム構造が出来上がるという仕掛けになっている。

注意すべきはcanmountプロパティがnoのデータセットたち。FreeBSD 12.2-RELEASE時点で該当するのは、zroot/usrzroot/varである。

子データセットのcanmountonなのに親がoffというのは一見不思議だが、なんて事はない、親は子供のための単なる“コンテナ”としての存在に過ぎない。逆に言うと、/usrの実体は各々のBoot Environmentに存在するため、zroot/usrがマウントされると都合が悪い。下表のように、同じ/usrでもパスによって格納先のデータセットが変わってくるため、移行作業では気を付ける必要がある。

ファイルパス ZFS的格納場所
/usr/bin/sh zroot/ROOT/defaultデータセット直下のusrディレクトリ内のbinディレクトリのshファイル
/usr/ports/UPDATING zroot/usr/portsデータセット直下に配置されているUPDATINGファイル

以上をふまえ、下記の手順で移行を試みる。

  1. 独自データセットの解消
  2. Boot Environment環境にないデータセットの解消
  3. Boot Environment環境の構築
  4. 既存環境と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構成にない旧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環境の各マウントポイントを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用のデータセットを作る。

# 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のマウントポイントを/にすれば、以前の環境がそのまま起動する。


1)
厳密にはある程度自由に構成できるがシステム標準に合わせておいた方がいいかなと
  • freebsd/freebsd_convert_legacy_root_on_zfs_to_boot_environment.txt
  • 最終更新: 2021-04-24 23:02
  • by Decomo