Linux下搭建PXE/EFI启动服务器

笔记 · 05-30 · 5700 人浏览

前言

在之前的工作中,我尝试使用了Pxe网启方式进入PE,然后完成全自动化的操作系统的安装及访问共享等操作,现在把实现思路记下来,以备不时之需。

环境介绍

服务器操作系统: Ubuntu 22.04 LTSC
所需软件包: isc-kea-dhcp4-server ntp tftpd-hpa nginx samba iPXE
另外需要两个虚拟机,启动方式分别为 pcbiosuefi,用以模拟和测试绝大部分电脑的启动方式


查看软件详细说明

isc-kea-dhcp4-server
用于dhcp下发ip地址,告知客户端从哪一台服务器上启动,并通过客户端的RFC标识符(RFC4578)决定给客户端下发什么文件,进行什么样的操作。

ntp
用于同步各客户端的时间,在下发ip地址时会告知局域网内存在NTP服务器,同步所有客户端时间至与服务器相同,方便各客户端进行时间调整。

tftpd-hpa
用于客户端通过tftp协议下载启动文件,但因tftp速度较慢(即使在局域网环境下),不适合下发大型镜像,在本案例中仅用于下发iPXE启动固件及选项菜单,不再持有其他镜像文件

nginx
用于客户端通过http下载一些资源,在本案例中主要为图片及其他菜单等,同样可用来下发一些中小型镜像及展示服务器主页,不再过多介绍。

samba
用于在不同客户端内共享资料,也用于中心化分发软件包和更新项目,用于客户端更新自身资料等。

iPXE
主要用于支持一些老电脑原生不支持的高级功能,iPXE会在启动的第一时间被加载,然后读取服务器上的菜单或高级选项等,做进一步的分流/调整,也可以导入部分文件到系统内部等。

1.在此之前

本文章假定你清楚UEFI以及BIOS的区别,以及网络启动的基本概念,如果你不清楚,请先阅读这一部分,以完全了解两者有何不同,方便更清楚地了解我们在做什么。

1.1 UEFI引导和BIOS引导

我想你可能知道BIOS是什么,不就是基本输入输出操作系统嘛(Basic Input Output System),但我们这里讲到的 BIOS 不同于你所理解的,这里的BIOS是指硬件系统的引导方式或类型,它使用经典的BIOS底层执行引导,通常伴随黑色背景的引导屏幕,以及不断闪烁的字符,操作系统以这种方式引导时速度较慢,且它不支持一些较为现代化的硬盘分区格式。

那么,UEFI是什么?你可能会问, UEFI是一个相对现代化的系统引导解决方案,在一些较新或最新的主板中,你进入“BIOS”,会看到一个相对华丽的图形UI而不是文字界面,那也是UEFI的一部分,UEFI允许使用新的功能和特性,它更安全也更现代化。

通常,在主板的启动设置页面内,BIOS引导方式会作为兼容性选项提供,名称条目一般均包含CSM(Compatibility Support Module 兼容性支持模块),启用它才可以让主板使用BIOS引导方式,我们不推荐使用这种引导方式,在本文章中同样作为“兼容性选项”进行配置,用于兼容较老的硬件。

2.准备软件包

本来我是想从操作系统安装开始写,不过相信大家都会,就跳过这一步,首先准备工具链,进行所需软件包的安装

2.1.iPXE

默认情况下,由于客户端平台的不统一性,不同的主板对于网络启动的支持不尽相同,所以我们需要给各个需要启动的客户端设置一个“起跑线”,以让接下来的步骤能够成功完成,要做到这点,我们就要用到一个对各个平台兼容最强也是网络启动元老级别的固件程序,iPXE,它可以兼容几乎所有市面上的主板,完美实现我们所需要的一个标准起跑线。

标准情况下,官方提供了一个官方发布版的iPXE固件程序,可用于UEFI和BIOS引导,名称分别为:ipxe.efiundionly.kpxe,但如果你在本实例中使用了官方发布的版本,你可能会陷入无尽的启动死循环中,例如下图以及这个文档所示

启动死循环

