Ubuntu 22.04 PXE网络装机实战:UEFI与Legacy双启动配置 兄弟们如果你和我一样一年里总有那么几次被装系统这事儿折腾到怀疑人生那这篇实操记录应该能帮上大忙。我这次搞定的目标很明确在 Ubuntu 22.04 Server 上从头搭一套PXE网络装机环境让网卡引导直接装系统彻底告别 U 盘和光驱而且必须同时兼容新机器的 UEFI 和老机器的 Legacy BIOS 启动模式。这活儿听起来不难真做起来坑全在细节里把踩过的坑和最终能直接用的配置都整理在下面。这套东西的核心价值不在于省一个 U 盘而是把重复性装机变成流水线网线插上、开机、喝茶机器自动分区、自动装包、自动配置网络半小时后过去收一台干净的 Ubuntu 22.04 就行。如果公司有批量交付服务器、实验室频繁重置节点、或者你是个整天折腾发行版的重度玩家这个方案能帮你省下大量时间。1. PXE网络装机到底在解决什么问题先把原理讲清楚1.1 一场真实的装机现场为什么我要搞PXE上周我接到一个活测试机房里 8 台机器要全部重装 Ubuntu 22.04其中 4 台是今年采购的新机器UEFI 固件 NVMe 硬盘另外 4 台是几年前的老家伙BIOS 引导 SATA 盘。我一开始拿出 U 盘做了个启动盘第一台 UEFI 机器顺利引导第二台老机器死活不从 U 盘启动折腾 BIOS 设置、安全启动开关半小时过去才搞好一台。8 台机器装完一天基本就废了。当时我脑子里只有一个念头这种活儿必须让网络来干。PXEPreboot eXecution Environment技术本身不新鲜任何一台带网卡的机器只要固件里支持 Network Boot通电后网卡就会向网络里喊一声我需要一个 IP 和一个启动文件地址服务器响应之后客户端从服务器拉取引导程序再挂载网络安装源把整个系统装进本地硬盘。这个过程完全不需要物理介质IP 分配、引导文件、安装源全部由一台服务器集中提供。之所以拖到现在才搞是因为Linux 服务器搭建 PXE这件事网上教程虽多但一半是好多年前的老版本另一半从头到尾只讲了 Legacy BIOS 一种情况。要在 Ubuntu 22.04 上同时兼容 UEFI 和 Legacy必须理解这两种固件在 PXE 阶段的行为差异否则照着教程配完新机器引导报错老机器也一脸懵最后只能弃坑。1.2 PXE启动链路四步走把 PXE 的完整链路拆开大致分四步。第一步客户端网卡的 PXE ROM 上电后会发送 DHCP Discover 广播这个广播和普通电脑获取 IP 的 DHCP 请求在协议层面是同一类东西但多出了一些关键标记其中最重要的是 option 93用来标识客户端固件架构。我的 dnsmasq 服务器收到广播后根据 option 93 的值打一个标签再往 DHCP Offer 里塞两个关键信息分配一个 IP 地址、告诉客户端你的启动文件在哪个 TFTP 服务器上、文件名是什么。这两个信息对应 DHCP option 66TFTP 服务器地址和 option 67启动文件名。第二步客户端拿到启动文件名后通过 TFTP 协议把这个文件下载到本机内存里并执行。注意 TFTP 是个非常古老的 UDP 协议没有加密、没有压缩默认一次只能传 512 字节但胜在实现简单PXE 固件里内置的客户端都能支持。这一步就是 Legacy 和 UEFI 分道扬镳的地方BIOS 固件的客户端会下载 pxelinux.0UEFI 客户端根据固件架构下载不同格式的 EFI 可执行文件通常是 grubx64.efi 或 bootx64.efi。第三步下载下来的引导程序开始工作它会再次发起 TFTP 请求从服务器上拉取自己的配置文件。PXELINUX 会去找 pxelinux.cfg 目录下的配置文件GRUB 会去找 grub/grub.cfg总之引导程序从配置文件里读到内核和 initrd 的路径、引导参数这些信息。第四步引导程序通过 TFTP 下载内核 vmlinuz 和 initrd.img 到内存然后把控制权交给内核。内核启动后安装器会根据引导参数里的 url 或 ds 参数从 HTTP 服务器挂载安装源读入自动安装配置开始无人值守装机。整条链路里TFTP 只负责传输小文件真正的安装大文件走 HTTP这也是为了性能考虑。1.3 这套方案的核心组件选型搞清楚原理之后组件选型就顺理成章了。我需要的东西有四件DHCP 服务、TFTP 服务、HTTP 服务、以及启动菜单配置文件。前三件里DHCP 和 TFTP 我选择用 dnsmasq 一个程序搞定HTTP 用系统自带的 Nginx引导文件则根据 Legacy/UEFI 分别准备 syslinux 和 GRUB2。dnsmasq 自带 DHCP、TFTP、DNS 三种服务我只用前两个它配置简单、依赖少而且最关键的是它支持 dhcp-match 和 dhcp-boot 组合使用可以针对客户端固件类型下发不同的启动文件这一个能力就解决了我双启动模式的核心需求。如果用传统的 isc-dhcp-server tftpd-hpa 组合也不是不行但配置文件要写两套匹配客户端架构还得写 if 判断远不如 dnsmasq 来得清爽。HTTP 服务没啥好说的装一个 Nginx把 Ubuntu 22.04 的 ISO 镜像挂载到目录里客户端通过 HTTP 直接访问安装源比传统的 NFS 方式更简单也更通用。NFS 不是不能用但 HTTP 不用装额外客户端支持现代安装器原生支持少一个依赖就少一个坑。2. 环境准备与目录规划2.1 服务器基础要求我用的是一台装了 Ubuntu 22.04 Server 的虚拟机分配了 2 核 CPU、4G 内存、100G 磁盘作为 PXE 服务器绰绰有余。真实环境里甚至不用这么好的配置因为 workload 主要是小文件 TFTP 传输和 HTTP 大文件下载对 CPU 和内存要求非常低1 核 1G 的机器也跑得动。网络规划上我给 PXE 服务器配置了静态 IP 192.168.10.10/24客户端网段是 192.168.10.0/24。这个网段和公司办公网、家庭 Wi-Fi 完全隔离避免 DHCP 冲突。如果你要接入现有网络务必确认现有网络里没有其他 DHCP 服务器或者只允许 dnsmasq 在特定的交换机 VLAN 里生效否则两边抢答客户端会拿到错误的 IP 和启动配置表现为有时能启动有时不能启动排查起来非常折磨人。2.2 安装软件包基础环境配置好后直接安装全部所需软件包在 Ubuntu 22.04 上一条命令搞定sudo apt update sudo apt install -y dnsmasq nginx syslinux grub-efi-amd64-bin tftpd-hpa这里解释一下这几个包的用途dnsmasq 是核心服务nginx 用来提供 HTTP 安装源syslinux 提供 Legacy BIOS 阶段需要的 pxelinux.0 和一堆 c32 模块grub-efi-amd64-bin 提供 UEFI 阶段需要的 GRUB 工具tftpd-hpa 严格来说我并不会启动它但装它的原因是它里面有 tftp 客户端命令可以用来自测 TFTP 服务是否正常。有些人会把 tftpd-hpa 和 dnsmasq 的 TFTP 服务搞混其实两个都是 TFTP 服务端用 dnsmasq 就必须停掉 tftpd-hpa 的服务否则端口冲突。装完之后立刻停掉和禁用 tftpd-hpasudo systemctl stop tftpd-hpa sudo systemctl disable tftpd-hpa2.3 目录结构与资源规划PXE 服务器的主要工作目录我按照下面这套结构来组织看起来清晰排错也方便/var/lib/tftpboot/ # TFTP 根目录 ├── pxelinux.0 # Legacy 引导文件 ├── ldlinux.c32 ├── vesamenu.c32 ├── libcom32.c32 ├── libutil.c32 ├── pxelinux.cfg/ │ └── default # Legacy 菜单配置 ├── grub/ │ ├── grubx64.efi # UEFI 引导文件 │ ├── grub.cfg # UEFI 菜单配置 │ └── fonts/ │ └── unicode.pf2 └── ubuntu2204/ ├── vmlinuz # 内核 └── initrd.img # 内存盘TFTP 根目录和 HTTP 目录是两回事TFTP 目录里只放启动阶段的小文件HTTP 目录里放安装源大文件。把两者分开是因为 TFTP 协议传输大文件效率很低容易超时而安装源动辄几个 GB走 HTTP 明显更靠谱。2.4 防火墙规则与网络规划Ubuntu 22.04 默认没开 ufw但如果你的环境开启了防火墙需要放行这些端口sudo ufw allow 67/udp # DHCP sudo ufw allow 69/udp # TFTP sudo ufw allow 80/udp # DHCP 响应也可能走这个保险起见 sudo ufw allow 80/tcp # HTTP 安装源注意 DHCP 服务除了监听 67 端口发送响应时还会从 68 端口出去很多教程只放行 67 就完事了结果客户端拿不到 DHCP Offer。我习惯直接把 udp 67、68、69 都放行省得排错时多一个变量。这个细节我曾经栽过跟头卡在客户端能发广播但收不到响应上最后抓包才发现是防火墙把 68 端口出方向的响应给拦了。3. 搭建DHCP TFTP双服务dnsmasq核心配置3.1 为什么是dnsmasq而不是isc-dhcp-server选 dnsmasq 这件事我在前面提到了但真正动手配置时才感受到它的好处。isc-dhcp-server 虽然是标准 DHCP 服务配置文件里写死了一堆 subnet、range、option 定义语法老派且冗长而 dnsmasq 的配置风格是最小可用很多默认值开箱即用文档也直白。更关键的是dnsmasq 不需要单独起一个 tftpd 进程来配合本身就带了 TFTP 模块配置里写两行就生效少了一个服务意味着少了一个故障点。而且 dnsmasq 支持在同一个配置文件里用 dhcp-match 和 dhcp-boot 实现基于客户端架构下发不同启动文件这是本次双启动方案的关键能力。在 isc-dhcp-server 里做同样的事需要在 class、subclass 上做文章配置复杂且抽象对后来接手的人也极不友好。我还是要在配置前多说一句dnsmasq 配置文件是 /etc/dnsmasq.conf但我习惯在自己的隔离网段场景下用 /etc/dnsmasq.d/pxe.conf 这样的独立文件然后在 /etc/dnsmasq.conf 末尾确保有conf-dir/etc/dnsmasq.d。这样改配置时只动自己加的文件回滚也方便。3.2 dnsmasq主配置详解下面是我最终使用的 /etc/dnsmasq.d/pxe.conf 完整配置每一行都值得讲清楚port0 interfaceeth0 bind-interfaces dhcp-range192.168.10.100,192.168.10.200,12h dhcp-optionoption:router,192.168.10.1 dhcp-optionoption:dns-server,223.5.5.5,119.29.29.29 dhcp-matchset:efi-x86_64,option:client-arch,7 dhcp-matchset:efi-x86_64,option:client-arch,9 dhcp-boottag:efi-x86_64,grub/grubx64.efi dhcp-bootpxelinux.0 enable-tftp tftp-root/var/lib/tftpboot tftp-no-blocksize log-dhcpport0表示关闭 dnsmasq 自带的 DNS 转发功能因为我只需要 DHCP 和 TFTP留着 DNS 反而可能和网络里的其他 DNS 解析产生干扰。interfaceeth0配合bind-interfaces意思很明确只监听 eth0 这张网卡不向其他接口提供服务避免把 PXE 服务广播到不该去的地方。dhcp-range192.168.10.100,192.168.10.200,12h定义了 DHCP 地址池是 192.168.10.100 到 200租期 12 小时。租期不用设太长因为 PXE 客户端都是一次性引导引导完成后租约就没用了短租期还能避免 IP 被大量占用。dhcp-optionoption:router和dns-server是给客户端后续访问网络用的如果客户端整个安装流程都只和内网服务器通信这两个选项甚至可以省略但实际装机过程中安装器需要访问 Ubuntu 软件源所以还是填上比较好。dhcp-matchset:efi-x86_64,option:client-arch,7是双启动的核心含义是如果 DHCP 客户端广播里 option 93client-arch的值是 7就设置一个名为 efi-x86_64 的标签。值 7 代表 x86_64 UEFI值 9 代表 UEFI 带 BCD 支持我这里把两个都打上标签。最后dhcp-boottag:efi-x86_64,grub/grubx64.efi的意思是带 efi-x86_64 标签的客户端启动文件是 grub/grubx64.efi而没有这个标签的客户端也就是老的 x86 BIOSoption 93 缺失或值不同执行默认的dhcp-bootpxelinux.0。这一套组合拳打下来UEFI 和 Legacy 客户端走完全不同的引导文件互不干扰这是我最终能实现双启动的关键。3.3 用同一套DHCP区分UEFI/Legacy终端的原理稍微深入讲讲 option 93 的原理。DHCP 协议本身是个扩展性很强的协议客户端在发送 Discover 时除了请求 IP还会带上各种 option网络启动场景下最常见的三个是option含义典型值60Vendor Class IdentifierPXEClient93Client System Architecture0BIOS, 7x86_64 UEFI, 9UEFI x86_6497Client Machine Identifier客户端 GUIDdnsmasq 的 dhcp-match 指令就是从 DHCP 报文里读取某个 option 的值然后设置标签。标签可以理解为一个临时标记后续的 dhcp-boot 配置根据标记来决定下发什么内容。这个机制比看 MAC 地址前缀、看 IP 网段都更可靠因为固件类型是客户端主动上报的不需要我事先维护任何清单。有些旧教程让你给 UEFI 和 Legacy 划分不同 IP 段再用地址池区分那是在没有 dhcp-match 支持时代的土办法现在完全没必要了。配置完 dnsmasq 后重启服务验证配置sudo systemctl restart dnsmasq sudo systemctl enable dnsmasq我习惯重启后用 journalctl 看日志确认没有语法错误journalctl -u dnsmasq | tail -20开机自启这一步千万别漏不然服务器一重启整个 PXE 环境就断了。4. Legacy BIOS启动链路搭建PXELINUX4.1 准备PXELINUX引导文件Legacy BIOS 这条链路引导程序用的是 PXELINUX它是 syslinux 项目的一个分支专门负责从网络加载内核。Ubuntu 22.04 的 syslinux 包安装后相关文件分布在不同目录需要手动拷贝到 TFTP 根目录。cd /var/lib/tftpboot sudo cp /usr/lib/PXELINUX/pxelinux.0 . sudo cp /usr/lib/syslinux/modules/bios/ldlinux.c32 . sudo cp /usr/lib/syslinux/modules/bios/libcom32.c32 . sudo cp /usr/lib/syslinux/modules/bios/libutil.c32 . sudo cp /usr/lib/syslinux/modules/bios/vesamenu.c32 . sudo cp /usr/lib/syslinux/modules/bios/menu.c32 .这里有个容易踩的坑pxelinux.0 加载后会在自己所在的目录找 ldlinux.c32这个文件如果缺了或者不在同一个目录屏幕上就会直接卡在 Failed to load ldlinux.c32 然后死机。所以以上文件必须全部放在 TFTP 根目录 /var/lib/tftpboot 下同一层不要想着放在子目录里归类。我曾经为了目录整洁把 ldlinux.c32 放进 modules 子目录结果调试了大半天最后发现是路径问题从那儿以后我学乖了反正这些文件也不多根目录堆就堆着。4.2 编写pxelinux.cfg启动菜单PXELINUX 引导起来之后会按照优先级查找配置文件先是网卡的 MAC 地址格式为 01-xx-xx-xx-xx-xx-xx再是根据 IP 地址转换为十六进制文件名的文件最后才是 pxelinux.cfg/default。这个查找顺序可以拿来针对特定机器做定制比如给某台机器单独的菜单项但大部分场景我们只需要最后兜底的 default 就能满足需求。在 TFTP 根目录创建 pxelinux.cfg 目录和 default 文件sudo mkdir /var/lib/tftpboot/pxelinux.cfg sudo vim /var/lib/tftpboot/pxelinux.cfg/default我用的菜单配置如下DEFAULT vesamenu.c32 PROMPT 0 TIMEOUT 30 MENU TITLE PXE Boot Menu (Legacy BIOS) LABEL install-ubuntu2204 MENU LABEL Install Ubuntu 22.04 LTS (Autoinstall) KERNEL ubuntu2204/vmlinuz APPEND initrdubuntu2204/initrd.img ipdhcp autoinstall dsnocloud-net;shttp://192.168.10.10/autoinstall/ urlhttp://192.168.10.10/ubuntu2204/ubuntu-22.04.2-live-server-amd64.iso LABEL local MENU LABEL Boot from local disk LOCALBOOT 0DEFAULT vesamenu.c32指定用图形化菜单PROMPT 0表示不等待用户按 Enter直接显示菜单TIMEOUT 30是菜单超时 30 秒超时后自动执行第一个 LABEL。KERNEL 指向 TFTP 根目录下 ubuntu2204 子目录里的 vmlinuzAPPEND 里的 initrd 参数指定了 initrd.img 的位置剩下的 autoinstall 和 ds 参数是 Ubuntu 安装器用来识别自动应答配置的后面第六章详细展开。4.3 用syslinux配置实现自动装机看似只是两个 LABEL 配置实际操作时要格外小心路径。KERNEL 和 initrd 的路径都是相对于 TFTP 根目录的所以ubuntu2204/vmlinuz对应/var/lib/tftpboot/ubuntu2204/vmlinuz。而 APPEND 里的urlhttp://...是安装器内部用来访问安装源的走的是 HTTP和 TFTP 路径完全无关。这两个路径容易搞混尤其是从 U 盘装机时代转过来的朋友总以为 initrd 也要跟着 url 走 HTTP其实 initrd 是先通过 TFTP 下载到内存的url 只是在 initrd 启动后才起作用。LOCALBOOT 0是 PXELINUX 提供的一个特殊指令表示退出网络引导从本地磁盘启动。这个功能很实用因为 PXE 服务器不会强制你每次都在菜单里选网络装装完系统后网卡还是优先从网络引导如果不想每次都手动跳过可以把默认超时动作指向 LOCALBOOT。我在这个 LABEL 上吃过一次亏某台机器装好系统后重启又走了一遍 PXE 菜单十几秒没人理它直接触发自动安装差点把刚装好的系统覆盖掉。所以我强烈建议在菜单里保留 local 这个选项时长设短一点养成习惯。5. UEFI启动链路搭建GRUB2网络引导5.1 grub-mknetdir生成网络引导镜像UEFI 引导链路和 Legacy 有本质区别。我前面配置了 dnsmasq 给 UEFI 客户端下发 grub/grubx64.efi现在要用 grub-mknetdir 生成这个 EFI 引导文件。sudo apt install -y grub-efi-amd64-bin sudo mkdir -p /var/lib/tftpboot/grub cd /var/lib/tftpboot sudo grub-mknetdir --net-directory/var/lib/tftpboot -d /usr/lib/grub/x86_64-efigrub-mknetdir 的工作逻辑是在指定的 net-directory 下生成一个 grub 目录里面放好 grubx64.efi、模块、字体等文件。运行后检查目录ls -l /var/lib/tftpboot/grub/如果一切正常你应该能看到 grubx64.efi 文件。这里要注意一个细节grubx64.efi 编译时的 prefix 通常被设为(pxe)/grub意思是 GRUB 启动后会从 TFTP 根目录的 grub 子目录里加载模块和配置文件。所以即使你把 grubx64.efi 这个文件拷到了其他目录GRUB 仍然会固执地去找 grub/grub.cfg。这个路径关系得记住很多 UEFI 引导失败的教程案例最后查下来都是 prefix 路径对不上。字体问题也要处理一下。GRUB 菜单如果想显示中文需要中文字体文件通常从 /usr/share/grub/unicode.pf2 拷贝过来sudo mkdir -p /var/lib/tftpboot/grub/fonts sudo cp /usr/share/grub/unicode.pf2 /var/lib/tftpboot/grub/fonts/5.2 编写grub.cfg菜单创建 /var/lib/tftpboot/grub/grub.cfg 文件sudo vim /var/lib/tftpboot/grub/grub.cfg内容如下set timeout30 set default0 menuentry Install Ubuntu 22.04 LTS (Autoinstall) { linuxefi /ubuntu2204/vmlinuz ipdhcp autoinstall dsnocloud-net;shttp://192.168.10.10/autoinstall/ urlhttp://192.168.10.10/ubuntu2204/ubuntu-22.04.2-live-server-amd64.iso initrdefi /ubuntu2204/initrd.img } menuentry Boot from local disk { exit }注意这里用的是 linuxefi 和 initrdefi 命令这是 GRUB2 在 UEFI 模式下特有的命令用来加载带 EFI 签名机制支持的内核。如果你用传统的 linux 和 initrd 命令在部分 UEFI 固件下会遇到问题。还有一个细节GRUB 网络引导时所有文件路径都是相对于 TFTP 根目录的所以/ubuntu2204/vmlinuz对应/var/lib/tftpboot/ubuntu2204/vmlinuz文件不能少。exit命令在 GRUB 里表示退出 GRUB回到 UEFI 固件的启动管理界面这实际上也等效于从本地磁盘启动因为大部分 UEFI 固件在你的引导管理器里会接着按顺序找本地启动项。如果 exit 没效果也可以试着用chainloader (hd0)1这类链式加载命令但这在不同机器上兼容性不一我倾向于保留 exit。5.3 Secure Boot的取舍UEFI 链路里绕不开 Secure Boot 这个话题。Secure Boot 是 UEFI 固件的一项安全特性只允许加载经过签名的 EFI 可执行文件。我用 grub-mknetdir 生成的 grubx64.efi 没有经过微软签名所以在默认开启 Secure Boot 的机器上直接引导大概率会看到类似 Verification failed: (0x1A) Security Violation 的报错然后卡死在启动界面。解决这个问题有两个方向。第一个方向是临时关闭客户机的 Secure Boot这是我在内网测试环境里的做法因为测试机没什么敏感数据关掉反而省心。第二个方向是使用 Ubuntu 官方签名的 shimx64.efi.signed 作为引导文件通过 shim 再加载 grubx64.efi但这套流程需要一个已经被签名的 grubx64.efi.signed而这个文件是给本地启动用的网络引导场景下使用它需要额外配置模块搜索路径复杂度陡增。我的建议是内网自己用直接关闭 Secure Boot把精力放在搭建和自动安装配置上。如果后续要给生产环境的机器用再考虑签名引导链否则会被兼容性问题拖入泥潭。生产环境里也可以通过 BIOS 管理工具批量下发关闭 Secure Boot的策略这属于运维平台层面的工作不在这里展开。6. 准备Ubuntu 22.04安装源与自动应答6.1 挂载ISO并提供HTTP访问启动文件准备好只是第一步安装源才是让安装器真正干活的东西。我用 Nginx 提供 HTTP 服务把 Ubuntu 22.04 LTS 的 ISO 镜像挂载到 Web 目录里。首先创建目录并挂载 ISO。我用的是 ubuntu-22.04.2-live-server-amd64.iso注意是 Server 版本因为它的安装器是 subiquity支持 autoinstall 自动应答。sudo mkdir -p /var/www/html/ubuntu2204 sudo mount -o loop /opt/iso/ubuntu-22.04.2-live-server-amd64.iso /var/www/html/ubuntu2204为了每次重启都自动挂载把挂载项写进 /etc/fstab/opt/iso/ubuntu-22.04.2-live-server-amd64.iso /var/www/html/ubuntu2204 iso9660 loop,defaults 0 0然后确认 Nginx 已经启动sudo systemctl enable --now nginx curl -I http://192.168.10.10/ubuntu2204/能看到 HTTP 200 就说明安装源对外可访问了。ISO 挂载后里面的目录结构大概是这样casper/ 目录下有 vmlinuz 和 initrd这两份文件就是要拷到 TFTP 根目录的 ubuntu2204 里的。sudo cp /var/www/html/ubuntu2204/casper/vmlinuz /var/lib/tftpboot/ubuntu2204/ sudo cp /var/www/html/ubuntu2204/casper/initrd /var/lib/tftpboot/ubuntu2204/注意文件名我通常把 initrd 改名为 initrd.img不然启动参数里也得跟着改成 initrd这里保持一致启动菜单里写起来顺手。6.2 编写autoinstall配置文件Ubuntu 22.04 的自动安装配置用的是 cloud-init 语法文件格式是 YAML放在 HTTP 可访问的目录下。我在 Nginx 默认站点里建一个 autoinstall 目录sudo mkdir -p /var/www/html/autoinstall创建 /var/www/html/autoinstall/autoinstall.yaml#cloud-config autoinstall: version: 1 locale: zh_CN.UTF-8 keyboard: layout: cn network: version: 2 ethernets: all: match: name: e* dhcp4: true storage: layout: name: lvm identity: hostname: pxe-node username: admin password: $6$yourhashedpassword realname: Admin ssh: install-server: true allow-pw-auth: true packages: - openssh-server - vim - qemu-guest-agent user-data: disable_root: true late-commands: - curtin in-target -- apt update shutdown: reboot身份配置里的 password 必须是密码哈希字符串我一般用 openssl passwd -6 生成openssl passwd -6 your-password这里有几个必须注意的点。第一storage 配置绝对不能省略。subiquity 安装器如果在 autoinstall 找不到存储配置会退回到交互模式让你手动选磁盘整条自动化链路就断了。我用的layout: lvm是让安装器自动把第一块盘用 LVM 方式分区如果你想要传统分区可以改成layout: direct或者自定义复杂的分区方案。第二network 配置也要小心如果安装源需要联网下载软件包网络配置必须让它能通过 DHCP 获取 IP。我这里用 match 匹配e*来匹配所有以 e 开头的网卡接口名这样无论是 eno1 还是 ens33 都能被捕获。第三identity 里的 username 和 password 决定安装后系统的主要用户忘了配置会导致装出来的系统没法登录别问我怎么知道的。6.3 引导参数如何传递autoinstall配置文件准备好后要把它传给安装器。回头看我启动菜单里的引导参数autoinstall dsnocloud-net;shttp://192.168.10.10/autoinstall/ urlhttp://192.168.10.10/ubuntu2204/ubuntu-22.04.2-live-server-amd64.isoautoinstall 关键字告诉 subiquity 进入自动安装模式。dsnocloud-net 是 cloud-init 的数据源配置格式是dsnocloud-net;shttp://...意思是让 cloud-init 去 http://192.168.10.10/autoinstall/ 这个目录下查找配置。cloud-init 会在那个目录下找名为 user-data 和 meta-data 的文件所以我们还需要把 autoinstall.yaml 重命名或复制一份为 user-datasudo cp /var/www/html/autoinstall/autoinstall.yaml /var/www/html/autoinstall/user-data sudo touch /var/www/html/autoinstall/meta-datameta-data 文件可以为空但必须存在否则 cloud-init 会报错找不到数据源。url 参数用来指定安装器要挂载的安装源 ISO 路径。注意 subiquity 安装器本质上是先启动一个小的 live 系统再从这个 ISO 源里读取 packages、squashfs 等文件进行安装。所以 url 指向的就是我在 Nginx 上挂载的 ISO 访问路径服务器端直接指向 ISO 文件也可以指向挂载目录也没问题我都试过。如果网速很慢可以加一个toram参数让安装器先把必要文件拉进内存但一般没必要。7. 双启动联调与常见问题排查7.1 建议的测试环境配置全部完成以后真正的大考是实际引导测试。我的建议是先用虚拟机验证流程再上真机。虚拟机我用 KVM/QEMU 比较多因为 VirtualBox 对 UEFI 网络引导的支持相对弱偶尔会出现网卡 PXE ROM 不被 UEFI 固件识别的问题容易误导排错方向。KVM 创建虚拟机时BIOS 类型和网卡启动可以通过 virt-manager 或命令行控制简单直接。测试时注意两点。第一虚拟机的网卡要设置为 e1000 或 virtio然后用 PXE 启动。第二如果需要测试 UEFI 模式要确保虚拟机固件用的是 OVMF也就是 UEFI 固件而不是默认的 SeaBIOS。在 virt-manager 创建虚拟机时CPU 配置和固件类型可以在创建向导里指定。我在测试时就遇到过一个哭笑不得的场景KVM 虚拟机 UEFI 模式引导时dnsmasq 日志显示客户端已经拿到了 grub/grubx64.efi但虚拟机屏幕上卡在 Start PXE over IPv4 然后就不动了。排查到后面发现是 GRUB 的模块文件缺失导致 EFI 程序加载到一半退出重新执行 grub-mknetdir 后用 ls 检查了 grub 目录里的模块完整性问题才解决。7.2 高频报错及处理速查表把我在整个搭建和测试过程中遇到的高频报错整理成了一个表格按报错现象、可能原因、解决办法三个维度记录方便各位直接查报错现象可能原因解决办法PXE-E53: no boot filename receiveddnsmasq 没有下发启动文件确认 dhcp-boot 配置和 dhcp-match 标签是否匹配查看 dnsmasq 日志的 bootfile 字段卡在 Loading /pxelinux.0...TFTP 传输块大小协商问题或防火墙拦截 UDP 69放行 UDP 69在 dnsmasq 配置加上 tftp-no-blocksize关闭块大小协商Failed to load ldlinux.c32PXELINUX 的 c32 模块缺失或不在根目录从 /usr/lib/syslinux/modules/bios/ 拷贝全部 c32 模块到 /var/lib/tftpboot/ 根目录UEFI 启动报 Security ViolationSecure Boot 拦截了未签名 EFI 文件临时关闭客户机 Secure Boot或在 BIOS 里允许从网络引导引导后进入 grub 命令行提示符grub.cfg 没被正确加载确认 grubx64.efi 的 prefix 指向 grub 目录确认 /var/lib/tftpboot/grub/grub.cfg 存在自动安装配置不生效安装器还是走交互式界面ds 参数路径不对或 cloud-init 没找到 user-data检查 dsnocloud-net;shttp://... 后的目录是否有 user-data 文件meta-data 是否存在客户端获取不到 IP 或获取缓慢网络里存在多个 DHCP 服务器关闭交换机/路由器的 DHCP 服务或者用 tcpdump 抓包确认是否有 DHCP Offer 抢答安装器能启动但下载安装包超时HTTP 源不可达或 DNS 解析失败curl 确认 http://IP/ubuntu2204/ 可访问检查安装器是否能连通外网软件源这张表看着简单每一条都是我实际踩过的坑换来的。特别是 tftp-no-blocksize 这个问题当时不加上这个参数pxelinux.0 加载到一半就卡死我以为是防火墙问题放行了所有端口依然不行最后翻 dnsmasq 文档才发现是老式 PXE 栈和 dnsmasq 默认的 TFTP 块大小协商有兼容性问题。7.3 排查技巧与抓包思路如果遇到速查表解决不了的问题那就得上干活的工具了。PXE 排错有一个原则先确认链路走到哪一步断了。我常用三部曲排查。第一部看服务器日志。dnsmasq 配置了log-dhcp之后每次 DHCP 交互都会写到系统日志里tail -f /var/log/syslog | grep dnsmasq能看到 DHCP DISCOVER、DHCP OFFER、TFTP 请求的记录就能判断客户端是否成功拿到 IP、是否请求了启动文件、请求的启动文件名是什么。这个信息能快速帮你区分是 DHCP 环节出问题还是 TFTP 环节出问题。第二部抓包确认报文细节。如果日志正常但客户端就是启动失败直接用 tcpdump 抓 DHCP 和 TFTP 流量sudo tcpdump -i eth0 -n port 67 or port 68 or port 69重点看 DHCP Offer 报文里的 option 66 和 option 67 字段确认服务器下发的 TFTP 服务器地址和启动文件名是否如预期。如果 option 67 显示的是 pxelinux.0但客户端是 UEFI 机器那问题就出在 dhcp-match 没有匹配上 option 93 的值需要回到 dnsmasq 配置检查 num 值。我在排查一个奇怪的案例时明明 UEFI 机器也拿到了 grubx64.efi但 GRUB 始终无法加载菜单抓包发现 TFTP 请求的 RRQ 是正常的但 GRUB 模块加载时请求的文件路径不对顺着 tcpdump 的 filter 再过滤port 69就看到了完整的 TFTP 请求路径。第三部手动验证 TFTP 和 HTTP。TFTP 服务是否正常可以客户端用 tftp 命令试试tftp 192.168.10.10 tftp get grub/grubx64.efi tftp quit能成功传输文件说明 TFTP 环节没问题。HTTP 同理curl -I http://192.168.10.10/ubuntu2204/看响应码是否为 200。这两个小动作能快速缩小问题范围比我 DEBUG 大半天效率高多了。最后分享一个小技巧pxelinux.cfg 目录里其实可以放多个配置文件最通用的是 default但你完全可以创建一个名为01-aa-bb-cc-dd-ee-ff的文件注意前缀 01 加 MAC 地址专门给某台机器分配特定菜单或自动安装配置。我之前给测试机房不同机器分配不同 hostname 时就是这么干的非常方便还不影响其他机器走默认配置。