☰
Ubuntu 上 ZFS 存储池实战:从安装到快照与数据保护
2026/10/22 8:30:54 网站建设 项目流程

1. 为什么要在 Ubuntu 上折腾 ZFS

如果你手头有一台 Ubuntu 机器,不管是家里淘汰下来的旧台式机、公司退役的二手服务器,还是自己攒的一台小主机,只要硬盘超过两块,迟早会碰到一个问题:数据怎么放才安心。普通的 ext4 分区用起来没问题,但一旦涉及多盘管理、快照回滚、数据校验,它就有点力不从心了。ZFS 就是在这个场景下进入视野的。

ZFS 最早是 Sun 公司在 Solaris 上搞出来的文件系统,后来开源社区把它移植到了 Linux 平台,也就是 OpenZFS。它跟传统文件系统最大的区别在于:ZFS 不只是个文件系统,它同时管着卷管理、RAID、快照、压缩、去重、校验和。你可以把它理解成一个“存储管家”,从物理磁盘到文件目录,中间那一层它全包了。在 Ubuntu 上装 ZFS,最直接的好处就是你能用上写时复制(Copy-on-Write)、端到端校验、原子快照这些企业级特性,而且不需要额外买硬件 RAID 卡。

这篇文章适合谁看?如果你是个有一定 Linux 基础的运维或者开发,手里有 Ubuntu 机器,想搞一套靠谱的存储方案,那这篇内容就是给你写的。如果你刚接触 Linux,也没关系,我会把每一步拆开讲,包括为什么要这么做、参数怎么算、踩过哪些坑。全文基于 Ubuntu 22.04 LTS 和 24.04 LTS 的实操经验,ZFS 版本以 Ubuntu 仓库自带的 OpenZFS 2.2.x 为主,其他版本大同小异。

先说清楚一件事:ZFS 对内存的要求比 ext4 高。它有个 ARC(Adaptive Replacement Cache),默认会吃掉大量内存来做缓存。官方建议每 1TB 存储配 1GB 内存,再加 4GB 基础内存。但这是推荐值,不是硬性门槛。我试过在 8GB 内存的机器上跑 4TB 的 ZFS 池,日常用没问题,只是别指望它跑得飞快。所以如果你机器内存小于 4GB,建议先加内存再考虑 ZFS。

另外,ZFS 在 Linux 上的授权是 CDDL,跟 Linux 内核的 GPL 不兼容,所以它不能直接编进内核,只能作为内核模块加载。Ubuntu 从 16.04 开始就把 ZFS 作为默认支持的文件系统之一,安装包里直接带,不需要自己编译。这也是我推荐在 Ubuntu 上玩 ZFS 的原因——省事。

2. 安装前的准备工作与磁盘规划

2.1 确认系统版本与内核头文件

在动手之前,先确认你的 Ubuntu 版本和内核版本。打开终端,执行:

lsb_release -a uname -r

输出里会显示你的发行版代号和内核版本。ZFS 模块是跟内核版本绑定的,每次内核升级,ZFS 模块都需要重新编译或者等待官方更新。Ubuntu 官方仓库里的zfs-linux包会跟着内核更新走,所以只要你正常apt upgrade,一般不会出问题。但如果你用的是自己编译的内核,那就得手动处理了。

我建议在安装 ZFS 之前,先确保系统已经更新到最新:

sudo apt update sudo apt upgrade -y

这一步不只是为了 ZFS,也是为了确保linux-headers跟当前内核匹配。因为 ZFS 模块编译时需要内核头文件,如果头文件版本对不上,dkms会报错。你可以用下面这条命令检查头文件是否已安装:

dpkg -l | grep linux-headers-$(uname -r)

如果没有输出,说明头文件没装,补上:

sudo apt install linux-headers-$(uname -r)

2.2 磁盘选择与池布局设计

ZFS 的核心概念是池(pool),也叫 zpool。一个池可以由一块或多块磁盘组成,池下面再划分数据集(dataset),数据集相当于传统意义上的分区,但更灵活。池的布局决定了你的容量、性能和冗余能力。

常见的池布局有这么几种:

布局类型最少磁盘数容量利用率冗余能力适用场景
stripe1100%无临时数据、测试
mirror250%允许坏一块系统盘、重要数据
raidz13(n-1)/n允许坏一块家用 NAS、一般存储
raidz24(n-2)/n允许坏两块重要数据、大容量
raidz35(n-3)/n允许坏三块极高可靠性需求

