为了享受 ZFS 文件系统的 mirror 带来的数据安全保障,我将 Arch Linux 系统从 ext4 文件系统迁移至了 ZFS 文件系统。相关的限制条件包括:原来的硬盘是 三星 840 EVO 256G,需要与 金士顿 SA400S37 480G 组合成一个 mirror pool,也就意味着需要先从 三星 复制到 金士顿,再把 三星 做成 ZFS,将数据重新复制到三星,然后重启进系统把金士顿 attach 到 三星 变成一个 mirror pool;另外,我不想按照 Arch Linux 的指南对三星进行分区,想要让 ZFS 直接管理整个三星硬盘,所以需要外置引导设备。
flowchart TD
A[三星 840 EVO(ext4)] –>|1. rsync 备份| B[金士顿 SA400S37 (ext4)]
C[三星 840 EVO(ZFS)]
A –> | 2. 格式化ZFS | C
B –> | 3. rsync 恢复 | C
B –> |4. attach 到三星| D[mirror zpool]
C –> D
1. 制作支持 ZFS 的 archiso
官方的 archiso 是不支持 ZFS 的,和 Linux 内核一样,是因为开源协议不兼容所以不能内置支持。因此我们需要自己制作一个修改版的 archiso 支持识别和操作 ZFS。参考官方指导:ZFS、官方指导:archiso,用 pacman 安装 archiso,创建一个目录用来制作 archiso,然后复制一套官方 iso 的配置文件到工作目录:
1
2
mkdir archlive
cp -r /usr/share/archiso/configs/releng/ archlive
然后修改 archlive/releng/pacman.conf
添加 archzfs 仓库:
1
2
3
4
5
6
7
8
9
10
11
12
13
[archzfs]
# Origin Server - Finland
Server = http://archzfs.com/$repo/$arch
# Mirror - Germany
Server = http://mirror.sum7.eu/archlinux/archzfs/$repo/$arch
# Mirror - Germany
Server = http://mirror.sunred.org/archzfs/$repo/$arch
# Mirror - Germany
Server = https://mirror.biocrafting.net/archlinux/archzfs/$repo/$arch
# Mirror - India
Server = https://mirror.in.themindsmaze.com/archzfs/$repo/$arch
# Mirror - US
Server = https://zxcvfdsa.com/archzfs/$repo/$arch
可能由于我之前已经安装过 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
3
4
5
6
7
8
9
10
11
12
13
.
├── out
│ └── archlinux-2024.06.23-x86_64.iso
├── releng
│ ├── airootfs
│ ├── efiboot
│ ├── grub
│ ├── syslinux
│ ├── bootstrap_packages.x86_64
│ ├── packages.x86_64
│ ├── pacman.conf
│ └── profiledef.sh
└── work
2. 制作用于引导的ZFSBootMenu U盘
这个U盘不用很大,质量也不用很好,但是需要长期插在电脑上,所以最好是闲置不用的拿过来发挥余热。根据 Portable ZFSBootMenu 的指导,假设U盘是 /dev/sdf
,那么用 fdisk
来进行分区、mkfs.fat
来格式化的示范如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sudo fdisk /dev/sdf
Command (m for help): g
Created a new GPT disklabel (GUID: 38E24376-CD07-4B87-85C5-28BC29ADF0A9).
Command (m for help): n
Partition number (1-128, default 1):
First sector (2048-31129566, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-31129566, default 31127551):
Created a new partition 1 of type 'Linux filesystem' and of size 14.8 GiB.
Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.
Command (m for help): w
sudo mkfs.fat /dev/sdf1
把分区挂载到 /mnt/usb
,创建 EFI/BOOT
路径并把 ZFSBootMenu 的 EFI 塞进去:
1
2
3
4
5
sudo mkdir /mnt/usb
sudo mount /dev/sdf1 /mnt/usb
sudo mkdir -p /mnt/usb/EFI/BOOT
curl -LJ https://get.zfsbootmenu.org/efi/recovery -o /mnt/usb/EFI/BOOT/BOOTX64.EFI
sudo umount /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
2
3
4
5
6
lrwxrwxrwx 1 root root 9 Jun 23 23:15 ata-KINGSTON_SA400S37480G_50026B7380698064 -> ../../sdb
lrwxrwxrwx 1 root root 10 Jun 23 23:15 ata-KINGSTON_SA400S37480G_50026B7380698064-part1 -> ../../sdb1
lrwxrwxrwx 1 root root 10 Jun 23 23:15 ata-KINGSTON_SA400S37480G_50026B7380698064-part9 -> ../../sdb9
lrwxrwxrwx 1 root root 9 Jun 23 23:15 ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF222103J -> ../../sda
lrwxrwxrwx 1 root root 10 Jun 23 23:15 ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF222103J-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 Jun 23 23:15 ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF222103J-part9 -> ../../sda9
然后用 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
2
3
mount /dev/sda3 /mnt
mount /dev/sda2 /mnt/boot
mount /dev/sda1 /mnt/efi
然后用 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
2
3
4
5
6
7
8
9
10
11
zpool create -f \
-O acltype=posixacl \
-O relatime=on \
-O xattr=sa \
-O dnodesize=legacy \
-O normalization=formD \
-O mountpoint=none \
-O canmount=off \
-O devices=off \
-R /mnt \
zroot /dev/disk/by-id/ata-Samsung_SSD_840_EVO_250GB_S1DBNSAF222103J
接下来是创建 dataset,类似于传统意义上的分区。我不搞那么复杂,只分开了 /
和 /home
:
1
2
3
4
zfs create -o mountpoint=none zroot/data
zfs create -o mountpoint=none zroot/ROOT
zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT/default
zfs create -o mountpoint=/home zroot/data/home
然后 export、重新导入 pool。这几条命令可以保存为一个 shell 脚本,之后如果需要重新进入 iso 环境抢救的时候,导入就不用手打了:
1
2
3
4
zpool export zroot
zpool import -d /dev/disk/by-id -R /mnt zroot -N
zfs mount zroot/ROOT/default
zfs mount -a
然后从金士顿把文件恢复回来:
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
2
MODULES=(zfs)
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block zfs filesystems)
由于我不需要在 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
2
zfs umount -a
zpool export zroot
接下来用 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 参数”。
就搞定了。