# 25.2 ZFS 磁盘扩容

本节系统阐述 ZFS 文件系统磁盘扩容的技术原理、操作流程与风险控制。作为企业级存储管理的核心功能之一，ZFS 提供了安全、灵活的存储池扩容机制，支持在线扩展而无需中断服务。ZFS 扩容分为两个步骤：首先调整底层分区大小以利用新增空间，然后通过 ZFS 命令让存储池重新识别设备的新容量，从而完成池的扩展。

## 磁盘扩容

> **警告**
>
> ZFS 文件系统只能扩展，不能缩小。因此无法撤销更改。

> **注意**
>
> 此方法仅适用于向后扩展，如果 freebsd-zfs 分区前方有空闲空间，则无法使用此方法进行扩展。

显示当前磁盘分区表和分区信息：

```sh
# gpart show
=>       40  167772087  nda0  GPT  (80G)
         40     532480     1  efi  (260M)
     532520       1024     2  freebsd-boot  (512K)
     533544        984        - free -  (492K)
     534528    4194304     3  freebsd-swap  (2.0G)
    4728832  142071775     4  freebsd-zfs  (68G)
  146800607   20971520        - free -  (10G)
```

可以看到，`free` 空闲空间是 10 GB。

根据分区表信息，选择位于空闲空间前方的 freebsd-zfs 分区（本例中为第 4 个分区）进行扩容，操作前请确认分区序号正确：

```sh
# gpart resize -i 4 nda0	# 调整 nda0 磁盘上第 4 个分区的大小
nda0p4 resized
```

显示当前磁盘分区表和分区信息：

```sh
# gpart show
=>       40  167772087  nda0  GPT  (80G)
         40     532480     1  efi  (260M)
     532520       1024     2  freebsd-boot  (512K)
     533544        984        - free -  (492K)
     534528    4194304     3  freebsd-swap  (2.0G)
    4728832  163043295     4  freebsd-zfs  (78G)
```

列出系统中所有 ZFS 池及其状态信息：

```sh
# zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot  67.5G  2.20G  65.3G        -         -     2%     3%  1.00x    ONLINE  -	# 可以观察到容量仍为 67.5G，尚未扩展。这是因为分区扩容仅调整了分区表，ZFS 池尚未感知到底层设备的空间变化，需要单独操作进行扩展。
```

显示 ZFS 池的详细状态信息，包括健康状况和错误信息：

```sh
# zpool status
  pool: zroot
 state: ONLINE
status: Some supported and requested features are not enabled on the pool.
	The pool can still be used, but some features are unavailable.
action: Enable all features using 'zpool upgrade'. Once this is done,
	the pool may no longer be accessible by software that does not support
	the features. See zpool-features(7) for details.
config:

	NAME        STATE     READ WRITE CKSUM
	zroot       ONLINE       0     0     0	# 池名为默认的 zroot
	  nda0p4    ONLINE       0     0     0	# 对应分区为 nda0p4

errors: No known data errors
```

扩展 ZFS 池：

```sh
# zpool online -e zroot nda0p4	# 对 ZFS 池 zroot 中的 nda0p4 分区进行在线扩展。-e 参数表示 expand，让 ZFS 重新扫描设备大小并扩展池容量
```

查看扩容后的所有 ZFS 池及其容量、使用情况和健康状态：

```sh
# zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot  77.5G  2.20G  75.3G        -         -     2%     2%  1.00x    ONLINE  -	# 扩容完成后显示的可用容量为 75.3G，符合预期
```

已经扩展完成。

### 参考文献

* FreeBSD Forums. Solved-extend ZFS partition\[EB/OL]. \[2026-04-02]. <https://forums.freebsd.org/threads/extend-zfs-partition.55964/>. FreeBSD 论坛上的 ZFS 分区扩容解决方案。

## 附录

本附录介绍一些 `gpart` 命令的额外用法。可以通过 `gpart show` 命令获取分区编号，也可以使用参数 `-p` 以完整路径显示所有磁盘及分区信息。

```sh
# gpart show -p
=>       40  244277168    mmcsd0  GPT  (116G)
         40     532480  mmcsd0p1  efi  (260M)
     532520       2008            - free -  (1.0M)
     534528  243740672  mmcsd0p2  freebsd-zfs  (116G)
  244275200       2008            - free -  (1.0M)

=>       34  976773101    nda0  GPT  (466G)
         34          6          - free -  (3.0K)
         40     567256  nda0p1  efi  (277M)
     567296  419436064  nda0p2  ms-basic-data  (200G)
  420003360  310592132  nda0p3  ms-basic-data  (148G)
  730595492          4          - free -  (2.0K)
  730595496  177626968  nda0p4  ms-basic-data  (85G)
  908222464   67100672  nda0p5  freebsd-swap  (32G)
  975323136    1445937  nda0p6  ms-recovery  (706M)
  976769073       4062          - free -  (2.0M)
```