举个例子,如果你有 4 块 4TB 硬盘,做 raidz1 的话,可用容量是 (4-1)×4TB = 12TB,允许任意一块盘坏掉而不丢数据。做 raidz2 的话,可用容量是 (4-2)×4TB = 8TB,允许任意两块盘同时坏。mirror 的话就是两两配对,可用容量 8TB,但性能更好,重建速度也快。

注意:ZFS 的 raidz 不是传统 RAID5/6,它没有写洞问题,重建时只重建实际有数据的块,所以比硬件 RAID 更安全。但 raidz1 在大容量磁盘(单盘 4TB 以上)上重建时间可能很长,期间如果再坏一块就全没了。所以单盘超过 2TB 的话,我建议至少上 raidz2。

选磁盘的时候,尽量用同一品牌、同一型号、同一容量的盘。ZFS 虽然允许混搭,但池的容量会以最小那块盘为准,而且性能会受最慢那块盘拖累。如果你手头有不同容量的盘,可以考虑做多个 vdev(虚拟设备),每个 vdev 内部保持一致。

还有一点:ZFS 最好直接管理裸盘,不要经过硬件 RAID 卡。硬件 RAID 会把磁盘的物理特性隐藏掉,ZFS 拿到的是一块逻辑盘,校验和重建都会受影响。如果你服务器上有 RAID 卡,建议把每块盘都配成 JBOD 模式,让 ZFS 直接看到物理盘。

2.3 安装 ZFS 软件包

准备工作做完,安装本身其实很简单:

sudo apt install zfsutils-linux -y

这个包会拉进来zfs-dkms、zfs-zed等依赖。zfs-dkms负责编译内核模块,zfs-zed是 ZFS 的事件守护进程,用来处理磁盘热插拔、池状态变化等事件。安装过程中会编译内核模块,根据机器性能,大概需要几十秒到几分钟。

安装完成后,验证一下模块是否加载:

lsmod | grep zfs

如果看到zfs、zcommon、znvpair等模块,说明加载成功。如果没有,手动加载:

sudo modprobe zfs

再检查 ZFS 版本:

zfs version

输出会显示zfs-2.2.x和zfs-kmod-2.2.x,两者版本应该一致。如果不一致,说明内核模块没编译成功,需要检查dkms status。

3. 创建池与数据集的核心操作

3.1 识别磁盘设备名

在创建池之前,得先知道哪块盘是哪块。用lsblk看:

lsblk -o NAME,SIZE,TYPE,MODEL

输出会列出所有块设备,包括磁盘和分区。ZFS 推荐使用磁盘的稳定标识符,而不是/dev/sda这种名字。因为/dev/sdX的顺序可能会变,重启后可能就乱了。稳定标识符有两种:/dev/disk/by-id/和/dev/disk/by-path/。我一般用by-id,因为它包含磁盘序列号,最可靠。

查看磁盘 ID:

ls -l /dev/disk/by-id/

你会看到类似ata-WDC_WD40EFRX-68N32N0_WD-WCC7K1234567这样的名字。创建池的时候就用这个路径。

提示:如果磁盘之前有分区或文件系统,ZFS 会拒绝使用。可以用wipefs -a /dev/disk/by-id/xxx清除,或者用sgdisk --zap-all清分区表。操作前确认盘里没重要数据。

3.2 创建存储池

假设你有四块 4TB 盘,ID 分别是ata-DISK1、ata-DISK2、ata-DISK3、ata-DISK4,想做一个 raidz2 池,名字叫tank:

sudo zpool create -o ashift=12 tank raidz2 \ /dev/disk/by-id/ata-DISK1 \ /dev/disk/by-id/ata-DISK2 \ /dev/disk/by-id/ata-DISK3 \ /dev/disk/by-id/ata-DISK4

这里有几个关键参数需要解释:

  • ashift=12:这是扇区大小,12 表示 2^12 = 4096 字节。现代磁盘基本都是 4K 扇区,如果 ashift 设成 9(512 字节),会导致读写放大,性能下降。这个参数一旦设定就不能改,所以创建池时一定要设对。对于 4K 盘,ashift=12 是标准选择。如果是 512e 盘(物理 4K,逻辑 512),也建议设 12。

  • raidz2:冗余级别,允许两块盘故障。

  • 池名tank:这是惯例叫法,你可以改成任何名字,但建议用简短、有意义的名字。

创建完成后,用zpool status查看状态:

sudo zpool status tank

输出会显示池的状态、每个 vdev 的磁盘、以及 scrub 进度(刚创建时可能在做初始校验)。如果看到state: ONLINE,说明池正常。

