ZFS 启动环境详解

这篇文章不会尝试给出 ZFS 的通用解释——我们只会专注于 ZFS 启动环境。关于它们存在许多误解和误会——有些人不知道 ZFS 启动环境里面有什么、ZFS 启动环境里面没有什么——但更糟的是——有些人完全不懂。

我假设读者确实了解什么是 ZFS 文件系统的概念和特性 —— 比如 ZFS 池或 ZFS 数据集之类的东西对读者来说是已知的……并且读者能够解释 ZFS 和 LVM 之间的区别。

ZFS 启动环境里有什么

这是首先经常被误解的主题……大概是因为没有理解 canmount=off 这个 ZFS 属性 —— 你可以在 man zfsprops(7) 中获得更多相关内容。

如果该属性被设置为 off,则文件系统无法被挂载,并且会被 zfs mount -a 忽略。将该属性设置为 off 类似于将 mountpoint 属性设置为 none,但数据集仍然具有正常的 mountpoint 属性,该属性可以被继承。将此属性设置为 off 允许数据集仅作为继承属性的机制使用。设置 canmount=off 的一个例子是创建两个具有相同挂载点的数据集,这样两个数据集的子数据集会显示在同一目录中,但可能具有不同的继承特性。

当设置为 noauto 时,数据集只能显式地挂载和卸载。数据集在创建或导入时不会自动挂载,也不会被 zfs mount -a 命令挂载,或被 zfs unmount -a 命令卸载。

该属性不可继承。——整理者加

这是我以前关于 “ZFS 启动环境” 的演讲中的幻灯片 —— 可以在链接 https://is.gd/BECTL 获得 —— 那是在 2018 NLUUG 会议上做的……颜色并不是随便选的……它们是特地准备成那样,以致敬 NLUUG 会议举办的国家 —— 荷兰。

/var/usr 使用 canmount=off 背后的想法是这样的:

/var/usr 这些 ZFS 数据集的内容确实位于 ZFS 启动环境中 —— 位于安装程序创建的 zroot/ROOT/default 这个 ZFS 启动环境中 —— 而其余像下面这些这样的内容:

zroot/tmp
zroot/usr/home
zroot/usr/ports
zroot/usr/src
zroot/var/audit
zroot/var/crash
zroot/var/log
zroot/var/mail
zroot/var/tmp

这些内容 被排除在这个 ZFS 启动环境之外

因此,在默认情况下,一切都包含在 ZFS 启动环境中 —— 如果你想从 /usr/var 中排除某些部分 —— 你就为这些需要排除的路径添加相应的 ZFS 数据集。

下面是你在 bsdinstall(8) 安装程序中选择 Auto(ZFS) 选项后,FreeBSD 默认的 ZFS 布局。

root@freebsd:~ # zfs set mountpoint=none zroot

root@freebsd:~ # zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
zroot                385M  18.5G    96K  none
zroot/ROOT           383M  18.5G    96K  none
zroot/ROOT/default   383M  18.5G   383M  /
zroot/home            96K  18.5G    96K  /home
zroot/tmp             96K  18.5G    96K  /tmp
zroot/usr            288K  18.5G    96K  /usr
zroot/usr/ports       96K  18.5G    96K  /usr/ports
zroot/usr/src         96K  18.5G    96K  /usr/src
zroot/var            600K  18.5G    96K  /var
zroot/var/audit       96K  18.5G    96K  /var/audit
zroot/var/crash       96K  18.5G    96K  /var/crash
zroot/var/log        120K  18.5G   120K  /var/log
zroot/var/mail        96K  18.5G    96K  /var/mail
zroot/var/tmp         96K  18.5G    96K  /var/tmp

root@freebsd:~ # zfs get canmount
NAME                PROPERTY  VALUE     SOURCE
zroot               canmount  on        default
zroot/ROOT          canmount  on        default
zroot/ROOT/default  canmount  noauto    local
zroot/home          canmount  on        default
zroot/tmp           canmount  on        default
zroot/usr           canmount  off       local
zroot/usr/ports     canmount  on        default
zroot/usr/src       canmount  on        default
zroot/var           canmount  off       local
zroot/var/audit     canmount  on        default
zroot/var/crash     canmount  on        default
zroot/var/log       canmount  on        default
zroot/var/mail      canmount  on        default
zroot/var/tmp       canmount  on        default

关键点在于,zroot/usrzroot/var 这两个 ZFS 数据集都具有 canmount=off 这个 ZFS 属性。这意味着 /usr 文件系统 并不存放在 zroot/usr 这个 ZFS 数据集中……它是存放在 zroot/ROOT/default 这个 ZFS 启动环境里的。

你可以用传统的 df(1) 命令进行验证如下。

