前言

在之前的在Mac上安装Gentoo Linux这篇文章里面我成功的给现在手中的Mac安装上了Gentoo Linux,但是最近在看一些文件系统的资料看到了ZFS其中的特性很是让我心动,虽然没有在虚拟机上面做一些尝试但是呢其中的快照、写时复制、数据压缩的特性很让我心动。

如果你有了解过一定的文件系统的知识你可能会想为啥不选择Btrfs呢?而且btrfs的话内核是可以直接支持的(需要比较新的内核),这个问题也是一个好问题。因为在我之后的规划里面,我打算自建一个机房。出于成本的考虑采取的方案是存算分离,存储的部分就是打算用ZFS。对于存储来说,连续性检查自动修复,RAID就显得尤为重要,这里也是为了这个计划做一个铺垫。也许你还会说这些btrfs都有啊!为啥还要用ZFS呢?由于我现在的水品还是十分有限,现阶段更多的是调研和测试,后续真正决定要用哪个文件系统或者是组合还是要看最终的测试结果。

因为当前用的这个Mac已经有了系统并且正常使用了一段时间,为了保持数据不丢失我先备份了一下系统。关于如何备份系统可以参考我这篇文章备份你的Gentoo系统

同时也趁着机会对之前的分区大小做一些调整:

分区文件系统大小
/dev/nvme0n1p1fat32128M
/dev/nvme0n1p2zfsAll

准备

这次安装需要如下物品:

  1. USB Livecd * 1 (Ubuntu 20.10 Desktop Livecd 选择这个就是因为支持ZFS Tools,我们需要用这个来初始化我们的pool)
  2. 另外一个电脑 (方便记录和寻找帮助)
  3. 备份硬盘(原来的系统备份)

更多准备阶段的内容可以参照这里在Mac上安装Gentoo Linux:环境准备

分区

首先我需要把之前的分区全部干掉,然后创建如下表的分区:

分区文件系统大小
/dev/nvme0n1p1fat32128M
/dev/nvme0n1p2zfsAll
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
fdisk /dev/nvme0n1
Welcome to fdisk (util-linux 2.36).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/nvme0n1: 1.86 TiB, 2048408248320 bytes, 4000797360 sectors
Disk model: KXG50PNV2T04 KIOXIA
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: AFD9539F-0A10-4184-9E73-20652BA3D677

Device          Start        End    Sectors  Size Type
/dev/nvme0n1p1   2048     133119     131072   64M Linux filesystem
/dev/nvme0n1p2 133120 4000797326 4000664207  1.9T Linux filesystem

Command (m for help): d # 删除分区2
Partition number (1,2, default 2):

Partition 2 has been deleted.

Command (m for help): d # 删除分区1
Selected partition 1
Partition 1 has been deleted.

Command (m for help):


Command (m for help): w # 写入硬盘

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

这里是把原来的分区全部都干掉了,接下来我们要按照上面的表格重新创建分区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fdisk /dev/nvme0n1
Welcome to fdisk (util-linux 2.36).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): n # 创建新分区
Partition number (1-128, default 1):
First sector (34-4000797326, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-4000797326, default 4000797326): +128M # 大小为128M

Created a new partition 1 of type 'Linux filesystem' and of size 128 MiB.
Partition #1 contains a vfat signature.

Do you want to remove the signature? [Y]es/[N]o: Y # 因为之前有过分区这里要移除原来的签名

The signature will be removed by a write command.

Command (m for help): n
Partition number (2-128, default 2):
First sector (264192-4000797326, default 264192):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (264192-4000797326, default 4000797326): # 留空全部给这个分区

Created a new partition 2 of type 'Linux filesystem' and of size 1.9 TiB.

Command (m for help): w # 写入
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

初始化分区

创建完成分区之后我们还需要对分区进行初始化

/dev/nvme0n1p1

1
mkfs.vfat -F32 /dev/nvme0n1p1

这里swap的话我们没有设置成单独的一个分区,打算是测试一下在zfs里面使用swapfile会不会吧内存打满,如果有问题的话我再调整一下加一个swap分区。

创建ZFS池