为了解除这个死循环,我们可以通过两种不同的方式。

  • 自行编译iPXE固件
  • 通过DHCP服务器识别判断

在我的测试中,通过DHCP服务器识别判断会偶尔出现不准确或者配置文件压根就没用的情况,所以我选择自行编译一份新的iPXE固件,使其无论是什么情况下均跳转到指定的入口,而不是自动从服务器上拉取,避免出现死循环。

编译iPXE固件

编译这个固件的方式相对来说很简单,根据官方文档所示,我们只需要做到以下几点

  1. 克隆官方仓库
  2. 修改编译选项
  3. 执行编译

很简单,接下来一步步进行,首先执行以下命令,克隆官方仓库。

#如果你没有安装git客户端
sudo apt install git g++ gcc nano
#克隆官方仓库
git clone https://github.com/ipxe/ipxe.git

然后,创建一份自定义的配置文件,用于编译出我们自己的一个版本。

#进入工作区
cd ipxe/src
#编辑自定义配置文件
nano demo.ipxe

接下来,将配置文件修改成大概下面的样子,

#ipxe编译选项
#!ipxe
#dhcp拉取地址
dhcp
#从tftp服务器链式启动nextboot文件
chain nextboot

最后,执行编译,打包出分别应用于UEFI和BIOS的固件包。

#编译BIOS固件
make bin/undionly.kpxe EMBED=demo.ipxe
#编译UEFI固件
make bin/ipxe.efi EMBED=demo.ipxe

OK,如果一切顺利的话,我们就会在bin目录下得到undionly.kpxe文件和ipxe.efi文件,保留好它们,待会儿用。

2.2.tftpd-hpa

如上所说,tftp服务器用于下发启动时的各种文件,上一步编译得到的文件同样需要在这里用到,先安装它

#安装tftp
sudo apt install tftpd-hpa

然后,修改下它的默认配置和文件存储目录,在这个文档中,我们假定tftp存储目录为 /mnt/boot/tftp,配置文件默认在/etc/default/tftpd-hpa,可参考的文档:WIKI

#TFTP用户名,可以不用修改
TFTP_USERNAME="tftp"
#TFTP存储目录
TFTP_DIRECTORY="/mnt/boot/tftp/"
#TFTP监听端口号
TFTP_ADDRESS=":69"
#附加选项
TFTP_OPTIONS="--secure"

然后,重载服务,并设置为开机启动

sudo systemctl restart tftpd-hpa
sudo systemctl enable tftpd-hpa

2.3.ntp

NTP提供了局域网内的时间同步服务,它并不重要,但很方便,我们不需要对它做过多的配置,简单安装即可。

sudo apt install ntp

它的配置文件路径是/etc/ntp.conf,我们不需要做什么修改,安装好了就行。

2.4.isc-kea-dhcp4-server

终于到了重头戏,dhcp服务器提供了一切功能的基础,它负责下发IP地址给接口下的所有客户端,也负责决定客户端会以什么固件启动,在之前,我会选用isc-dhcp-server,但它现在已经停止开发,isc团队给了一个新的选择,isc-kea-dhcp4-sever,用于更好地管理和控制。

首先,Ubuntu的默认软件安装源并不包括isc-kea-dhcp4-server这个软件包,我们需要使用到官方发布源,来自于cloud smithm,以下是安装脚本。

curl -1sLf   'https://dl.cloudsmith.io/public/isc/kea-2-6/setup.deb.sh'   | sudo -E bash

在之后,我们就可以执行安装isc-kea-dhcp4-server,由于我们不需要任何其他的功能以及IPV6的支持,所以在这里就不安装团队提供的其他附加安装包,仅使用ipv4的dhcp服务器即可。它的配置文件默认路径为/etc/kea/kea-dhcp4.conf

sudo apt install isc-kea-dhcp4-server

根据官方文档,它同样支持各种配置和骚操作,但默认配置文件有很多无用配置项,我们删除它,并重新创建一份配置文件。

sudo rm -rf /etc/kea/kea-dhcp4.conf
sudo nano /etc/kea/kea-dhcp4.conf

