PVE 中的 ZFS 存储全攻略:池规划、性能调优与日常运维实战

全面解析 Proxmox VE 中 ZFS 存储的部署、配置与运维——从 RAID 级别选择、ARC 缓存调优、压缩算法对比到快照备份策略,附真实踩坑记录。

前言

如果你用 Proxmox VE 搭建 Homelab,迟早会面对一个问题:存储选什么?

PVE 原生支持多种存储后端:ZFS、LVM、LVM-thin、Directory、NFS、Ceph……但在这之中,ZFS 是绝大多数 Homelab 用户的首选。原因很简单——PVE 安装时默认就会问你是否要用 ZFS,而且 ZFS 自带的数据校验、压缩、快照、去重等功能,几乎是虚拟化场景的完美拍档。

不过,ZFS 也是一个"能力越大,责任越大"的存储方案。配置不当会导致性能拉垮、内存吃光、甚至数据丢失。本文从我自己的 Homelab 实战经验出发,带你完整走一遍 PVE 中 ZFS 的规划、部署、调优和运维全流程。


1. 为什么 PVE 推荐 ZFS?

先说说 ZFS 在 PVE 场景下的核心优势:

特性对 PVE 的价值
校验和 (Checksum)自动检测并修复静默数据损坏,VM 磁盘文件不出错
透明压缩虚拟机磁盘节省 30%-50% 空间,SSD 还能减少写入量
快照 (Snapshot)秒级创建 VM/LXC 快照,备份时只需几秒
克隆 (Clone)基于快照的即时克隆,实验室快速复制环境
ARC 缓存热数据缓存在内存中,VM 读取加速
RAID 集成软件 RAID 无需硬件卡,支持 mirror / raidz / raidz2

对我来说,最关键的其实是压缩+快照这个组合。跑了一台 Jellyfin 媒体 VM,ZFS 压缩让几百 GB 的媒体文件实际占用不到 60% 的空间;升级系统前打个快照,出问题 1 秒回滚。


2. PVE 安装时的 ZFS vs 事后配置

2.1 安装时选择 ZFS

如果你是新装 PVE,安装程序会让你选择文件系统和 RAID 类型:

1
2
3
4
5
6
Filesystem options:
  ⚬ ext4 ── 传统方案,无法使用 ZFS 功能
  ● ZFS (RAID-0) ── 条带化,无冗余
  ○ ZFS (RAID-1) ── 镜像,2 盘 50% 可用
  ○ ZFS (RAID-10) ── 条带镜像,4 盘起步
  ○ ZFS (RAID-5/6) ── raidz/raidz2,3 盘起步

建议: 对于 Homelab,最稳妥的是 ZFS RAID-1(2 块盘镜像)。如果你数据量不大但追求性能,raid10(4 盘)也很不错。

2.2 事后添加 ZFS 池

大多数 Homelab 用户都是先用一块 SSD 装了 PVE,后面加硬盘时才想用 ZFS。完全来得及:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看可用磁盘
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT

# 创建 mirror 池(假设 /dev/sdb 和 /dev/sdc 是两块新盘)
zpool create -f -o ashift=12 data_pool mirror /dev/sdb /dev/sdc

# 创建 raidz 池(3 块盘,有一个盘容错)
zpool create -f -o ashift=12 data_pool raidz /dev/sdb /dev/sdc /dev/sdd

# 创建 raidz2 池(4 块盘,有两个盘容错)
zpool create -f -o ashift=12 data_pool raidz2 /dev/sdb /dev/sdc /dev/sdd /dev/sde

创建完成后,在 PVE Web UI 的 Datacenter → Storage → Add → ZFS 中将其添加为存储位置,就能用来存放 VM 磁盘和 ISO 镜像了。

⚠️ 血的教训: 一定不要用 zpool create 不加 -f 或指定整盘设备(如 /dev/sdb 而不是 /dev/sdb1)。ZFS 推荐使用整盘(whole disk),这样它自己管理分区表,避免与现有分区冲突。


3. RAID 级别怎么选?

ZFS 的 RAID 选择和硬件 RAID 有些不同,结合 Homelab 场景来说:

Mirror(镜像)

  • 最少磁盘数: 2
  • 可用容量: 50%
  • 读取性能: ✅ 好(双盘并发读)
  • 写入性能: ✅ 好
  • 重建速度: ✅ 快

推荐场景: 2~4 块 SSD 的 Homelab。性能好,重建快,扩容灵活(加一组 mirror 即可)。

1
2
3
4
5
# 2 块盘 mirror
zpool create tank mirror /dev/sda /dev/sdb

