# 34.10.使用 PXE 进行无盘操作

Intel® 预启动执行环境（PXE）能通过网络启动操作系统。例如，FreeBSD 系统可以通过网络启动，并在没有本地磁盘的情况下运行，使用从 NFS 服务器挂载的文件系统。PXE 支持通常在 BIOS 中提供。要在机器启动时使用 PXE，请在 BIOS 设置中选择“从网络启动”选项，或在系统初始化过程中按下功能键。

为了提供操作系统通过网络启动所需的文件，PXE 设置还需要正确配置的 DHCP、TFTP 和 NFS 服务器，其中：

* 初始参数，如 IP 地址、可执行启动文件名和位置、服务器名称以及根路径，从 DHCP 服务器获取。
* 操作系统加载器文件通过 TFTP 启动。
* 文件系统通过 NFS 加载。

当计算机通过 PXE 启动时，它会通过 DHCP 获取有关从哪里获取初始启动加载程序文件的信息。在主机计算机接收到这些信息后，它通过 TFTP 下载启动加载程序并执行它。在 FreeBSD 中，启动加载程序文件是 **/boot/pxeboot**。当 **/boot/pxeboot** 执行后，FreeBSD 内核将被加载，并且 FreeBSD 的其余启动过程将继续，如 [FreeBSD 启动过程](https://docs.freebsd.org/en/books/handbook/boot/#boot) 中所述。

> **注意**
>
> 对于基于 UEFI 的 PXE 启动，实际使用的启动加载程序文件是 **/boot/loader.efi**。请参阅下文的 [调试 PXE 问题](https://docs.freebsd.org/en/books/handbook/advanced-networking/#_debugging_pxe_problems) 部分，了解如何使用 **/boot/loader.efi**。

本节介绍如何在 FreeBSD 系统上配置这些服务，以便其他系统可以通过 PXE 启动到 FreeBSD。有关更多信息，请参考 [diskless(8)](https://man.freebsd.org/cgi/man.cgi?query=diskless\&sektion=8\&format=html)。

> **当心**
>
> 如前所述，提供这些服务的系统是不安全的。它应该位于网络的受保护区域，并且不应受到其他主机的信任。

## 34.10.1. 配置 PXE 环境

本节中的步骤配置了内置的 NFS 和 TFTP 服务器。下一节将演示如何安装和配置 DHCP 服务器。在此示例中，将包含 PXE 用户使用的文件的目录为 **/b/tftpboot/FreeBSD/install**。确保该目录存在，并且在 **/etc/inetd.conf** 和 **/usr/local/etc/dhcpd.conf** 中设置相同的目录名称非常重要。

> **注意**
>
> 以下命令示例假设使用 [sh(1)](https://man.freebsd.org/cgi/man.cgi?query=sh\&sektion=1\&format=html) shell。使用 [csh(1)](https://man.freebsd.org/cgi/man.cgi?query=csh\&sektion=1\&format=html) 和 [tcsh(1)](https://man.freebsd.org/cgi/man.cgi?query=tcsh\&sektion=1\&format=html) 的用户需要启动一个 [sh(1)](https://man.freebsd.org/cgi/man.cgi?query=sh\&sektion=1\&format=html) shell 或将命令适应 [csh(1)](https://man.freebsd.org/cgi/man.cgi?query=csh\&sektion=1\&format=html) 语法。

1. 创建根目录，该目录将包含一个可以通过 NFS 挂载的 FreeBSD 安装：

   ```sh
   # export NFSROOTDIR=/b/tftpboot/FreeBSD/install
   # mkdir -p ${NFSROOTDIR}
   ```
2. 通过向 **/etc/rc.conf** 添加以下行来启用 NFS 服务器：

   ```sh
   nfs_server_enable="YES"
   ```
3. 通过向 **/etc/exports** 添加以下内容来通过 NFS 导出无盘根目录：

   ```sh
   /b -ro -alldirs -maproot=root
   ```
4. 启动 NFS 服务器：

   ```sh
   # service nfsd start
   ```
5. 通过向 **/etc/rc.conf** 添加以下行来启用 [inetd(8)](https://man.freebsd.org/cgi/man.cgi?query=inetd\&sektion=8\&format=html)：

   ```sh
   inetd_enable="YES"
   ```
6. 取消注释 **/etc/inetd.conf** 中的以下行，确保它前面没有 `#` 符号：

   ```sh
   tftp dgram udp wait root /usr/libexec/tftpd tftpd blocksize 1468 -l -s /b/tftpboot
   ```

> **注意**
>
> 指定的 tftp 块大小（例如 1468 字节）替换了默认的 512 字节大小。有些 PXE 版本需要使用 TCP 版本的 TFTP。在这种情况下，取消注释第二个 `tftp` 行，其中包含 `stream tcp`。

7. 启动 [inetd(8)](https://man.freebsd.org/cgi/man.cgi?query=inetd\&sektion=8\&format=html)：

   ```sh
   # service inetd start
   ```
8. 将基本系统安装到 **${NFSROOTDIR}** 中，可以通过解压官方归档文件或重新构建 FreeBSD 内核和用户空间来完成（有关更详细的说明，请参考 [“从源代码更新 FreeBSD”](https://docs.freebsd.org/en/books/handbook/cutting-edge/#makeworld)，但在运行 `make installkernel` 和 `make installworld` 命令时不要忘记添加 `DESTDIR=${NFSROOTDIR}`）。
9. 测试 TFTP 服务器是否正常工作，并能通过 PXE 获取启动加载程序：

   ```sh
   # tftp localhost
   tftp> get FreeBSD/install/boot/pxeboot
   Received 264951 bytes in 0.1 seconds
   ```
10. 编辑 **${NFSROOTDIR}/etc/fstab**，并创建一个条目以通过 NFS 挂载根文件系统：

```sh
# Device                                         Mountpoint    FSType   Options  Dump Pass
myhost.example.com:/b/tftpboot/FreeBSD/install       /         nfs      ro        0    0
```

将 *myhost.example.com* 替换为 NFS 服务器的主机名或 IP 地址。在此示例中，根文件系统以只读方式挂载，以防止 NFS 客户端可能删除根文件系统的内容。

11. 为 PXE 启动的客户端机器设置根密码：

```sh
# chroot ${NFSROOTDIR}
# passwd
```

12. 如果需要，通过编辑 **${NFSROOTDIR}/etc/ssh/sshd\_config** 文件并启用 `PermitRootLogin`，为 PXE 启动的客户端机器启用 [ssh(1)](https://man.freebsd.org/cgi/man.cgi?query=ssh\&sektion=1\&format=html) 根登录。该选项在 [sshd\_config(5)](https://man.freebsd.org/cgi/man.cgi?query=sshd_config\&sektion=5\&format=html) 中有详细说明。
13. 在 **${NFSROOTDIR}** 中执行任何其他所需的自定义操作。这些自定义操作可能包括安装软件包或使用 [vipw(8)](https://man.freebsd.org/cgi/man.cgi?query=vipw\&sektion=8\&format=html) 编辑密码文件。

当从 NFS 根卷启动时，**/etc/rc** 检测到 NFS 启动并运行 **/etc/rc.initdiskless**。在这种情况下，**/etc** 和 **/var** 需要是内存支持的文件系统，以便这些目录可以写入，而 NFS 根目录保持只读：

```sh
# chroot ${NFSROOTDIR}
# mkdir -p conf/base
# tar -c -v -f conf/base/etc.cpio.gz --format cpio --gzip etc
# tar -c -v -f conf/base/var.cpio.gz --format cpio --gzip var
```

当系统启动时，将创建并挂载 **/etc** 和 **/var** 的内存文件系统，并将 **cpio.gz** 文件的内容复制到其中。默认情况下，这些文件系统的最大容量为 5 兆字节。如果归档文件无法容纳，通常是 **/var** 存储了二进制包，则可以通过在 **${NFSROOTDIR}/conf/base/etc/md\_size** 和 **${NFSROOTDIR}/conf/base/var/md\_size** 文件中输入所需的 512 字节扇区数量（例如，5 兆字节是 10240 扇区）来请求更大的大小。

## 34.10.2. 配置 DHCP 服务器

DHCP 服务器不必与 TFTP 和 NFS 服务器在同一台机器上，但它需要在网络中可访问。

DHCP 并不是 FreeBSD 基本系统的一部分，但可以通过 [net/isc-dhcp44-server](https://cgit.freebsd.org/ports/tree/net/isc-dhcp44-server/) Port 或包进行安装。

安装完成后，编辑配置文件 **/usr/local/etc/dhcpd.conf**。配置 `next-server`、`filename` 和 `root-path` 设置，如下所示：

```sh
subnet 192.168.0.0 netmask 255.255.255.0 {
   range 192.168.0.2 192.168.0.3 ;
   option subnet-mask 255.255.255.0 ;
   option routers 192.168.0.1 ;
   option broadcast-address 192.168.0.255 ;
   option domain-name-servers 192.168.35.35, 192.168.35.36 ;
   option domain-name "example.com";

   # TFTP 服务器的 IP 地址
   next-server 192.168.0.1 ;

   # 通过 tftp 获取的启动加载程序路径
   filename "FreeBSD/install/boot/pxeboot" ;

   # pxeboot 启动加载程序将尝试通过 NFS 挂载该目录作为根文件系统
   option root-path "192.168.0.1:/b/tftpboot/FreeBSD/install/" ;

}
```

`next-server` 指令用于指定 TFTP 服务器的 IP 地址。

`filename` 指令定义了 **/boot/pxeboot** 的路径。使用相对文件名，这意味着路径中不包括 **/b/tftpboot**。

`root-path` 选项定义了 NFS 根文件系统的路径。

编辑完成后，通过向 **/etc/rc.conf** 添加以下行来启用 DHCP 启动：

```sh
dhcpd_enable="YES"
```

然后启动 DHCP 服务：

```sh
# service isc-dhcpd start
```

## 34.10.3. 调试 PXE 问题

配置并启动所有服务后，PXE 客户端应该能够通过网络自动加载 FreeBSD。如果某个特定客户端无法连接，在该客户端机器启动时，进入 BIOS 配置菜单并确认其设置为从网络启动。

本节描述了一些调试提示，用于在没有客户端能够 PXE 启动时，隔离配置问题的源头。

1. 使用 [net/wireshark](https://cgit.freebsd.org/ports/tree/net/wireshark/) 软件包和 Ports 来调试 PXE 启动过程中的网络流量，过程如下面的图示所示。![pxe nfs](https://docs.freebsd.org/images/books/handbook/advanced-networking/pxe-nfs.png)

   图 1. 使用 NFS 根挂载的 PXE 启动过程

   1. 客户端广播 DHCPDISCOVER 消息。
   2. DHCP 服务器响应，返回 IP 地址、next-server、filename 和 root-path 值。
   3. 客户端向 next-server 发送 TFTP 请求，请求获取 filename。
   4. TFTP 服务器响应并将 filename 发送给客户端。
   5. 客户端执行 filename，即 pxeboot(8)，然后加载内核。当内核执行时，指定的 root-path 所述的根文件系统通过 NFS 挂载。
2. 在 TFTP 服务器上，查看 **/var/log/xferlog**，确保 **pxeboot** 是从正确的位置获取的。测试此示例配置：

   ```sh
   # tftp 192.168.0.1
   tftp> get FreeBSD/install/boot/pxeboot
   Received 264951 bytes in 0.1 seconds
   ```

   [tftpd(8)](https://man.freebsd.org/cgi/man.cgi?query=tftpd\&sektion=8\&format=html) 和 [tftp(1)](https://man.freebsd.org/cgi/man.cgi?query=tftp\&sektion=1\&format=html) 的 `BUGS` 部分记录了一些 TFTP 的局限性。
3. 确保根文件系统可以通过 NFS 挂载。测试此示例配置：

   ```sh
   # mount -t nfs 192.168.0.1:/b/tftpboot/FreeBSD/install /mnt
   ```
4. 对于基于 UEFI 的 PXE 启动，使用 **boot/loader.efi** 文件替换 **boot/pxeboot** 文件：

   ```sh
   # chroot ${NFSROOTDIR}
   # mv boot/pxeboot boot/pxeboot.original
   # cp boot/loader.efi boot/pxeboot
   ```