再用zpool list看容量:

sudo zpool list tank

输出里的SIZE是原始容量,ALLOC是已分配,FREE是可用,CAP是使用率。注意 raidz2 的可用容量不是简单的 (n-2)×单盘容量,因为 ZFS 有校验块和元数据开销,实际可用会略少。

3.3 创建数据集与属性调优

池创建好之后,默认会挂载到/tank。但实际使用中,我们一般会在池下面创建数据集,因为数据集可以单独设属性,比如压缩、配额、快照策略。

创建一个数据集:

sudo zfs create tank/data

这会在/tank/data创建一个目录,但背后是一个独立的数据集。你可以用zfs list查看:

sudo zfs list

输出会列出tank和tank/data,以及各自的已用空间、可用空间、挂载点。

接下来设置一些常用属性:

sudo zfs set compression=lz4 tank/data sudo zfs set atime=off tank/data sudo zfs set xattr=sa tank/data sudo zfs set recordsize=1M tank/data

逐个解释:

  • compression=lz4:LZ4 压缩算法,压缩比不错,CPU 开销极低,几乎不影响性能。实测下来,文本、日志、代码这类数据能压到 50% 以下,而且读写速度反而可能更快,因为磁盘 I/O 减少了。

  • atime=off:关闭访问时间更新。每次读文件都写一次元数据,对性能有影响,而且大多数场景不需要精确的访问时间。

  • xattr=sa:扩展属性存在 inode 里,而不是单独的目录。对 Samba、NFS 共享场景有性能提升。

  • recordsize=1M:记录大小,默认 128K。对于大文件存储(视频、备份),设成 1M 能减少元数据开销,提升吞吐。但如果是数据库这种随机读写多的场景,保持 128K 或者更小更好。

这些属性可以在创建时指定,也可以后续修改。修改只影响新写入的数据,已有数据不会自动重写。

3.4 挂载点管理与权限设置

ZFS 数据集的挂载点默认是/池名/数据集名。你可以改成其他路径:

sudo zfs set mountpoint=/mnt/data tank/data

改完之后,ZFS 会自动卸载旧挂载点,挂到新路径。注意不要设成/或/usr这种系统目录,会出问题。

权限方面,ZFS 数据集默认属于 root。如果你想让普通用户读写,可以改属主:

sudo chown -R user:user /mnt/data

或者用 ACL 做更细的控制。ZFS 支持 POSIX ACL 和 NFSv4 ACL,在 Linux 上默认用 POSIX ACL。

注意:ZFS 数据集挂载后,chmod和chown的行为跟普通文件系统一样,但快照里的权限是只读的,改不了。回滚快照时,权限也会跟着回滚。

4. 快照、回滚与数据保护实战

4.1 手动快照与自动快照

ZFS 的快照是原子操作,瞬间完成,不占额外空间(只记录变化块)。创建快照:

sudo zfs snapshot tank/data@20250101

快照名格式是数据集@快照名。你可以用zfs list -t snapshot查看所有快照:

sudo zfs list -t snapshot

输出会显示每个快照的已用空间(USED),这个值是相对于父数据集的变化量。如果快照刚创建,USED 是 0。

回滚到某个快照:

sudo zfs rollback tank/data@20250101

这会丢弃快照之后的所有写入,把数据集恢复到快照时的状态。操作不可逆,执行前确认清楚。

删除快照:

sudo zfs destroy tank/data@20250101

手动快照适合临时保护,比如升级软件前打个快照,出问题就回滚。但日常运维不可能天天手动打,所以需要自动快照。

Ubuntu 上常用的自动快照工具是zfs-auto-snapshot,安装:

sudo apt install zfs-auto-snapshot -y

安装后会创建 cron 任务,默认保留策略是:15 分钟一次保留 4 个,每小时一次保留 24 个,每天一次保留 7 个,每周一次保留 4 个,每月一次保留 12 个。你可以改/etc/cron.d/zfs-auto-snapshot和/etc/zfs-auto-snapshot/下的配置。

另一个选择是sanoid,功能更强,支持快照复制和保留策略模板。但zfs-auto-snapshot更轻量,够用。

4.2 快照发送与接收

ZFS 的send和receive是备份利器。你可以把一个数据集的快照流式传输到另一个池,甚至另一台机器。

本地复制:

sudo zfs send tank/data@20250101 | sudo zfs receive backup/data

这会创建backup/data数据集,内容跟快照一致。如果backup/data已存在,会增量接收。

增量发送:

sudo zfs send -i tank/data@20250101 tank/data@20250102 | sudo zfs receive backup/data

-i表示增量,只发送两个快照之间的差异。第一次全量,后续增量,效率很高。

远程复制可以通过 SSH 管道:

sudo zfs send tank/data@20250101 | ssh user@remote sudo zfs receive backup/data

注意:远程接收需要目标机器也装了 ZFS,而且 SSH 用户有 sudo 权限。建议用专用备份用户,配好免密和权限限制。

4.3 scrub 与数据校验

ZFS 的 scrub 会遍历池中所有块,校验校验和,发现坏块就用冗余副本修复。建议每月跑一次:

sudo zpool scrub tank

查看进度:

sudo zpool status tank

输出里会显示 scrub 的进度、已修复的错误数。scrub 期间池性能会下降,建议在低峰期跑。

如果 scrub 发现不可修复的错误,会记录在zpool status里。你可以用zpool clear tank清除错误计数,但先确认问题原因。

提示:ZFS 的校验和是端到端的,从应用写到磁盘,再从磁盘读回应用,全程校验。如果内存有问题导致数据损坏,ZFS 也能发现(前提是开了checksum,默认开)。但内存错误本身需要 ECC 内存来防,ZFS 不能替代 ECC。

5. 常见问题与排查技巧实录

5.1 池无法导入或磁盘丢失

这是最常见的问题。机器重启后,zpool status显示池UNAVAIL或DEGRADED。先看zpool import:

sudo zpool import

这会列出所有可导入的池。如果池在列表里,直接导入:

sudo zpool import tank

如果提示“one or more devices is currently unavailable”,说明有盘没识别到。检查lsblk和dmesg | grep -i error,看是不是硬件接触不良或者盘坏了。

如果盘换了插槽,/dev/sdX变了,但 ZFS 用 by-id 就没问题。如果你当初用的是/dev/sdX,那就得用zpool import -d /dev/disk/by-id指定搜索路径。

5.2 内存占用过高

ZFS 的 ARC 会尽量占用空闲内存,这是正常行为。但如果你发现系统开始用 swap,或者应用内存不足,可以限制 ARC 大小。

临时调整:

echo 4294967296 | sudo tee /sys/module/zfs/parameters/zfs_arc_max

这是设成 4GB。永久生效需要写进/etc/modprobe.d/zfs.conf:

options zfs zfs_arc_max=4294967296

然后更新 initramfs:

sudo update-initramfs -u

重启后生效。注意zfs_arc_max是字节数,4GB = 4294967296。

5.3 性能不如预期

ZFS 性能受很多因素影响。先看zpool iostat -v 1,观察每个 vdev 的读写带宽和 IOPS。如果某个盘明显慢,可能是盘有问题。

常见优化点:

  • 确认ashift=12,如果设成 9,性能会差很多。
  • 开compression=lz4,减少 I/O。
  • 调整recordsize,大文件用 1M,数据库用 16K 或 128K。
  • 加 SSD 做cache或logvdev。cache加速读,log加速同步写。
  • 关atime。

如果还是慢,检查是不是内存不足导致 ARC 命中率低。arc_summary可以看命中率:

sudo apt install zfs-zed sudo arc_summary

命中率低于 90% 的话,考虑加内存。

5.4 快照占用空间过大

快照本身不占空间,但它阻止了被引用块的释放。如果某个快照的USED很大,说明父数据集在快照之后改了很多数据。定期清理旧快照:

sudo zfs list -t snapshot -o name,used -s used

按使用量排序,删掉不需要的。自动快照工具一般有保留策略,配好就行。

5.5 内核升级后 ZFS 模块加载失败

Ubuntu 内核升级后,如果zfs-dkms没来得及编译新模块,重启后 ZFS 池可能挂不上。预防措施是确保dkms正常工作:

sudo dkms status

输出应该显示zfs模块跟当前内核匹配。如果不匹配,手动触发:

sudo dkms autoinstall

或者重新安装zfs-dkms:

sudo apt install --reinstall zfs-dkms

提示:生产环境建议锁定内核版本,或者用 Ubuntu 的 HWE 内核,官方支持周期长,ZFS 模块更新及时。

5.6 常见问题速查表

现象可能原因排查命令解决方法
池 UNAVAIL磁盘丢失或损坏zpool status、lsblk检查硬件,替换坏盘
导入失败设备名变化zpool import -d by-id用稳定标识符重新导入
内存不足ARC 占用过高arc_summary限制zfs_arc_max
性能差ashift 错误zpool get ashift重建池,设 ashift=12
快照占空间旧快照未清理zfs list -t snapshot删除旧快照
模块加载失败内核不匹配dkms status重装 zfs-dkms