# 4 块盘 dual mirror(相当于 RAID 10)
zpool create tank mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sdd

Raidz(单盘容错,类似 RAID5)

  • 最少磁盘数: 3
  • 可用容量: (N-1)/N
  • 性能: 写性能较差(校验计算)
  • 重建速度: ❌ 慢

推荐场景: 3~5 块 HDD 的大容量存储(如媒体库)。不建议用 SSD 做 raidz——写放大和校验开销得不偿失。

Raidz2(双盘容错,类似 RAID6)

  • 最少磁盘数: 4
  • 可用容量: (N-2)/N
  • 安全性: 最高

推荐场景: 6 块以上 HDD 的重要数据存储。重建期间即使再坏一块盘也不丢数据。

我的推荐

1
2
3
SSD(VM/系统盘):mirror 或 mirror×2(RAID10)
HDD(媒体/数据):raidz2(4~6 盘)或 raidz(3 盘+定期备份)
NVMe(高性能 VM):mirror 就够了

4. 关键调优参数

ZFS 默认配置偏保守,在 Homelab 环境下值得调整的参数不少。

4.1 ashift — 对齐扇区大小

这可能是 ZFS 新手最容易忽略的参数。错误的 ashift 会导致写入放大,性能下降 50% 以上。

1
2
3
4
5
6
7
# ashift=12 对应 4K 扇区(现代 SSD/HDD 默认)
# ashift=9 对应 512B 扇区(老盘)
# ashift=13 对应 8K 扇区(某些 NVMe)
zpool create -o ashift=12 tank mirror /dev/sda /dev/sdb

# 检查当前 ashift
zdb -e | grep ashift

注意: ashift 只能在创建 pool 时设置,改不了!所以创建前一定要确认。现在 99% 的盘都是 4K 扇区(即使标 512 也是模拟的),统一用 ashift=12 最安全

4.2 recordsize — 记录大小

这影响文件的 I/O 行为,对 VM 磁盘特别重要:

1
2
3
4
5
6
# 默认 128K,对 VM 来说偏大
# 建议在 VM 数据集上设为 16K(更匹配 4K 随机 IO)
zfs set recordsize=16K data_pool/vm-100-disk-0

# 大文件存储(ISO、媒体)保持 1M
zfs set recordsize=1M data_pool/iso

经验法则:

用途推荐 recordsize
VM 系统盘(qemu 直连)4K ~ 16K
VM 数据盘64K
ISO 镜像 / 备份1M
文件共享(SMB/NFS)128K

4.3 压缩

ZFS 的透明压缩是免费的性能提升——尤其是用 SSD 时,压缩后数据量减少,实际读写速度反而更快。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看支持的压缩算法
zfs get compression data_pool

# 设置压缩(推荐 lz4,速度几乎无感知)
zfs set compression=lz4 data_pool

# 如果是 PVE 8.x 以上,试试 zstd(压缩率更高,但略慢)
zfs set compression=zstd-3 data_pool

# 查看实际压缩率
zfs get compressratio data_pool

实测数据: 我的 VM 磁盘使用 lz4 压缩,压缩比约 1.5x~2.0x。一台 50GB 的 Windows VM,实际只占 28GB。zstd-3 压缩率更高(可达 2.5x),但 CPU 开销也略高。

4.4 ARC 缓存调优

ZFS 默认会吃到物理内存的 50% 作为 ARC 缓存。如果你的 PVE 是 64GB 内存,ZFS 会吃掉 32GB——这对运行多个 VM 的主机来说太奢侈了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看当前 ARC 大小
arcstat 1

# 临时调整(立即生效但重启丢失)
echo 1073741824 > /sys/module/zfs/parameters/zfs_arc_max  # 限制为 1GB

# 永久限制:创建 /etc/modprobe.d/zfs.conf
cat > /etc/modprobe.d/zfs.conf << 'EOF'
# 限制 ZFS ARC 最大为 4GB(单位:字节)
options zfs zfs_arc_max=4294967296
# 最小 ARC(可选,保证基本缓存)
options zfs zfs_arc_min=536870912
EOF

# 更新 initramfs 使其生效
update-initramfs -u -k all

# 重启后验证
reboot
cat /sys/module/zfs/parameters/zfs_arc_max

⚠️ 坑: ARC 限制太小(低于 512MB)会导致频繁的 ARC 回收,严重降低存储性能。我的建议是:总内存的 1/4 分配给 ARC。16GB 主机给 4GB,32GB 给 8GB。


5. 日常运维命令

5.1 健康检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看池状态
zpool status -v

# 查看所有池的 IO 统计
zpool iostat -v 1