这里需要注意一点是像是我们之前分区的/dev/nvme0n这种设备或者是/dev/sda这样的,如果插拔U盘之类的可能盘序会发生变化,但是ZFS池是不会意识到这个变化的,这样就会产生zfs池不可用的问题,我们需要一些方法来获取到磁盘的id并以此来创建我们的zfs池。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ls -l /dev/disk/by-id
lrwxrwxrwx 1 root root 13 Feb 13 12:29 nvme-KXG50PNV2T04_KIOXIA_Y9IS103FTHDM -> ../../nvme0n1
lrwxrwxrwx 1 root root 15 Feb 13 12:32 nvme-KXG50PNV2T04_KIOXIA_Y9IS103FTHDM-part1 -> ../../nvme0n1p1
lrwxrwxrwx 1 root root 15 Feb 13 12:29 nvme-KXG50PNV2T04_KIOXIA_Y9IS103FTHDM-part2 -> ../../nvme0n1p2
lrwxrwxrwx 1 root root 13 Feb 13 12:29 nvme-eui.000000000000001000080d0200600f01 -> ../../nvme0n1
lrwxrwxrwx 1 root root 15 Feb 13 12:32 nvme-eui.000000000000001000080d0200600f01-part1 -> ../../nvme0n1p1
lrwxrwxrwx 1 root root 15 Feb 13 12:29 nvme-eui.000000000000001000080d0200600f01-part2 -> ../../nvme0n1p2
lrwxrwxrwx 1 root root  9 Feb 13 11:41 usb-ACASIS_MAC3E_000000000001-0:0 -> ../../sda
lrwxrwxrwx 1 root root 10 Feb 13 11:41 usb-ACASIS_MAC3E_000000000001-0:0-part1 -> ../../sda1
lrwxrwxrwx 1 root root 10 Feb 13 11:41 usb-ACASIS_MAC3E_000000000001-0:0-part2 -> ../../sda2
lrwxrwxrwx 1 root root 10 Feb 13 11:41 usb-ACASIS_MAC3E_000000000001-0:0-part3 -> ../../sda3
lrwxrwxrwx 1 root root 10 Feb 13 11:41 usb-ACASIS_MAC3E_000000000001-0:0-part4 -> ../../sda4
lrwxrwxrwx 1 root root  9 Feb 13 11:41 usb-APPLE_SD_Card_Reader_000000000820-0:0 -> ../../sdb

这里的KXG50PNV2T04_KIOXIA_Y9IS103FTHDM就是我们的硬盘nvme-KXG50PNV2T04_KIOXIA_Y9IS103FTHDM-part1就是我们创建作为未来的/boot分区

当我们创建zfs池的时候尽可能选择用这种id的方式去创建比如说

1
/dev/disk/by-id/nvme-eui.000000000000001000080d0200600f01-part2

现在我们创建一个加密的zpool:

1
zpool create -f -o ashift=12 -o cachefile=/etc/zfs/zpool.cache -O compression=lz4 -O xattr=sa -O relatime=on -O acltype=posixacl -O dedup=off  -O encryption=on -O keyformat=passphrase -m none -R /mnt/gentoo rock /dev/disk/by-id/nvme-eui.000000000000001000080d0200600f01-part2

根据提示输入密码(注意密码不会有回显,并不是键盘坏了)。

这条命令创建了一个名为 rock的zfs池 使用的压缩算法为lz4

如果说你想创建一个不带加密的zpool可以去掉-O encryption=on -O keyformat=passphrase这两个选项。

创建 rootfs zfs datasets

接下来我们要创建自己的rootfs,并且在上面打开加密功能

创建os和/

1
2
zfs create -o mountpoint=none -o canmount=off rock/os
zfs create -o mountpoint=/ rock/os/gentoo

安装系统

从硬盘恢复

这次安装系统的话,是打算直接从硬盘恢复的。

首先插上备份好的硬盘。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fdisk -l
........
Disk /dev/sdc: 232.88 GiB, 250055122432 bytes, 488388911 sectors
Disk model: 00AAJS-00B4A0
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 4096 bytes / 33553920 bytes
Disklabel type: dos
Disk identifier: 0x80f01a9c

Device     Boot Start       End   Sectors   Size Id Type
/dev/sdc1        2048 488388910 488386863 232.9G 83 Linux

这里看到我们的备份硬盘

我们要创建一个目录用来挂载这个分区:

1
mkdir -pv /backup

打开LUKS分区

1
2
cryptsetup luksOpen /dev/sdc1 backup
Enter passphrase for /dev/sdc1:

输入之前设置的密码

挂载分区

1
mount -v /dev/mapper/backup /backup