6. 日常维护与进阶建议

6.1 定期检查与告警

ZFS 有个zed守护进程,可以配置邮件告警。编辑/etc/zfs/zed.d/zed.rc,设置ZED_EMAIL_ADDR和ZED_NOTIFY_VERBOSE=1。这样池状态变化、scrub 完成、磁盘故障都会发邮件。

另外,zpool status -x只输出有问题的池,适合放进监控脚本:

if ! zpool status -x | grep -q "all pools are healthy"; then echo "ZFS pool issue detected" | mail -s "ZFS Alert" admin@example.com fi

6.2 替换故障磁盘

如果池是 raidz 或 mirror,坏盘可以直接替换。假设ata-DISK2坏了,新盘是ata-DISK5:

sudo zpool replace tank /dev/disk/by-id/ata-DISK2 /dev/disk/by-id/ata-DISK5

ZFS 会自动开始 resilver(重建)。重建期间池性能下降,但数据可用。重建完成后,zpool status会显示新盘在线。

注意:重建期间不要再动其他盘,避免二次故障。如果池是 raidz1,重建时再坏一块就全没了。所以大容量盘建议 raidz2。

6.3 调整池布局与扩容

ZFS 池不能直接加盘扩容(除非是 mirror 加盘做 stripe)。要扩容,一般是加一个新的 vdev:

sudo zpool add tank raidz2 /dev/disk/by-id/ata-DISK6 /dev/disk/by-id/ata-DISK7 /dev/disk/by-id/ata-DISK8

新 vdev 的冗余级别最好跟原来一致。加完之后,池容量增加,但数据不会自动重新平衡。新写入的数据会分布到所有 vdev,旧数据还在原来的 vdev 上。

如果想把单盘池改成 mirror,可以zpool attach:

sudo zpool attach tank /dev/disk/by-id/ata-DISK1 /dev/disk/by-id/ata-DISK2

这会把 DISK1 和 DISK2 组成 mirror,然后 resilver。

6.4 备份策略建议

ZFS 的快照不是备份,它跟源数据在同一块盘上。盘坏了,快照也没了。所以必须有异地备份。我一般用zfs send定期推送到另一台机器,或者用syncoid(sanoid 套件里的工具)做增量同步。

备份频率看数据重要性。关键数据每天一次,普通数据每周一次。保留策略至少保留 3 个版本,防止误删后所有备份都过期。

提示:zfs send的流可以用gzip或zstd压缩再传输,节省带宽。但 ZFS 本身有压缩,如果数据集已经开了 lz4,再压缩效果有限。实测zstd对已压缩数据还能再压 10-20%。

6.5 个人实操心得

我在多台机器上部署过 ZFS,踩过的坑不少。最大的教训是:不要用 USB 硬盘做 ZFS 池。USB 接口不稳定,经常掉盘,一掉盘池就 DEGRADED,甚至 UNAVAIL。如果非要用外置盘,至少用 eSATA 或者 Thunderbolt。

另一个坑是ashift 设错。早期我不知道这个参数,用了默认值,结果 4K 盘跑在 512 字节扇区模式下,写入性能只有正常的一半。后来重建池设了 ashift=12,性能立刻上来了。所以创建池之前,一定确认磁盘的物理扇区大小,用blockdev --getpbsz /dev/sdX查看。

还有内存。ZFS 确实吃内存,但没传说中那么恐怖。8GB 内存跑 4TB raidz1,日常文件共享和备份完全够用。关键是别把 ARC 限制得太死,留 2-3GB 给系统就行。如果跑数据库或者虚拟机,那就得多留内存,或者加 SSD 做 L2ARC。

最后,定期 scrub 和 SMART 检查不能省。我有一块盘,SMART 没报错,但 scrub 发现了校验错误,ZFS 自动用冗余修复了。如果没有 scrub,这些坏块可能一直潜伏,等到真正读的时候才发现,那就晚了。所以每月一次 scrub,每周一次smartctl -a,花不了多少时间,但能提前发现隐患。

这个内容后续还可以这样扩展:如果你有多台机器,可以搞 ZFS 复制集群,用sanoid+syncoid做自动故障转移;或者结合iSCSI把 ZFS 池共享出去,给其他机器当块存储用。再深入一点,可以研究 ZFS 的dedup去重,不过那玩意儿吃内存太狠,一般场景不建议开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询