ZFS 文件系统

请以(OpenZFS)官方文档为准:OpenZFS 文档

oracle 也有个文档是 Solaris 的 zfs:OracleSolarisZFS 文档

本文使用的是 Ubuntu 22.04 系统。使用的是 OpenZFS。

本文仅个人学习使用 zfs 笔记。

概念

许多常见的文件系统都被设计用在一个单独的分区或者逻辑卷上(例如 ext3/4 及 NTFS)。ZFS 则整合了逻辑卷管理功能,因此一个存储池可以被部署在许多块存储盘之上。存储池在 ZFS 中被称为 zpool 。在一个 zpool 里面可以存在多个 VDEV (Virtual DEVices, 虚拟设备) 。写入 zpool 的数据将被分散到各个 VDEV 上。

虽然 zpool 和 RAID 看上去的确很相似,ZFS 并不会简单地将要写的数据平均写到各个盘。实际上,ZFS 有一套很复杂的分配及调度机制来决定何时写数据,以及数据将写到何处。

zfs 支持以下几种磁盘阵列组合方式:

  • single(default):不指定时默认为此种方式组合。简单的单盘 VDEV,类似 raid0
  • mirror:镜像 VDEV,允许多块存储盘,只要一块可用就可以保证数据安全,性能最高。类似于 RAID 1
  • raidz:最少需要 3 块磁盘,允许 1 块损坏。类似 raid5
  • raidz2:最少需要 4 块磁盘,允许 2 块损坏。类似 raid6
  • raidz3:最少需要 5 块磁盘,允许 3 块损坏。

raidz 阵列需要至少 2 + p 块存储盘(p 代表 raidz 冗余等级,raidz 中 p 为 1,raidz2 中为 2,raidz3 中为 3)。举个例子,假设所有盘都是相同容量:

  • 2 存储盘 + 1 冗余盘 (raidz)
    • 2 / 2+1 ≈ 66.7% 空间效率,允许 3 块盘中损失 1 块
  • 4 存储盘 + 1 冗余盘 (raidz)
    • 4 / 4+1 = 80% 空间效率,允许 5 块盘中损失 1 块
  • 4 存储盘 + 2 冗余盘 (raidz2)
    • 4 / 4+2 ≈ 66.7% 空间效率,允许 6 块盘中损失 2 块

注意: 的是如果混用不同容量的盘,则 ZFS 将把所有盘视作最小盘的大小。

设备选择

