上个月帮一家做产线检测设备的朋友收拾机房的烂摊子,三十多台工控机要统一换成同一套 Windows 镜像,还要在其中三台上顺带装一套 Linux 做数据采集。机房在地下室,没有机柜,机器就堆在货架上,一台一台插 U 盘装,光是拔插和等待就得耗掉两个通宵。我当时提了个方案:搭一个 PXE 网络引导环境,把机器全部改成网卡启动,按一下电源键,剩下的交给网络。朋友的第一反应是"这玩意儿是不是得装一堆服务,配不好网络就全废了",这也是大多数人对 PXE 网络批量安装操作系统的固有印象。
实际做过一次就知道,真正麻烦的不是原理,而是那些散落在各个角落的细节:DHCP 选项 66 和 67 到底填什么、UEFI 和 Legacy BIOS 为什么不能共用一套引导文件、WinPE 加载到一半提示找不到网卡、几十台机器同时拉镜像时 TFTP 直接卡死。这篇就把我用 TinyPXEServer 这套小工具做网络批量装机的完整过程摊开讲,从网络拓扑怎么选、目录怎么摆、引导文件怎么备,到无人值守应答文件怎么写、报错怎么排查。适合手上有十几到上百台机器要部署、又没有成套自动化运维平台的场景,也适合想搞明白 PXE 到底是怎么跑起来的技术同学。
1. 为什么我最后选了 TinyPXEServer,而不是在 Linux 上折腾 dnsmasq
1.1 PXE 装机的三段式链路:DHCP 派地址、TFTP 递引导、镜像走文件服务
PXE 这个东西第一次接触会觉得玄乎,拆开看其实只有三个环节首尾相接。客户端网卡上的 PXE ROM 在开机时先广播一个 DHCP 请求,这个请求和普通上网要地址的请求长得不一样,它带了一段厂商扩展信息,大意是在说"我不光要 IP,我还要一个能启动的东西"。第一段链路就是响应这个请求,把 IP 地址、网关、DNS 连同"去哪台机器拿引导文件""引导文件叫什么名字"一起发回去。第二段链路是客户端拿着返回的地址和文件名,用 TFTP 协议去把那几百 KB 到几 MB 的引导程序拉回本地内存执行。第三段链路才是引导程序接管之后的事,它去读取菜单、加载内核或者 WinPE 镜像,这个阶段的数据量是几百 MB 级别,用 TFTP 拉会非常痛苦,所以通常换成 HTTP。
理解这三段之后,很多"玄学问题"就有了归处。比如提示拿不到引导文件名,问题在第一段;提示 TFTP 超时,问题在第二段;引导菜单出来了但加载镜像卡住,问题在第三段。我后来的排查习惯就是先看进度条走到了哪一步,再决定去查哪个环节。
网络批量安装操作系统的本质,是把"介质"从 U 盘换成了网卡。U 盘装机的流程是:插盘、读盘、写盘、拔盘、下一台。PXE 把它变成了:开机、等待、重启、下一台。省掉的是人工拔插和值守,但换来的代价是你要提前把网络、引导、应答文件这三块都准备好,而且一旦配错,影响的不是一台,而是一整个网段。
1.2 那些"重方案"在什么场景下反而拖后腿
做批量部署,网上一搜基本都是两条路:一条是 Windows 侧的 WDS 加 MDT,另一条是 Linux 侧的 dnsmasq 加 tftp-hpa 加 nginx 加 syslinux 全家桶。这两条路都很成熟,功能也远比小工具强,但它们的门槛在具体场景里会被放大。
WDS 需要域环境或者至少一台 Windows Server,装完角色还要配 WDS 的引导映像、安装映像、应答文件,部署一台服务器本身的成本就比要装的机器还高。MDT 更重,任务序列、驱动库、数据库,学习曲线陡得吓人。如果是给公司做长期标准化运维,这套投入是值的;但如果只是临时把三十台机器刷成同一个镜像,投进去的时间根本收不回来。
Linux 那套组合拳的问题在依赖。dnsmasq 需要改配置、处理端口冲突、注意和系统里已有的 DHCP 客户端打架;tftp-hpa 的根目录权限、SELinux 策略、防火墙规则,每一样都能让人卡半天。更麻烦的是这套环境搭在 Linux 上,而你要装的往往是 Windows,中间还得跨系统共享镜像文件,路径和权限又是一轮折腾。
我最后选 TinyPXEServer,理由很朴素:单个绿色程序,双击就能跑,DHCP、ProxyDHCP、TFTP、HTTP 四个服务全在里面,配置文件就是一个ini,随时拷走换机器。对于"一周之内把几十台机器刷完然后拆掉"这类一次性任务,它的性价比高得离谱。
1.3 TinyPXEServer 的能力边界,以及它做不了的事
用得顺手不代表它是万能的,恰恰相反,越小的工具边界越清晰。它擅长的是:在已经有网络的环境里,快速提供一个 PXE 引导入口,把客户端引到引导文件上,再把镜像通过 HTTP 分发出去。它不擅长的是:精细化的 DHCP 管理,比如租约保留、按 MAC 分配固定 IP、多网段地址池这些;也不提供并发调度,不会因为你同时来五十台客户端就自动分配带宽;更没有任务序列引擎,装完系统之后自动装软件、加域、跑脚本这些事得靠应答文件或者装完之后的脚本。
我的做法是把它当成一个"引导跳板",而不是完整方案。真正复杂的后置动作,我放在镜像里的首次启动脚本中执行,让系统自己装完之后拉一个批处理下来跑。这样工具的边界和工作的边界就对齐了,不会硬塞给它不该干的活。
还有一点需要提前说清楚:TinyPXEServer 界面上的功能是有限的,更多时候我是先用界面把服务跑通,然后再去改 ini 文件加细节。改的时候要留个备份,有些字段写错会导致程序启动直接报错,而不是给你一个友好的提示。
2. 动手之前,先把网络这层想明白
2.1 单网段直连:最省事的拓扑
如果你的待装机器和跑 TinyPXEServer 的机器在同一个二层网络里,中间没有三层设备,那事情简单得超乎想象。把服务端机器的有线网卡设一个固定 IP,比如 192.168.10.1,掩码 255.255.255.0,然后在这个网卡上开 TinyPXEServer 的 DHCP 服务,地址池设成 192.168.10.100 到 192.168.10.200,网关和 DNS 都指向 192.168.10.1。
这里有一个容易忽略的点:服务端机器如果同时连着公司的办公网,它就有两块网卡,而你肯定不希望 TinyPXEServer 的 DHCP 去给办公网发地址。程序里一般有一个绑定网卡或者绑定 IP 的选项,一定要选成那块专门用于装机的网卡。我在第一次做的时候忘了这回事,结果整个办公区断网两分钟,场面相当尴尬。
拓扑上建议待装机器和服务端之间用一个独立的小交换机,不要和生产网混在一起。独立交换机有两个好处,一是广播域干净,PXE 的广播包不会外泄;二是真的出问题的时候,拔掉一根线就能把影响范围控制住。
2.2 网络里已经有 DHCP 服务器怎么办:ProxyDHCP 模式
更常见的情况是,待装机器所在的网段本来就有一个 DHCP 服务器在工作,可能是路由器,也可能是核心交换机上跑的服务。这时候如果 TinyPXEServer 也开普通 DHCP,两个服务器抢着发地址,客户端的表现就是时好时坏,一会儿能进 PXE,一会儿进不去,排查起来极其费神。
正确的做法是启用 ProxyDHCP 模式,我通常叫它"代理启动模式"。开启之后,TinyPXEServer 不再负责分配 IP 地址,地址仍然由原有 DHCP 服务器发,它只负责回答客户端那个"我去哪拿引导文件"的问题。这个模式的好处是零侵入,不会动到现有网络的地址分配,装完机器关掉程序就完事。
要注意的是这种模式下,原有的 DHCP 服务器不能禁用 PXE 相关的响应选项,也不能屏蔽客户端的广播。有些路由器的 DHCP 设置里会有一个"禁止未知 PXE 客户端"之类的开关,需要确认一下。我在一次实施中就遇到过某品牌路由器默认丢弃带厂商扩展的 DHCP 请求,导致 ProxyDHCP 完全收不到客户端的广播,把那个选项关掉之后立刻正常。
如果原有 DHCP 服务器和服务端不在同一个二层网络,那还需要在中间的三层设备上配置 DHCP 中继,让它把广播转成单播投递过来。这一步已经超出了小工具的范围,属于网络层面的配置,提前和网管沟通好比较省事。
2.3 Legacy BIOS 与 UEFI:同一批机器可能需要两套引导文件
这是新手最容易翻车的地方。Legacy BIOS 和 UEFI 是两套完全不同的固件引导规范,它们对引导文件的要求也不一样。Legacy 时代网卡 ROM 找的是像 pxelinux.0 这样的二进制程序,UEFI 时代找的是 .efi 结尾的可执行文件,比如 bootx64.efi 或者 iPXE 的 efi 版本。两者之间不能互换。
麻烦的是现实场景里往往是混着的。老一点的工控机、收银机、瘦客户机基本都是 Legacy,新买的笔记本、迷你主机基本都是 UEFI,有些主板还同时支持两种,靠一个开关切换。如果只准备了一套引导文件,那另一半机器就会卡在"找不到启动文件"的阶段。
我的处理办法是在 DHCP 的启动文件名配置里区分两类客户端。TinyPXEServer 这类工具通常允许你在引导文件名那一栏填一个"委托"文件,让引导程序自己根据架构决定加载哪个。iPXE 就支持这种做法,它的 .pxe 版本被加载之后,可以判断自己运行在什么固件环境下,再去拉对应的 .efi 或者 .kpxe。另一条路是干脆把启动文件名留空,让客户端通过 PXE 的架构标识选项去分别匹配,不过这需要 DHCP 服务端支持按选项值分流,小工具不一定做得到。
最省心的办法其实是"引导程序套引导程序":无论 Legacy 还是 UEFI 都先加载 iPXE,然后在 iPXE 的脚本里判断环境再加载对应的下一级引导器。这样 DHCP 那层只需要配一次。
3. TinyPXEServer 的各项配置逐条拆开讲
3.1 装机目录长什么样:TFTP 根、HTTP 根、引导文件该放哪
TinyPXEServer 的目录结构是理解它的关键。程序文件夹里通常有几类东西:可执行文件本身、一个 ini 配置文件、以及若干用于存放引导文件和镜像的子目录。不同版本目录命名可能略有差异,但职责划分逻辑是一样的。
TFTP 服务的根目录,是客户端通过 TFTP 拉取文件时的"根"。比如客户端请求的文件名是 ipxe.pxe,那这个文件就得放在 TFTP 根目录下面。PXE 客户端的请求文件名是不带路径的,所以你在 DHCP 里配置的启动文件名要和实际存放的文件名严格一致,大小写敏感的问题在某些 TFTP 客户端上也存在,虽然多数 PXE ROM 不区分,但我习惯统一用小写。
HTTP 服务的根目录,是给引导程序加载大镜像用的。比如 iPXE 脚本里写kernel http://192.168.10.1/boot/wimboot,那 wimboot 这个文件就要放在 HTTP 根目录下的 boot 文件夹里。HTTP 根和 TFTP 根可以是同一个物理目录,也可以是不同的目录,我一般让它俩指向同一处,减少文件重复拷贝的麻烦。
目录规划上我习惯按"引导层"和"镜像层"分开。引导层放几个小文件,几百 KB 到几 MB,走 TFTP;镜像层放 boot.wim、install.wim、ISO、initrd 这些大块头,走 HTTP。分开之后排查也方便,TFTP 出问题就只看引导层那几个文件。
有一点要注意,路径里尽量不要出现中文和空格。TFTP 和 HTTP 的路径解析在处理非 ASCII 字符时表现不一致,轻则找不到文件,重则直接报错。我给目录命名一律用英文加数字,简单粗暴但从来没出过问题。
3.2 DHCP 面板:地址池、网关、DNS、启动文件名的填写逻辑
打开 DHCP 面板,通常要填这几项:起始 IP、结束 IP、子网掩码、网关、DNS、启动文件名。每一项都有它的作用,填错的后果也不一样。
起始和结束 IP 划定了地址池范围。这个范围要和你的服务端 IP 在同一网段,且不能包含服务端自己的地址。比如服务端是 192.168.10.1,地址池可以设成 192.168.10.100 到 192.168.10.200,留出前面一段给静态设备。地址池大小要大于同时开机的机器数,注意 PXE 阶段的租约是短期的,如果地址池太小,先开机的机器还在装,后开机的就分不到地址了。
子网掩码不用多说,网关和 DNS 在纯装机场景里其实不那么关键,但有些引导程序会尝试解析域名,填一个可用的 DNS 能避免一些奇怪的等待。我通常把网关和 DNS 都指向服务端自身,服务端有没有真正联网并不影响装机,因为所有文件都在本地。
启动文件名是整个配置里最核心的一项。填的就是客户端要通过 TFTP 拉取的那个文件。填错了或者在 TFTP 根目录里找不到这个文件,客户端就会报出"没有收到启动文件名"或者"TFTP 打开超时"的错误。不同引导方案这里填的东西不一样,Legacy 加 PXELINUX 填 pxelinux.0,Legacy 加 iPXE 填 ipxe.pxe,UEFI 填 ipxe.efi 或者 bootx64.efi。
还有一项容易被忽略的是租约时间。PXE 阶段希望租约短一点,让装完的机器快速释放地址;但如果租约太短,装机过程中可能因为续租失败导致网络中断。我一般设成十几分钟到半小时之间,比整个装机过程略长一点。
3.3 大镜像别走 TFTP:HTTP 通道的开启方式
TFTP 这个协议诞生在上世纪八十年代,设计目标是简单可靠,不是快。它的传输单位默认是 512 字节一个块,发一个包等一个确认,网络稍有抖动就会重传。用它传几 MB 的引导程序还算能忍,传一个 4 GB 的 install.wim 基本等于自虐。
TinyPXEServer 内置了 HTTP 服务,开启之后客户端可以用 HTTP 协议拉文件,速度快得多,而且支持多客户端并发。开启方式通常就是在界面上勾一个"启用 HTTP"的选项,端口默认 80,如果和机器上已有的服务冲突就改成 8080 之类。
真正用起来的关键不在服务端,而在引导脚本。iPXE 支持 http:// 开头的地址,脚本里写成kernel http://192.168.10.1/boot/wimboot就会走 HTTP。PXELINUX 则不支持 HTTP(新版本有 lpxelinux.0 可以,但兼容性一般),所以如果你用 PXELINUX,大镜像还是得想办法,通常是挂一个 SMB 或者 NFS 共享,让 WinPE 启动之后自己去挂载。
我的实际组合是:TFTP 只负责发一个几百 KB 的 iPXE,后面所有东西——wimboot、boot.sdi、boot.wim、ISO——全部走 HTTP。这样 TFTP 的压力几乎为零,几十台机器同时开机也不会卡。
3.4 config.ini 的手改法与容易改坏的地方
界面上能配的东西有限,很多细节要改 ini。改之前先备份一份,这是我踩过坑之后的习惯。ini 的字段名一般能自解释,但有几个地方的坑比较集中。
一是路径分隔符。Windows 下习惯用反斜杠,但 ini 里的路径有时需要转义,或者干脆用正斜杠更保险。我改成全用正斜杠之后,路径相关的报错基本绝迹了。
二是布尔值的写法。有的版本用 1 和 0,有的版本用 true 和 false,混着写会解析失败。改的时候看一眼原文件里已有的写法,照着来。
三是行尾的注释和空格。有些解析器对行尾空格敏感,删掉注释后留下的空格可能导致值多出一个字符。改完保存之前习惯性看一眼行尾。
四是编码。用记事本保存成带 BOM 的 UTF-8,某些版本会读不出来。用支持无 BOM 的编辑器保存比较省事。
改完 ini 之后,程序要重启才能生效。我一般会先在一台机器上试,确认配置改动没问题,再放开给全部机器。
4. 引导文件怎么备:从 PXELINUX 到 iPXE 再到 wimboot
4.1 Legacy 路线:pxelinux.0 加 ldlinux.c32 再加菜单文件
PXELINUX 是 Syslinux 项目的一部分,在 Legacy BIOS 环境下用得最广。它的核心文件是 pxelinux.0,但只有这一个文件是跑不起来的,还需要 ldlinux.c32 这个核心模块,以及如果你想要一个可选择的菜单,还得有 menu.c32 和字体文件。
这几个文件之间是版本绑定的。pxelinux.0 是 6.03 版本,ldlinux.c32 是 6.04 版本,放在一起就可能报"找不到模块"或者直接黑屏。我第一次做的时候就是从不同的压缩包里各拿了一个,卡了整整一个下午。后来学乖了,永远从同一个版本的发行包整套提取。
菜单文件放在 TFTP 根目录下的 pxelinux.cfg 文件夹里,文件名一般是 default。这个文件里写每个启动项的 label、kernel、append 参数。比如一个典型的 Windows PE 启动项,kernel 指向 wimboot,append 里用initrd=依次挂上 bootmgr.exe、BCD、boot.sdi、boot.wim。顺序不能乱,wimboot 对顺序有要求。
PXELINUX 的优点是成熟、资料多、老机器兼容性好。缺点是它对 HTTP 支持有限,而且菜单文件那一套语法比较古板,写错一个字符就整个菜单不显示。
4.2 UEFI 路线:bootx64.efi 与 iPXE 之间的取舍
UEFI 环境下,引导文件名一般填 bootx64.efi,这是 UEFI 规范里约定的默认引导程序名。但光有一个 bootx64.efi 还不够,它通常是 GRUB2、iPXE 或者 Syslinux 的 UEFI 版本之一,不同的实现后续的配置方式完全不同。
我选的是 iPXE 的 UEFI 版本,文件名叫 ipxe.efi 或者 snponly.efi。选它的原因是 iPXE 有脚本能力,可以在脚本里判断网卡、拼 URL、走 HTTP,比 GRUB2 的配置文件灵活得多。写一个简单的 ipxe 脚本,几行就能实现"显示菜单、默认十秒后自动进入、选中后从 HTTP 拉 wimboot 和 boot.wim"。
要注意的是 UEFI 有一个 Secure Boot 的机制。如果目标机器的 Secure Boot 是开启的,未经签名的 iPXE 可能被固件拒绝加载,表现就是加载到一半提示校验失败或者干脆重启。解决办法有两个:一是在 BIOS 里把 Secure Boot 关掉,这在自建机房里通常可以接受;二是用签过名的引导程序,但那就得走微软的签名流程,成本很高。批量装机的话,进 BIOS 关掉 Secure Boot 是最省事的做法,虽然要多一道人工操作。
还有一点,某些主板的 UEFI 网络栈实现有问题,加载 .efi 引导程序后会丢失网络连接,导致后续 HTTP 拉文件失败。这种情况可以试试用 snponly.efi,它直接使用网卡的 UEFI 驱动,绕开固件自带的网络栈。
4.3 WinPE 引导的关键几件套:wimboot、boot.sdi、boot.wim
要装 Windows,中间必须经过 WinPE 这一层。WinPE 是一个精简的 Windows 环境,它负责分区、展开镜像、写引导记录。把 WinPE 通过网络引导起来,靠的是 iPXE 的 wimboot 模块。
wimboot 本身是一个很小的二进制文件,它的作用是解析 WIM 格式的镜像并把它当作启动镜像加载。要用它,需要准备四样东西:wimboot、bootmgr.exe、BCD、boot.sdi,再加上实际的 boot.wim。前四个从 Windows 安装镜像的 sources 目录和 boot 目录里能找到,boot.wim 也在 sources 目录下。
iPXE 脚本里典型写法是这样的:
kernel http://192.168.10.1/boot/wimboot initrd http://192.168.10.1/boot/bootmgr.exe bootmgr.exe initrd http://192.168.10.1/boot/BCD BCD initrd http://192.168.10.1/boot/boot.sdi boot.sdi initrd http://192.168.10.1/boot/boot.wim boot.wim boot这里的顺序是 wimboot 要求的固定顺序,改成别的顺序会加载失败。另外 BCD 这个文件是二进制的引导配置数据库,它规定了 WinPE 启动时去哪个路径找系统。如果 BCD 里的路径和实际不符,WinPE 会报错退出。
boot.wim 有两个索引,索引 1 是 Windows 安装程序,索引 2 是 WinPE 本身。网络引导时要指定用哪一个,通常用索引 2 更干净,启动后直接进命令提示符或者自定义的启动脚本。索引 1 会直接进入安装界面,如果想要全自动安装也可以用它。
4.4 网卡驱动注入:让新主板不至于卡在找不到网卡
用 WinPE 网络引导有一个致命前提:WinPE 自己得能联网或者至少能读到你放在网上的镜像。如果目标机器的网卡型号比较新,而 boot.wim 里自带的驱动包不包含它,结果就是 WinPE 起来了,但找不到网卡,后续所有需要网络的动作全部失败。
这个问题的表现是:图形界面卡在"正在寻找网络"或者命令提示符里 ipconfig 什么都没有。很多人会以为是 PXE 配置的问题,其实是驱动的问题。
解决办法是用 DISM 往 boot.wim 里注入驱动。先把 boot.wim 挂载到一个临时目录,把网卡驱动的 inf、sys、cat 文件拷进去,用 Add-Driver 命令注入,然后卸载并提交。整套命令大概长这样:
dism /Mount-Image /ImageFile:boot.wim /Index:2 /MountDir:C:\mount dism /Image:C:\mount /Add-Driver /Driver:C:\drivers /Recurse dism /Unmount-Image /MountDir:C:\mount /Commit注入完的 boot.wim 体积会变大,这是正常的。我一般会准备一个"驱动库"目录,把近两年常见的主板网卡驱动都塞进去,一次注入,后面换机器就不用反复折腾。
需要注意的是,WinPE 的驱动模型和完整 Windows 不完全一样,有些驱动在完整系统里能用,在 WinPE 里加载会失败。注入之后一定要实际跑一遍,确认网卡能识别、能拿到地址,再批量开工。
5. 让"批量"两个字落地:无人值守与镜像分发
5.1 Windows:autounattend.xml 里必须写对的几个节点
引导起来只是第一步,如果每台机器都要人工点"下一步",那批量装机就名不副实了。真正的自动化靠的是应答文件,Windows 下叫 autounattend.xml。
这个文件可以放在几个位置:U 盘根目录、安装镜像根目录、或者通过 WinPE 启动参数指向一个网络地址。网络装机场景下我倾向放在 HTTP 根目录,然后在 WinPE 的启动命令行里加上setup.exe /unattend:\\192.168.10.1\unattend\autounattend.xml这样的参数,或者更简单,直接把文件塞进 boot.wim 或者安装源目录里。
应答文件里比较关键的几个节点:磁盘分区配置、产品密钥、语言和时区、计算机名规则、管理员账户、以及首次登录后要执行的命令。磁盘分区配置最容易出问题,因为不同机器的磁盘数量和容量不一样,如果写死了磁盘号和分区大小,遇到配置不同的机器就会报错。稳妥的做法是用 DiskConfiguration 里的 WillWipeDisk 和 ModifyPartitions 配合通配符,或者干脆让安装程序自动分区。
计算机名可以设成一个前缀加随机数或者序列号后几位,避免几十台机器重名导致网络冲突。首次登录后执行的命令是挂后置脚本的入口,我一般让它去拉一个批处理,批处理里做装驱动、改注册表、装常用软件这些事。
有一点要提醒:autounattend.xml 里的密码是明文,即便用 Base64 编码也只是编码不是加密。放在网络共享上要确保只有装机网段能访问,装完之后及时删掉。
5.2 Linux:kickstart 与 preseed 该怎么选
要装 Linux 的话,无人值守是另一套机制。RedHat 系用 kickstart(ks.cfg),Debian 系用 preseed。两者都是把安装过程中的所有交互项提前写好,安装程序启动时读这个文件,一路自动走完。
kickstart 文件通过内核启动参数指定,比如在 PXELINUX 或者 iPXE 的菜单项里写append initrd=initrd.img ks=http://192.168.10.1/ks/centos.ks。文件内容大致分几块:安装源地址、磁盘分区方案、网络配置、软件包选择、安装后脚本。安装后脚本是做批量定制的关键,可以在这里统一改主机名、配 yum 源、装监控 agent。
需要留神的是安装源。如果没把完整的安装镜像挂到 HTTP 上,kickstart 会去找默认的源地址,在国内网络环境下大概率超时。老老实实把 ISO 解开放在 HTTP 根目录,然后 ks 里用url --url=http://192.168.10.1/os/指定本地源,最不容易出岔子。
preseed 的思路类似,只是参数名和文件格式不一样,而且它是通过内核参数auto=true priority=critical url=http://...指定。两种机制我都用过,kickstart 的文档和示例更多,遇到问题更好查。
5.3 镜像瘦身与传输提速的几个实操手段
几十台机器同时从一台服务器拉镜像,带宽和磁盘 IO 都会成为瓶颈。我做过一次测算,一个 5 GB 的 install.wim 分发给三十台机器,如果服务器网卡是千兆,理论上限是 125 MB/s,全部传完至少要二十分钟,而且这期间服务器什么别的都干不了。
几个提速的做法值得一试。把镜像格式从 wim 转成 esd 或者用压缩率更高的方式打包,体积能减少三到四成,代价是解压时 CPU 占用高一点,但因为传输时间大幅缩短,整体还是赚。把镜像放在 SSD 上,避免机械盘成为瓶颈。网卡如果支持链路聚合,把两条千兆绑在一起,带宽直接翻倍。
还有一个思路是分批上线。不要三十台一起开机,而是分三批,每批十台,这样单台的下载速度能维持在比较理想的水平,整体耗时反而更短。PXE 服务端还有个细节,TFTP 是 UDP 单线程的,几十个客户端同时请求小文件很容易丢包重传,所以把引导文件做得越小越好,让 TFTP 阶段快速过去,把大头都交给 HTTP。
6. 故障排查:从 PXE-E32 到 "No boot filename received"
6.1 一套固定的排查顺序,照着走就行
排查 PXE 问题最怕东一榔头西一棒子,我今天查 DHCP,明天查 TFTP,最后发现问题在网线。我总结了一套固定顺序,按链路从下往上走,基本能在十分钟内定位。
第一步看物理层。网线插好没有、交换机端口灯亮不亮、网卡在 BIOS 里有没有启用网络引导。这一步能解决大概两成的问题,尤其是新机房里线序做错、跳线坏掉这类事。
第二步看客户端拿没拿到地址。如果 PXE 阶段连"正在获取 IP"都过不去,那就是 DHCP 层面的问题,检查服务有没有启动、地址池有没有配错、绑定的是不是正确的网卡、有没有和别的 DHCP 服务器打架。
第三步看引导文件名。拿到地址之后客户端会请求引导文件,这一步失败通常提示"没有收到启动文件名"或者"TFTP 打开超时"。先去 TFTP 根目录确认文件名存在,再确认大小写和路径分隔符,最后确认防火墙放行了对应端口。
第四步看引导程序跑没跑起来。引导程序加载成功后会出菜单或者出 iPXE 的命令行界面。如果到这里黑屏或者重启,多半是引导文件版本不匹配或者固件环境不对。
第五步看大文件传输。菜单出来之后加载镜像卡住,问题在 HTTP 服务或者镜像本身。用浏览器直接访问那个 URL 试试,能下载说明服务没问题,那就是引导脚本里的地址写错了。
6.2 常见报错对照表
| 报错信息 | 可能的根因 | 处理动作 |
|---|---|---|
| PXE-E51: No DHCP or proxyDHCP offers were received | 服务未启动、地址池耗尽、绑定网卡错误 | 检查服务状态与网卡绑定,核对地址池范围 |
| PXE-E53: No boot filename received | 启动文件名未配置或配置为空 | 在 DHCP 面板填写正确的引导文件名 |
| PXE-E32: TFTP open timeout | TFTP 服务未启动、防火墙拦截、文件名不存在 | 放行 UDP 69 端口,确认文件在 TFTP 根目录 |
| PXE-E55: ProxyDHCP service did not reply | 客户端广播被拦截、服务端不在同一广播域 | 检查交换机配置与 VLAN 划分 |
| 引导菜单显示但加载镜像失败 | HTTP 地址错误、镜像文件缺失、路径大小写不符 | 用浏览器直接访问 URL 验证 |
| UEFI 下提示 boot failed | 引导文件不是 efi 版本、Secure Boot 开启 | 换用 efi 引导文件,关闭 Secure Boot |
| WinPE 启动后找不到网卡 | boot.wim 缺少对应驱动 | 用 DISM 注入网卡驱动后重新生成 |
这张表是我自己遇到过的报错汇总,大部分场景都能在里面找到对应项。表里没有的情况,就得靠抓包了。
6.3 抓包这件事,什么时候值得做
前面五步都排完了还是找不到原因,就该抓包了。在服务端机器上开着抓包工具,过滤 UDP 67 和 68 端口,重启一台客户端,看整个交互过程。
正常的流程是:客户端广播 DHCPDISCOVER,服务端回 DHCPOFFER,客户端发 DHCPREQUEST,服务端回 DHCPACK,紧接着客户端向 TFTP 端口 69 发请求。如果中间某一步断了,问题就锁定在那一环。
抓包能看出很多表面看不出的东西。比如服务端确实回了 DHCPOFFER,但里面有启动文件名这一项是空的,那问题就在配置而不是网络。又比如客户端发了多次 DHCPREQUEST 但服务端一次没回,说明请求根本没到达服务端,中间有设备在拦截。
这个手段听起来重,但真遇到疑难问题时是最快的。我遇到过一次"偶发失败"的情况,十台机器里有两台死活进不去 PXE,抓包一看,这两台的 DHCP 请求延迟比其他机器高出几百毫秒,原因是它们接在一个串联的下级交换机上,中间多了一跳。把线改接主交换机之后问题消失。
7. 规模化装机之后的几点运维心得
7.1 并发上线时的瓶颈在哪
一次装十台和一次装五十台,瓶颈位置完全不同。十台的时候基本感觉不到压力,五十台一起上的话,最先扛不住的往往是 TFTP。它是 UDP 服务,没有连接状态,每个包都要单独确认,几十个客户端同时请求几百 KB 的引导文件时,丢包率会明显上升。
我的应对是把引导层压缩到极致。能用一个引导文件解决就绝不用两个,能在脚本里少写一行就少写一行。iPXE 的好处就是它可以把大部分逻辑放在脚本里,引导文件本身只有几百 KB。另外把 TFTP 的超时时间和重传次数适当调大一点,给网络留点余量。
第二个瓶颈是服务器磁盘。镜像文件放在机械盘上,几十个 HTTP 请求同时打过来,磁头来回寻道,速度会掉到几 MB/s。换 SSD 之后这个问题基本消失,如果条件允许,把镜像提前读进系统缓存,效果更好。
第三个瓶颈其实是交换机。很多便宜的千兆交换机背板带宽不足,多个端口同时大流量传输的时候,实际每端口速率会掉。这个不太好从软件层面解决,只能分批上线或者换设备。
7.2 镜像仓库的版本管理与回滚
装了几轮之后你会发现,镜像不是一成不变的。今天加个新驱动,明天改个配置,后天换个软件版本。如果每次都直接在原镜像上改,出了问题是回不去的。
我的做法是给每个镜像建立一个版本目录,比如 v1、v2、v3,引导脚本里用一个变量指向当前生效的版本。发现新版本有问题,改一个变量就能退回旧版本,不用重新打包。目录里同时放一个说明文件,记清楚这一版改了什么、什么时候改的、谁改的。
镜像文件本身也要留一份基准。我用的是最原始的 Windows 安装镜像加一版已经定制好的镜像并存,出问题的时候能快速判断是定制环节的问题还是原始镜像的问题。
还有一点,装完机器的机器上跑的系统和你手上的镜像可能会慢慢偏离,因为用户会改设置、装软件。定期从一台标准机器上重新采集镜像,能避免版本漂移得太远。
7.3 装完之后别忘了收尾
装机这件事,装完不等于结束。还有几件事必须做,否则后面会惹麻烦。
第一是关掉服务。装完之后把 DHCP 和 TFTP 服务停掉,尤其是 DHCP,如果留着,它会和正常使用的 DHCP 服务器抢地址,表现就是随机断网。我在一个项目上忘了这回事,一周后接到电话说整个部门网络时断时续,查了半天才发现是自己留下的 DHCP 在捣乱。
第二是清理应答文件里的密码和密钥。autounattend.xml 和 ks.cfg 里往往带着管理员密码,这些文件在装完之后就没用了,留在共享目录里是个隐患。
第三是记录。记清楚这次用了哪个版本的镜像、哪套引导文件、地址池范围,下次再装同样的机器时可以直接复用。我把这些信息写成一个简短的文档放在镜像目录里,省得下次再从零回忆。
我在实际操作中的体会是,PXE 批量装机这件事的价值不在于工具本身有多强,而在于你愿不愿意花两三个小时把前面那几步做扎实。网络想清楚了、引导文件备对了、应答文件写全了,剩下的事情就是按电源键和等,几十台机器一个下午全刷完,那种感觉比你插五十次 U 盘要舒服太多。