# 检查数据完整性(定期运行 scrub)
zpool scrub data_pool

# 查看 scrub 进度
zpool status data_pool

推荐定期 scrub: 在 PVE 中设置 cron 任务,每月自动 scrub 一次。ZFS 的校验能力只有在 scrub 时才会真正修复静默数据损坏。

1
2
# 每月 1 日凌晨 3 点 scrub
0 3 1 * * /sbin/zpool scrub data_pool

5.2 快照与回滚

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 手动创建 VM 快照(PVE UI 也有按钮)
# 但命令行可以批量处理
for vmid in 100 101 102; do
    zfs snapshot -r data_pool/vm-${vmid}-disk-0@pre-upgrade-$(date +%Y%m%d)
done

# 列出快照
zfs list -t snapshot

# 回滚到快照
zfs rollback data_pool/vm-100-disk-0@pre-upgrade-20240701

# 删除快照
zfs destroy data_pool/vm-100-disk-0@pre-upgrade-20240701

5.3 扩容

ZFS 的扩容方式和硬件 RAID 不同:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 方式 1:mirror 池扩容(加一组 mirror)
# 加两块新盘 /dev/sde /dev/sdf 做另一组 mirror
zpool add data_pool mirror /dev/sde /dev/sdf

# 方式 2:替换单块盘(先标记故障,再替换)
# 假设 /dev/sdb 坏了
zpool replace data_pool /dev/sdb /dev/sde

# 方式 3:raidz 扩容(OpenZFS 2.0+ 支持 raidz 扩容)
# 注意:raidz 不能像 mirror 那样直接加 vdev,需要重新创建或使用替换法

重要: ZFS 不支持从 raidz 减容或直接缩小 pool。扩容全靠加 vdev 或替换更大容量的盘。所以创建 pool 时就要想好——留够未来 2 年的容量。


6. PVE 中的 ZFS 专用配置

6.1 VM 磁盘使用 ZFS

PVE 中创建 VM 时,如果存储选择 ZFS,会自动创建格式为 vm-<VMID>-disk-<N> 的 ZVOL。默认参数已经不错,但有几个可以微调:

1
2
3
4
5
6
7
8
# 开启 VM 磁盘的 discard/trim(SSD 必备)
zfs set discard=on data_pool/vm-100-disk-0

# 关闭 atime 减少写入
zfs set atime=off data_pool/vm-100-disk-0

# 设置合适的 recordsize(见上文)
zfs set recordsize=16K data_pool/vm-100-disk-0

6.2 使用 ZFS 作为 PVE 的备份存储

PVE 的 vzdump 工具可以直接备份到 ZFS datasets:

1
2
3
4
5
6
7
8
# 创建专门存备份的 dataset
zfs create data_pool/dump

# 启用压缩
zfs set compression=lz4 data_pool/dump

# PVE Web UI → Datacenter → Storage → Add → Directory
# 路径填 /data_pool/dump,类型选 VZDump backup file

这样备份文件也能享受到 ZFS 的压缩和保护。

6.3 ZFS 与 PVE 集群

如果你有多台 PVE 组成集群(Cluster),ZFS 默认不能跨节点共享。需要配合其他方案:

  • ZFS over iSCSI — 一台 PVE 做存储节点,导出 ZVOL 给其他节点
  • NFS over ZFS — 在 ZFS dataset 上挂载 NFS 共享
  • Ceph — 如果节点够多(3+),用 Ceph 替代 ZFS 做分布式存储

7. 踩坑记录

❌ 坑 1:ARC 耗尽主机内存导致 VM OOM

现象: PVE 跑着跑着,VM 突然被 OOM Killer 杀掉。查看内存:ZFS 的 ARC 吃掉了 50% 物理内存。

原因: ZFS 默认 zfs_arc_max 是物理内存的 50%(/proc/spl/kstat/zfs/arcstats 可看),如果 PVE 上跑着吃内存的 VM(如 Windows),加上 ARC 占了将近一半,内存很快不够用。

解决: 强制限制 ARC 上限(见 4.4 节)。我 64GB 的机器限制 ARC 为 8GB,稳如泰山。

❌ 坑 2:ashift 未对齐导致写入放大

现象: 写入速度明显低于磁盘标称值,iostat 看到写量远大于实际数据量。

原因: 创建 pool 时没有指定 ashift=12,ZFS 自动检测为 ashift=9(512B),导致 4K 扇区盘每次写操作都要读-改-写,性能打 5 折。

查看方法: zdb -e pool_name | grep ashift——如果显示 9 就踩坑了。

