网络启动原理与实践

1/19/2024 引导网络

# 前言

最近进行服务器的装维工作,为了节省安装系统的时间,研究了一下网络启动,经过实践成功,记录于此。

前置知识:

  • 计算机网络(到 IP 层即可)
  • x86 引导过程

# 引入

假设你是一名服务器管理员,每天都在进行服务器的装维工作。这一天,你的老板要求你给新购置的服务器装个系统。

Boss: 小管,给服务器装个 Ubuntu Server。
You : 没问题,等我烧个镜像先。
Boss: 好的,也不多,就 20 台,一个上午能搞定吧?
You : ... (20台?我哪有20个U盘啊啊啊啊,还TM要一上午搞定??)
1
2
3
4

于是,为了完成老板的压榨性任务,你不得不另辟蹊径。经过一番思考,你猛然想起,所有的服务器都会事先连接到网络(Hardware Setup),而且要安装的系统都是 Ubuntu Server,如果能够通过网络分发安装镜像并启动安装,配合一下云端配置,就能快速解决这个问题了。

之后,你打开了浏览器,发现已经有了成熟的解决方案,而这个方案就是 PXE -- Preboot Execution Environment。

# PXE 与 iPXE

观察传统 x86 的磁盘引导过程可以发现,引导是由 BIOS 读取活动磁盘的第一扇区(MBR)并载入到内存 0x7c00 处执行,再由 MBR 程序引导真正的启动管理器。不难想到,如果 BIOS 不是载入 MBR,而是从一个固定位置运行一个预置的程序,那只要这个程序能够驱动网卡,并从网络上拿到真正的引导程序,就可以支持网络启动了。这样的程序提供了启动程序的执行环境,我们称之为预启动执行环境,即PXE。

PXE 是由 Intel 开发的。PXE 通常由网卡提供,可以被 BIOS 执行。PXE是基于C/S架构的,原始的 PXE 一般只支持最基本的网络启动功能,如 DHCP 和 TFTP。DHCP 就不用多说,多台服务器的 IP 地址分配一定需要依赖 DHCP,而 DHCP 中可以选择带有自定义的节,这样就可以下发启动配置数据。而 TFTP 是 Trivial File Transfer Protocol 的缩写,即简单文件传输协议。与 FTP 最大的不同是“简单”,走 UDP 69 端口通信,且只有上传下载等基本功能,不能列出文件。这使得它易于实现,代码量也小。

原始的 PXE 支持的功能实在有限,且支持的速度很低,只有 10 Mbps。这对于通常大于 1G 的安装镜像是灾难性的,下载的速度实在太慢,还没有完整性保证。由此,前人也有高招,即 PXE 不直接载入安装镜像,而是载入一个更强大的 PXE,由新的 PXE 支持更高速率的网络、更多协议,再加载镜像启动。这样的二级 PXE ,也有很多,最常用的是 pxelinux 和 iPXE。pxelinux 是 linux 内核启动的附属工程,专用于支持 linux 的网络启动。而 iPXE 则是 GNU GPL 开源的网络启动固件。其官网为:ipxe.org (opens new window)。描述如下:

iPXE is the leading open source network boot firmware. It provides a full PXE implementation enhanced with additional features such as:

- boot from a web server via HTTP
- boot from an iSCSI SAN
- boot from a Fibre Channel SAN via FCoE
- boot from an AoE SAN
- boot from a wireless network
- boot from a wide-area network
- boot from an Infiniband network
- control the boot process with a script
1
2
3
4
5
6
7
8
9
10

通过 iPXE,我们可以轻松地从 HTTP 服务器获得安装文件,还可以使用 sanboot 功能将 HTTP 服务器上得镜像挂载为一个 SAN 设备支持启动(当然也可以走诸如 iPXE + GRUB4DOS 这样弯曲的路)。

# PXE 的实现

如上文所述,PXE 的主要依赖是 DHCP 和 TFTP。流程主要如下图所示:

PXE流程

需要说明的是,iPXE 的启动配置文件名不是固定的,而是可以写死在启动文件里(需要重新编译),或者通过 DHCP 下发。这样一来,DHCP 服务器就不能一直返回初始的启动文件路径,而要根据条件选择。这个选择,只需利用 DHCP user class。具体而言,PXE请求DHCP时会使用机器的 user class,而 iPXE 在请求 DHCP 时会使用自定义的 user class,即 "iPXE"。官方推荐的配置 DHCP 服务器时 ISC DHCPd 或者 Microsoft DHCP server,方便起见,我使用了 dnsmasq 在 OpenWRT 软路由上搭建,TFTP 服务器也由dnsmasq 一并提供。参考配置文件如下:


