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),数据集相当于传统意义上的分区,但更灵活。池的布局决定了你的容量、性能和冗余能力。
常见的池布局有这么几种:
| 布局类型 | 最少磁盘数 | 容量利用率 | 冗余能力 | 适用场景 |
|---|---|---|---|---|
| stripe | 1 | 100% | 无 | 临时数据、测试 |
| mirror | 2 | 50% | 允许坏一块 | 系统盘、重要数据 |
| raidz1 | 3 | (n-1)/n | 允许坏一块 | 家用 NAS、一般存储 |
| raidz2 | 4 | (n-2)/n | 允许坏两块 | 重要数据、大容量 |
| raidz3 | 5 | (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 fi6.2 替换故障磁盘
如果池是 raidz 或 mirror,坏盘可以直接替换。假设ata-DISK2坏了,新盘是ata-DISK5:
sudo zpool replace tank /dev/disk/by-id/ata-DISK2 /dev/disk/by-id/ata-DISK5ZFS 会自动开始 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去重,不过那玩意儿吃内存太狠,一般场景不建议开。