root@freebsd:~ # df -g /usr
Filesystem         1G-blocks Used Avail Capacity  Mounted on
zroot/ROOT/default        18    0    18     2%    /

root@freebsd:~ # df -g /var
Filesystem         1G-blocks Used Avail Capacity  Mounted on
zroot/ROOT/default        18    0    18     2%    /

如你所见,/usr/var 都只是 zroot/ROOT/default 这个 ZFS 数据集中的目录。数据并 保存在 zroot/usrzroot/var 这些 ZFS 数据集中……再次强调,这是因为它们具有 canmount=off 这个 ZFS 属性。

现在,这个 FreeBSD 默认设置的主要理念 / 概念是:所有内容都保存在 zroot/ROOT/default 这个 ZFS 数据集中,而所有其他 ZFS 数据集都是从它中 排除(exclude) 出去的。比如 zroot/var/audit 具有 canmount=on 这个 ZFS 属性……其他所有的也都是完全相同的情况。

说实话,为了让事情变得傻瓜式地简单,我会把 zroot/usrzroot/var 这两个 ZFS 数据集换成一个能准确说明用途的名称 —— zroot/exclude,因为在它下面的所有 ZFS 数据集,本质上都是被排除在 ZFS 启动环境 之外的。

root@freebsd:~ # zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
zroot                385M  18.5G    96K  none
zroot/ROOT           383M  18.5G    96K  none
zroot/ROOT/default   383M  18.5G   383M  /
zroot/home            96K  18.5G    96K  /home
zroot/tmp             96K  18.5G    96K  /tmp
zroot/usr            288K  18.5G    96K  /usr
zroot/usr/ports       96K  18.5G    96K  /usr/ports
zroot/usr/src         96K  18.5G    96K  /usr/src
zroot/var            600K  18.5G    96K  /var
zroot/var/audit       96K  18.5G    96K  /var/audit
zroot/var/crash       96K  18.5G    96K  /var/crash
zroot/var/log        120K  18.5G   120K  /var/log	
zroot/var/mail        96K  18.5G    96K  /var/mail
zroot/var/tmp         96K  18.5G    96K  /var/tmp

root@freebsd:~ # zfs create -o canmount=off -o mountpoint=none zroot/exclude

root@freebsd:~ # zfs rename -u zroot/usr  zroot/exclude/usr
root@freebsd:~ # zfs rename -u zroot/var  zroot/exclude/var
root@freebsd:~ # zfs rename -u zroot/home zroot/exclude/home
root@freebsd:~ # zfs rename -u zroot/tmp  zroot/exclude/tmp

root@freebsd:~ # zfs list
NAME                      USED  AVAIL  REFER  MOUNTPOINT
zroot                     385M  18.5G    96K  none
zroot/ROOT                383M  18.5G    96K  none
zroot/ROOT/default        383M  18.5G   383M  /
zroot/exclude            1.16M  18.5G    96K  none
zroot/exclude/home         96K  18.5G    96K  /home
zroot/exclude/tmp          96K  18.5G    96K  /tmp
zroot/exclude/usr         288K  18.5G    96K  /usr
zroot/exclude/usr/ports    96K  18.5G    96K  /usr/ports
zroot/exclude/usr/src      96K  18.5G    96K  /usr/src
zroot/exclude/var         612K  18.5G    96K  /var
zroot/exclude/var/audit    96K  18.5G    96K  /var/audit
zroot/exclude/var/crash    96K  18.5G    96K  /var/crash
zroot/exclude/var/log     132K  18.5G   132K  /var/log
zroot/exclude/var/mail     96K  18.5G    96K  /var/mail
zroot/exclude/var/tmp      96K  18.5G    96K  /var/tmp

现在更容易理解了吗?我希望是的……即使在上面进行了所有这些重命名,ZFS 的挂载点也 没有任何变化,因为 mountpoint 是个独立的 ZFS 属性——所以即使我们对 ZFS 数据集的命名逻辑进行了重新组织,只要没有从上层继承,这个 ZFS mountpoint 属性在逻辑重组后依然保持不变。

之前使用 df(1) 的测试结果与之前完全相同。

root@freebsd:~ # df -g /usr
Filesystem         1G-blocks Used Avail Capacity  Mounted on
zroot/ROOT/default        18    0    18     2%    /

root@freebsd:~ # df -g /var
Filesystem         1G-blocks Used Avail Capacity  Mounted on
zroot/ROOT/default        18    0    18     2%    /

/usr/var 的数据都保存在 default ZFS 启动环境内。

我们甚至可以移除 /usr/var 的挂载点,这样会更加清晰。

