ZFS

出自 ArchWiki

ZFS 是一个先进的文件系统,最初由 Sun Microsystems 开发并发布,用于 Solaris 操作系统。如今,ZFS 通常指的是 OpenZFS 分支,它将原始实现移植到包括 Linux 在内的其他操作系统,同时继续开发 Solaris ZFS。本文将 ZFS 视为 OpenZFS 的同义词。

ZFS 功能丰富。ZFS 的功能包括:改进的页面缓存算法:ARC、重复数据删除、池化存储、快照、复制、数据完整性验证和自动修复(数据擦洗)、RAID-Z 等等。

概念

与常规文件系统(文件系统驻留在单个块设备上)不同,ZFS 将数据存储在存储池中。存储池由 vdev(虚拟设备)组成,而 vdev 本身又由块设备组成。存储池始终将数据写入剩余空间百分比最高的 vdev(因此数据自然地条带化分布在 vdev 上)。另一方面,vdev 可以使用更复杂的配置,例如 RAIDZ 和镜像。在最简单的配置中,可以在由单个分区组成的单个 vdev 上创建存储池,其行为类似于常规文件系统。

创建后,可以从存储池中分配存储资源。这些资源被分组到称为数据集的单元中。有 4 种类型的数据集

  1. 文件系统:文件系统基本上是一个目录树,可以像常规文件系统一样挂载到系统命名空间中。
  2. 卷 (zvol):卷表示为块设备
  3. 快照:文件系统或卷的快照
  4. 书签:不保存数据的快照,用于增量复制。

数据集通过唯一的路径标识,语法如下