配置文件主要是JSON格式,但支持注释,以下是我们需要填写的字段,以及字段的相应解释,你同样可以复制一份直接使用,同样,你也可以参考官方文档

# 配置开始
{
    # DHCPv4配置开始
    "Dhcp4": {
        #客户端类型定义,如果客户端符合任意成员test字段内条件,则下发该成员内的所有键值,对于默认定义的重复键值,以该成员内的为准
        "client-classes":[{
            #名称
            "name":"ipxe_x64",
            #条件
            "test":"option[93].hex == 0x0000",
            #启动服务器地址
            "next-server": "10.1.0.1",
            #启动服务器主机名
            "server-hostname":"bootserver.cn",
            #启动文件
            "boot-file-name":"undionly.kpxe"
        }],
        #接口设置
        "interfaces-config": {
            #要监听的网络接口
            "interfaces": [ "ens37" ],
            #DHCP数据包类型
            "dhcp-socket-type": "raw"
        },
        #有效时间
        "valid-lifetime": 4000,
        #更新时间
        "renew-timer": 1000,
        #重绑定时间
        "rebind-timer": 2000,
        #子网配置信息
        "subnet4": [{
           #启动服务器IP地址
           "next-server":"10.1.0.1",
           #启动文件
           "boot-file-name":"ipxe.efi",
           #地址池
           "pools": [
                      {
                        #地址池
                        "pool": "10.1.0.2-10.1.254.254",
                        #额外选项
                        "option-data":
                         [
                         #DNS服务器
                         {"name":"domain-name-servers","data":"10.1.0.1"},
                         #名称服务器
                         {"name":"name-servers","data":"10.1.0.1"},
                         #NTP时间服务器
                         {"name":"time-servers","data":"10.1.0.1"},
                         #默认网关
                         {"name":"routers","data":"10.1.0.1"}
                         ]
                      }
            ],
           #子网
           "subnet": "10.1.0.0/16",
           #子网编号
           "id": 1
        }],
       #日志配置
       "loggers": [{
            #配置名称
            "name": "*",
            #日志等级
            "severity": "INFO"
        }]
    }

# 配置结束
}

配置项中的具体项目,请参考以下文档

在之后,我们需要给配置文件中所指定的网卡一个IP地址,作为基准网关地址,在以上配置文件中,地址为10.1.0.1,另外,记得将该地址排除在地址池之外,以免分配冲突,Ubuntu中使用netplan作为网卡配置工具,配置文件为/etc/netplan/00-installer-config.yaml(默认情况下),修改网卡对应的IP地址,在这个配置文件中是 ens37

network:
  ethernets:
    ens37:
      dhcp4: false
      addresses: ['10.1.0.1/16']
  version: 2

配置完成后,应用到网卡

#应用配置
sudo netplan apply
#查看网卡状态
ip addr

#ens37: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#    link/ether 00:0c:29:01:5a:68 brd ff:ff:ff:ff:ff:ff
#    altname enp2s5
#    inet 10.1.0.1/16 brd 10.1.255.255 scope global ens37
#       valid_lft forever preferred_lft forever
#    inet6 fe80::20c:29ff:fe01:5a68/64 scope link
#       valid_lft forever preferred_lft forever

然后重启对应服务,使配置文件生效

#重启服务
sudo systemctl restart isc-kea-dhcp4-server
#检查服务状态
sudo systemctl status isc-kea-dhcp4-server

#● isc-kea-dhcp4-server.service - Kea DHCPv4 Service
#     Loaded: loaded (/lib/systemd/system/isc-kea-dhcp4-server.service; enabled; vendor preset: enabled)
#     Active: active (running) since Thu 2024-05-30 06:18:49 CST; 13min ago
#       Docs: man:kea-dhcp4(8)
#   Main PID: 54910 (kea-dhcp4)
#      Tasks: 13 (limit: 4515)
#     Memory: 3.3M
#        CPU: 49ms
#     CGroup: /system.slice/isc-kea-dhcp4-server.service
#             └─54910 /usr/sbin/kea-dhcp4 -c /etc/kea/kea-dhcp4.conf

