在命令行中使用 ZFS 相关命令
坑边闲话:TrueNAS 提供了良好的 ZFS 使用体验,但是频繁使用 Web 界面并非是一个令人舒适的事情。我的原则是,与存储相关的问题尽量在命令行里解决。此外,将 Debian 系统配置为一个理想的存储服务器是很吸引人的,但这依旧需要用户对存储管理的命令行有深入的理解。
本文包括但不限于以下内容;
- ZFS 打快照、删除快照
- ZFS 存储吃创建
- ZFS 文件系统创建与销毁
- ZFS 文件系统的参数调整
1. 安装 ZFS 相关组件·
Debian 或者 Ubuntu 等 Linux 发行版不具备 TrueNAS 的网页图形化界面,因此操作 ZFS 时颇为繁琐,为此我们做简单介绍。
1.1 常规安装·
在 Debian 12 的 的 stable 上游中,zfs-linux
的版本比较老旧,很多新的特性无法支持。为此,我们需要使用 bookworm-backports
中的 zfs-linux
.
首先确保在 apt
配置文件里开启了相关上游,即确保下面的行存在:
1 | deb http://deb.debian.org/debian bookworm-backports main contrib |
随后,修改系统 APT 偏好:
1 | sudo tee /etc/apt/preferences.d/90_zfs > /dev/null << EOF |
其中,Pin-Priority
数字越大优先级越高。若 990
依然不够大,可自行调整相对优先级。
最后,安装 ZFS 相关模块即可:
1 | sudo apt update |
随后软件会自行注册两个 Systemd 服务:
1 | Created symlink /etc/systemd/system/zed.service → /lib/systemd/system/zfs-zed.service. |
1.2 问题排查·
如果遇到 zfs-load-module.service
服务失败时,大概率是 Linux Kernel 升级了但是系统并没有下载对应的 linux-headers
文件,可通过下列命令安装头文件并重新安装 ZFS DKMS 模块。
1 | sudo apt install linux-headers-$(uname -r) |
2. 存储池相关·
存储池是最关键的部分,因为它是数据集的底层。存储池与 RAID 类型密切相关,一般存储池将会涉及以下内容:
- 存储冗余度,
- 虚设备,如 Slog、L2ARC 等。
如果不能保证底层的可靠,用户侧做再多设置都是错的。接下来将介绍与存储池相关的高频操作。
2.1 创建存储池·
对于磁盘列表,可以使用 nvme-cli
等工具查看硬盘列表。
对于 SATA 硬盘,可能需要使用 lsblk
等命令进行查看。
获得了磁盘列表,并成功地将物理磁盘与磁盘句柄(盘符)进行配对之后,即可进行存储池创建:
1 | $POOL_NAME=tank |
2.2 对存储池进行导入和导出·
在创建存储池之后,如果需要将硬盘拔除,则需要先把存储池下线,即 export
导出。
1 | $POOL_NAME=tank |
如果需要恢复存储池,则使用相反的命令 import
导入。
1 | $POOL_NAME=tank |
如果需要对存储池进行重命名,则需要先导出再导入且在导入的时候使用新的名字,同时应该注意,并没有 zpool-rename
这种命令。
1 | # Rename a zfs pool |
此外,不加参数地使用 zfs import
可以列出所有可以被导入的 ZFS 存储池。
2.3 设置存储池相关属性·
2.3.1 autotrim
·
TRIM 是一种用于固态硬盘的操作,用来通知 SSD 固件哪些数据块已经不再被文件系统使用,可以被标记为空闲。这有助于 SSD 执行垃圾回收从而提高性能和寿命。
- 文件系统的角色:
- 文件系统知道哪些数据块被删除或释放(例如文件删除或文件系统调整时)。
- TRIM 命令由文件系统发出,通知存储设备这些块可以清除。
- SSD 固件的角色:
- SSD 固件接收 TRIM 命令后,将对应的块标记为空闲。
- 固件会根据内部策略在适当时间对这些块执行垃圾回收或优化操作。
延迟 TRIM 操作可能提高效率,但会带来短期性能下降的风险,因为 SSD 的写入性能与剩余可用块的数量密切相关。
通过 zpool trim
命令,可以手动进行 TRIM. 反之,通过设置存储池的自动 trim 参数,也可激活自动 trim 行为。
1 | sudo zpool set autotrimi=on ${POOL_NAME} |
该参数的引入可能会导致存储池频繁进行垃圾回收,进而降低性能。
2.3.2 bclone
·
通过执行下列命令,可开启 ZFS 内核模块的相关特性。
1 | bash -c ' |
开启 bclone
特性之后,在同一个存储池内部拷贝数据,将直接触发 ZFS 内部函数,通过创建新的 dentry
、inode
并指向原有的数据块,可快速拷贝新文件同时不占用双倍空间。不过需要注意,在进行 ZFS Replication 时,zfs send
会把所有节约的空间重新展开,如果目标池没有足够的空间,将无法完成 replication 工作。
3. 数据集相关·
在完成了底层存储池的构建之后,我们所关心的主要是用户侧的内容,如存储池和文件共享等。
存储池已经
- 通过 data vdev 提供了基本的存储能力,
- 通过 Slog 和 L2ARC 提供了读写强化(可选),
- 通过 Hot spare 盘提供了一定程度的故障自动转移(可选)。
数据集就是建立在存储池之上的抽象的文件系统,也是用户真正可读写的最基本单元。存储池使用 zpool
系列命令进行配置,数据集通过 zfs
命令进行配置。从命令名也可以看出,数据集就是真正的 ZFS 文件系统。
3.1 创建数据集·
3.1.1 创建 ZFS 文件系统·
文件系统,指的是可以在里面创建目录、文件的 ZFS 对象。执行下列命令,可以直接创建一个最基本的文件系统。
1 | $POOL_NAME=tank |
3.1.2 创建 ZVol 块设备·
ZVol 是一种基于 ZFS 的块设备,可以类比为一块虚拟磁盘,可以用来格式化、创建其他类型的文件系统。一般来说,在 Linux 系统上都有 ZFS 模块,因此没有使用块设备的必要性,但是如果要通过 iSCSI 给 Windows 系统共享块存储,ZVol 就是一个很明智的选择。
1 | $POOL_NAME=tank |
由此,可以创建一个 ZVol,它具有以下性质:
- 容量:128 GiB
- 底层块压缩方式:LZ4 算法
- volume 块大小:16 KiB
- 名字:
Intel_750_RAID-Z1/openwrt-compile
3.2 设置数据集的相关属性·
熟悉 C# 编程的朋友应该清楚,通过设置一对 get
、set
函数,可以用来获取、修改 class 的属性。ZFS 的数据集属性访问与 C# 的语法非常相似。
- 获取属性使用
zfs get
- 设置属性使用
zfs set
3.2.1 设置压缩方式·
1 | sudo zfs set compression=lz4 ${POOL_NAME}/${DATASET_NAME} |
3.2.2 设置权限类型·
1 | sudo zfs set acltype=nfsv4 ${POOL_NAME}/${DATASET_NAME} |
3.2.3 设置挂载入口·
这是唯一一个特殊的命令,它使用 zfs
控制存储池。在存储池创建之后,原则上需要一个文件系统入口才可被用户访问,mountpoint
即该入口。
1 | sudo zfs set mountpoint=${/PATH/OF/MOUNT_POINT}] ${POOL_NAME} |
设置了存储池的入口之后,存储池下的数据集也将以对应目录结构被自动挂载。
3.2.4 重命名数据集·
使用 zfs rename
命令可对数据集进行重命名,如
1 | $OLD_DATASET_NAME |
4. 快照相关·
4.1 创建快照·
打快照建议使用标准化命名方式,随意取名字可能会导致后期管理紊乱。
1 | sudo zfs snapshot ${POOL_NAME}/${DATASET_NAME}@$(date +"manual-%Y-%m-%d_%H-%M") |
4.2 移除快照·
过于老旧的快照会占用很多不必要的空间,及时清理不再需要的快照是个好习惯。
1 | sudo zfs destroy ${POOL_NAME}/${DATASET_NAME}@${SNAPSHOT_NAME} |
4.3 创建 systemd 快照任务·
用户可根据自身需求,定期创建快照。快照遵循严格的命名规范,后期可根据快照名进行自动化备份。
4.3.1 创建快照生成文件·
1 | sudo vim /usr/local/bin/zfs_snapshot.sh |
填入以下内容:
1 |
|
随后赋予该文件可执行权限:
1 | sudo chmod +x /usr/local/bin/zfs_snapshot.sh |
4.3.2 创建 systemd 任务·
编辑 systemd 服务文件:
1 | sudo vim /etc/systemd/system/zfs_snapshot.service |
填写以下内容:
1 | [Unit] |
随后,创建定时器文件进行时间调度。编辑定时器文件:
1 | sudo vim /etc/systemd/system/zfs_snapshot.timer |
填写以下内容:
1 | [Unit] |
最后,开启 systemd 相应定时器:
1 | sudo systemctl daemon-reload |
此后,系统将会周期性地对目标数据集执行快照。
5. 数据集迁移与备份·
5.1 数据集迁移·
ZFS 数据集迁移比较优雅,极具 Unix I/O 流的特点。
- ZFS 数据集可以被序列化为 io_stream,因此可以把数据集保存为一个本地的文件;
zfs send
生成流,zfs receive
接受流,本地某文件也可以接受流;- ZFS 快照之间的增量部分也可以被重定向为流。
由此可见,ZFS 数据集迁移非常灵活。一个常见的模式如下:
1 | sudo zfs send ${POOL_NAME}/${DATASET_NAME}@${SNAPSHOT_NAME} | sudo zfs receive ${TARGET_POOL}/${TARGET_DATASET} |
如果接受数据流的是另一台机器,则可通过网络使用 SSH 传输:
1 | sudo zfs send ${POOL_NAME}/${DATASET_NAME}@${SNAPSHOT_NAME} | ssh -p ${PORT} ${USERNAME}@${HOSTNAME} sudo zfs receive ${TARGET_POOL}/${TARGET_DATASET} |
在 send 和 receive 之间添加 pv -tea
步骤,可以看到当前管道的传输速率。命令格式如下:
1 | zfs send ... | pv -tea | zfs receive ... |
此外,若要指定接收端新建数据集的特性,可使用 -o
参数,如:
1 | sudo zfs send ${POOL_NAME}/${DATASET_NAME}@${SNAPSHOT_NAME} | pv -tea | sudo zfs receive -o compression=lz4 -o recordsize=128k Toshiba_MG06S_RAID-Z1/Downloads |
其中,
compression=lz4
将会设置接收端的压缩算法;recordsize=128k
将会设置接收端的 ZFS 记录值。
5.2 查看快照与数据集之间的差异·
1 | sudo zfs diff -F ${POOL_NAME}/${DATASET_NAME}@${OLD_SNAPSHOT} ${POOL_NAME}/${DATASET_NAME}@${NEW_SNAPSHOT} | while read -r line; do echo -e "$line"; done |
5.2 TrueNAS 数据迁移要诀·
使用 SSH+netcat 进行 replication 时需要注意两边至少有一方安装了 py-libzfs.
1 | sudo apt install python3-libzfs |
总结·
ZFS 因为优秀的特性使得它在一众文件系统中独领风骚很多年,至今它都是最优秀的文件系统之一,可以说在开源方案中,ZFS 是最优秀的之一。然而,因为 Linux Kernel 和 OpenZFS 的开源许可冲突,导致两者至今不能合并,这或许也是一个开源届的损失。但是我认为,这依旧是社会契约的一种合理表现。或许在未来的某一天我们能够看到 OpenZFS 正式融入 Linux Kernel.
除此之外,本文不涉及 boot from ZFS,毕竟这还不是一个非常完善的操作。使用 ZFS 进行系统引导,可参考 ZFSBootMenu 工程项目。