* 打印分区类型 GUID（适用于 GPT）或原始分区类型（适用于 MBR）

以可重现和完整的路径形式显示磁盘及分区信息：

```sh
# gpart show -rp
=>       40  244277168    mmcsd0  GPT  (116G)
         40     532480  mmcsd0p1  c12a7328-f81f-11d2-ba4b-00a0c93ec93b  (260M)
     532520       2008            - free -  (1.0M)
     534528  243740672  mmcsd0p2  516e7cba-6ecf-11d6-8ff8-00022d09712b  (116G)
  244275200       2008            - free -  (1.0M)

=>       34  976773101    nda0  GPT  (466G)
         34          6          - free -  (3.0K)
         40     567256  nda0p1  c12a7328-f81f-11d2-ba4b-00a0c93ec93b  (277M)
     567296  419436064  nda0p2  ebd0a0a2-b9e5-4433-87c0-68b6b72699c7  (200G)
  420003360  310592132  nda0p3  ebd0a0a2-b9e5-4433-87c0-68b6b72699c7  (148G)
  730595492          4          - free -  (2.0K)
  730595496  177626968  nda0p4  ebd0a0a2-b9e5-4433-87c0-68b6b72699c7  (85G)
  908222464   67100672  nda0p5  516e7cb5-6ecf-11d6-8ff8-00022d09712b  (32G)
  975323136    1445937  nda0p6  de94bba4-06d1-4d40-a16a-bfd50179d6ac  (706M)
  976769073       4062          - free -  (2.0M)
```

* 显示磁盘 `mmcsd0` 的详细分区信息：

```sh
# gpart list mmcsd0
```

### 参考文献

* 金步国. GPT 分区详解\[EB/OL]. \[2026-04-02]. <https://www.jinbuguo.com/storage/gpt.html>. GPT 分区基础技术文档。
* 匆匆过客. 如何轻松改变分区类型 ID？试试这 2 种方法！\[EB/OL]. (2022-11-20)\[2026-04-02]. <https://www.disktool.cn/content-center/change-partition-type-id-2111.html>. 介绍分区类型 ID 与分区 UUID 的区别。

## 故障排除

本节介绍 ZFS 磁盘扩容过程中可能遇到的常见问题及解决方案。

### `gpart: table 'ada0' is corrupt: Operation not permitted`

提示分区表错误，需要重置 GPT 分区表。此问题多发生在直接导入的裸磁盘映像上。`gpart recover` 命令会从备份的 GPT 表头恢复主分区表信息。

```sh
gpart recover ada0
```

此问题多发生在直接导入的裸磁盘映像上。

### ZFS 无法向前扩展

**错误示例方法**

> **警告**
>
> 此方法不正确，请勿在生产环境中使用。

如果使用 ZFS 作为 `/` 根文件系统：

```sh
# gpart add -t freebsd-zfs -a 4k -s 210751598 -l add100G diskid/DISK-FXS690MQ233011234   # 在指定磁盘上添加 FreeBSD ZFS 分区，4K 对齐，大小为 210751598 个扇区，标签为 add100G
# zpool add root /dev/gpt/add100G	# 将新分区添加到 root ZFS 池
```

重启后，启动加载器会报错 `ZFS: i/o error - all block copies unavailable`。这是因为 FreeBSD 引导加载器设计上仅能从单一的 ZFS vdev 引导，添加新 vdev 后引导加载器无法正确定位根文件系统。

对于根文件系统的向前扩展，需要采用备份数据、重新分区、恢复数据的方案。

#### 参考文献

* Yubao Liu. FreeBSD root on ZFS 千古奇坑\[EB/OL]. (2019-02-18)\[2026-04-02]. <https://dieken.gitlab.io/posts/bsd-as-desktop-system/>. 记录类似的 ZFS 启动错误案例。
* FreeBSD Forums. 10.1 doesn't boot anymore from zroot after applying p25\[EB/OL]. \[2026-04-02]. <https://forums.freebsd.org/threads/10-1-doesn't-boot-anymore-from-zroot-after-applying-p25.54422/#post-308876>. FreeBSD 论坛上的相关问题讨论。

## 课后习题

1. 在 QEMU 中创建一个 FreeBSD 虚拟机，为其根文件系统磁盘添加 20 GB 空闲空间，完成扩容后测试系统启动与数据完整性。
2. 选取 ZFS 磁盘扩容的向后扩展机制，编写一个最小脚本在虚拟机中复现向前扩展的启动错误，并分析其根源尝试解决。