最难的部分已经完成了,接下来继续我们的操作

3.文件环境部署

还记得我们在第二部分第一小节编译的两个iPXE文件以及TFTP文件存储路径吗?将两个iPXE文件放到TFTP的存储路径下,同时,在TFTP存储目录下新建一个文件,名称为nextboot,名称与你在编译部分的demo.ipxe文件中chain后面的名称相同,用于iPXE固件加载这个文件。

#创建nextboot文件
touch /mnt/boot/tftp/nextboot

在完成创建后,我们需要修改它,让其显示一个基本菜单,用于测试BIOS及UEFI启动是否正常。


展开配置文件内容

#!ipxe
#============== Set Variables ===============
#设置默认时间
 set menu-timeout 16000
#判断启动平台的方式,设定默认启动项
 iseq ${platform} efi && set menu-default win64 || set menu-default win32
#将http用xieyi代替
 set xieyi:string http
#测试是否存在ip,否则dhcp自动获取
 isset ${ip} || dhcp
#测试是否存在dhcp服务器,否则指定next-server为192.168.0.101
 isset ${next-server} || set next-server 192.168.0.101
 
#============== Set Menu ===============
#菜单开始标记
:start
#显示menu标题,并显示平台和ip地址
menu iPXE Boot Menu --${platform}--${ip}
#列项,可以指定热键,别名,添加--gap --分割线
item --gap --             -------------------------------- WINPE TOOL ---------------------------
item win32                Windows10PE x32
item win64                Windows10PE x64
item --gap --             ----------------------------------- TOOL ------------------------------
item maxdos                MaxDOS 9.1
item --gap --             -------------------------------- Advanced -----------------------------
item --key h local        [H]Boot from local drive
item --key s shell         [S] Drop to iPXE Shell
item --key r reboot        [R] Reboot the Computer
item --key x exit            [X] Exit iPXE and Continue BIOS Booting
item --key c config       [C]Configure settings
item --key m msboot       [M]Enter Windows Boot Manager Menu (BIOS)

#在默认时间到时选择默认启动项
choose --timeout ${menu-timeout} --default ${menu-default} selected
goto ${selected}
 
#============ Main Menu Options =============
#启动32位PE,将PE32.wim赋值给pefile,并跳转至bootpe
:win32
set pefile PE32.wim
goto bootpe
#启动64位PE,将PE64.wim赋值给pefile,并跳转至bootpe
:win64
set pefile PE64.wim
goto bootpe
 
#PE镜像启动代码块==============================
:bootpe
kernel ${xieyi}://${next-server}/boot/wimboot gui || goto retry
initrd ${xieyi}://${next-server}/pxeautorun.txt ${next-server}.cmd || goto nextg
:nextg
initrd ${xieyi}://${next-server}/boot/boot.sdi   boot.sdi  || goto retry
iseq ${platform} pcbios || goto winefi
initrd ${xieyi}://${next-server}/bootmgr.exe bootmgr.exe || goto retry
initrd ${xieyi}://${next-server}/boot/bxe bxd || goto retry
initrd ${xieyi}://${next-server}/boot/${pefile}  boot.WIM  || goto retry
boot || goto retry
goto start
:retry
imgfree
prompt Error! press any key to retry
goto start
#PE镜像启动代码块==============================
 
 
#启动MaxDOS镜像
:maxdos
kernel ${boot-url}/memdisk
initrd ${boot-url}/MAXDOS9.img
boot
 
#从本地硬盘启动
:local
sanboot --no-describe --drive 0x80
 
#打开命令行
:shell
echo Type 'exit' to go back to the menu.
shell
goto start
 
#重启
:reboot
reboot
 
#退出
:exit
exit
 
#配置
:config
config
goto start
 
#更换启动文件
:msboot 
chain tftp://${next-server}/pxelinux.0
goto start

然后保存它,开启一个虚拟机测试是否正常。

BIOS引导测试

请输入图片描述

iPXE UEFI BIOS 网络启动
  1. 兄弟你好香 05-30

    好好好