root@freebsd:~ # zfs set -u mountpoint=/var/tmp   zroot/exclude/var/tmp
root@freebsd:~ # zfs set -u mountpoint=/var/mail  zroot/exclude/var/mail
root@freebsd:~ # zfs set -u mountpoint=/var/log   zroot/exclude/var/log
root@freebsd:~ # zfs set -u mountpoint=/var/crash zroot/exclude/var/crash
root@freebsd:~ # zfs set -u mountpoint=/var/audit zroot/exclude/var/audit
root@freebsd:~ # zfs set -u mountpoint=none       zroot/exclude/var

root@freebsd:~ # zfs set -u mountpoint=/usr/src   zroot/exclude/usr/src
root@freebsd:~ # zfs set -u mountpoint=/usr/ports zroot/exclude/usr/ports
root@freebsd:~ # zfs set -u mountpoint=none       zroot/exclude/usr

root@freebsd:~ # zfs list
NAME                      USED  AVAIL  REFER  MOUNTPOINT
zroot                     385M  18.5G    96K  none
zroot/ROOT                383M  18.5G    96K  none
zroot/ROOT/default        383M  18.5G   383M  /
zroot/exclude            1.16M  18.5G    96K  none
zroot/exclude/home         96K  18.5G    96K  /home
zroot/exclude/tmp          96K  18.5G    96K  /tmp
zroot/exclude/usr         288K  18.5G    96K  none
zroot/exclude/usr/ports    96K  18.5G    96K  /usr/ports
zroot/exclude/usr/src      96K  18.5G    96K  /usr/src
zroot/exclude/var         612K  18.5G    96K  none
zroot/exclude/var/audit    96K  18.5G    96K  /var/audit
zroot/exclude/var/crash    96K  18.5G    96K  /var/crash
zroot/exclude/var/log     132K  18.5G   132K  /var/log
zroot/exclude/var/mail     96K  18.5G    96K  /var/mail
zroot/exclude/var/tmp      96K  18.5G    96K  /var/tmp

root@freebsd:~ # df -g
Filesystem              1G-blocks Used Avail Capacity  Mounted on
/dev/gpt/efiboot0               0    0     0     0%    /boot/efi
devfs                           0    0     0     0%    /dev
zroot/ROOT/default             18    0    18     2%    /
zroot/exclude/tmp              18    0    18     0%    /tmp
zroot/exclude/home             18    0    18     0%    /home
zroot/exclude/var/log          18    0    18     0%    /var/log
zroot/exclude/var/tmp          18    0    18     0%    /var/tmp
zroot/exclude/var/mail         18    0    18     0%    /var/mail
zroot/exclude/var/crash        18    0    18     0%    /var/crash
zroot/exclude/var/audit        18    0    18     0%    /var/audit
zroot/exclude/usr/src          18    0    18     0%    /usr/src
zroot/exclude/usr/ports        18    0    18     0%    /usr/ports

够清楚了吗?

新建 ZFS 启动环境时会发生什么

另一个经常被误解的谜题……或者说没有被清楚理解的部分。

通常,ZFS 启动环境 是由某个时间点创建的 ZFS 快照 克隆出来的可写 ZFS clone

让我用我最喜欢的 Enterprise Architect ASCII Edition 软件来可视化一下。

                                 |
                    ZFS DATASET  |   ZFS MOUNTPOINT => WHY
                                 |
          +-------------------+  |
          | ZFS 'zroot' pool  |  |  /sys            => # zfs set mountpoint=/sys sys
          |     (dataset)     |  |  canmount:on     => # zfs set canmount=on     sys
          +---------+---------+  |
                    |            |
            +-------+-------+    |
            |     ROOT      |    |  (none)          => # zfs set mountpoint=none sys/ROOT
            |   (dataset)   |    |  canmount:on     => # zfs set canmount=on     sys/ROOT
            +-------+-------+    |
                    |            |
              +-----+-----+      |
              |  default  |      |  /               => # zfs set mountpoint=/    sys/ROOT/${DATASET}
              | (dataset) |      |  canmount:noauto => # zfs set canmount=noauto sys/ROOT/${DATASET}
           +--+-----------+      |
           |                     |
           +- @2025-11-11@10:10  |  point-in-time   => # zfs snapshot            sys/ROOT/${DATASET}@2025-11-11@10:10
              | (snapshot)       |  (read only)        # beadm create            sys/ROOT/${DATASET}@2025-11-11@10:10
              |                  |
              +- safe            |  clone           => # zfs clone               sys/ROOT/${DATASET}@2025-11-11@10:10 sys/ROOT/safe
              | (clone)          |  (writable)         # beadm create -e default@2025-11-11@10:10 safe
              |                  |
              +- test            |  clone           => # zfs clone               sys/ROOT/${DATASET}@2025-11-11@10:10 sys/ROOT/test
                (clone)          |  (writable)         # beadm create -e default@2025-11-11@10:10 test
                                 |

关于上面的 ASCII 图,beadm(8) 命令的输出将显示类似的信息。

