如何:重建根 zpool
这篇和较早的 线上重做 FreeBSD GPT 引导分区 情况有些类似,但略有不同。
前段时间 Andriy Gapon 为 Samsung 860 / 870 SSD 增加了一个 quirk。 Samsung 的 SSD 内部使用的是 8K 或 16K 的存储页,但为了和业界标准兼容, 它的控制器为 4K 扇区做了优化。
当然这不是重点,重点是我老人家当年创建根存储池(纯爷们当然要从 ZFS 存储池启动系统)时, 竟然忘记了设置 ashift,而由于当年这个 SSD 汇报的扇区尺寸是 512 字节,因此 ZFS 也就愉快地创建了一个 512 字节扇区的根存储池。而经过改进之后,现在 GEOM 会正确地汇报扇区尺寸了:
$ geom disk list ada1
Geom name: ada1
Providers:
1. Name: ada1
Mediasize: 1000204886016 (932G)
Sectorsize: 512
Stripesize: 4096
Stripeoffset: 0
Mode: r3w3e6
descr: Samsung SSD 860 EVO 1TB
lunid: XXXXXXXXXXXXXXXX
ident: XXXXXXXXXXXXXXX
rotationrate: 0
fwsectors: 63
fwheads: 16
这样一来, zpool status
就会汇报:
status: One or more devices are configured to use a non-native block size.
Expect reduced performance.
action: Replace affected devices with devices that support the
configured block size, or migrate data to a properly configured
pool.
根存储池并不会经常读写,但看着实在是逼死强迫症,我觉得得做点什么。
备份我是做了的,而且我确定备份不是上回书小故事里的那样。 我不特别介意重装一遍,但是毕竟这得花更多的时间,而且因为我在笔记本上跑的是 -CURRENT, 还需要做一个启动的 U 盘,稍微有些麻烦。综合考虑了碎片、压缩等其他因素,我决定重建一个根存储池, 这样恰好把顺便数据整体重写一次(而不是做mirror)。
由于我的习惯是分配一个比较大(通常是和RAM相同尺寸的)的加密swap分区。现时的内存通常远大于根 pool 的尺寸, 因此可以把 swap 分区挪用来做临时存储空间。
首先是把加密的 swap 分区关闭:
$ sudo swapoff -a
然后是把swap分区尺寸改小:
$ sudo gpart show ada1
=> 40 1953525088 ada1 GPT (932G)
40 262144 1 efi (128M)
262184 8388608 2 freebsd-zfs (4.0G)
8650792 67108864 3 freebsd-swap (32G)
75759656 1877765472 4 freebsd-zfs (895G)
$ sudo gpart resize -i 3 -s 28g ada1
$ sudo gpart add -t freebsd-zfs ada1
这样我们获得了一个新的、同样是 4GB / 8388608 扇区的 ada1p5。
将新设备作为 mirror 插入到根存储池,并拆下原先的 ada1p2:
$ sudo zpool attach p51-boot ada1p2 ada1p5
$ sudo zpool scrub p51-boot
$ zpool status p51-boot # 到scrub做完为止
$ sudo zpool detach p51-boot ada1p2
这样我们就把 p51-boot
存储池挪到了 ada1p5 上面。为了防止 zpool 再创建出 ashift=9
的存储池,修改 vfs.zfs.vdev.min_auto_ashift:
$ sudo sysctl vfs.zfs.min_auto_ashift=12
在 ada1p2 上重建根存储池:
$ sudo dd if=/dev/random of=/dev/ada1p2 bs=1m # 抹去原来的数据,just in case
$ sudo zpool create -m none -o altroot=/tmp/1 -O atime=off -O setuid=off \
-O compression=zstd -O canmount=off -O checksum=skein p51-boot2 /dev/ada1p2
$ sudo zfs create -o mountpoint=none p51-boot2/ROOT
$ sudo zfs create -o setuid=on -o mountpoint=legacy p51-boot2/ROOT/default
在原存储池上打一个快照以方便复制。
$ sudo zfs snapshot -r p51-boot2@20240422
(此处我是对整个存储池做了快照,但实际上我们只需要 ROOT/default 这一个文件系统,因此实际上也可以只快照它)
将快照复制到新的存储池:
$ sudo zfs send p51-boot/ROOT/default@20240422 | \
sudo zfs receive -F -v -x compression p51-boot2/ROOT/default
$ sudo zfs destroy p51-boot2/ROOT/default@20240422
我之前使用的是 compression=on
(lz4) 压缩,而新的存储池使用了 zstd。FreeBSD 支持从启用了这两种压缩算法的存储池引导系统,因此在接收快照时排除 compression
属性,直接使用本地的新值,这样重写时会以新的压缩算法重新压缩一遍。
我们可能需要修改一些东西。例如,我这个根存储池是从更早的旧式设计中迁移过来的,因此我的 /etc/fstab
中明确指定了 /
是来自 p51-boot
(后来改成了 Boot Environment 风格的 p51-boot/ROOT/default
)。现时已经不再需要明确指定(EFI引导加载器会根据 bootfs
正确设置),以及我们希望更新一下 /boot/zfs/zpool.cache
之类,所以挂载一下新的 /:
$ sudo mount -t zfs p51-boot2/ROOT/default /mnt
# 编辑必要的文件,例如删去 /etc/fstab 中关于 / 的定义,等等
这样基本上就做完迁移了,由于接下来的那次引导时引导加载器将看到两个未加密的 ZFS 存储池,因此有必要明确我们希望从 p51-boot2 启动系统:
$ sudo zpool set bootfs=p51-boot2/ROOT/default p51-boot2
$ sudo zpool set bootfs= p51-boot
重启,确定我们是从 p51-boot2 引导的系统之后就可以把原来的 p51-boot 删除掉了。我们顺便把加密swap分区归位:
$ sudo zpool destroy p51-boot # 毁掉原来的根存储池
$ sudo dd if=/dev/random of=/dev/ada1p5 bs=1m # 抹去临时分区的数据,防止数据泄漏。
$ sudo swapoff -a # 卸下加密swap分区
$ sudo gpart delete -i 5 # 删去临时的分区
$ sudo gpart resize -i 3 ada1 # 不指定size表示充满此区域
$ sudo service swaplate start # 重新挂上swap分区
重启确认一下系统还能动,特别提醒,为了避免这一环节成为深夜提神节目,做本文所描述的任何操作之前, 请务必确认一下备份是完整的,勿谓言之不预。