pool(/segment)+((#|@)bookmark/snapshot name)?

其中 # 用于书签,@ 用于快照。

注意: 这是 zpoolconcepts(7)zfsconcepts(7) 的简短摘要。强烈建议阅读它们以熟悉概念以及此处未涵盖的技术术语。

树外模块的复杂性

由于复杂的法律原因,Linux 内核维护人员拒绝接受 ZFS 进入 Linux 内核。因此,ZFS 作为树外模块开发。这种安排的一个后果是,内核更新会不时破坏 ZFS 使用的内核 API。每当发生这种情况时,ZFS 都必须更改其代码以适应这个新的 API。这意味着会有一段时间 ZFS 在最新的主线内核版本上无法工作。

提示: 由于 linux 软件包紧密跟踪最新稳定分支的更新,如果您不想将 linux 软件包锁定到不受支持的版本,则最好使用 linux-lts

安装

作为树外模块,您可以选择安装 2 种类型的软件包。可以将其安装为针对特定内核版本构建的二进制内核模块,也可以将其源代码安装为 DKMS 模块,该模块会在内核更新时自动重建。

除了内核模块外,用户还需要安装用户空间工具,例如 zpool(8)zfs(8)。这些用户空间工具通常打包成一个名为 zfs-utils* 的软件包。

下面提到的所有内核模块软件包都正确指定了它们对相应 zfs-utils* 软件包的依赖关系,因此在安装时您只需要满足它们的依赖关系即可。

二进制内核模块

软件包比较
软件包 仓库 ZFS 发布类型 目标内核 二进制软件包 备注
zfs-linux-ltsAUR AUR 稳定版 linux-lts 强烈建议在构建新版本的 ZFS 软件包时使用 devtools,否则您必须卸载当前的 ZFS 软件包才能升级内核。
zfs-linuxAUR AUR 稳定版 linux
zfs-linux-lts-poscat archlinuxcn 稳定版 linux-lts 由构建机器人每隔几个小时自动更新并针对新的内核版本重建,此外还为基于 systemd 的 initrd 提供 mkinitcpio hook
zfs-linux-lts-rc-poscat archlinuxcn 候选发布版 linux-lts
zfs-linux archzfs 稳定版 linux
zfs-linux-lts archzfs 稳定版 linux-lts

DKMS

Root on ZFS

参见 在 ZFS 上安装 Arch Linux

配置

启动时导入存储池

ZFS 提供了 systemd 服务,用于自动导入存储池,并提供了目标,供其他单元确定 ZFS 初始化的状态。这些是

  • zfs.target:当所有 ZFS 服务完成时到达
  • zfs-import.target:当 ZFS 存储池完成导入时到达
  • zfs-volumes.target:当所有 zvol 出现在 /dev 下时到达
  • zfs-import-scan.service:通过使用 libblkid 扫描设备来导入存储池
  • zfs-import-cache.service:通过查阅 zpool.cache 文件来导入存储池
  • zfs-volume-wait.service:等待所有 zvol 可用。

您应该在 zfs-import-scan.servicezfs-import-cache.service 之间选择一个并启用其余的服务。

zfs-import-scan

zfs-import-scan.service 使用 zpool import 的默认逻辑,即使用 blkid 扫描设备,这意味着不需要 zpool.cache 文件。这是推荐的方法,因为 zpool.cache弃用

重要的是要确保您的任何存储池都没有启用 cachefile 选项导入,因为如果 zpool.cache 存在且不为空,则 zfs-import-scan.service 将不会启动。您可以通过启用 zfs 模块的 zfs_autoimport_disable 选项来实现这一点。您还应该删除现有的 zpool.cache 或在启动时将所有导入存储池的 cachefile 设置为 none

警告: 应该注意的是,如果 root 使用 ZFS,最好的方法仍然是 zfs-import-cache,因为带有 root 的存储池在每次启动时都会将其 cachefile 属性更改为 default,并且 /etc/zfs 中的 zpool.cache 文件将被重新创建,导致 zfs-import-scan 无法使用。一种解决方法是使用 touch 重新创建一个空的 zpool.cache 文件,并使用 chattr +i 使其不可写,但是这样,在使用 mkinitcpio 生成 initramfs 时,系统将因为损坏的 zpool.cache 而无法启动。

zfs-import-cache

zfs-import-cache.service 在导入存储池时使用 zpool import -c <zpool.cache>,它从 zpool.cache 中读取设备路径。

使用此方法意味着您需要在创建 ZFS 存储池时注意设备路径,因为某些设备路径可能会在启动或硬件修改之间发生更改,这将导致缓存过时和存储池导入失败。请参阅 持久块设备命名,了解如何选择持久的设备路径。

自动挂载文件系统

zfs-import-scan.servicezfs-import-cache.service 服务将导入存储池,但不会挂载任何文件系统。要在启动时也挂载文件系统,有两种方法,具体取决于您的文件系统是否使用 mountpoint=legacy 配置。如果您的文件系统配置了传统挂载和非传统挂载的混合,则需要同时使用这两种方法。

zfs-mount-generator

如果您的文件系统使用非传统挂载,建议使用 zfs-mount-generator,它是一个 systemd.generator(7),它为所有导入的 zfs 存储池的文件系统生成 systemd 挂载单元,并将属性 canmount=on 设置为在启动时挂载文件系统。但默认情况下,zfs-mount-generator 不会执行任何操作,因为它需要 zfs list 缓存。您需要

  1. 启用启动 zfs-zed.service
  2. 创建 /etc/zfs/zfs-list.cache 目录。
    # mkdir -p /etc/zfs/zfs-list.cache
  3. 通过在 /etc/zfs/zfs-list.cache/ 中创建以您的存储池命名的空文件来启用对单个存储池的跟踪。Zed 仅在存储池的文件已存在且可写时更新文件系统列表。
    # touch /etc/zfs/zfs-list.cache/pool-name
  4. 检查 /etc/zfs/zfs-list.cache/pool-name 的内容。如果为空,则 zed 尚未检测到任何事件,因此尚未刷新缓存文件。更改存储池上某处受监视的属性以发出新的 ZFS 事件,这将触发 ZEDLET 并刷新缓存文件。以下第一个示例假定在存储池 zroot 上启用了 relatime,并且它由子文件系统 fs1 继承。通过更改属性然后再恢复它来触发事件
    # zfs set relatime=off zroot/fs1
    # zfs inherit relatime zroot/fs1
    
    如果继承不可用(例如,您正在处理根存储池),只需在存储池本身上进行更改并手动恢复它,而不是使用继承
    # zfs set relatime=off zroot
    # zfs set relatime=on zroot
    

fstab

如果您的文件系统使用传统挂载,则应在 fstab 文件中指定挂载点。设备字段应为您的文件系统的名称(完整路径),dump 和 fsck 字段应保留为 0。

创建 hostid 文件

虽然不是绝对必要,但通常最好创建一个 /etc/hostid 文件

# zgenhostid $(hostid)

存储池

ZFS 实验

希望在没有真实数据丢失可能性的情况下试验 ZFS 的用户可以参考 ZFS/虚拟磁盘

创建 ZFS 存储池

提示: 您可能希望先阅读 #ashift 属性,因为它可能建议在创建存储池时设置 ashift

要创建 ZFS 存储池

# zpool create -R <root> -o <poolopts> -O <dsetprops> <pool> <vdevs>

其中每个 vdev 都是设备或具有以下格式

<vdev type> <device> ... <device>
  • -R:挂载此目录下的所有文件系统,对于不干扰现有系统很有用
  • -o:指定存储池的属性,可以多次使用。某些属性(例如 ashift)一旦创建就无法更改。(从技术上讲,ashift 是每个 vdev 的,但是要为每个 vdev 配置它,您需要使用 zpool add
  • -O:指定存储池的根数据集的属性,可以多次使用。某些属性(例如 normalization)一旦创建就无法更改。
  • pool:这是存储池的名称。
  • vdev 类型:有关支持的 vdev 类型列表,请参见 zpoolconcepts(7)
  • device:块设备,可以是完整路径或路径的文件名部分
注意: 根据您选择使用的 方法 来挂载存储池,您可能需要注意用于创建存储池的设备路径。

例如,要在单个分区上创建存储池

# zpool create -R /mnt pool /dev/sda

要创建具有单个 raidz1 vdev 的存储池

# zpool create -R /mnt pool \
               raidz1 \
                  ata-ST3000DM001-9YN166_S1F0KDGY \
                  ata-ST3000DM001-9YN166_S1F0JKRR \
                  ata-ST3000DM001-9YN166_S1F0KBP8 \
                  ata-ST3000DM001-9YN166_S1F0JTM1

要创建具有两个 mirror vdev 的存储池

# zpool create -R /mnt pool \
               mirror \
                  ata-ST3000DM001-9YN166_S1F0KDGY \
                  ata-ST3000DM001-9YN166_S1F0JKRR \
               mirror \
                  ata-ST3000DM001-9YN166_S1F0KBP8 \
                  ata-ST3000DM001-9YN166_S1F0JTM1

ashift 属性

ashift 是一个不可变的每个 vdev 属性,它确定(逻辑)扇区大小,即 2^ashift 字节。为了获得最佳性能,逻辑扇区大小应始终大于或等于磁盘的物理扇区大小。

默认情况下,zpool create 应该能够正确确定设备的物理扇区大小。这在单磁盘设置中应该足够了。

但是,如果您正在(或打算)使用可以替换故障磁盘的 vdev 设置(例如 mirrorraidzX),则通常最好始终使用 ashift=12,因为在 512b 物理磁盘上使用 4kb 逻辑扇区大小不会有性能损失,而反之则会。(除非您的设备是 极少数使用 8kb 扇区大小的 SSD

提示: 使用
$ lsblk --filter 'TYPE=="DISK"' -o NAME,PHY-SEC

检查磁盘的物理扇区大小。

此外,如果您使用的是 NVMe 驱动器,则可以使用比出厂默认值更高效的 LBA 格式对其进行格式化(请参阅 nvme-format(1))。

GRUB 兼容的存储池创建

默认情况下,zpool create 会在存储池上启用所有功能。如果在使用 GRUB/boot 驻留在 ZFS 上,则您必须仅启用 GRUB 支持的功能,否则 GRUB 将无法读取存储池。ZFS 包括兼容性文件(请参见 /usr/share/zfs/compatibility.d),以帮助创建具有特定功能集的存储池,其中 grub2 是一个选项。

您可以创建一个仅启用兼容功能的存储池

# zpool create -o compatibility=grub2 $POOL_NAME $VDEVS

验证存储池状态

如果命令成功,则不会有任何输出。使用 mount 命令将显示存储池已挂载。使用 zpool status 将显示存储池已创建

# zpool status
  pool: bigdata
 state: ONLINE
 scan: none requested
config:

        NAME                                       STATE     READ WRITE CKSUM
        bigdata                                    ONLINE       0     0     0
          -0                                       ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0KDGY-part1  ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0JKRR-part1  ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0KBP8-part1  ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0JTM1-part1  ONLINE       0     0     0

errors: No known data errors

通过 ID 导入创建的存储池

根据 zpool-import.8 手册,要导入现有存储池,您需要使用标志

  • -c cachefile 从给定的缓存文件中读取配置,该缓存文件是使用 cachefile 存储池属性创建的。此缓存文件用于代替搜索设备。
  • -d dir/device 使用设备或在 dir 中搜索设备或文件。可以多次指定 -d 选项。
警告: 避免将内核名称(例如 /dev/sda)用于需要保存新缓存文件的操作。如果您将通过保存缓存文件来使用这些内核名称,则如果它们发生更改,系统将无法自动挂载存储池。
注意: 当无法使用 cachefile 或您想使用其他属性/挂载点挂载现有存储池时,请使用 /dev/disk/by-id/ 中的 ID 名称来挂载。

要导入现有存储池以在其上运行 chroot,请查阅 Export/Import dei pool(Italiano)

注意: 不幸的是,此页面和 在 ZFS 上安装 Arch Linux 页面正在进行更改,并且已从指南中删除了一些关键命令。

销毁存储池

要销毁整个存储池

# zpool destroy <pool>

现在检查状态时

# zpool status
no pools available

导出存储池

要导出存储池

# zpool export <pool>

扩展现有存储池

可以将设备(分区或磁盘)添加到现有的 zpool

# zpool add <pool> <device-id>

附加设备到(创建)镜像

可以将设备(分区或磁盘)附加到现有设备旁边以作为其镜像(类似于 RAID 1)

# zpool attach <pool> <device-id|mirror> <new-device-id>

您可以将新设备附加到已有的镜像 vdev(例如,从 2 设备镜像升级到 3 设备镜像),或者 将其附加到单个设备以创建新的镜像 vdev

重命名存储池

重命名已创建的存储池分 2 步完成

# zpool export oldname
# zpool import oldname newname

设置不同的挂载点

可以使用一个命令随意移动给定 zpool 的挂载点

# zfs set mountpoint=/foo/bar poolname

升级存储池

将 ZFS 升级到新版本时,可能会有新功能可用。但是,出于兼容性原因,ZFS 不会自动在以前创建的存储池上启用新功能。相反,需要为每个存储池手动启用它们。

要检查升级可用性

$ zpool upgrade
This system supports ZFS pool feature flags.

All pools are formatted using feature flags.

Every feature flags pool has all supported and requested features enabled.

带有可升级存储池的示例输出

This system supports ZFS pool feature flags.

All pools are formatted using feature flags.


Some supported features are not enabled on the following pools. Once a
feature is enabled the pool may become incompatible with software
that does not support the feature. See zpool-features(7) for details.

Note that the pool 'compatibility' feature can be used to inhibit
feature upgrades.

POOL  FEATURE

rpool redaction_list_spill raidz_expansion fast_dedup longname large_microzap

要升级单个存储池

# zpool upgrade <pool>

要升级所有存储池

# zpool upgrade -a

创建数据集

用户可以选择在 zpool 下创建数据集,而不是手动在 zpool 下创建目录。除了快照之外,数据集还允许更高级别的控制(例如配额)。为了能够创建和挂载数据集,同名的目录不得预先存在于 zpool 中。要创建数据集,请使用

# zfs create <nameofzpool>/<nameofdataset>

然后可以将 ZFS 特定的属性应用于数据集。例如,可以为数据集中的特定目录分配配额限制

# zfs set quota=20G <nameofzpool>/<nameofdataset>/<directory>

要查看 ZFS 中所有可用的命令,请参见 zfs(8)zpool(8)

原生加密

ZFS 提供以下支持的加密选项:aes-128-ccmaes-192-ccmaes-256-ccmaes-128-gcmaes-192-gcmaes-256-gcm。当 encryption 设置为 on 时,将使用 aes-256-gcm。有关原生加密的描述(包括限制),请参见 zfs-change-key(8)

支持以下密钥格式:passphraserawhex

使用 passphrase-o pbkdf2iters <n> 时,还可以指定/增加 PBKDF2 的默认迭代次数,但这可能会增加解密时间。

提示
  • 要导入带有密钥的存储池,需要指定 -l 标志,如果没有此标志,加密数据集将保持不可用状态,直到加载密钥。请参见 #通过 ID 导入创建的存储池
  • 原生 ZFS 加密已在稳定版 0.8.0 或更高版本中提供。以前,它仅在开发版本中可用,例如 zfs-linux-gitAURzfs-dkms-gitAUR 或其他开发版本提供的软件包。仅使用开发版本进行原生加密的用户现在可以根据需要切换到稳定版本。
  • 默认加密套件在 0.8.4 版本中从 aes-256-ccm 更改为 aes-256-gcm

要创建包含原生加密和密码的数据集,请使用

# zfs create -o encryption=on -o keyformat=passphrase <nameofzpool>/<nameofdataset>

要使用密钥而不是密码

# dd if=/dev/random of=/path/to/key bs=32 count=1 iflag=fullblock
# zfs create -o encryption=on -o keyformat=raw -o keylocation=file:///path/to/key <nameofzpool>/<nameofdataset>

以人类可读形式制作密钥的简单方法 (keyformat=hex)

# od -Anone -x -N 32 -w64 /dev/random | tr -d [:blank:] > /path/to/hex.key

要验证密钥位置

# zfs get keylocation <nameofzpool>/<nameofdataset>

要更改密钥位置

# zfs set keylocation=file:///path/to/key <nameofzpool>/<nameofdataset>

您还可以使用以下命令之一手动加载密钥

# zfs load-key <nameofzpool>/<nameofdataset> # load key for a specific dataset
# zfs load-key -a # load all keys
# zfs load-key -r zpool/dataset # load all keys in a dataset

要挂载创建的加密数据集

# zfs mount <nameofzpool>/<nameofdataset>

启动时解锁/挂载:systemd

可以使用 systemd 单元在启动时自动解锁存储池数据集。例如,创建以下服务以解锁任何特定数据集

/etc/systemd/system/zfs-load-key@.service
[Unit]
Description=Load %I encryption keys
Before=systemd-user-sessions.service zfs-mount.service
After=zfs-import.target
Requires=zfs-import.target
DefaultDependencies=no

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/bash -c 'until (systemd-ask-password "Encrypted ZFS password for %I" --no-tty | zfs load-key %I); do echo "Try again!"; done'

[Install]
WantedBy=zfs-mount.service

启用/启动 每个加密数据集的服务(例如 zfs-load-key@pool0-dataset0.service)。请注意 - 的使用,它是 systemd 单元定义中转义的 /。有关更多信息,请参见 systemd-escape(1)

注意: Before=systemd-user-sessions.service 确保在本地 IO 设备移交给 桌面环境 之前调用 systemd-ask-password。

另一种方法是加载所有可能的密钥

/etc/systemd/system/zfs-load-key.service
[Unit]
Description=Load encryption keys
DefaultDependencies=no
After=zfs-import.target
Before=zfs-mount.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/zfs load-key -a
StandardInput=tty-force

[Install]
WantedBy=zfs-mount.service

启用/启动 zfs-load-key.service

登录时解锁:PAM

如果您不加密根卷,而仅加密 home 卷或用户特定的卷,另一种方法是 等待登录以解密它[死链 2024-11-06 ⓘ]。此方法的优点是系统启动不会中断,并且当用户登录时,可以使用相同的密码进行身份验证和解密 home 卷,这样密码只需输入一次。

首先将挂载点设置为传统挂载,以避免被 zfs mount -a 挂载

# zfs set mountpoint=legacy zroot/data/home

确保它在 /etc/fstab 中,以便 mount /home 可以工作

/etc/fstab
zroot/data/home         /home           zfs             rw,xattr,posixacl,noauto        0 0

或者,如果您同时使用以下两者,则可以继续使用 ZFS 挂载

# zfs set canmount=noauto zroot/data/home
# zfs set org.openzfs.systemd:ignore=on zroot/data/home

第一个将阻止 ZFS 自动挂载它,第二个将阻止 systemd,但您仍然可以手动(或通过以下脚本)挂载它。如果您有子数据集,org.openzfs.systemd:ignore=on 将被继承,但是您需要在每个子数据集上设置 canmount=noauto,因为它不可继承,否则它们将尝试在没有挂载点的情况下挂载。

在单用户系统中,只有一个 /home 卷具有与用户密码相同的加密密码,可以在登录时按如下方式解密:首先创建 /usr/local/bin/mount-zfs-homedir

/usr/local/bin/mount-zfs-homedir
#!/bin/bash
set -eu

# $PAM_USER will be the username of the user, you can use it for per-user home volumes.
HOME_VOLUME="zroot/data/home" 

if [ "$(zfs get keystatus "${HOME_VOLUME}" -Ho value)" != "available" ]; then
  PASSWORD=$(cat -)
  zfs load-key "${HOME_VOLUME}" <<< "$PASSWORD" || continue
fi

# This will also mount any child datasets, unless they use a different key.
echo "$(zfs list -rHo name,keystatus,mounted "${HOME_VOLUME}")" | while IFS=$'\t' read -r NAME KEYSTATUS MOUNTED; do
  if [ "${MOUNTED}" != "yes" ] && [ "${KEYSTATUS}" == "available" ]; then
    zfs mount "${NAME}" || true
  fi
done

不要忘记使其成为 可执行文件;然后让 PAM 运行它,方法是将以下行添加到 /etc/pam.d/system-auth

/etc/pam.d/system-auth
auth       optional                    pam_exec.so          expose_authtok /usr/local/bin/mount-zfs-homedir

现在,当您在任何地方登录时,它将透明地解密和挂载 /home 卷:在控制台上、通过 ssh 等。

SSH

一个需要注意的是,由于您的 ~/.ssh 目录未挂载,如果您通过 ssh 登录,则第一次必须使用密码身份验证,而不是依赖 ~/.ssh/authorized_keys

如果您不希望启用(不安全的)密码验证,您可以将 ~/.ssh/authorized_keys 移动到新的位置。创建 /etc/ssh/user_config/ 目录,并在其中为每个用户创建一个文件夹,确保文件夹的所有者是该用户,且权限为 700。然后将每个用户的 authorized_keys 移动到他们各自的文件夹中,并编辑系统 sshd 配置文件。

/etc/ssh/sshd_config
AuthorizedKeysFile /etc/ssh/user_config/%u/authorized_keys

然后重启 sshd.service 服务。您也可以选择为每个用户从 ~/.ssh/authorized_keys 链接到新的位置,以便用户仍然可以像以前一样编辑它。

这样您就可以登录了,但是您的 home 分区将不会被挂载,您需要手动挂载。有多种方法可以解决这个问题。

SSH 密钥 & 需要时密码

可以设置 PAM,使其仅在需要解密您的 home 分区时才通过 SSH 提示输入密码。您需要同时启用 publickeykeyboard-interactive 身份验证方法。

/etc/ssh/sshd_config
PubkeyAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

## Example of excluding a certain user who does not have an encrypted home directory.
#Match User nohome
#  KbdInteractiveAuthentication no
#  AuthenticationMethods publickey
警告: 请注意 AuthenticationMethods publickey,keyboard-interactive 中的逗号,这意味着您需要同时使用这两种身份验证方法才能通过 SSH 登录。非常相似的 AuthenticationMethods publickey keyboard-interactive 意味着您可以使用任一方法登录,这将允许某人绕过您的公钥验证。
注意: 您可能会问为什么是 keyboard-interactive 而不是 passwordpassword 是在客户端完成的,所以即使跳过验证,用户仍然会被提示输入密码,但密码只是被丢弃了。使用 keyboard-interactive,当我们跳过验证时,用户根本不会收到提示。

这意味着它会在验证密钥后要求输入密码,但是使用 PAM,我们可以阻止它在不需要时要求输入密码。我们创建一个脚本,当密钥不可用时,脚本将失败。

/usr/local/bin/require-encrypted-homedir
#!/bin/bash
set -eu

HOME_VOLUME="zroot/data/home" # You can use $PAM_USER to use the username in the volume for a per-user solution.

if [ "$(zfs get keystatus "${HOME_VOLUME}" -Ho value)" != "available" ]; then
  exit 27 # PAM_TRY_AGAIN
elif [[ "${SSH_AUTH_INFO_0:-""}" =~ ^"publickey " ]]; then
  exit 0
else
  # If this happens, it implies a configuration error: either you are allowing auth without a public 
  # key, or have enabled this in a non-SSH PAM service. Both are dangerous and this should block it, 
  # but if you see it, fix your configuration.
  exit 3 # PAM_SERVICE_ERR
fi

并使其成为可执行文件。

现在我们想要配置 PAM 来调用这个脚本,并且如果脚本成功(因为我们已经有可用的密钥),则跳过密码询问。将这一行添加到您想要跳过的现有身份验证行(SSH 服务的全部,除非您有其他设置)之上。

/etc/pam.d/sshd
auth sufficient pam_exec.so /usr/local/bin/require-encrypted-homedir
警告: 这是针对 /etc/pam.d/sshd不是像上面提到的 /etc/pam.d/system-auth。您不希望没有公钥的本地用户能够跳过密码。脚本中有一个针对此情况的保护措施,但最好还是小心。
注意: 当使用私钥时,PAM 中的身份验证步骤会被跳过,因为私钥身份验证完全由 sshd 处理。这意味着我们在此处添加的脚本永远不会为私钥运行,并且它们无法被跳过。但是,我们仍然会进行深度防御检查,以尝试确保密钥已被检查。

这样配置后,只有当密钥未加载时,您才会被提示输入密码。

SSH 密钥 & 密码

一个更简单的选项是直接启用这两种方法,这意味着您的密钥仍然会被检查,但之后您也必须输入密码,这将解密您的 home 分区。

/etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication yes
AuthenticationMethods publickey,password
警告: 请注意 AuthenticationMethods publickey,password 中的逗号,这意味着您需要同时使用这两种身份验证方法才能通过 SSH 登录。非常相似的 AuthenticationMethods publickey password 意味着您可以使用任一方法登录,这将允许某人绕过您的公钥验证。

这种方法有效(并且不会让任何人仅使用密码进行身份验证),但是缺点是每次都需要输入密码。

您也可以指定类似这样的配置:

AuthenticationMethods publickey password,publickey

这允许客户端仅使用公钥,或者同时使用公钥和密码。客户端将使用哪种方式取决于 PreferredAuthentications 选项。 -o PreferredAuthentications=password,publickey 将会询问密码,而 -o PreferredAuthentications=publickey 则不会。这比自动回退更手动,但组件更少,并且避免了每次都询问您是否默认首选 publickey(您可以在客户端上使用特定于主机的选项来简化这些选项的设置)。

交换卷

警告

ZFS 不允许使用交换文件,但是用户可以使用 ZFS 卷 (ZVOL) 作为交换空间。重要的是将 ZVOL 块大小设置为与系统页面大小匹配,系统页面大小可以使用 getconf PAGESIZE 命令获取(x86_64 上的默认值为 4KiB)。另一个有助于在低内存情况下保持系统良好运行的选项是不缓存 ZVOL 数据。

创建一个 8 GiB 的 zfs 卷

# zfs create -V 8G -b $(getconf PAGESIZE) -o compression=zle \
              -o logbias=throughput -o sync=always\
              -o primarycache=metadata -o secondarycache=none \
              -o com.sun:auto-snapshot=false <pool>/swap

将其准备为交换分区

# mkswap -f /dev/zvol/<pool>/swap
# swapon /dev/zvol/<pool>/swap

为了使其永久生效,请编辑 /etc/fstab。ZVOL 支持 discard 操作,这可能有助于 ZFS 的块分配器,并在交换空间未满时减少所有其他数据集的碎片。

/etc/fstab 中添加一行

/dev/zvol/<pool>/swap none swap discard 0 0

访问控制列表

要在数据集上使用 ACL

# zfs set acltype=posixacl <nameofzpool>/<nameofdataset>
# zfs set xattr=sa <nameofzpool>/<nameofdataset>

出于性能原因,建议设置 xattr [1]

最好在 zpool 上启用 ACL,因为数据集将继承 ACL 参数。可能需要设置 aclinherit=passthrough,因为默认模式是 restricted [2];然而,值得注意的是 aclinherit 不影响 POSIX ACL [3]

# zfs set aclinherit=passthrough <nameofzpool>
# zfs set acltype=posixacl <nameofzpool>
# zfs set xattr=sa <nameofzpool>

数据库

与大多数其他文件系统不同,ZFS 具有可变的记录大小,或者通常所说的块大小。默认情况下,ZFS 上的 recordsize 为 128KiB,这意味着它将根据写入文件的大小动态分配 512B 到 128KiB 之间的任意大小的块。这通常有助于减少碎片和提高文件访问速度,但代价是每次只写入几个字节时,ZFS 都必须分配新的 128KiB 块。

本文或本节内容的准确性存在争议。

原因: 至少 MariaDB 使用 16Kib 页面的默认值!在设置此值之前,请检查您的具体 DBMS。(在 Talk:ZFS 中讨论)

大多数 RDBMS 默认使用 8KiB 大小的块。虽然 MySQL/MariaDBPostgreSQL 和 Oracle 数据库的块大小是可调的,但它们都默认使用 8KiB 的块大小。出于性能考虑以及为了最大限度地减少快照差异(为了备份目的,这很有用),通常需要调整 ZFS 以适应数据库,可以使用如下命令:

# zfs set recordsize=8K <pool>/postgres

这些 RDBMS 也倾向于实现自己的缓存算法,通常类似于 ZFS 自己的 ARC。为了节省内存,最好简单地禁用 ZFS 对数据库文件数据的缓存,并让数据库完成自己的工作。

注意: L2ARC 需要 primarycache 才能工作,因为它是由从 primarycache 中逐出的数据馈送的。如果您打算使用 L2ARC,请不要设置下面的选项,否则将不会有实际数据缓存在 L2ARC 上。
# zfs set primarycache=metadata <pool>/postgres

ZFS 使用 ZIL 进行崩溃恢复,但是数据库通常会在事务提交时自行将数据文件同步到文件系统。最终结果是 ZFS 会将数据两次提交到数据磁盘,这可能会严重影响性能。您可以告诉 ZFS 首选不使用 ZIL,在这种情况下,数据只会提交到文件系统一次。但是,在非固态存储(例如 HDD)上这样做可能会由于碎片而导致读取性能下降(OpenZFS Wiki)——对于机械硬盘,请考虑使用专用 SSD 作为 ZIL,而不是设置下面的选项。此外,为非数据库文件系统或配置了日志设备的池设置此选项也可能对性能产生负面影响,请注意。

# zfs set logbias=throughput <pool>/postgres

这些也可以在文件系统创建时完成,例如

# zfs create -o recordsize=8K \
             -o primarycache=metadata \
             -o mountpoint=/var/lib/postgres \
             -o logbias=throughput \
              <pool>/postgres

请注意:这些类型的调优参数非常适合像 RDBMS 这样的专用应用程序。在通用文件系统(例如您的 /home 目录)上设置这些参数很容易损害 ZFS 的性能。

/tmp

如果您想使用 ZFS 存储您的 /tmp 目录,这对于存储任意大小的文件集或简单地保持 RAM 免受空闲数据占用可能很有用,您通常可以通过禁用文件系统同步来提高某些写入 /tmp 的应用程序的性能。这将导致 ZFS 忽略应用程序的同步请求(例如,使用 fsyncO_SYNC)并立即返回。虽然这会对应用程序端的数据一致性产生严重后果(永远不要为数据库禁用同步!),但 /tmp 中的文件不太可能重要且受到影响。请注意,这会影响 ZFS 本身的完整性,只会影响应用程序期望在磁盘上的数据在崩溃后可能未实际写入的可能性。

# zfs set sync=disabled <pool>/tmp

此外,出于安全目的,您可能需要在 /tmp 文件系统上禁用 setuiddevices,这可以防止某些类型的特权升级攻击或设备节点的使用。

# zfs set setuid=off <pool>/tmp
# zfs set devices=off <pool>/tmp

将所有这些组合在一起以创建一个命令将如下所示:

# zfs create -o setuid=off -o devices=off -o sync=disabled -o mountpoint=/tmp <pool>/tmp

另请注意,如果您想在 ZFS 上使用 /tmp,您需要屏蔽(禁用)systemd 的自动 tmpfs 支持的 /tmp (`tmp.mount`),否则 ZFS 将无法在启动时或导入时挂载您的数据集。

使用 ZFS Send 和 ZFS Recv 传输快照

可以通过配对 zfs sendzfs recv 将 ZFS 快照通过管道传输到任意目标。这是通过标准输出完成的,这允许将数据发送到任何文件、设备、跨网络,或者通过在管道中加入其他程序来在传输过程中进行操作。

以下是常见场景的示例

基本的 ZFS Send

首先,创建一个 ZFS 文件系统的快照

# zfs snapshot zpool0/archive/books@snap

现在将快照发送到不同 zpool 上的新位置

# zfs send -v zpool0/archive/books@snap | zfs recv zpool4/library

zpool0/archive/books@snap 的内容现在已在 zpool4/library 中生效。

提示: 有关标志的详细信息,请参阅 man zfs-sendman zfs-recv
发送到文件和从文件接收

首先,创建一个 ZFS 文件系统的快照

# zfs snapshot zpool0/archive/books@snap

将快照写入 gzip 文件

# zfs send zpool0/archive/books@snap > /tmp/mybooks.gz
警告: 如果您希望在发送过程中保留加密,请确保使用 -w 标志运行 zfs send

现在从文件恢复快照

# gzcat /tmp/mybooks.gz | zfs recv -F zpool0/archive/books

通过 ssh 发送

首先,创建一个 ZFS 文件系统的快照

# zfs snapshot zpool1/filestore@snap

接下来,我们将我们的 “send” 流量通过管道传输到运行 “recv” 的 ssh 会话。

# zfs send -v zpool1/filestore@snap | ssh $HOST zfs recv coldstore/backups

-v 标志打印有关正在生成的数据流的信息。如果您正在使用密码短语或密钥,系统会提示您输入。

增量备份

您可能希望更新先前发送的 ZFS 文件系统,而无需重新传输所有数据。或者,在长时间传输过程中可能需要保持文件系统在线,现在是时候发送自初始快照以来所做的写入。

首先,创建一个 ZFS 文件系统的快照

# zfs snapshot zpool1/filestore@initial

接下来,我们将我们的 “send” 流量通过管道传输到运行 “recv” 的 ssh 会话。

# zfs send -v -R zpool1/filestore@initial | ssh $HOST zfs recv coldstore/backups

一旦写入更改,创建另一个快照

# zfs snapshot zpool1/filestore@snap2

以下命令将发送本地 zpool1/filestore@initialzpool1/filestore@snap2 之间存在的差异,并为远程文件系统 coldstore/backups 创建一个额外的快照。

# zfs send -v -i -R zpool1/filestore@initial | ssh $HOST zfs recv coldstore/backups

现在 zpool1/filestorecoldstore/backups 都具有 @initial@snap2 快照。

在远程主机上,您现在可以将最新的快照提升为活动文件系统。

# rollback coldstore/backups@snap2

调优

通用

可以使用参数进一步调整 ZFS 池和数据集。

注意: 除了配额和预留空间外,所有可设置的属性都从父数据集继承其值。

要检索当前池参数状态

# zfs get all <pool>

要检索当前数据集参数状态

# zfs get all <pool>/<dataset>

要禁用访问时间 (atime),默认情况下是启用的。

# zfs set atime=off <pool>

要在特定数据集上禁用访问时间 (atime)

# zfs set atime=off <pool>/<dataset>

除了完全关闭 atime 之外,还可以使用 relatime。这为 ZFS 带来了默认的 ext4/XFS atime 语义,其中只有在修改时间或更改时间发生变化,或者现有访问时间在过去 24 小时内未更新时,才会更新访问时间。这是 atime=offatime=on 之间的折衷方案。此属性atimeon 时生效。

# zfs set atime=on <pool>
# zfs set relatime=on <pool>

压缩就是那样,对数据进行透明压缩。ZFS 支持几种不同的算法,目前 lz4 是默认算法,gzip 也可用于不常写入但高度可压缩的数据;有关更多详细信息,请查阅 OpenZFS Wiki

要启用压缩

# zfs set compression=on <pool>

要将池和/或数据集的属性重置为其默认状态,请使用 zfs inherit

# zfs inherit -rS atime <pool>
# zfs inherit -rS atime <pool>/<dataset>
警告: 使用 -r 标志将递归重置 zpool 的所有数据集。

数据擦洗(Scrubbing)

每当读取数据且 ZFS 遇到错误时,如果可能,它会在后台静默修复,重写回磁盘并记录下来,以便您可以了解池中的错误概况。ZFS 没有 fsck 或等效工具。相反,ZFS 支持一种称为数据擦洗的功能。这会遍历池中的所有数据,并验证是否可以读取所有块。

要擦洗池

# zpool scrub <pool>

要取消正在运行的擦洗

# zpool scrub -s <pool>

我应该多久执行一次?

来自 Oracle 博客文章 Disk Scrub - Why and When?

这个问题对于支持团队来说很难回答,因为真正的答案始终是“视情况而定”。因此,在提供一般指南之前,这里有一些技巧可以帮助您创建更适合您的使用模式的答案。
  • 您最旧的备份的过期时间是什么时候?您可能应该至少与最旧的磁带过期频率相同地擦洗您的数据,以便您有一个已知的良好还原点。
  • 您多久经历一次磁盘故障?虽然热备盘的加入会触发 “resilver” —— 仅针对丢失磁盘的 VDEV 的有针对性的擦洗 —— 您可能应该至少与您在特定环境中平均经历磁盘故障的频率相同地进行擦洗。
  • 您磁盘上最旧的数据多久被读取一次?您应该偶尔进行擦洗,以防止非常旧、非常陈旧的数据在您不知情的情况下经历位腐烂并损坏。
如果您对上述任何问题的回答是“我不知道”,则一般指南是:您可能应该至少每月擦洗一次您的 zpool。这是一个适用于大多数用例的时间表,在所有繁忙且负载最重的系统上,它都提供了足够的时间让擦洗完成,然后再重新启动,即使在非常大的 zpool(192+ 个磁盘)上,也应该在磁盘故障之间相当频繁地完成。

在 Aaron Toponce 的 ZFS Administration Guide 中,他建议每周擦洗一次消费级磁盘。

从服务或计时器开始

注意: 从 OpenZFS 2.1.3 开始,包含每周和每月的 systemd 计时器/服务。要使用这些,请为所需的池启用/启动 zfs-scrub-weekly@pool-to-scrub.timerzfs-scrub-monthly@pool-to-scrub.timer

使用 systemd 计时器/服务可以自动擦洗池。

要对特定池每月执行擦洗

/etc/systemd/system/zfs-scrub@.timer
[Unit]
Description=Monthly zpool scrub on %i

[Timer]
OnCalendar=monthly
AccuracySec=1h
Persistent=true

[Install]
WantedBy=multi-user.target
/etc/systemd/system/zfs-scrub@.service
[Unit]
Description=zpool scrub on %i

[Service]
Nice=19
IOSchedulingClass=idle
KillSignal=SIGINT
ExecStart=/usr/bin/zpool scrub %i

[Install]
WantedBy=multi-user.target

启用/启动 zfs-scrub@pool-to-scrub.timer 单元,以每月擦洗指定的 zpool。

启用 TRIM

要快速查询您的 vdevs 的 TRIM 支持,您可以在 zpool status 中使用 -t 包含 TRIM 信息。

$ zpool status -t tank
pool: tank
 state: ONLINE
  scan: none requested
 config:

	NAME                                     STATE     READ WRITE CKSUM
	tank                                     ONLINE       0     0     0
	  ata-ST31000524AS_5RP4SSNR-part1        ONLINE       0     0     0  (trim unsupported)
	  ata-CT480BX500SSD1_2134A59B933D-part1  ONLINE       0     0     0  (untrimmed)

errors: No known data errors

ZFS 能够按需或定期通过 autotrim 属性来 TRIM 支持的 vdevs。

手动在 zpool 上执行 TRIM 操作

 # zpool trim <zpool>

在池中所有受支持的 vdevs 上启用定期 TRIM

 # zpool set autotrim=on <zpool>
注意: 由于自动 TRIM 和完整的 zpool trim 在操作上有所不同,偶尔运行手动 TRIM 可能是有意义的。

要使用 systemd 计时器/服务对特定池每月执行完整的 zpool trim

/etc/systemd/system/zfs-trim@.timer
[Unit]
Description=Monthly zpool trim on %i

[Timer]
OnCalendar=monthly
AccuracySec=1h
Persistent=true

[Install]
WantedBy=multi-user.target
/etc/systemd/system/zfs-trim@.service
[Unit]
Description=zpool trim on %i
Documentation=man:zpool-trim(8)
Requires=zfs.target
After=zfs.target
ConditionACPower=true
ConditionPathIsDirectory=/sys/module/zfs

[Service]
Nice=19
IOSchedulingClass=idle
KillSignal=SIGINT
ExecStart=/bin/sh -c '\
if /usr/bin/zpool status %i | grep "trimming"; then\
exec /usr/bin/zpool wait -t trim %i;\
else exec /usr/bin/zpool trim -w %i; fi'
ExecStop=-/bin/sh -c '/usr/bin/zpool trim -s %i 2>/dev/null || true'

[Install]
WantedBy=multi-user.target

启用/启动 zfs-trim@pool-to-trim.timer 单元,以每月 TRIM 指定的 zpool。

SSD 缓存

如果您的池没有配置日志设备,ZFS 会在池的数据磁盘上为其意图日志(ZIL,也称为 SLOG)保留空间。如果您的数据磁盘速度较慢(例如 HDD),则强烈建议在固态驱动器上配置 ZIL,以获得更好的写入性能,并考虑使用二级自适应替换缓存 (L2ARC)。添加它们的过程与添加新的 VDEV 非常相似。

以下所有对 device-id 的引用都是来自 /dev/disk/by-id/* 的 ID。

ZIL

添加镜像 ZIL

 # zpool add <pool> log mirror <device-id-1> <device-id-2>

或者添加单个设备 ZIL

 # zpool add <pool> log <device-id>

因为 ZIL 设备存储尚未写入池中的数据,所以重要的是使用在断电时可以完成写入的设备。使用冗余也很重要,因为设备故障可能会导致数据丢失。此外,ZIL 仅用于同步写入,因此当您的数据驱动器与您的 ZIL 驱动器一样快时,可能不会提供任何性能改进。

L2ARC

添加 L2ARC

# zpool add <pool> cache <device-id>

L2ARC 只是一个读取缓存,因此冗余是不必要的。自 ZFS 2.0.0 版本以来,L2ARC 在重启后仍然持久存在。[4]

L2ARC 通常仅在热数据量大于系统内存,但又小到可以放入 L2ARC 的工作负载中才有用。L2ARC 由系统内存中的 ARC 索引,每个记录(默认 128KiB)消耗 70 字节。因此,RAM 使用量的公式是

(L2ARC size) / (recordsize) * 70 bytes

因此,在某些工作负载中,L2ARC 可能会损害性能,因为它会从 ARC 中占用内存。

ZVOLs

ZFS 卷 (ZVOL) 可能会遇到与 RDBMS 相同的块大小相关问题,但值得注意的是,ZVOL 的默认 recordsize 已经是 8 KiB。如果可能,最好将 ZVOL 中包含的任何分区与您的 recordsize 对齐(当前版本的 fdisk 和 gdisk 默认自动以 1MiB 段对齐,这有效),并将文件系统块大小设置为相同大小。除此之外,您还可以根据需要调整 recordsize 以适应 ZVOL 内部的数据(尽管 8 KiB 往往是大多数文件系统的好值,即使在该级别上使用 4 KiB 块)。

RAIDZ 和高级格式物理磁盘

ZVOL 的每个块都有自己的奇偶校验磁盘,如果您有逻辑块大小为 4096B、8192B 等的物理介质,则奇偶校验需要存储在整个物理块中,这会大大增加 ZVOL 的空间需求,需要的物理存储容量是 ZVOL 逻辑容量的 2 倍或更多。将 recordsize 设置为 16k 或 32k 可以大大减少这种占用空间。

有关详细信息,请参阅 OpenZFS 问题 #1807

I/O 调度器

虽然预计 ZFS 可以与包括 mq-deadlinenone 在内的现代调度器良好地配合工作,但在 ZFS 磁盘上实验手动设置 I/O 调度器可能会提高性能。ZFS 的建议是“[...] 用户保留默认调度器“除非您遇到特定问题,或者已经明确衡量了您的工作负载的性能改进”[5]

故障排除

创建 zpool 失败

如果发生以下错误,则可以修复。

# the kernel failed to rescan the partition table: 16
# cannot label 'sdc': try using parted(8) and then provide a specific slice: -1

发生这种情况的一个原因是 ZFS 期望池创建时间少于 1 秒[6][7]。这在正常情况下是一个合理的假设,但在许多情况下可能需要更长的时间。在进行另一次尝试之前,需要再次清除每个驱动器。

# parted /dev/sda rm 1
# parted /dev/sda rm 1
# dd if=/dev/zero of=/dev/sdb bs=512 count=1
# zpool labelclear /dev/sda

可以一次又一次地尝试暴力创建,如果运气好,ZPool 创建将花费不到 1 秒。创建速度减慢的一个原因可能是驱动器上的突发读取写入速度慢。通过在 ZPool 创建的同时从磁盘读取数据,可以提高突发速度。

# dd if=/dev/sda of=/dev/null

这可以通过多个驱动器完成,方法是将上述每个驱动器的命令保存在单独行的文件中并运行

# cat $FILE | parallel

然后同时运行 ZPool 创建。

ZFS 正在使用过多的 RAM

默认情况下,ZFS 使用主机上最多一半的可用系统内存来缓存文件操作(ARC)。要调整 ARC 大小,请将以下内容添加到 内核参数 列表

zfs.zfs_arc_max=536870912 # (for 512MiB)

如果 zfs_arc_min 的默认值(系统内存的 1/32)高于指定的 zfs_arc_max,则还需要将以下内容添加到 内核参数 列表

zfs.zfs_arc_min=268435456 # (for 256MiB, needs to be lower than zfs.zfs_arc_max)

您可能还希望改为增加 zfs_arc_sys_free(在本例中为 8GiB)

# echo $((8*1024**3)) > /sys/module/zfs/parameters/zfs_arc_sys_free

有关更详细的描述以及其他配置选项,请参阅 Gentoo:ZFS#ARC

ZFS 应该在应用程序预留更多 RAM 时释放 ARC,但某些应用程序仍然 感到困惑,并且报告 可用 RAM 始终是错误的。但是,如果您的所有应用程序都按预期工作并且您没有问题,则无需更改 ARC 设置。

未找到 hostid

在启动时发生的错误,以下行出现在 initscript 输出之前

ZFS: No hostid found on kernel command line or /etc/hostid.

出现此警告是因为 ZFS 模块无法访问 spl hosted。对此有两种解决方案。一种是将 spl hostid 放在引导加载程序中的 内核参数 中。例如,添加 spl.spl_hostid=0x00bab10c

另一种解决方案是确保 /etc/hostid 中存在 hostid,然后重新生成 initramfs 镜像。这将把 hostid 复制到 initramfs 镜像中。

从 SAS/SCSI 设备启动时找不到池

如果您正在启动基于 SAS/SCSI 的系统,您可能会偶尔遇到启动问题,即找不到您尝试从中启动的池。一个可能的原因是您的设备在启动过程中初始化得太晚。这意味着当 zfs 尝试组装您的池时,它找不到任何设备。

在这种情况下,您应该强制 scsi 驱动程序等待设备上线后再继续。您可以通过将此内容放入 /etc/modprobe.d/zfs.conf 来完成此操作。

/etc/modprobe.d/zfs.conf
options scsi_mod scan=sync

之后,重新生成 initramfs

这样做有效是因为 zfs 钩子会将 /etc/modprobe.d/zfs.conf 中的文件复制到 initcpio 中,然后在构建时使用。

启动时 zfs 池未挂载,并显示:“池可能正在被其他系统使用”

未导出的池

如果新安装无法启动,因为 zpool 无法导入,请 chroot 进入安装并正确导出 zpool。请参阅 #Emergency chroot repair with archzfs

进入 chroot 环境后,加载 ZFS 模块并强制导入 zpool,

# zpool import -a -f

现在导出池

# zpool export <pool>

要查看可用的池,请使用,

# zpool status

必须导出池,因为 ZFS 使用 hostid 来跟踪创建 zpool 的系统。hostid 部分基于网络设置生成。在 archiso 安装期间,网络配置可能不同,从而生成与新安装中包含的 hostid 不同的 hostid。一旦 zfs 文件系统被导出,然后在新安装中重新导入,hostid 将被重置。请参阅 Re: Howto zpool import/export automatically? - msg#00227

如果 ZFS 在每次重启后都抱怨“池可能正在使用中”,请按照上述说明正确导出池,然后在正常启动的系统中重新生成 initramfs

错误的 hostid

仔细检查池是否已正确导出。导出 zpool 会清除标记所有权的 hostid。因此,在首次启动期间,zpool 应该正确挂载。如果未挂载,则存在其他问题。

再次重启,如果 zfs 池拒绝挂载,则意味着 hostid 尚未在早期启动阶段正确设置,并且它混淆了 zfs。手动告诉 zfs 正确的数字,一旦 hostid 在重启过程中保持一致,zpool 将正确挂载。

使用 zfs_force 启动并记下 hostid。这只是一个例子。

$ hostid
0a0af0f8

此数字必须作为 spl.spl_hostid=0x0a0af0f8 添加到 内核参数 中。另一种解决方案是将 hostid 写入 initram 镜像中,请参阅安装指南[无效链接:无效章节]中关于此的说明。

用户始终可以忽略此检查,方法是在内核参数中添加 zfs_force=1,但这不建议作为永久解决方案。

设备具有不同的扇区对齐方式

一旦驱动器出现故障,应尽快 (A.S.A.P.) 更换为相同的驱动器。

# zpool replace bigdata ata-ST3000DM001-9YN166_S1F0KDGY ata-ST3000DM001-1CH166_W1F478BD -f

但在这种情况下,会产生以下错误

cannot replace ata-ST3000DM001-9YN166_S1F0KDGY with ata-ST3000DM001-1CH166_W1F478BD: devices have different sector alignment

ZFS 使用 ashift 选项来调整物理块大小。当更换故障磁盘时,ZFS 尝试使用 ashift=12,但故障磁盘正在使用不同的 ashift (可能是 ashift=9),这会导致错误。

对于块大小为 4 KiB 的高级格式磁盘,建议使用 ashift12 以获得最佳性能。请参阅 OpenZFS FAQ: 性能注意事项ZFS 和高级格式磁盘

使用 zdb 查找 zpool 的 ashift:zdb ,然后使用 -o 参数设置更换驱动器的 ashift

# zpool replace bigdata ata-ST3000DM001-9YN166_S1F0KDGY ata-ST3000DM001-1CH166_W1F478BD -o ashift=9 -f

检查 zpool 状态以进行确认

# zpool status -v
pool: bigdata
state: DEGRADED
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
scan: resilver in progress since Mon Jun 16 11:16:28 2014
    10.3G scanned out of 5.90T at 81.7M/s, 20h59m to go
    2.57G resilvered, 0.17% done
config:

        NAME                                   STATE     READ WRITE CKSUM
        bigdata                                DEGRADED     0     0     0
        raidz1-0                               DEGRADED     0     0     0
            replacing-0                        OFFLINE      0     0     0
            ata-ST3000DM001-9YN166_S1F0KDGY    OFFLINE      0     0     0
            ata-ST3000DM001-1CH166_W1F478BD    ONLINE       0     0     0  (resilvering)
            ata-ST3000DM001-9YN166_S1F0JKRR    ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0KBP8    ONLINE       0     0     0
            ata-ST3000DM001-9YN166_S1F0JTM1    ONLINE       0     0     0

errors: No known data errors

Pool resilvering 卡住/重启/缓慢?

根据 ZFS 问题 #840,这是一个自 2012 年以来 ZFS-ZED 的已知问题,它会导致 resilvering 进程不断重启,有时会卡住,并且对于某些硬件而言通常会很慢。最简单的缓解方法是停止 zfs-zed.service 服务,直到 resilver 完成。

修复因 initramfs zpool.cache 中无法导入不可用池而导致的启动缓慢问题

如果您在导入了额外的但非永久连接的池时更新 intitramfs(例如,在进行内核更新时),您的启动时间可能会受到显着影响,因为这些池将被添加到您的 initramfs zpool.cache 中,并且 ZFS 将尝试在每次启动时导入这些额外的池,无论您是否已导出它并将其从常规 zpool.cache 中删除。

如果您注意到 ZFS 在启动时尝试导入不可用的池,请首先运行

$ zdb -C

检查您的 zpool.cache 中是否有您不想在启动时导入的池。如果此命令显示(一个或多个)额外的、当前不可用的池,请运行

# zpool set cachefile=/etc/zfs/zpool.cache zroot

清除 zpool.cache 中除名为 zroot 的池之外的任何池。有时不需要刷新您的 zpool.cache,而是您只需要重新生成 initramfs

ZFS 命令历史

ZFS 原生地将池结构的更改记录为环形缓冲区(无法关闭)中执行命令的日志。在恢复降级或失败的池时,该日志可能很有用。

# zpool history zpool
History for 'zpool':
2023-02-19.16:28:44 zpool create zpool raidz1 /scratch/disk_1.img /scratch/disk_2.img /scratch/disk_3.img
2023-02-19.16:31:29 zfs set compression=lz4 zpool
2023-02-19.16:41:45 zpool scrub zpool
2023-02-19.17:00:57 zpool replace zpool /scratch/disk_1.img /scratch/bigger_disk_1.img
2023-02-19.17:01:34 zpool scrub zpool
2023-02-19.17:01:42 zpool replace zpool /scratch/disk_2.img /scratch/bigger_disk_2.img
2023-02-19.17:01:46 zpool replace zpool /scratch/disk_3.img /scratch/bigger_disk_3.img

提示和技巧

创建具有 ZFS 支持的 Archiso 镜像

请参阅 Install Arch Linux on ZFS#将 ZFS 模块嵌入到自定义 archiso 中

自动快照

zrepl

zreplAUR 软件包提供了一个 ZFS 自动复制服务,它也可以用作快照服务,很像 snapper

有关如何配置 zrepl 守护程序的详细信息,请参阅 zrepl 文档。配置文件应位于 /etc/zrepl/zrepl.yml。然后,运行 zrepl configcheck 以确保配置文件的语法正确。最后,启用 zrepl.service

sanoid

sanoidAUR 是一个策略驱动的快照工具。Sanoid 还包括 syncoid,用于复制快照。它带有 systemd 服务和一个定时器。

Sanoid 仅修剪本地系统上的快照。要在远程系统上修剪快照,也在该系统上运行带有修剪选项的 sanoid。可以使用 --prune-snapshots 命令行选项,也可以将 --cron 命令行选项与 autoprune = yesautosnap = no 配置选项一起使用。

Linux 的 ZFS 自动快照服务

注意: zfs-auto-snapshot-gitAUR 自 2019 年以来未见任何更新,并且功能非常有限。建议您切换到更新的工具,例如 zreplAUR

zfs-auto-snapshot-gitAUR 软件包提供了一个 shell 脚本,用于自动化快照管理,每个快照都按日期和标签(每小时、每天等)命名,从而可以快速方便地对所有 ZFS 数据集进行快照。该软件包还为每刻钟、每小时、每天、每周和每月快照安装 cron 任务。根据快照要追溯的时间范围(默认情况下,每月脚本最多保留一年的数据),可以选择从默认值调整 --keep parameter

要完全阻止数据集被快照,请在其上设置 com.sun:auto-snapshot=false。同样,也可以按标签设置更精细的控制,例如,如果快照上不保留每月快照,例如,设置 com.sun:auto-snapshot:monthly=false

注意: zfs-auto-snapshot-git 在 scrubbing 期间不会创建快照。可以通过编辑提供的 systemd 单元并从 ExecStart 行中删除 --skip-scrub 来覆盖此行为。后果未知,请知情人士编辑。

安装软件包后,启用并启动选定的定时器 (zfs-auto-snapshot-{frequent,daily,weekly,monthly}.timer)。

创建共享

ZFS 支持通过 NFSSMB 创建共享。

NFS

确保 NFS 已安装/配置,请注意无需编辑 /etc/exports 文件。对于通过 NFS 共享,应启动 nfs-server.servicezfs-share.service 服务。

使池在网络上可用

# zfs set sharenfs=on nameofzpool

使数据集在网络上可用

# zfs set sharenfs=on nameofzpool/nameofdataset

为特定的 IP 范围启用读/写访问权限

# zfs set sharenfs="rw=@192.168.1.100/24,rw=@10.0.0.0/24" nameofzpool/nameofdataset

检查数据集是否已成功导出

# showmount -e `hostname`
Export list for hostname:
/path/of/dataset 192.168.1.100/24

要更详细地查看当前加载的导出状态,请使用

# exportfs -v
/path/of/dataset
    192.168.1.100/24(sync,wdelay,hide,no_subtree_check,mountpoint,sec=sys,rw,secure,no_root_squash,no_all_squash)

查看 ZFS 当前的 NFS 共享列表

# zfs get sharenfs

SMB

注意: SMB 功能非常有限。usershare 路径必须是 /var/lib/samba/usershares,并且唯一支持的 sharesmb 选项是 onoff。不支持通过 sharesmb=guest_ok=y 启用访客访问。

通过 SMB 共享时,在 /etc/samba/smb.conf 中使用 usershares 将允许 ZFS 设置和创建共享。有关详细信息,请参阅 Samba#启用 Usershares

/etc/samba/smb.conf
[global]
    usershare path = /var/lib/samba/usershares
    usershare max shares = 100
    usershare allow guests = yes
    usershare owner only = no

以 root 用户身份创建用户目录并设置权限

# mkdir /var/lib/samba/usershares
# chmod +t /var/lib/samba/usershares

使池在网络上可用

# zfs set sharesmb=on nameofzpool

使数据集在网络上可用

# zfs set sharesmb=on nameofzpool/nameofdataset

检查数据集是否已成功导出

# smbclient -L localhost -U%
        Sharename       Type      Comment
        ---------       ----      -------
        IPC$            IPC       IPC Service (SMB Server Name)
        nameofzpool_nameofdataset        Disk      Comment: path/of/dataset
SMB1 disabled -- no workgroup available

查看 ZFS 当前的 SMB 共享列表

# zfs get sharesmb

在 ZFS 中使用 dm-crypt 加密

OpenZFS 0.8.0 版本 之前,ZFS 不直接支持加密(请参阅 #原生加密)。相反,zpool 可以在 dm-crypt 块设备上创建。由于 zpool 是在明文抽象层上创建的,因此可以在加密数据的同时拥有 ZFS 的所有优点,例如重复数据删除、压缩和数据稳健性。此外,使用 dm-crypt 将加密 zpool 的元数据,这是原生加密本身无法提供的。[8]

dm-crypt(可能通过 LUKS)在 /dev/mapper 中创建设备,并且它们的名称是固定的。因此,您只需更改 zpool create 命令以指向这些名称。其思路是配置系统以创建 /dev/mapper 块设备并从那里导入 zpool。由于 zpool 可以在多个设备中创建(raid、镜像、条带化等),因此所有设备都必须加密,否则保护可能会部分丢失。

例如,可以使用纯 dm-crypt(不带 LUKS)创建加密的 zpool,命令如下:

# cryptsetup open --type=plain --hash=sha256 --cipher=aes-xts-plain64 --offset=0 \
             --key-file=/dev/sdZ --key-size=512 /dev/sdX enc
# zpool create zroot /dev/mapper/enc

对于根文件系统池,mkinitcpio.conf HOOKS 行将启用键盘以输入密码、创建设备并加载池。它将包含类似以下内容:

HOOKS=(... keyboard encrypt zfs ...)

由于 /dev/mapper/enc 名称是固定的,因此不会发生导入错误。

创建加密的 zpool 可以正常工作。但是,如果您需要加密目录,例如保护用户的主目录,ZFS 会失去一些功能。

ZFS 将看到加密的数据,而不是明文抽象层,因此压缩和重复数据删除将无法工作。原因是加密数据始终具有高熵,这使得压缩无效,即使来自相同的输入,您也会得到不同的输出(由于加盐),这使得重复数据删除变得不可能。为了减少不必要的开销,可以为每个加密目录创建一个子文件系统,并在其上使用 eCryptfs

例如,要拥有一个加密的 home 目录:(两个密码,加密密码和登录密码,必须相同)

# zfs create -o compression=off -o dedup=off -o mountpoint=/home/<username> <zpool>/<username>
# useradd -m <username>
# passwd <username>
# ecryptfs-migrate-home -u <username>
<log in user and complete the procedure with ecryptfs-unwrap-passphrase>

使用 archzfs 进行紧急 chroot 修复

要从 live 系统进入 ZFS 文件系统进行维护,有两种选择

  1. 构建带有 ZFS 的自定义 archiso,如 #创建具有 ZFS 支持的 Archiso 镜像 中所述。
  2. 启动最新的官方 archiso 并启动网络。然后像往常一样在 live 系统中启用 archzfs 仓库,同步 pacman 软件包数据库并安装 archzfs-archiso-linux 软件包。

要开始恢复,请加载 ZFS 内核模块

# modprobe zfs

导入池

# zpool import -a -R /mnt

挂载启动分区和 EFI 系统分区(如果有)

# mount /dev/sda2 /mnt/boot
# mount /dev/sda1 /mnt/efi

Chroot 进入 ZFS 文件系统

# arch-chroot /mnt /bin/bash

检查内核版本

# pacman -Qi linux
# uname -r

uname 将显示 archiso 的内核版本。如果它们不同,请使用 chroot 安装的正确内核版本运行 depmod(在 chroot 中)

# depmod -a 3.6.9-1-ARCH (version gathered from pacman -Qi linux but using the matching kernel modules directory name under the chroot's /lib/modules)

这将为 chroot 安装中安装的内核版本加载正确的内核模块。

重新生成 initramfs。应该没有错误。

绑定挂载

这里创建了从 /mnt/zfspool 到 /srv/nfs4/music 的绑定挂载。该配置确保在创建绑定挂载之前 zfs 池已准备就绪。

fstab

有关 systemd 如何使用 systemd-fstab-generator(8) 将 fstab 转换为挂载单元文件的更多信息,请参阅 systemd.mount(5)

/etc/fstab
/mnt/zfspool		/srv/nfs4/music		none	bind,defaults,nofail,x-systemd.requires=zfs-mount.service	0 0

事件监控/邮件通知

有关更多信息,请参阅 ZED: The ZFS Event Daemon

需要一个电子邮件转发器,例如 S-nail 才能完成此操作。测试它以确保它工作正常。

取消注释配置文件中的以下内容

/etc/zfs/zed.d/zed.rc
 ZED_EMAIL_ADDR="root"
 ZED_EMAIL_PROG="mailx"
 ZED_NOTIFY_VERBOSE=0
 ZED_EMAIL_OPTS="-s '@SUBJECT@' @ADDRESS@"

ZED_EMAIL_ADDR="root" 中的 'root' 更新为您要接收通知的电子邮件地址。

如果您将 mailrc 保存在您的主目录中,您可以通过设置 MAILRC 来告诉 mail 从那里获取它

/etc/zfs/zed.d/zed.rc
export MAILRC=/home/<user>/.mailrc

这是可行的,因为 ZED 会读取此文件,因此 mailx 可以看到此环境变量。

如果您想接收电子邮件,无论您的池的状态如何,您都需要设置 ZED_NOTIFY_VERBOSE=1。您需要临时执行此操作以进行测试。

启动启用 zfs-zed.service

使用 ZED_NOTIFY_VERBOSE=1,您可以通过以 root 身份运行 scrub 来进行测试:zpool scrub <pool-name>

将 shell 命令包装在快照前和快照后

由于创建快照非常廉价,我们可以将其用作敏感命令(例如系统和软件包升级)的安全措施。如果我们先创建一个快照,然后再创建一个快照,我们可以稍后比较这些快照,以找出命令执行后文件系统中发生了哪些更改。此外,如果结果不理想,我们还可以回滚。

znp

例如:

# zfs snapshot -r zroot@pre
# pacman -Syu
# zfs snapshot -r zroot@post
# zfs diff zroot@pre zroot@post 
# zfs rollback zroot@pre

一个自动化在 shell 命令周围创建快照前和快照后的实用程序是 znp

例如:

# znp pacman -Syu
# znp find / -name "something*" -delete

您将获得在提供的命令之前和之后创建的快照,以及记录到文件的命令输出,以供将来参考,以便我们知道哪个命令创建了一对快照前/快照后中看到的差异。

远程解锁 ZFS 加密根目录

警告: 下面提到的一些 hooks,例如 mkinitcpio-netconfmkinitcpio-tinysshmkinitcpio-dropbear,长期未维护,并且存在多个问题,其中一些问题与安全相关。考虑使用 mkinitcpio-extrasAUR,因为它提供了上述 hooks 的功能(以及其他功能,例如配置 shell、更改 SSH 服务器的端口等),并且正在积极维护。

PR #261 开始,archzfs 支持通过 SSH 解锁原生加密的 ZFS 数据集。本节介绍如何使用此功能,并且主要基于 dm-crypt/Specialties#基于 Busybox 的 initramfs(使用 mkinitcpio 构建)

  1. 安装 mkinitcpio-netconf 以提供用于设置早期用户空间网络的 hooks。
  2. 选择要在早期用户空间中使用的 SSH 服务器。选项是 mkinitcpio-tinysshmkinitcpio-dropbear,两者互斥。
    1. 如果使用 mkinitcpio-tinyssh,还建议安装 tinysshtinyssh-convert-gitAUR。此工具将现有的 OpenSSH hostkey 转换为 TinySSH 密钥格式,保留密钥指纹并避免连接警告。TinySSH 和 Dropbear mkinitcpio 安装脚本将在生成新的 initcpio 镜像时自动转换现有的 hostkey。
  3. 决定是使用现有的 OpenSSH 密钥还是为将要连接和解锁加密 ZFS 机器的主机生成新密钥(推荐)。将公钥复制到 /etc/tinyssh/root_key/etc/dropbear/root_key。生成 initcpio 镜像时,此文件将添加到 root 用户的 authorized_keys 中,并且仅在 initrd 环境中有效。
  4. ip= 内核参数 添加到您的引导加载程序配置中。ip 字符串是 高度可配置的。下面显示了一个简单的 DHCP 示例。
    ip=:::::eth0:dhcp
  5. 编辑 /etc/mkinitcpio.conf 以在 zfs hook 之前包含 netconfdropbeartinysshzfsencryptssh hooks
    HOOKS=(... netconf <tinyssh>|<dropbear> zfsencryptssh zfs ...)
  6. 重新生成 initramfs.
  7. 重新启动并尝试一下!

更改 SSH 服务器端口

默认情况下,mkinitcpio-tinysshmkinitcpio-dropbear 监听端口 22。您可能希望更改此端口。

对于 TinySSH,将 /usr/lib/initcpio/hooks/tinyssh 复制到 /etc/initcpio/hooks/tinyssh,并在 run_hook() 函数中查找/修改以下行

/etc/initcpio/hooks/tinyssh
/usr/bin/tcpserver -HRDl0 0.0.0.0 <new_port> /usr/sbin/tinysshd -v /etc/tinyssh/sshkeydir &

对于 Dropbear,将 /usr/lib/initcpio/hooks/dropbear 复制到 /etc/initcpio/hooks/dropbear,并在 run_hook() 函数中查找/修改以下行

/etc/initcpio/hooks/tinyssh
 /usr/sbin/dropbear -E -s -j -k -p <new_port>

重新生成 initramfs.

使用 PuTTY/Plink 从 Windows 机器解锁

首先,我们需要使用 puttygen.exe 导入并将先前生成的 OpenSSH 密钥转换为 PuTTY 的 .ppk 私钥格式。在本示例中,我们将其称为 zfs_unlock.ppk

上面的 mkinitcpio-netconf 进程没有设置 shell(我们也不需要 shell)。但是,由于没有 shell,PuTTY 会在成功连接后立即关闭。可以在 PuTTY SSH 配置中禁用此行为(Connection > SSH > [X] Do not start a shell or command at all),但它仍然不允许我们查看 stdout 或输入加密密码。相反,我们使用带有以下参数的 plink.exe

plink.exe -ssh -l root -i c:\path\to\zfs_unlock.ppk <hostname>

plink 命令可以放入批处理脚本中以方便使用。

启用 bclone 支持

要使用 cp --reflink 和其他需要 bclone 支持的命令,如果来自 2.2.2 之前的版本,则需要升级功能标志。这将允许池支持 bclone。如果池的状态显示这是可能的,则可以使用 zpool upgrade 完成此操作。

还需要启用模块参数,否则用户空间应用程序将无法使用此功能。您可以通过将其放入 /etc/modprobe.d/zfs.conf 来完成此操作

/etc/modprobe.d/zfs.conf
options zfs zfs_bclone_enabled=1

使用命令检查它是否正常工作以及节省了多少空间:zpool get all POOLNAME | grep clon

另请参阅