root@freebsd:~ # beadm list
BE                  Active Mountpoint  Space Created
default             NR     /           24.0G 2025-10-08 01:42
safe                -      -            1.3G 2025-06-10 09:47
test                -      -            6.4G 2025-08-22 23:02

root@freebsd:~ # zfs list -r -t all zroot/ROOT
NAME                                                 USED  AVAIL  REFER  MOUNTPOINT
zroot/ROOT                                          29.8G   154G    96K  none
zroot/ROOT/default                                   748K   154G  19.5G  /
zroot/ROOT/default@2025-11-11@10:10                 16.1G      -  24.2G  -
zroot/ROOT/safe                                        8K   154G  20.5G  /
zroot/ROOT/test                                      810M   154G  20.9G  /

root@freebsd:~ # beadm list -a
BE/Dataset/Snapshot                Active Mountpoint  Space Created

default
  zroot/ROOT/default               NR     /           24.0G 2025-10-08 01:42

safe
  zroot/ROOT/safe                  -      -          748.0K 2025-06-10 09:47
    default@2025-11-11@10:10       -      -            1.3G 2025-10-08 01:42

test
  zroot/ROOT/test                  -      -          810.0M 2025-08-22 23:02
    default@2025-11-11@10:10        -      -            5.6G 2025-08-22 23:02


root@freebsd:~ # zfs get origin zroot/ROOT/default
NAME                PROPERTY  VALUE   SOURCE
zroot/ROOT/default  origin    -       -

root@freebsd:~ # zfs get origin zroot/ROOT/safe
NAME             PROPERTY  VALUE                                SOURCE
zroot/ROOT/safe  origin    zroot/ROOT/default@2025-11-11@10:10  -

root@freebsd:~ # zfs get origin zroot/ROOT/test
NAME             PROPERTY  VALUE                                SOURCE
zroot/ROOT/test  origin    zroot/ROOT/default@2025-11-11@10:10  -

root@freebsd:~ # zpool get bootfs
NAME   PROPERTY  VALUE                SOURCE
zdata  bootfs    -                    default
zroot  bootfs    zroot/ROOT/default   local

现在……有趣的部分来了——你可以创建任意数量的额外 ZFS 启动环境,或者使用 ZFS 的 send|recv 功能。

下面是这种设置的示例。

                                        |
                       ZFS DATASET      |       MOUNTPOINT => WHY
                                        |
         +-----------------------+      |
         |    ZFS 'zroot' pool   |      |  /sys            => # zfs set mountpoint=/sys sys
         |     (ZFS dataset)     |      |  canmount:on     => # zfs set canmount=on     sys
         +-----------+-----------+      |
                     |                  |
           +---------+---------+        |
           |       ROOT        |        |  (none)          => # zfs set mountpoint=none sys/ROOT
           |   (ZFS dataset)   |        |  canmount:on     => # zfs set canmount=on     sys/ROOT
           +---------+---------+        |
                     |                  |
      +--------------+----------+       |
      |              |          |       |
 +----+----+ +-------+---+ +----+----+  |
 | default | |  15.0-RC1 | |  12.2   |  |  /               => # zfs set mountpoint=/    sys/ROOT/${DATASET}
 |(dataset)| | (dataset) | |(dataset)|  |  canmount:noauto => # zfs set canmount=noauto sys/ROOT/${DATASET}
 +---------+ +-----------+ +---------+  |
                                        |

……并且你可以从每一个这些 ZFS dataset 创建额外的 ZFS 启动环境。

在系统之间传输 ZFS 启动环境

……而且这些系统可以是 任意 系统。你可以在笔记本之间传输 ZFS 启动环境,或者从笔记本传到服务器……甚至从 Bhyve VM 传到服务器……随你喜欢。

下面你将看到一些示例,展示如何将现有的 ZFS 启动环境通过网络从一个 FreeBSD UNIX 系统复制到另一个系统。

这里的 mbuffer(1) 并非关键——它只是通过确保随时有数据可发送来加快传输过程。

root@freebsd:~ # beadm export 14.3 | mbuffer | ssh 10.26 doas beadm import 14.3.w520
summary: 30.3 GiByte in 34min 04.8sec - average of 15.2 MiB/s

上面演示的是将我在 ThinkPad W520 上的 14.3 ZFS 启动环境发送到 ThinkPad T14 系统——这也是为什么我想在另一端将其命名为 14.3.w520,以确保我记得它的来源。

你也可以像我在文章 Other FreeBSD Version in ZFS Boot Environment 中描述的那样,从零创建新的 ZFS 启动环境。

总结

我希望现在 ZFS 启动环境 的主要原理更加清晰了。

……如果还有不明白的地方,欢迎提出你的疑问。

最后更新于

这有帮助吗?