解决: 这个参数创建后改不了。唯一的办法是备份数据、销毁重创、恢复数据。所以创建前一定要设好 ashift=12。

❌ 坑 3:磁盘名称变化导致 zpool 无法导入

现象: 系统重启后 zpool import 找不到池,或者 /dev/sdb 变成了 /dev/sdc

原因: Linux 内核给磁盘分配的 sdX 名称在重启后可能变化(尤其是插拔过硬盘或 USB 设备后)。

解决: 使用磁盘的 IDby-id 路径来创建池:

1
2
3
4
5
6
# 推荐做法:用 /dev/disk/by-id/ 创池
ls -l /dev/disk/by-id/

zpool create -o ashift=12 tank mirror \
  /dev/disk/by-id/ata-WDC_WD40EFAX-68JH4N1_XXXXXX \
  /dev/disk/by-id/ata-WDC_WD40EFAX-68JH4N1_YYYYYY

这样即使 sdX 名称变了,ZFS 也能通过磁盘的 WWN 或序列号找到正确的盘。

❌ 坑 4:重度碎片化导致性能下降

现象: 池用了 1~2 年后,写入速度逐渐变慢,zpool iostat -v 看到大量碎片。

原因: VM 磁盘频繁创建和删除导致 ZFS spacemap 碎片化。

解决:

1
2
3
4
5
6
7
# 查看碎片率
zpool list -v tank

# 降低碎片的方法:
# 1. 预留一些空闲空间(池使用率 < 80%)
# 2. 定期快照 -> 回滚 -> 删除旧数据
# 3. 极端情况:备份 -> 重建池 -> 恢复

经验: 保持池使用率在 80% 以下,碎片化不会成为大问题。超过 90% 就要小心了。


8. 完整部署示例

最后给一个从零开始的完整示例,假设你有一台 32GB 内存的 PVE,两块 1TB NVMe SSD 做系统+VM,四块 4TB HDD 做数据存储:

 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
# 步骤 1:创建系统池(mirror,两块 NVMe)
# ashift=12,开启压缩和 atime 关闭
zpool create -o ashift=12 rpool mirror \
  /dev/disk/by-id/nvme-SAMSUNG_MZVL21T0_XXXXXX \
  /dev/disk/by-id/nvme-SAMSUNG_MZVL21T0_YYYYYY

# 步骤 2:创建数据池(raidz2,四块 HDD)
zpool create -o ashift=12 data_pool raidz2 \
  /dev/disk/by-id/ata-WDC_WD40EFAX_0001 \
  /dev/disk/by-id/ata-WDC_WD40EFAX_0002 \
  /dev/disk/by-id/ata-WDC_WD40EFAX_0003 \
  /dev/disk/by-id/ata-WDC_WD40EFAX_0004

# 步骤 3:设置 rpool 参数
zfs set compression=lz4 rpool
zfs set atime=off rpool

# 步骤 4:设置数据池参数
zfs set compression=zstd-3 data_pool
zfs set atime=off data_pool
zfs set recordsize=1M data_pool  # 大文件存储

# 步骤 5:限制 ARC
cat > /etc/modprobe.d/zfs.conf << 'EOF'
options zfs zfs_arc_max=8589934592    # 8GB
options zfs zfs_arc_min=1073741824    # 1GB
EOF
update-initramfs -u -k all

# 步骤 6:创建用于备份的 dataset
zfs create data_pool/vzdump
zfs set compression=lz4 data_pool/vzdump

# 步骤 7:重启确认
reboot

完成后在 PVE Web UI 中将 rpooldata_pool 添加为存储,就可以愉快地创建 VM 了。


总结

ZFS 是 PVE 上最值得花时间研究的存储方案。它能给你带来:

收益需要付出的
✅ 数据完整性校验⏱ 规划池结构的思考时间
✅ 透明压缩节省 30-50% 空间💾 合理分配 ARC 内存
✅ 秒级快照和回滚🔧 定期 scrub 维护
✅ 软 RAID 零硬件成本📚 学习几个核心命令

记住三个黄金规则:

  1. 创建前规划好——ashift、RAID 级别、后续扩容路径,建好了就改不了
  2. 限制 ARC——不要让它吃光内存,给 VM 留够资源
  3. 定期 scrub——ZFS 的校验能力只有 scrub 时才能真正发挥作用

最后,如果你是从 ext4/LVM 迁移到 ZFS,建议先在非生产环境上实操一遍上面的命令。ZFS 虽然强大,但操作错了代价也不小。

下篇预告: PVE 中部署 Grafana + Prometheus + Node Exporter,全面监控你的 Homelab 集群。欢迎关注!

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计