> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/zfs/an-zhuang-zhi-yin/ubuntu/18.04-zfs.md).

# 构建以 ZFS 为根文件系统的 Ubuntu 18.04

## 概览

### 可用的新版本

* 有关新的安装教程，请参阅 [Ubuntu 20.04 Root on ZFS](https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/Ubuntu%2020.04%20Root%20on%20ZFS.html)。本指南已不再频繁更新，仅保留，以供已经按照此指南安装的用户参考。

### 注意事项

* 本教程使用整个物理磁盘。
* 不要使用这些说明来安装双系统。
* 备份你的数据，任何现有数据将会丢失。

### 系统要求

* [Ubuntu 18.04.3 (“Bionic”) 桌面 CD](https://releases.ubuntu.com/18.04.3/ubuntu-18.04.3-desktop-amd64.iso)（**不要**使用任何服务器镜像）
* 在提供 4 KiB 逻辑扇区的驱动器（“4Kn”驱动器）上安装仅支持 UEFI 引导。这并非 ZFS 独有的问题。[GRUB 在 4Kn 驱动器上使用传统 BIOS 引导无法工作，也不会支持。](http://savannah.gnu.org/bugs/?46700)

在内存少于 2 GiB 的计算机上运行 ZFS，会存在严重的性能瓶颈。建议 4 GiB 内存以在基础工作负载下获得正常性能。如果你想使用去重（deduplication），你将需要 [大量内存](http://wiki.freebsd.org/ZFSTuningGuide#Deduplication)。启用去重是一项不可轻易撤销的永久性更改。

### 加密

本指南支持两种加密方案：未加密和 LUKS（全盘加密）。无论哪种选项，ZFS 的所有功能均可用。Ubuntu 18.04 不支持 ZFS 原生加密。

未加密方案当然不会加密任何内容，因此性能最佳。

LUKS 会加密几乎所有数据。唯一未加密的数据是引导程序、内核和 initrd。系统必须在控制台输入密码才能启动。性能良好，但 LUKS 位于 ZFS 底层，如果使用多块磁盘（镜像或 raidz 拓扑），数据需要在每个磁盘上加密一次。

## 第 1 步：准备安装环境

1.1 启动 Ubuntu Live CD。选择“Try Ubuntu”。根据需要将系统连接到互联网（例如，连接 WiFi 网络）。打开终端（按 Ctrl-Alt-T）。

1.2 设置更新软件源：

```sh
sudo apt-add-repository universe
sudo apt update
```

1.3 可选：在 Live CD 环境中安装并启动 OpenSSH 服务器：

如果你有第二台设备，通过 SSH 访问目标系统会很方便：

```sh
passwd
# 当前没有密码；在提示时直接按回车。
sudo apt install --yes openssh-server
```

> **提示：**
>
> 可以使用 `ip addr show scope global | grep inet` 查看你的 IP 地址，然后从主机通过 `ssh ubuntu@IP` 连接。

1.4 切换到 root 用户：

```sh
sudo -i
```

1.5 在 Live CD 环境中安装 ZFS：

```sh
apt install --yes debootstrap gdisk zfs-initramfs
```

## 第 2 步：磁盘格式化

2.1 设置磁盘变量：

```ini
DISK=/dev/disk/by-id/scsi-SATA_disk1
```

使用 ZFS 时务必使用长别名 `/dev/disk/by-id/*`。直接使用设备节点 `/dev/sd*` 可能导致偶发的导入失败，尤其是在系统中存在多个存储池时。

**提示：**

* `ls -la /dev/disk/by-id` 可以列出所有别名。
* 如果在虚拟机中操作，虚拟磁盘在 `/dev/disk/by-id` 中缺失，可在 KVM virtio 下使用 `/dev/vda`，其他情况请参考[故障排查](https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/Ubuntu%2018.04%20Root%20on%20ZFS.html#troubleshooting)。
* 对于镜像（mirror）或 RAID-Z 拓扑，请使用 `DISK1`、`DISK2` 等变量。
* 选择 boot 存储池大小时，请考虑实际使用。内核和 initrd 可能占用约 100M。如果安装多个内核并使用快照，尤其是在需要重新生成 initramfs（约 85M/个）时，boot 存储池空间可能不足。请根据需求合理规划。

2.2 如果重用磁盘，需要清理：

若磁盘曾用作 MD 阵列，清除 superblock：

```sh
apt install --yes mdadm
mdadm --zero-superblock --force $DISK
```

清空分区表：

```sh
sgdisk --zap-all $DISK
```

2.3 分区：

若需 Legacy（BIOS）启动：

```sh
sgdisk -a1 -n1:24K:+1000K -t1:EF02 $DISK
```

若使用 UEFI 启动（当前或后续使用）：

```sh
sgdisk -n2:1M:+512M -t2:EF00 $DISK
```

创建 boot pool：

```sh
sgdisk -n3:0:+1G -t3:BF01 $DISK
```

选择以下其中之一：

2.3a 不加密：

```sh
sgdisk -n4:0:0 -t4:BF01 $DISK
```

2.3b LUKS 加密：

```sh
sgdisk -n4:0:0 -t4:8300 $DISK
```

若创建镜像或 RAID-Z 拓扑，请对所有池内磁盘重复分区操作。

2.4 创建 boot 存储池：

```sh
zpool create -o ashift=12 -d \
    -o feature@async_destroy=enabled \
    -o feature@bookmarks=enabled \
    -o feature@embedded_data=enabled \
    -o feature@empty_bpobj=enabled \
    -o feature@enabled_txg=enabled \
    -o feature@extensible_dataset=enabled \
    -o feature@filesystem_limits=enabled \
    -o feature@hole_birth=enabled \
    -o feature@large_blocks=enabled \
    -o feature@lz4_compress=enabled \
    -o feature@spacemap_histogram=enabled \
    -O acltype=posixacl -O canmount=off -O compression=lz4 -O devices=off \
    -O normalization=formD -O relatime=on -O xattr=sa \
    -O mountpoint=/ -R /mnt bpool ${DISK}-part3
```

通常无需自定义 boot 存储池的选项。

GRUB 并不支持所有 zpool 特性。请参见 [grub-core/fs/zfs/zfs.c](http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/fs/zfs/zfs.c#n276) 中的 `spa_feature_names`。此步骤为 `/boot` 创建单独的 boot 存储池，仅启用 GRUB 支持的特性，从而能让 root 存储池使用任意特性。注意 GRUB 会以只读方式打开池，因此所有只读兼容特性均被“支持”。

**提示：**

* 如果创建镜像（mirror）或 RAID-Z（raidz）拓扑，可使用如下命令创建 boot 存储池：

  ```sh
  zpool create ... bpool mirror /dev/disk/by-id/scsi-SATA_disk1-part3 /dev/disk/by-id/scsi-SATA_disk2-part3
  ```

  或将 `mirror` 替换为 `raidz`、`raidz2` 或 `raidz3`，并列出其他磁盘的分区。
* 可自定义存储池的名称，但如果修改，必须在整个配置中保持一致。本教程中使用 `bpool` 作为约定名称。

**特性说明：**

* `userobj_accounting` 特性在理论上是只读兼容的，但实际使用 GRUB 时可能出现“invalid dnode type”错误。该特性对 `/boot` 无关紧要。

2.5 创建 root 存储池：

选择以下其中之一：

2.5a 不加密：

```sh
zpool create -o ashift=12 \
    -O acltype=posixacl -O canmount=off -O compression=lz4 \
    -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa \
    -O mountpoint=/ -R /mnt rpool ${DISK}-part4
```

2.5b LUKS 加密：

```sh
cryptsetup luksFormat -c aes-xts-plain64 -s 512 -h sha256 ${DISK}-part4
cryptsetup luksOpen ${DISK}-part4 luks1
zpool create -o ashift=12 \
    -O acltype=posixacl -O canmount=off -O compression=lz4 \
    -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa \
    -O mountpoint=/ -R /mnt rpool /dev/mapper/luks1
```

**说明：**

* 这里推荐使用 `ashift=12`，因为许多现代硬盘的物理扇区为 4 KiB（或更大），即使它们报告为 512 B 逻辑扇区。同时，未来更换硬盘时可能有 4 KiB 物理扇区（此时 `ashift=12` 可取）或 4 KiB 逻辑扇区（此时必须 `ashift=12`）。
* 设置 `-O acltype=posixacl` 可全局启用 POSIX ACL。如果不希望全局启用，可删除此选项，但之后需在创建 `/var/log` 的 ZFS 时加上 `-o acltype=posixacl`（注意小写“o”），因为 [journald 需要 ACL](https://askubuntu.com/questions/970886/journalctl-says-failed-to-search-journal-acl-operation-not-supported)。
* 设置 `normalization=formD` 可消除 UTF-8 文件名的一些极端问题，同时隐含 `utf8only=on`，意味着只允许 UTF-8 文件名。如果希望支持非 UTF-8 文件名，请不要使用该选项。[关于强制 UTF-8 文件名的问题讨论](http://utcc.utoronto.ca/~cks/space/blog/linux/ForcedUTF8Filenames)。
* `recordsize` 未设置，保持默认 128 KiB。若希望调整，可参考 [这些](https://jrs-s.net/2019/04/03/on-zfs-recordsize/)、[博客文章 1](http://blog.programster.org/zfs-record-size)、[博客文章 2](https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSFileRecordsizeGrowth)、[博客文章 3](https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSRecordsizeAndCompression)。
* 设置 `relatime=on` 是经典 POSIX `atime` 行为（性能影响大）和 `atime=off`（最佳性能，但禁用访问时间更新）之间的折中方案。从 Linux 2.6.30 起，`relatime` 已成为其他文件系统的默认值。[RedHat 文档说明](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/power_management_guide/relatime)。
* 设置 `xattr=sa` [显著提升扩展属性性能](https://github.com/zfsonlinux/zfs/commit/82a37189aac955c81a59a5ecc3400475adb56355)。ZFS 内部使用扩展属性实现 POSIX ACL，也可被用户空间应用使用。[部分桌面 GUI 应用使用扩展属性](https://en.wikipedia.org/wiki/Extended_file_attributes#Linux)。Samba 可利用扩展属性存储 Windows ACL 和 DOS 属性，Active Directory 域控制器必需该功能。[Linux 特有](https://openzfs.org/wiki/Platform_code_differences)。若考虑迁移到其他 OpenZFS 实现（非 ZFS-on-Linux），扩展属性可能不可读（数据仍可访问）。即使不希望全盘使用 `xattr=sa`，用于 `/var/log` 通常是可行的。
* 确保包含磁盘路径的 `-part4` 部分，否则指定的是整个磁盘，ZFS 会重新分区，可能会丢失 bootloader 分区。
* 对于 LUKS，加密密钥长度选 512 位。但 XTS 模式需要两把密钥，LUKS 会将密钥对半分，因此 `-s 512` 实际为 AES-256。
* 密码通常是最薄弱环节，请慎重选择。[cryptsetup FAQ 第 5 节](https://gitlab.com/cryptsetup/cryptsetup/wikis/FrequentlyAskedQuestions#5-security-aspects) 提供参考指导。

**说明：**

* 如果要创建镜像（mirror）或 RAIDZ 拓扑，请使用类似命令创建 root 存储池：

```sh
zpool create -o ashift=12 rpool mirror /dev/disk/by-id/scsi-SATA_disk1-part4 /dev/disk/by-id/scsi-SATA_disk2-part4
```

或将 `mirror` 替换为 `raidz`、`raidz2` 或 `raidz3`，并列出其他磁盘的分区。对于 LUKS，需要使用 `/dev/mapper/luks1`、`/dev/mapper/luks2` 等设备，这些需通过 `cryptsetup` 创建。

* ZFS pool 的名称可以任意选择。如果更改了名称，后续使用中必须保持一致。在支持自动安装到 ZFS 的系统上，root 存储池默认命名为 `rpool`。

## 步骤 3：系统安装

3.1 创建作为容器的文件系统数据集：

```sh
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
```

在 Solaris 系统上，root 文件系统会被克隆，且在通过 `pkg image-update` 或 `beadm` 进行重大系统更改时，后缀会递增。在 Ubuntu 20.04 中，类似功能由 `zsys` 工具实现，虽然它的 dataset 布局更复杂。即便没有该工具，仍可使用 rpool/ROOT 和 bpool/BOOT 容器手动创建克隆。

3.2 为 root 和 boot 文件系统创建数据集：

```sh
zfs create -o canmount=noauto -o mountpoint=/ rpool/ROOT/ubuntu
zfs mount rpool/ROOT/ubuntu

zfs create -o canmount=noauto -o mountpoint=/boot bpool/BOOT/ubuntu
zfs mount bpool/BOOT/ubuntu
```

通常情况下，ZFS 并不需要手动使用命令 `mount` 或 `zfs mount` 挂载文件系统。由于 `canmount=noauto`，这里必须手动挂载一次。

3.3 创建其他数据集：

```sh
zfs create                                 rpool/home
zfs create -o mountpoint=/root             rpool/home/root
zfs create -o canmount=off                 rpool/var
zfs create -o canmount=off                 rpool/var/lib
zfs create                                 rpool/var/log
zfs create                                 rpool/var/spool
```

下面的数据集为可选，根据个人喜好或软件选择决定是否创建。

如果希望排除在快照之外：

```sh
zfs create -o com.sun:auto-snapshot=false  rpool/var/cache
zfs create -o com.sun:auto-snapshot=false  rpool/var/tmp
chmod 1777 /mnt/var/tmp
```

如果系统使用 `/opt`：

```sh
zfs create                                 rpool/opt
```

如果系统使用 `/srv`：

```sh
zfs create                                 rpool/srv
```

如果系统使用 `/usr/local`：

```sh
zfs create -o canmount=off                 rpool/usr
zfs create                                 rpool/usr/local
```

如果系统将安装游戏：

```sh
zfs create                                 rpool/var/games
```

如果系统将在 `/var/mail` 存储本地邮件：

```sh
zfs create                                 rpool/var/mail
```

如果系统使用 Snap 包：

```sh
zfs create                                 rpool/var/snap
```

如果系统使用 `/var/www`：

```sh
zfs create                                 rpool/var/www
```

如果系统使用 GNOME：

```sh
zfs create                                 rpool/var/lib/AccountsService
```

如果系统使用 Docker（管理自身数据集与快照）：

```sh
zfs create -o com.sun:auto-snapshot=false  rpool/var/lib/docker
```

如果系统使用 NFS（锁定）：

```
zfs create -o com.sun:auto-snapshot=false  rpool/var/lib/nfs
```

如果希望为 `/tmp` 创建独立数据集（后续建议使用 tmpfs）：

```sh
zfs create -o com.sun:auto-snapshot=false  rpool/tmp
chmod 1777 /mnt/tmp
```

该数据集布局的主要目的是将操作系统与用户数据分离，使得 root 文件系统可回滚而不影响用户数据。`com.sun:auto-snapshot` 设置可让部分 ZFS 快照工具排除临时数据。

如果不进行额外操作，`/tmp` 会作为 root 文件系统的一部分存储。也可以像上面示例那样创建独立数据集，将 `/tmp` 数据排除在 root 快照之外，同时可对 `rpool/tmp` 设置空间配额限制。否则，可在后续使用 tmpfs（RAM 文件系统）。

3.4 安装最小系统：

```sh
debootstrap bionic /mnt
zfs set devices=off rpool
```

`debootstrap` 命令会留下一个未配置的新系统。另一种方法是将已配置好的系统完整复制到新的 ZFS root 中。

## 步骤 4：系统配置

4.1 配置主机名：

将 `主机名` 替换为所需的主机名：

```sh
echo 主机名 > /mnt/etc/hostname
vi /mnt/etc/hosts
```

```ini
添加一行：
127.0.1.1       HOSTNAME
或如果系统在 DNS 中有真实名称：
127.0.1.1       FQDN HOSTNAME
```

> **提示：**
>
> 如果觉得 `vi` 难以使用，可以使用 `nano`。

4.2 配置网络接口：

查找接口名称：

```sh
ip addr show
```

将下方的 `NAME` 替换为你的接口名称：

```sh
vi /mnt/etc/netplan/01-netcfg.yaml
```

```yaml
network:
  version: 2
  ethernets:
    NAME:
      dhcp4: true
```

如果系统不是 DHCP 客户端，可对该文件进行自定义。

4.3 配置软件源：

```sh
vi /mnt/etc/apt/sources.list
```

```ini
deb http://archive.ubuntu.com/ubuntu bionic main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu bionic-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu bionic-security main restricted universe multiverse
```

4.4 将 LiveCD 环境的虚拟文件系统绑定到新系统，进入 chroot：

```sh
mount --rbind /dev  /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys  /mnt/sys
chroot /mnt /usr/bin/env DISK=$DISK bash --login
```

> **注意：**
>
> 使用的是 `--rbind`，而非 `--bind`。

4.5 配置基础系统环境：

```sh
ln -s /proc/self/mounts /etc/mtab
apt update
```

即便你选择的系统语言不是英语，也要确保 `en_US.UTF-8` 可用：

```sh
dpkg-reconfigure locales
dpkg-reconfigure tzdata
```

如果偏好 `nano` 而非 `vi`，可安装：

```sh
apt install --yes nano
```

4.6 在 chroot 环境中安装 ZFS：

```sh
apt install --yes --no-install-recommends linux-image-generic
apt install --yes zfs-initramfs
```

> **提示：**
>
> 对于 HWE 内核，安装 `linux-image-generic-hwe-18.04` 替代 `linux-image-generic`。

4.7 仅针对 LUKS 安装，设置 `/etc/crypttab`：

```sh
apt install --yes cryptsetup

echo luks1 UUID=$(blkid -s UUID -o value ${DISK}-part4) none \
    luks,discard,initramfs > /etc/crypttab
```

使用 `initramfs` 是为了解决 [cryptsetup 不支持 ZFS](https://bugs.launchpad.net/ubuntu/+source/cryptsetup/+bug/1612906) 的问题。

> **提示：**
>
> 如果创建镜像或 raidz 拓扑，需要为每个磁盘重复 `/etc/crypttab` 条目，如 `luks2` 等。

4.8 安装 GRUB

选择以下方案之一：

4.8a 为 legacy（BIOS）引导安装 GRUB：

```sh
apt install --yes grub-pc
```

使用空格键选择池中所有磁盘（而非分区）。

4.8b 为 UEFI 引导安装 GRUB：

```sh
apt install dosfstools
mkdosfs -F 32 -s 1 -n EFI ${DISK}-part2
mkdir /boot/efi
echo PARTUUID=$(blkid -s PARTUUID -o value ${DISK}-part2) \
    /boot/efi vfat nofail,x-systemd.device-timeout=1 0 1 >> /etc/fstab
mount /boot/efi
apt install --yes grub-efi-amd64-signed shim-signed
```

**注意：**

* `-s 1` 仅对呈现 4 KiB 逻辑扇区（“4Kn”驱动器）的磁盘必要，以满足 FAT32 最小簇大小（给定分区大小 512 MiB）。对 512 B 扇区的磁盘也适用。
* 对于镜像或 raidz 拓扑，此步骤仅在第一个磁盘上安装 GRUB，其他磁盘稍后处理。

4.9（可选）移除 os-prober：

```sh
apt purge --yes os-prober
```

可避免 `update-grub` 报错。os-prober 仅在双系统配置中必要。

4.10 设置 root 密码：

```sh
passwd
```

4.11 启用 bpool 导入：

确保始终导入 `bpool`，无论是否存在 `/etc/zfs/zpool.cache`、是否在 cachefile 中，是否启用 `zfs-import-scan.service`。

```sh
vi /etc/systemd/system/zfs-import-bpool.service
```

```ini
[Unit]
DefaultDependencies=no
Before=zfs-import-scan.service
Before=zfs-import-cache.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/zpool import -N -o cachefile=none bpool

[Install]
WantedBy=zfs-import.target
```

```sh
systemctl enable zfs-import-bpool.service
```

4.12 可选（但推荐）：将 `/tmp` 挂载为 tmpfs

如果之前已为 `/tmp` 创建 dataset，可跳过此步骤。否则，可通过启用 `tmp.mount` 单元，将 `/tmp` 放在 tmpfs（内存文件系统）中：

```sh
cp /usr/share/systemd/tmp.mount /etc/systemd/system/
systemctl enable tmp.mount
```

4.13 设置系统组：

```sh
addgroup --system lpadmin
addgroup --system sambashare
```

## 步骤 5：安装 GRUB

5.1 验证 ZFS 引导文件系统是否被识别：

```sh
grub-probe /boot
```

5.2 刷新 initrd 文件：

```sh
update-initramfs -c -k all
```

> **注意：**
>
> 在使用 LUKS 时，可能会显示“WARNING could not determine root device from /etc/fstab”。这是因为 [cryptsetup 不支持 ZFS](https://bugs.launchpad.net/ubuntu/+source/cryptsetup/+bug/1612906)。

5.3 解决 GRUB 缺失 zpool-features 的问题：

```sh
vi /etc/default/grub
# 设置: GRUB_CMDLINE_LINUX="root=ZFS=rpool/ROOT/ubuntu"
```

5.4 可选（强烈建议）：便于调试 GRUB：

```ini
vi /etc/default/grub
# 注释掉: GRUB_TIMEOUT_STYLE=hidden
# 设置: GRUB_TIMEOUT=5
# 在 GRUB_TIMEOUT 下添加: GRUB_RECORDFAIL_TIMEOUT=5
# 从 GRUB_CMDLINE_LINUX_DEFAULT 中移除 quiet 和 splash
# 取消注释: GRUB_TERMINAL=console
# 保存并退出
```

系统重启两次并确认一切正常后，可根据需要撤销这些更改。

5.5 更新启动配置：

```sh
update-grub
```

> **注意：**
>
> 如果出现 os-prober 错误，可忽略。

5.6 安装引导加载程序：

5.6a 对于 legacy（BIOS）引导，安装 GRUB 到 MBR：

```sh
grub-install $DISK
```

> **注意**
>
> 这是将 GRUB 安装到整个磁盘，而非单个分区。

如果创建镜像或 raidz 拓扑，请对池中的每个磁盘重复 `grub-install` 命令。

5.6b 对于 UEFI 引导，安装 GRUB：

```sh
grub-install --target=x86_64-efi --efi-directory=/boot/efi \
    --bootloader-id=ubuntu --recheck --no-floppy
```

无需指定磁盘。若创建镜像或 raidz 拓扑，其他磁盘稍后处理。

5.7 修正文件系统挂载顺序：

[在 ZFS 支持 systemd mount generator 之前](https://github.com/zfsonlinux/zfs/issues/4898)，挂载文件系统与启动某些守护进程之间可能存在竞争条件。实际上，问题（例如 [#5754](https://github.com/zfsonlinux/zfs/issues/5754)）通常出现在 `/var` 下的某些文件系统，特别是 `/var/log` 和 `/var/tmp`。将它们设置为 `legacy` 挂载并在 `/etc/fstab` 中列出，使 systemd 知道这些是独立挂载点。这样，`rsyslog.service` 会通过 `local-fs.target` 依赖 `var-log.mount`，使用 systemd `PrivateTmp` 功能的服务会自动依赖 `After=var-tmp.mount`。

在 initramfs 中尚不支持挂载 `/boot` 前，我们也需要挂载它，因为它被标记为 `canmount=noauto`。UEFI 下，还需确保 `/boot/efi` 在其父文件系统挂载前挂载。

`rpool` 由 initramfs 保证导入，因此无需为这些文件系统添加 `x-systemd.requires=zfs-import.target`。

UEFI 引导时，先卸载 `/boot/efi`：

```sh
umount /boot/efi
```

其他步骤对 BIOS 和 UEFI 都适用：

```sh
zfs set mountpoint=legacy bpool/BOOT/ubuntu
echo bpool/BOOT/ubuntu /boot zfs \
    nodev,relatime,x-systemd.requires=zfs-import-bpool.service 0 0 >> /etc/fstab

zfs set mountpoint=legacy rpool/var/log
echo rpool/var/log /var/log zfs nodev,relatime 0 0 >> /etc/fstab

zfs set mountpoint=legacy rpool/var/spool
echo rpool/var/spool /var/spool zfs nodev,relatime 0 0 >> /etc/fstab
```

如果创建了 `/var/tmp` 数据集：

```sh
zfs set mountpoint=legacy rpool/var/tmp
echo rpool/var/tmp /var/tmp zfs nodev,relatime 0 0 >> /etc/fstab
```

如果创建了 `/tmp` 数据集：

```sh
zfs set mountpoint=legacy rpool/tmp
echo rpool/tmp /tmp zfs nodev,relatime 0 0 >> /etc/fstab
```

## 步骤 6：首次启动

6.1 对初始安装进行快照：

```sh
zfs snapshot bpool/BOOT/ubuntu@install
zfs snapshot rpool/ROOT/ubuntu@install
```

以后在每次升级前，通常也会想先创建快照，并在适当的时候删除旧快照（包括此快照）以节省空间。

6.2 退出 `chroot` 环境，返回 LiveCD 环境：

```sh
exit
```

6.3 在 LiveCD 环境下运行以下命令卸载所有文件系统：

```sh
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export -a
```

6.4 重启：

```sh
reboot
```

等待新安装的系统正常启动，并以 root 登录。

6.5 创建用户账户：

将 `用户名` 替换为所需用户名：

```sh
zfs create rpool/home/用户名
adduser 用户名

cp -a /etc/skel/. /home/用户名
chown -R 用户名:用户名 /home/用户名
usermod -a -G audio,cdrom,dip,floppy,netdev,plugdev,sudo,video 用户名
```

6.6 镜像 GRUB

如果安装了多个磁盘，需要在其他磁盘上安装 GRUB：

6.6a 对于 legacy（BIOS）引导：

```sh
dpkg-reconfigure grub-pc
# 一直按回车，直到出现设备选择界面。
# 使用空格键选择池中所有磁盘（而非分区）。
```

6.6b 对于 UEFI 引导：

先卸载 `/boot/efi`：

```sh
umount /boot/efi
```

对于第二块及后续磁盘（依次将 ubuntu-2 增加为 -3 等）：

```sh
dd if=/dev/disk/by-id/scsi-SATA_disk1-part2 \
   of=/dev/disk/by-id/scsi-SATA_disk2-part2
efibootmgr -c -g -d /dev/disk/by-id/scsi-SATA_disk2 \
    -p 2 -L "ubuntu-2" -l '\EFI\ubuntu\shimx64.efi'

mount /boot/efi
```

## 步骤 7：（可选）配置 Swap

> **注意**
>
> 在内存压力极高的系统上，无论 swap 空间剩余多少，使用 zvol 作为 swap 设备都可能导致系统锁死。此问题正在调查中：<https://github.com/zfsonlinux/zfs/issues/7734>

7.1 创建用于 swap 的卷数据集（zvol）：

```sh
zfs create -V 4G -b $(getconf PAGESIZE) -o compression=zle \
    -o logbias=throughput -o sync=always \
    -o primarycache=metadata -o secondarycache=none \
    -o com.sun:auto-snapshot=false rpool/swap
```

可以根据需要调整大小（`4G` 部分）。

压缩算法设置为 `zle`，因为它是开销最小的算法。由于本指南推荐使用 `ashift=12`（磁盘上 4 KiB 块），常见的 4 KiB 页面大小意味着没有压缩算法能减少 I/O。例外情况是全零页面会被 ZFS 丢弃，但必须启用某种形式的压缩才能实现此行为。

7.2 配置 swap 设备：

> **注意**
>
> 在配置文件中总是使用长别名 `/dev/zvol`。切勿使用短设备名 `/dev/zdX`。

```sh
mkswap -f /dev/zvol/rpool/swap
echo /dev/zvol/rpool/swap none swap discard 0 0 >> /etc/fstab
echo RESUME=none > /etc/initramfs-tools/conf.d/resume
```

`RESUME=none` 用于禁用休眠恢复。因为 zvol 在恢复脚本运行时尚未导入，所以无法正常工作。如果不禁用，启动过程会等待 swap zvol 出现约 30 秒的停滞。

7.3 启用 swap 设备：

```sh
swapon -av
```

## 步骤 8：安装完整软件

8.1 升级最小系统：

```sh
apt dist-upgrade --yes
```

8.2 安装常规软件：

选择以下方案之一：

8.2a 仅安装命令行环境：

```sh
apt install --yes ubuntu-standard
```

8.2b 安装完整 GUI 环境：

```sh
apt install --yes ubuntu-desktop
vi /etc/gdm3/custom.conf
# 在 [daemon] 部分添加：InitialSetupEnable=false
```

> **提示**
>
> 如果要安装完整 GUI 环境，你可能需要使用 NetworkManager 管理网络：

```sh
rm /mnt/etc/netplan/01-netcfg.yaml
vi /etc/netplan/01-network-manager-all.yaml
```

```yaml
network:
  version: 2
  renderer: NetworkManager
```

8.3 可选：禁用日志压缩：

由于 `/var/log` 已由 ZFS 压缩，logrotate 的压缩会消耗 CPU 和磁盘 I/O，而通常收益甚微。此外，如果你对 `/var/log` 做快照，logrotate 的压缩实际上会浪费空间，因为未压缩的数据会保留在快照中。你可以手动编辑 `/etc/logrotate.d` 下的文件注释掉 `compress`，或者使用如下循环（推荐复制粘贴执行）：

```sh
for file in /etc/logrotate.d/* ; do
    if grep -Eq "(^|[^#y])compress" "$file" ; then
        sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "$file"
    fi
done
```

8.4 重启系统：

```sh
reboot
```

## 步骤 9：最后的清理

9.1 等待系统正常启动。使用你创建的账户登录，确保系统（包括网络）正常工作。

9.2 可选：删除初始安装的快照：

```sh
sudo zfs destroy bpool/BOOT/ubuntu@install
sudo zfs destroy rpool/ROOT/ubuntu@install
```

9.3 可选：禁用 root 密码：

```sh
sudo usermod -p '*' root
```

9.4 可选：重新启用图形启动过程：

如果你希望使用图形启动过程，可以现在重新启用。如果使用 LUKS，会使密码提示更美观。

```sh
sudo vi /etc/default/grub
# 取消注释：GRUB_TIMEOUT_STYLE=hidden
# 在 GRUB_CMDLINE_LINUX_DEFAULT 中添加 quiet 和 splash
# 注释掉：GRUB_TERMINAL=console
# 保存并退出

sudo update-grub
```

> **注意**
>
> 如出现 os-prober 错误，可忽略。

9.5 可选：仅针对 LUKS 安装，备份 LUKS 头信息：

```sh
sudo cryptsetup luksHeaderBackup /dev/disk/by-id/scsi-SATA_disk1-part4 \
    --header-backup-file luks1-header.dat
```

将该备份存放在安全位置（如云存储）。虽然受你的 LUKS 密码保护，但你可能希望额外加密。

> **提示**
>
> 如果创建了镜像或 raidz 拓扑，请对每个 LUKS 卷重复此操作（如 `luks2` 等）。

## 故障排除

### 使用 Live CD 进行救援

按照[步骤 1：准备安装环境](https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/Ubuntu%2018.04%20Root%20on%20ZFS.html#step-1-prepare-the-install-environment)进行操作。

对于 LUKS，首先解锁磁盘：

```sh
cryptsetup luksOpen /dev/disk/by-id/scsi-SATA_disk1-part4 luks1
# 如果是镜像或 raidz 拓扑，重复此操作解锁其他磁盘。
```

正确挂载所有文件系统：

```sh
zpool export -a
zpool import -N -R /mnt rpool
zpool import -N -R /mnt bpool
zfs mount rpool/ROOT/ubuntu
zfs mount -a
```

如有需要，可以 `chroot` 进入已安装环境：

```sh
mount --rbind /dev  /mnt/dev
mount --rbind /proc /mnt/proc
mount --rbind /sys  /mnt/sys
chroot /mnt /bin/bash --login
mount /boot/efi
mount -a
```

进行必要的系统修复操作。

操作完成后，清理环境：

```sh
exit
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
zpool export -a
reboot
```

### MPT2SAS

本教程的大多数问题报告都与 `mpt2sas` 硬件相关，这类硬件会执行较慢的异步驱动器初始化，例如某些 IBM M1015 卡或已经刷回参考 LSI 固件的 OEM 品牌卡。

基本问题是，这些控制器上的磁盘在常规系统启动前不会被 Linux 内核识别，而 ZoL 不会热插拔池成员。详见 <https://github.com/zfsonlinux/zfs/issues/330>。

大多数 LSI 卡与 ZoL 完全兼容。如果你的 LSI 卡存在此问题，可尝试在 `/etc/default/zfs` 中设置：

```ini
ZFS_INITRD_PRE_MOUNTROOT_SLEEP=X
```

系统将在导入池之前等待 X 秒，以确保所有磁盘都出现。

### Areca

需要 `arcsas` 驱动程序的系统应将其添加到 `/etc/initramfs-tools/modules` 文件中，并运行：

```sh
update-initramfs -c -k all
```

如果在内核日志中出现类似 `RIP: 0010:[<ffffffff8101b316>] [<ffffffff8101b316>] native_read_tsc+0x6/0x20` 的信息，请升级或降级 Areca 驱动。ZoL 在出现此错误信息的系统上并不稳定。

### VMware

* 在 vmx 文件或 vSphere 配置中设置：

```ini
disk.EnableUUID = "TRUE"
```

这样可以确保在虚拟机中创建 `/dev/disk` 别名。

### QEMU/KVM/XEN

为每个虚拟磁盘设置唯一的序列号，可通过 libvirt 或 qemu 实现，例如：

```ini
-drive if=none,id=disk1,file=disk1.qcow2,serial=1234567890
```

若希望在虚拟机中使用 UEFI（而不仅仅是 BIOS 启动），请在宿主机上执行：

```sh
sudo apt install ovmf
sudo vi /etc/libvirt/qemu.conf
```

取消对以下若干行的注释：

```ini
nvram = [
   "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd",
   "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd"
]
```

然后重启 libvirt 服务：

```sh
sudo systemctl restart libvirtd.service
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://book.bsdcn.org/zfs/an-zhuang-zhi-yin/ubuntu/18.04-zfs.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