dhcp-name-match=set:dhcp_bogus_hostname,localhost
dhcp-name-match=set:dhcp_bogus_hostname,wpad

dhcp-match=set:iPXE,175

dhcp-vendorclass=set:flag,PXEClient:Arch:00000
dhcp-vendorclass=set:flag,PXEClient:Arch:00006
dhcp-vendorclass=set:flag,PXEClient:Arch:00007
dhcp-vendorclass=set:flag,PXEClient:Arch:00009

tag-if=set:load,tag:!iPXE,tag:flag

pxe-prompt="Press F8 or Enter key for PXE menu.", 5

#BIOS MENU
pxe-service=tag:load,X86PC, "BIOS IPXE (UNDI)", legacy/undionly.kpxe
pxe-service=tag:load,X86PC, "BIOS IPXE", legacy/ipxe.pxe
# pxe-service=tag:load,X86PC, "BIOS Microsoft PXE", pxeboot.n12
# pxe-service=tag:load,X86PC, "Memtest", memtest.bin
pxe-service=tag:load,X86PC, "Boot from local", 0


#UEFI MENU
# pxe-service=tag:load,IA32_EFI, "Microsoft UEFI (IA32_EFI)", bootia32.efi
# pxe-service=tag:load,X86-64_EFI, "Microsoft UEFI (X86-64_EFI)", bootx64.efi
# pxe-service=tag:load,BC_EFI, "Microsoft UEFI(BC-EFI)", bootx64.efi

# pxe-service=tag:load,6, "iPXE snponly UEFI32(6)", snponly32.efi
pxe-service=tag:load,7, "iPXE snponly UEFI", efi/snponly.efi
pxe-service=tag:load,9, "iPXE snponly UEFI", efi/snponly.efi

# pxe-service=tag:load,06,  "iPXE UEFI32(06)", ipxe32.efi
pxe-service=tag:load,07,  "iPXE UEFI", efi/ipxe.efi
pxe-service=tag:load,09,  "iPXE UEFI", efi/ipxe.efi
pxe-service=tag:load,X86-64_EFI, "GRUB4 EFI", grub/BOOTX64.EFI
# pxe-service=tag:load,X86-64_EFI, "Memtest", memtest.bin

dhcp-boot=tag:iPXE, boot.ipxe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

这个配置文件支持 BIOS 和 EFI 启动。所需的大部分文件可以从 boot.ipxe.org (opens new window) 获取。

# iPXE 启动 Ubuntu Server 镜像

# 启动镜像

如前所述,经过一番配置,我们已经能够启动到 iPXE 中。接下来就是由 iPXE 启动 Ubuntu Server 镜像了。首先就要下载镜像,然后开启一个 HTTP 服务器提这个镜像,这里不再详细描述。在 iPXE 控制台中,使用

sanhook --drive 0x81 ${url}
1

加载 url 为一个 SAN 设备 (设备号0x81,即磁盘1,可以为其他值,参考 x86 实模式的 BIOS 磁盘操作)。随后,使用

sanboot --no-describe --drive 0x81
1

启动镜像。

# 修改启动参数

到这里,应该可以进入 GRUB2 了。但是在真正启动内核之前,我们还需要修改启动参数,指定 url 供内核获取镜像并挂载。由于这里的 SAN 设备是虚拟的,即使用了 x86 的中断 Hook ,也只能在实模式下起效,ubuntu 的内核没有特殊支持,只能重新从 HTTP 服务器获取一份完整镜像并保存在内存中再挂载。

要做到这一点,只需按e编辑菜单命令,进入编辑器后,找到 linux 开头的一行,在该行末尾 (如有--,在这之前) 添加

ip=dhcp url="${url}"
1

此处需将${url}替换为具体值。

修改完成后,按 F10 启动,就可以了。

# 使用第三方工具简化流程

你已经读到这里了,好厉害~

相信你已经发现,手动搭建整个服务并不容易,即便是使用 dnsmasq 这样的集成了 DHCP 和 TFTP 的服务端也一样。

人的本性是偷懒,包括网络启动的初衷,也是偷懒。所以,一懒到底,使用第三方的集成工具快速解决问题。这里推荐一个,iVentoy (opens new window),可以傻瓜式操作,详细配置什么的看文档就行。

# 进一步了解

  • ProxyDHCP
  • EFI
  • SAN
  • iSCSI