为了享受 ZFS 文件系统的 mirror 带来的数据安全保障,我将 Arch Linux 系统从 ext4 文件系统迁移至了 ZFS 文件系统。相关的限制条件包括:原来的硬盘是 三星 840 EVO 256G,需要与 金士顿 SA400S37 480G 组合成一个 mirror pool,也就意味着需要先从 三星 复制到 金士顿,再把 三星 做成 ZFS,将数据重新复制到三星,然后重启进系统把金士顿 attach 到 三星 变成一个 mirror pool;另外,我不想按照 Arch Linux 的指南对三星进行分区,想要让 ZFS 直接管理整个三星硬盘,所以需要外置引导设备。
1. 制作支持 ZFS 的 archiso
官方的 archiso 是不支持 ZFS 的,和 Linux 内核一样,是因为开源协议不兼容所以不能内置支持。因此我们需要自己制作一个修改版的 archiso 支持识别和操作 ZFS。参考官方指导:ZFS、官方指导:archiso,用 pacman 安装 archiso,创建一个目录用来制作 archiso,然后复制一套官方 iso 的配置文件到工作目录:
1 | mkdir archlive |
然后修改 archlive/releng/pacman.conf
添加 archzfs 仓库:
1 | [archzfs] |
可能由于我之前已经安装过 archzfs 的内容,所以不需要做一个 trust gpg key 的动作。接下来修改 archlive/releng/packages.x86_64
,添加 linux-header
、zfs
、zfs-dkms
、zfs-utils
四个额外的包。完成之后,以 root 身份 cd 到 archlive 目录,执行 mkarchiso -v ./releng
开始构建 archiso。完成之后的镜像在 archlive
目录下的 out
中,把它复制出来用 rufus (dd模式)写入到一个U盘中,这一步就完成了。
1 | . |
2. 制作用于引导的ZFSBootMenu U盘
这个U盘不用很大,质量也不用很好,但是需要长期插在电脑上,所以最好是闲置不用的拿过来发挥余热。根据 Portable ZFSBootMenu 的指导,假设U盘是 /dev/sdf
,那么用 fdisk
来进行分区、mkfs.fat
来格式化的示范如下:
1 | sudo fdisk /dev/sdf |
把分区挂载到 /mnt/usb
,创建 EFI/BOOT
路径并把 ZFSBootMenu 的 EFI 塞进去:
1 | sudo mkdir /mnt/usb |
这样就获得了一个能够引导 ZFS 上的内核的U盘了。
3. 重启并迁移数据
首先要确保当前的系统已经能够操作 ZFS 再重启,由于我的系统对于数据盘早已使用 ZFS,所以不用额外装其他东西;如果你没有,那么请一定要装好 zfs-dkms
、zfs-utils
以及你的内核对应的header
再重启,否则数据迁移好了系统也是加载不了的。
先不要插 ZFSBootMenu 那个U盘,只插 archiso,重启进 iso 环境之后,根据 ls -lh /dev/disk/by-id/
的输出来确认硬盘哪个是哪个,比如在我的机子上只有1个三星和1个金士顿,那么就会比较清晰(无关的硬盘已省略):
1 | lrwxrwxrwx 1 root root 9 Jun 23 23:15 ata-KINGSTON_SA400S37480G_50026B7380698064 -> ../../sdb |
然后用 fdisk -l /dev/sda
来确定现在的三星上的分区,常见的比如 sda1 是 EFI,sda2 是 /boot
,sda3 是 /
。
在进行接下来的操作之前,请确保金士顿的数据已经备份好了,它的所有数据都会被删掉的。
3.1 格式化金士顿、rsync 备份
由于三星是 ext4,所以我们把金士顿也格式化为 ext4,来确保文件系统属性兼容。根据上面的输出,金士顿是/dev/sdb
,所以用 fdisk /dev/sdb
新建分区表、创建一个分区、保存分区表,然后mkfs.ext4 /dev/sdb1
,再挂载到/king
。
接下来把三星挂载到 /mnt
,根据前面的信息进行以下命令:
1 | mount /dev/sda3 /mnt |
然后用 rsync 把数据备份到金士顿:
1 | rsync -a -P --xattrs --acls --numeric-ids --hard-links /mnt/ /king/ |
搞定之后 umount -R /mnt
释放三星。
3.2 格式化三星为 ZFS、rsync 恢复
这一节参考Install Arch Linux on ZFS,但是简化了很多不必要的步骤。
用单设备创建 zpool 的命令把三星喂给 ZFS。注意,我们不走官方的分区模式,二是让 ZFS 直接接管这个硬盘;这里使用的是硬盘id:
1 | zpool create -f \ |
接下来是创建 dataset,类似于传统意义上的分区。我不搞那么复杂,只分开了 /
和 /home
:
1 | zfs create -o mountpoint=none zroot/data |
然后 export、重新导入 pool。这几条命令可以保存为一个 shell 脚本,之后如果需要重新进入 iso 环境抢救的时候,导入就不用手打了:
1 | zpool export zroot |
然后从金士顿把文件恢复回来:
1 | rsync -a -P --xattrs --acls --numeric-ids --hard-links /king/ /mnt/ |
4. 修改配置
下面的这些操作都是 arch-chroot /mnt
之后操作的,改的是原来的系统的配置而不是 archiso 的配置。
4.1 修改/etc/fstab
要把 /etc/fstab
中关于三星、金士顿的条目都删掉,因为我们这个配置方法不是 legacy 挂载点,开机的时候 ZFS 会负责处理挂载的事情,不需要 fstab 来做。
4.2 修改 /etc/mkinitcpio.conf
要把 zfs 添加到 MODULES=()
中。也要在 HOOKS=
的 block
后面添加 zfs
。在我的系统上,这两个是这样的:
1 | MODULES=(zfs) |
由于我不需要在 initrd 中使用 systemd,所以官方指南里的sd-zfs、systemd就直接跳过了。
暂时先不做重新生成 initramfs 的操作,还有其他文件要改,现在生成等下还要重新生成。
4.3 设置 bootfs 、挂载读写(rw)模式、开机自动挂载
虽然我们使用外置 ZFSBootMenu 似乎不需要设置 bootfs,但是我没有试过不做这一步的结果,所以就写上了。执行 zpool set bootfs=zroot/ROOT/default zroot
来将 zroot 的 bootfs 设置成我们存放 / 的 dataset。
接下来是非常重要的一步,设置 zroot/ROOT
在 import 时的属性为 读写(rw)模式,否则会默认 import 为 只读(ro)模式,启动了之后是改不了的——会提示只有 import 的时候才能设置这个 property:
1 | zfs set org.zfsbootmenu:commandline="rw" zroot/ROOT |
然后是开机自动挂载 zroot:
1 | zpool set cachefile=/etc/zfs/zpool.cache zroot |
要 enable 一些 service 和 target 才能开机时自动挂载:
1 | systemctl enable zfs.target zfs-import-cache.service zfs-mount.service zfs-import.target |
根据官方指南还需要设置 hostid,用以下命令生成、写入 /etc/hostid:
1 | zgenhostid $(hostid) |
4.4 重新生成 initramfs
完成了上面的所有配置之后,就可以重新生成 initramfs 了:mkinitcpio -P
。
5. 重启进入系统
首先退出 chroot 环境,回到 archiso 环境,然后卸载并 export
zroot——根据官方指南,如果忘了 export 的话,重启之后加载系统的时候会提示该 pool 不能 import,正在被其他系统使用。确实是这样的,包括之前在用的数据盘们所属的 pool,我在做第3步之前没有 export,所以重启之后需要 zpool import --force
才能用回来;但是根文件系统就没办法 “force” 了,所以必须 export。
1 | zfs umount -a |
接下来用 poweroff 关机,拔掉 archiso,插上第2步制作的 ZFSBootMenu U盘,按开关开机;一切正常的话会正确显示 ZFSBootMenu 的界面,提示倒计时10秒后引导 zroot 上的那个内核。老天保佑一次过,就能成功进入系统了。
6. 重新 import 其他 zpool、把金士顿 attach 组成 mirror pool
之前忘了 export 其他 zpool 的话,开机进系统之后用 zpool import
显示可以导入的 pool,然后通常来说都要 zpool import -f 池子名字
来强制导入的。
接下来用 attach 来将金士顿跟三星组合。根据第3步开头的那些信息(如果不确定,可以用 zpool status -v
来确认 zroot 现在的设备),注意参数顺序,是 三星 在前面,金士顿 在后面:
1 | zpool attach -f zroot ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF222103J ata-KINGSTON_SA400S37480G_50026B7380698064 |
如果拿不准,就先不加-f
参数,应该会产生提示信息类似于:“金士顿这个设备上有ext4文件系统,如果确定要使用它的话请加上 -f 参数”。
就搞定了。