systemd的新时代网络启动

通过systemd 257以后提供的systemd-import-generator组件,我们现在终于可以完全依赖UEFI HTTP Boot协议实现完整的新时代网络启动了。

什么是UEFI HTTP Boot?

UEFI HTTP Boot是一种网络启动技术,它支持通过两种方式:

  1. 固件配置:在固件的HTTP Boot Configuration页面直接配置地址。
  2. DHCP服务器响应:将Option 60配置为"HTTPClient",然后通过Option 67配置文件URL。

使主机的UEFI固件在引导时从一个指定的HTTP URL下载:

  1. 一个UEFI PE程序并加载。
  2. 一个Raw Disk镜像并加载其中的回退引导项/EFI/BOOT/BOOTX64.EFI

systemd组很欣赏这一网络启动技术,并试图将其集成到用户空间以实现更加精简的,全链路的网络启动。

这是怎么做到的?

这是通过以下几个技术实现的:

  • EDK2的UEFI HTTP Boot:实现从UEFI固件层从HTTP服务器下载并加载EFI PE二进制程序。
  • systemd-boot:经过更新后,SDBoot现在支持在Type 1引导项中设置uki-url配置项,这一配置项会使SDBoot利用UEFI HTTP Boot的协议栈,从HTTP服务器拉取链式加载的UKI。
  • systemd-import-generator:systemd 257的新组件,其功能是可以根据内核命令行参数的配置,从HTTP服务器拉取各种DDI(Discoverable Disk Image,主要是根文件系统镜像和USR文件系统镜像)镜像。
  • mkosi:用于轻松地构建所有这些镜像(NetESP镜像、根文件系统镜像等等)。

由此可见,藉由这些组件的搭配组合,我们的网络启动链路是一个高度可自定义化的过程,我们可以自由决定我们的启动过程在哪种程度上是“网络化”的

主要的思考点有:

systemd-http-boot.png

  1. 是否使用UEFI HTTP Boot?
    a. 是。
    b. 否,使用本地ESP。
  2. UEFI HTTP Boot提供什么?
    a. 提供一个只有ESP的磁盘镜像,ESP中包含systemd-boot及其引导项。
    b. 提供PE程序,PE程序是UKI。
    c. 提供PE程序,PE程序是USI,启动。
  3. (接2a)systemd-boot做什么?
    a. 通过uki-url拉取远程UKI。
    b. 通过uki-url拉取USI,启动。
    c. 加载(磁盘镜像或本地)的ESP中的UKI。
  4. (接2b,或接3a,或接3c)UKI中做什么?
    a. 通过ip=dhcp rd.systemd.pull=XXX root=XXX从远程拉取根文件系统镜像,启动。
    b. 通过ip=dhcp rd.systemd.pull=XXX mount.usr=XXX从远程拉取USR镜像,释放根文件系统,启动。

实践

以标准的1a -> 2a -> 3a -> 4a为例。

构建镜像

你可以从这里快速获取我的镜像模板:MoltenArmor/mkosi-template

镜像构建配置项的含义可以从这里参考:mkosi/mkosi/resources/man/mkosi.1.md

NetESP

mkosi.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Distribution]
Distribution=debian

[Content]
# 只安装systemd-boot,而不安装UKI
Bootable=no
Packages=
systemd-boot
systemd-boot-efi

[Output]
Format=esp
ImageVersion=
Output=unbootable-netesp

mkosi.extra/efi/loader/loader.conf

1
timeout 5

mkosi.extra/efi/loader/entries/10-netboot.conf

1
2
3
title NetBoot
architecture x64
uki-url http://ServerURL

随后执行构建:

1
mkosi build

Rootfs

根文件系统怎样都好,只需要确保构建产品格式为Disk,按照你的个性化构建即可,如果你实在懒得客制化,使用这条命令:

1
mkosi --include mkosi-vm --distribution debian --format disk build

UEFI HTTP Boot

如前所述,UEFI HTTP Boot有两种配置方式:

  1. 在UEFI固件配置中手动配置选择。
  2. 使用DHCP服务器传递特定报文。

第一种自不必说,在自动化场景中可能显得不够实用,我们主要讨论第二种情况。我们选择systemd-networkd作为DHCP服务器实现,如果是内网唯一的DHCP服务器,那么配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Match]
Name=enp?s?

[Network]
Address=192.168.1.1/24
Gateway=网关路由器IP
DHCPServer=yes

[DHCPServer]
PoolOffset=0
PoolSize=254
DNS=114.114.114.114
SendOption=60:string:HTTPClient
SendOption=67:string:http://XXX/netesp.raw

如果不是内网唯一的DHCP服务器,那么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Match]
Name=enp?s?

[Network]
Address=192.168.1.1/24
Gateway=网关路由器IP
DHCPServer=yes

[DHCPServer]
PoolOffset=254
PoolSize=1
EmitDNS=no
EmitRouter=no
SendOption=60:string:HTTPClient
SendOption=67:string:http://XXX/netesp.raw

systemd-boot

通过HTTP服务器提供的Raw Disk镜像只能包含一个ESP分区(其他分区就算包含也会被忽略),ESP中必须在/EFI/BOOT/BOOTX64.EFI位置安装systemd-boot,并在/loader/entries/目录下配置它的引导项,指定提供UKI的服务器:

1
2
3
title NetBoot
architecture x64
uki-url http://AnotherServer/YYY.efi

HTTP服务器可以选择Busybox、Python或任意一个实现。

内核命令行参数与systemd-import-generator

我们需要进行配置,以使得UKI的Initrd中的systemd-importd会自动拉取Rootfs,并将其加载到内存中,用于之后的启动过程。因此内核命令行参数需要配置systemd-importd的行为:

1
ip=dhcp rd.systemd.pull=raw,machine,verify=no,blockdev:rootdisk:http://XXX/rootfs.raw root=dissect rd.systemd.mask=systemd-repart.service systemd.mask=systemd-repart.service
  • ip=dhcp:触发Initrd中的systemd-networkd配置网络。
  • rd.systemd.pull=:触发Initrd中的saystemd-importd拉取镜像。
  • root=dissect:自动解析镜像中的根文件系统并进行挂载。
  • rd.systemd.mask=systemd-repart.service systemd.mask=systemd-repart.service:镜像是不可变的,systemd-repart无力进行重新分区,如果不Mask掉会启动失败。

这样在启动时,systemd-importd就会导入rootfs.raw,并使systemd从rootfs.raw启动。

启动!

接前文,如果希望手动配置UEFI固件,进入EFI固件后,进入Device Manager -> Network Device List,然后选择HTTP Boot Configuration,选择HTTP服务器提供的文件的URL。

EFI-HTTPBOOT1.png

EFI-HTTPBOOT2.png

接着就可以在EFI Boot Manager找到引导项了。

EFI-HTTPBOOT3.png

随后系统启动,会拉取镜像并启动systemd-boot,选择网络启动的引导项,即可启动系统:

EFI-HTTPBOOT4.png