还有一个分区我们要注意一下就是/boot分区

创建boot分区的挂载点

1
mkdir -pv /mnt/gentoo/boot

挂载分区

1
mount -v /dev/nvme0n1p1 /mnt/gentoo/boot

将所有文件同步到zfs的dataset里面

1
rsync -aAXv --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","/backup/*","/swapfile"} /backup/ /mnt/gentoo

这个复制需要一定的时间,耐心等待完成,期间不要断开硬盘或者是拔掉电源。

chroot

等到复制原来的系统完成之后我们就需要进入chroot环境,然后完成其他的必要配置了。

拷贝ZFS池缓存文件

1
2
mkdir -pv /mnt/gentoo/etc/zfs
cp -v  /etc/zfs/zpool.cache /mnt/gentoo/etc/zfs

拷贝网络的DNS

1
cp --dereference /etc/resolv.conf /mnt/gentoo/etc/

挂载必要的文件系统

1
2
3
4
5
mount --types proc /proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --make-rslave /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
mount --make-rslave /mnt/gentoo/dev

我们不是在官方的Livecd下面还需要执行:

1
2
3
test -L /dev/shm && rm /dev/shm && mkdir /dev/shm
mount --types tmpfs --options nosuid,nodev,noexec shm /dev/shm
chmod 1777 /dev/shm

进入chroot

1
2
3
chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) $PS1"

安装ZFS和配置必要项目

安装ZFS

首先是要给原来的系统安装上ZFS的支持

首先确保内核打开了Zlib的支持

1
2
3
4
5
6
7
General Architecture Dependent Options --->
  GCC plug ins  --->  
    [ ]   Randomize layout of sensitive kernel structures 
Cryptographic API --->
  <*> Deflate compression algorithm
Security options  ---> 
  [ ] Harden common str/mem functions against buffer overflows

我们还需要调整portage让ZFS包接收测试分支的包

1
2
echo "sys-fs/zfs-kmod ~amd64" >> /etc/portage/package.accept_keywords/zfs-kmod
echo "sys-fs/zfs ~amd64" >> /etc/portage/package.accept_keywords/zfs

如果你想要使用实时的包可以:

1
2
echo "=sys-fs/zfs-kmod-9999 **" >> /etc/portage/package.accept_keywords/zfs-kmod
echo "=sys-fs/zfs-9999 **" >> /etc/portage/package.accept_keywords/zfs

但是不推荐用最新的,要到处找patch。当然如果你是老手当我没说.jpg

安装ZFS

1
emerge -av zfs

有一个很重要的点,每次更新内核或者是编译内核之后最好是重新构建一下模块,否则有可能遇到zpool无法正常初始化的问题

1
emerge -va @module-rebuild

将zfs加入到开机启动项和对应的启动级别

1
2
3
4
rc-update add zfs-import boot
rc-update add zfs-mount boot
rc-update add zfs-share default
rc-update add zfs-zed default

生成和验证zfs hostid文件

这个文件是用于genkernel生成initramfs和zfs导入池的时候验证完整性时候需要的

1
2
zgenhostid
file /etc/hostid

Bootload fstab and Initramfs

GRUB

修改grub的配置文件,内容如下

1
GRUB_CMDLINE_LINUX="dozfs root=ZFS=rock/os/gentoo"

安装bootload

1
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Gentoo

生成配置文件

1
grub-mkconfig -o /boot/grub/grub.cfg

fstab

挂载的工作这次交给zfs来去完成,我们这里还需要修改一下fstab

首先查看分区的id

1
2
3
blkid
/dev/nvme0n1p1: UUID="129F-3405" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="537bd932-cc1f-5643-a297-feebaeb6a5ea"
/dev/nvme0n1p2: LABEL="rock" UUID="7999529021869478878" UUID_SUB="11607083434113154920" BLOCK_SIZE="4096" TYPE="zfs_member" PARTUUID="d68d6b86-a407-4141-a1a7-1379cbe1e049"

可以看到我们的/boot分分区UUID是129F-3405,现在可以修改/etc/fstab

1
nano -w /etc/fstab

内容如下

1
2
# /dev/nvme0n1p1
/dev/nvme0n1p1      	/boot     	vfat      	rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro	0 2

initramfs

重新生成initramfs

之前的initramfs没有zfs的支持这次要加上,为了保证工作正常还需要将genkrenel切换到testing分支

1
echo "=sys-kernel/genkernel-9999 **" > /etc/portage/package.accept_keywords/genkernel

更新genkernel

1
emerge -av genkernel

重新生成initramfs文件

1
genkernel initramfs --zfs --compress-initramfs

重启

在重启验证之前我们需要先做一些清理工作

首先退出chroot环境

1
exit

卸载备份分区

1
umount -R /backup

关闭backup

1
cryptsetup luksClose backup

然后就可以重启啦

1
reboot

故障排除

进入到livecd之后,打开zpool

1
zpool import -f rock

设置rootfs的挂载点

1
zfs set mountpoint=/mnt/gentoo rock/os/gentoo

打开rock/os/gentoo dataset

1
zfs mount -l -a

挂载/boot分区

1
mount /dev/nvme0n1p1 /mnt/gentoo/boot/

进入chroot

挂载必要的文件系统

1
2
3
4
5
mount --types proc /proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --make-rslave /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
mount --make-rslave /mnt/gentoo/dev

我们不是在官方的Livecd下面还需要执行:

1
2
3
test -L /dev/shm && rm /dev/shm && mkdir /dev/shm
mount --types tmpfs --options nosuid,nodev,noexec shm /dev/shm
chmod 1777 /dev/shm

进入chroot

1
2
3
chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) $PS1"

结束维护之后

退出chroot

1
exit 

卸除挂载

1
umount -R /mnt/gentoo

rootfs的挂载点改回来

1
zfs set mountpoint=/ rock/os/gentoo

重启

1
reboot

问题

在重启后你会发现在initramfs阶段我们的rootfs没有被解锁,不像是之前使用的LUKS加密那样子在initramfs阶段提示输入密码,必须要进入initramfsshell

根据提示进入到shell中。

首先我们需要导入我们的rockzpool(需要输入密码,同样的不会有回显)

1
zpool import -f -l rock

最后退出 initramfs shell就可以进入到正常的引导中了。

1
exit

完成这次操作之后,之后在重启或者是开机就像是之前使用LUKS加密那样子直接在initramfs阶段提示输入密码,然后就可以进入系统了!

远程解锁zpool

在遇到更新内核的时候需要重启机器,但是由于zpool是加密之后的需要输入密码方式去解锁zpool才能够正常的引导进入系统。为了方便能够远程解锁这里借助genkernel来帮我们构建一个支持ssh同时还带有zfs工具的initramfs。

首先创建文件夹(用于存放公钥):

1
mkdir -pv /etc/dropbear/

创建并编辑/etc/dropbear/authorized_keys文件将自己的公钥填写进去:

1
vi /etc/dropbear/authorized_keys

使用genkernel重新生成initramfs:

1
genkernel --ssh --zfs initramfs

修改/etc/default/grub文件(这里是给内核参数让其在initramfs就直接配置好一个临时的ip用于远程使用)的GRUB_CMDLINE_LINUX,添加内容如下:

1
GRUB_CMDLINE_LINUX="dozfs root=ZFS=rock/os/gentoo ip=172.22.0.3/24 dosshd gk.sshd.port=22  root_trim=yes rhgb  alpha_support=1 loglevel=7 iommu=pt intel_iommu=on"

这里的ip就是后续要远程的ip,我这里是通过其他的设备跳转过来的出于安全考虑我并没有配置网关。

具体的配置可以参考: Initramfs kernel command-line parameters

重新生成grub的配置:

1
grub-mkconfig -o /boot/grub/grub.cfg

验证

重启之后使用ssh远程到配置好的ip上:

1
ssh root@172.22.0.3

这里就会到一个远程的交互shell,在这个shell中包含了基本的zfs工具,我们可以使用unlock-zfs来去解锁zpool:

1
unlock-zfs

在解锁zpool的时候会提示输入密码,输入密码同样是没有回显的。

在解锁完成之后我们还需要输入以下这条命令让其进入到正常的引导流程中:

1
resume-boot

参考链接

结束语

在这次的迁移当中学到了很多关于文件系统的概念,单单从数据上来说首先体验到的一个好处就是文件压缩的功能,在之前的文件系统上是有200G左右的数据,迁移到ZFS之后在140G左右非常的明显。

ZFS感觉是功能很强大,有很多的功能还是没有用上,后期研究研究如何将一些特性应用到实际的场景当中。

玩的开心~