创建存储池时,有多种方式让我们选择设备。推荐使用 /dev/disk/by-id/ 方式创建你的阵列。以下简单列举几种方式:

  • /dev/sdX、/dev/hdX:适合开发/测试
    • 在所有的 Linux 发行版都是可用的,但是名字并不是固定的,并且会根据磁盘的检测顺序而改变。当发生变化后需要删除/etc/zfs/zpool.cache文件并使用新名称重新导入池。
  • **/dev/disk/by-id/**:适合小型池(少于 10 个磁盘)
    • 此目录包含具有更易读名称的磁盘标识符。磁盘标识符通常由接口类型供应商名称型号设备序列号分区号组成。标识符名称是永久的,并且不受磁盘插入物理位置,识别顺序的先后影响。
    • 注意此方式不适合虚拟机环境下。因为虚拟机并不会生成正确的唯一标识符。
  • **/dev/disk/by-path/**:适用于大型池(大于 10 个磁盘)
    • 此目录包含系统中物理电缆布局的设备名称,这意味着特定磁盘绑定到特定位置。该名称描述了 PCI 总线号,以及机箱名称和端口号。这允许在配置大型池时进行最大程度的控制。
  • **/dev/disk/by-uuid/**:不是一个很好的选择
  • /dev/disk/by-partuuid/by-partlabel:仅适用于现有分区
    • 分区UUID是在创建时生成的,所以使用受限。而且如果没有提前记录相关信息,定位故障盘会变的很困难。

存储池

zfs 中,使用 zpool 命令创建与维护。

创建存储池

指令格式如下:

1
zpool create <options> <pool_name> [raidz|raidz2|raidz3|mirror] <volumes/devices>
1
2
# 示例
zpool create my_pool /dev/sda /dev/sdb /dev/sdc

使用上面的命令 zpool 会创建存储池后,自动挂载存储到根目录下和存储池同名的文件夹下。使用df -hT即可查看到。

并且默认情况下如果不特别指定存储池类型,创建的是single(raid0)类型阵列。

为了使用方便,通常我们会增加几个常用参数,如下:

1
zpool create -f -o ashift=12 -m <mountpoint> <pool_name> [raidz|raidz2|raidz3|mirror] <volumes>
  • -f 用于避免 Does not contain an EFI label 错误。这个可能是 Linux 特有的错误,具体查看手册。
  • -o ashift=12 指定扇区大小。ashift 值范围从 9 到 16,默认值 0 意味着 zfs 应该自动检测扇区大小。该值实际上是一个位移值,因此 512 字节的 ashift 值为 9 (2^9 = 512),而 4,096 字节的 ashift 值为 12 (2^12 = 4,096),8,192字节的 ashift 值为 13 (2^13 = 8,192)。具体查看官方手册-高级格式化磁盘
  • -m <mountpoint> 指定默认挂载点。

创建 raidz(raid5)类型的存储池,并且指定格式化扇区单元为 4k 并指定挂载点位置。

1
2
# 示例
sudo zpool create -f -o ashift=12 -m /mnt/zfs-raid5-pool raid5-pool raidz /dev/sda /dev/sdb /dev/sdc

创建raid(10)类型的存储池,创建需要最少 4 块硬盘,在同一个存储池中创建两个镜像。

1
sudo zpool create -f -o ashift=12 -m /mmt/zfs-raid10-pool raid10-pool mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sdd

删除存储池

删除存储池之前必须停止所有的活动,命令行不能在挂载点的目录内,关闭所有打开的相关文件。如果无法达成上述操作,那么只能使用 强制删除

1
2
3
4
sudo zpool destroy [-f] <pool name>

# 强制删除
sudo zpool destroy -f <pool name>

查看存储池状态

可以执行显示某个存储池,也可以不指定显示所有存储池。

1
sudo zpool status [options] [pool name]

部分参数列举:

  • -x:仅显示出现错误或不可用的存储池状态。
  • -v:显示详细的错误信息。
  • -t:显示 VDEV 的 TRIM 状态
  • -s:显示 VDEV 的慢 I/O 操作数量,这是未完成的 I/O 操作数 zio_slow_io_ms毫秒 (30000 默认情况下)。这并不一定意味着 I/O 操作无法完成,只是花费了不合理的长时间。这可能表明底层存储存在问题。
  • -P:显示 VDEV 的完整路径
  • -p:以可解析(精确)值显示数字。
  • -g:显示 VDEV 的 GUID ,可用于代替 zpool detach/offline/remove/replace 命令的设备名称。
  • -i:显示 VDEV 初始化状态。
  • -c:显示额外的指定磁盘信息。具体参考下面列举:
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
37
38
39
40
41
42
43
Available 'zpool status -c' commands:
ata_err Show SMART ATA errors (ATA).
ses Show disk's enc, enc device, slot, and fault/locate LED values.
temp Show SMART drive temperature in celsius (all drives).
encdev Show /dev/sg* device associated with the enclosure disk slot.
fault_led Show value of the disk enclosure slot fault LED.
iostat-1s Do a single 1-second iostat sample and show values.
dm-deps Show device mapper dependent (underlying) devices.
iostat Show iostat values since boot (summary page).
test_progress Show SMART self-test percentage done.
nonmed Show SMART non-medium errors (SAS).
realloc Show SMART reallocated sectors count (ATA).
pwr_cyc Show SMART power cycle count (ATA).
serial Show disk serial number.
upath Show the underlying path for a device.
r_proc Show SMART read GBytes processed over drive lifetime (SAS).
size Show the disk capacity.
defect Show SMART grown defect list (SAS).
enc Show disk enclosure w:x:y:z value.
test_type Show SMART self-test type (short, long... ).
pend_sec Show SMART current pending sector count (ATA).
smart_test Show SMART self-test results summary.
w_ucor Show SMART write uncorrectable errors (SAS).
cmd_to Show SMART command timeout count (ATA).
locate_led Show value of the disk enclosure slot locate LED.
test_ended Show when the last SMART self-test ended (if supported).
vendor Show the disk vendor.
label Show filesystem label.
r_ucor Show SMART read uncorrectable errors (SAS).
off_ucor Show SMART offline uncorrectable errors (ATA).
smart Show SMART temperature and error stats (specific to drive type)
smartx Show SMART extended drive stats (specific to drive type).
media Show whether a vdev is a file, hdd, ssd, or iscsi.
model Show disk model number.
slot Show disk slot number as reported by the enclosure.
iostat-10s Do a single 10-second iostat sample and show values.
test_status Show SMART self-test status.
w_proc Show SMART write GBytes processed over drive lifetime (SAS).
health Show reported SMART status (all drives).
rep_ucor Show SMART reported uncorrectable count (ATA).
nvme_err Show SMART NVMe errors (NVMe).
hours_on Show number of hours drive powered on (all drives).
lsblk Show the disk size, vendor, and model number.

查询存储池列表

1
sudo zpool list

重新设置挂载点

mountpoint 属性更改时,文件系统将自动从旧挂载点取消挂载,并重新挂载到新挂载点。挂载点目录根据需要进行创建。如果 ZFS 由于文件系统正处于活动状态而无法将其取消挂载,则会报告错误,并需要强制进行手动取消挂载。

1
sudo zfs set mountpoint=<mount path(abs)> <pool name>

当 mountpoint 设置为 legacy或者none时,则 zfs 不会在自动挂载该存储池。

手动挂载参考:oracle-管理 zfs 挂载点

取消挂载存储池

1
sudo zfs unmount [-f] <pool name>

使用-f参数强制取消挂载

挂载所有存储池

1
sudo zfs mount -a

查看所有存储池挂载状态

1
sudo zfs mount

查看所有已挂载存储池

1
sudo zfs get mounted

查看所有存储池挂载点信息

1
sudo zfs get mountpoint

查看存储池修改历史

1
sudo zpool history <pool name>

添加新的 VDEV

添加新的 VDEV 来扩展存储池的空间。

1
sudo zpool add <pool name> [raidz|raidz2|raidz3|mirror] <volumes/devices>

zpool.add.8 有该指令的详细描述

替换 VDEV

当设备发生损坏/故障时,可以使用 replace命令替换故障的设备。

1
sudo zpool replace <pool name> <old volume/device> <new volume/device>

zpool.replace.8 有该指令的详细描述

转换简单盘为 mirror

1
sudo zpool attach <pool name> <exisitng_volume/VDEV_name> <new_volumes>

zpool.attach.8 有该指令的详细描述

删除存储池中的设备

目前,OpenZFS 只支持从不包含 RAIDz 的存储池中移除 single 和 mirror 类型的 VDEV。这个操作会把数据迁移至剩下的存储盘上,并相应地降低存储池的大小。

1
sudo zpool remove <pool name> <volume/device>

zpool.remove.8 有该指令的详细描述

导入导出存储池(迁移)

如果要在别的设备/操作系统上使用存储池,首先需要将存储池导出。

1
sudo zpool export <pool name>

导入存储池时需注意,默认会使用非持久命名导入数据盘。为了避免磁盘排布变化造成无法开机时载入存储池,导入时应注明从哪个位置搜索磁盘:

1
zpool import -d /dev/disk/by-id <pool name>

快照

得益于cow机制,zfs 的快照非常快速,基本不占用额外的空间,但是因为快照会让 zfs 保留旧的数据,时间长了之后快照就会占用很多空间。

建议经常清理快照。

创建快照

1
2
3
sudo zfs snapshot <pool name>@<snapshot name>
# 示例
zfs snapshot raid5-pool@2022-08-28

查看快照

1
sudo zfs list -t snapshot

删除快照

1
2
3
sudo zfs destroy <pool name>@<snapshot name>
# 示例
sudo zfs destroy raid5-pool@2022-08-28

回滚快照

1
sudo zfs rollback -r <pool name>@<snapshot name>
  • -r:销毁比指定快照更新的快照,并回滚。(销毁指定快照之后创建的快照,并回滚)

zfs.rollback.8 有该指令的详细描述

数据清理&重建

本文未实践过的操作,此处记录备份,日后有机会更新完善此处。

zfs 默认不进行任何清理检查工作。

只有在读取到错误的数据时,zfs 才会知道数据已经损坏。

定期执行 scrub 指令可以,尽早检测出错误。

1
sudo zpool scrub <pool name>

建议设置上面的指令为计划任务,每周 或者 每月一次。

当 zfs 对您的存储池进行数据清理(scrub)时,它将根据其已知的校验值和逐一检查存储池中的每个块。默认情况下,从上到下的每个块都使用适当的算法进行校验。当前是使用的“fletcher4” 算法,它是 256 位的算法,速度很快。当然你也可以更改为使用 SHA-256 算法,但不推荐这样做,因为计算 SHA-256 校验和的开销比 fletcher4 更大。

如果阵列磁盘发生了损坏。我们需要手动 replace 阵列中的磁盘。然后等待 zfs 的自愈机制,自动修复该数据。