机房里几十台裸金属服务器等着安装系统,如果还靠插U盘、挂光驱一台台手动点,整个人都会被机械劳动淹没。PXE(预启动执行环境)就是为了解决这类批量装机而生的:目标服务器只要支持网卡引导,就能通过网络自动获取IP、引导文件、安装介质和应答脚本,从头到尾不需要人到现场按任何键。这篇文章从实际搭建过的角度,把PXE自动化安装裸金属服务器的完整链路、关键配置和排错心得拆开讲清楚,适合准备做批量装机的新手运维,也适合自动化平台建设人员参考。
1. 为什么服务器安装还专门搞一套PXE
1.1 手工装机的四个瓶颈
先说最传统的装机方式。拿到一台新拆箱的裸金属服务器,运维一般要干这些事:准备一个U盘或挂载ISO镜像、进入BIOS改启动顺序、选择安装介质、手动分区、设置主机名、设置密码、选择软件包,然后等安装流程跑完再重启。单台机器顺利的话,四十分钟到一个小时总是要的,前提还是不遇到驱动缺失、分区选错这类问题。
第一个瓶颈就是时间。一次给二十台、五十台扩容,一台台操作下来,一天基本就没了。而且这种活儿没有任何技术含量,纯靠人肉反复点击,团队里最熟练的老手也快不了多少。
第二个瓶颈是状态不可控。不同批次机器可能用了不同镜像版本,有人手动改了分区方案,有人忘了做某种配置,装完之后系统状态千奇百怪。等到后面出问题,谁都不知道哪台机器当初是怎么装的。这种“隐性的环境差异”在集群故障排查时非常致命。
第三个瓶颈是人工操作容易出错。BIOS里启动顺序看走眼把系统盘选错、分区时把原有的数据盘抹掉、软件包少勾了导致服务跑不起来——这些我都见过。手动操作越多,出错的概率就越大。
第四个瓶颈是远程场景根本没法进行。机房不在身边、或者只有带外管理卡,人过不去就插不了U盘,只能干瞪眼。这几个痛点叠加在一起,使得网络引导几乎成了批量化交付的必选项。
1.2 PXE把安装流程变成了什么
PXE在这里面做的事,可以理解成把“U盘里的安装程序”通过网络递送给服务器。服务器开机后会主动进入PXE流程:先通过DHCP获取IP地址和引导服务器位置,再用TFTP拉取一小段引导程序,引导程序加载内核和initrd,最后交给安装器去访问完整的安装仓库。配合自动应答文件,分区、网络、软件包、时区和后置脚本全部自动完成。
实际的用户体验就是:把服务器开机的启动顺序调整为网络优先,插上网线,按下电源键,然后等它自己装完,最后SSH能连上就是一个成功交付的系统。整个过程中人工参与的只有物理接电和开机这一件事。
所以PXE本质上是把安装介质、网络服务和配置策略三者分离。安装介质放仓库,网络服务负责下发引导,策略文件决定这台机器该装什么版本、怎么分区、初始化哪些组件。这套结构看起来很重,但一旦跑起来,后续扩容就只是“加机器、开机”两个动作。
1.3 什么时候可以不用PXE
不是所有场景都必须上PXE。单台机器且人就在现场,直接U盘安装反而更快,搭一套网络引导环境还需要维护多个服务,不太划算。纯虚拟机场景用云平台的镜像模板克隆也更高效。还有一类基于克隆分发的环境,只要模板做得干净,批量复制的速度远超逐台安装。
但裸金属服务器不一样,它通常有固定的硬件配置、多样的机型、复杂的存储方案,底层系统必须干净地从安装介质构建。这种场景下PXE仍然是最稳、最通用的基础手段。我的建议是:如果团队刚到三台以上需要频繁重装或批量交付的裸金属,布置一套PXE环境的时间成本绝对值回票价。
2. 核心组件与协议拆解:从DHCP到PXE菜单
2.1 DHCP:给裸机分配地址并指明引导方向
裸机开机时完全不知道服务器在哪里,整个PXE链条的起点就是DHCP探测。客户端先广播一个DHCP DISCOVER,DHCP服务器在回应OFFER时,除了给IP地址,还会捎带两个关键的引导信息:next-server(下一跳服务器地址)和boot file(要下载的引导文件名)。客户端拿到这两个信息,才知道该去谁的TFTP目录里取什么文件。
这里有个容易混淆的点。很多网卡固件在PXE阶段会发送特殊的DHCP选项请求,比如选项66和选项67。标准DHCP配置文件里可以分别设置next-server和filename来回应,而dnsmasq则用一行dhcp-boot简化处理。两种写法含义一样,只是工具语法不同。在我现在用的环境里,dnsmasq更顺手,因为它的DHCP和TFTP是同一个进程在管,少装一个服务,排错链路也更短。
还有一条生产经验:DHCP分配出去的地址范围最好单独划给装机网段,不要和业务网混在一起。PXE客户的启动行为相对“野蛮”,如果这台机器配置错误或者有奇怪固件,可能会对业务网络产生干扰。单独管理网段,无论是抓包还是后续隔离都方便很多。
2.2 TFTP:第一公里的轻量文件传输
DHCP告诉客户端前往哪台机器取文件之后,客户端就会发起TFTP请求。TFTP是个非常古老的协议,基于UDP 69端口,没有复杂的确认机制,也没有安全认证,简单到网卡固件就能实现。正因为它简单,它在整体流程里只负责传输那些“轻量级”的引导文件:pxelinux.0、grub启动器、内核vmlinuz和initrd、菜单配置文件。
很多人第一次搭PXE时会犯一个错误:试图通过TFTP把整个ISO镜像传过去。这是一个很糟糕的主意。TFTP的块大小默认很小,在局域网里传一个几百MB的文件会奇慢无比,而且中断后没有续传机制,任何一个丢包都会拖垮整个安装。正确做法是TFTP只传引导所需的小文件,真正的大体量安装源交给HTTP或NFS。
在我的习惯里,TFTP根目录尽量保持精简。一个典型目录可能只有几个文件:引导器、内核、initrd、一个pxelinux.cfg目录。这样不管是拷贝还是排查,一眼能看明白。
2.3 pxelinux.cfg:配置文件查找顺序是排错突破口
加载pxelinux.0以后,它并不会自动知道要装什么系统,而是会去TFTP服务器的pxelinux.cfg目录里查找菜单配置。这里的设计非常巧妙,PXE会根据请求者的特征按顺序查找文件名:
- 以客户端MAC地址命名的文件,格式是01-aa-bb-cc-dd-ee-ff,前面的01表示以太网类型
- 以客户端IP地址十六进制形式命名的文件,比如10.0.0.101会变成0A000065
- 最后兜底的文件default
这个查找顺序是PXE排错的关键。想对某一台特定MAC的机器定制安装菜单,只要创建对应的MAC文件就行;想让一个网段走默认流程,就只放default文件。我在维护多环境装机目录时,经常靠这一机制实现“不同MAC走到不同安装参数”,比改DHCP配置更直观。
默认菜单文件里写的是SYSLINUX的语法,主要包含label和append行。label是菜单里显示的名字,append后面接着内核参数。内核参数会一路传给下一步加载的安装器,所以inst.repo、ks这些关键信息都写在这里。
2.4 安装仓库与自动应答文件:真正开始读“安装光盘”
引导器把内核和initrd下载到内存之后,目标机就进入安装程序阶段。这时安装器会通过参数里的inst.repo去访问真正的软件仓库,也就是安装介质的解压目录。RHEL系安装器会去读repodata目录下的元数据,然后按软件包列表拉取包。
仓库协议的选择,早期环境里NFS用得很多,但现在我强烈建议用HTTP。NFS对网络稳定性要求高,安装过程中如果网络闪断,常见root挂载会超时,安装直接中断;HTTP在这种场景下表现好得多,而且nginx可以轻松撑起多台机器同时并发安装。另一个好处是,系统装完以后,这个HTTP地址还能继续当软件源用,后续装补丁也方便。
自动应答部分的接入简单直接。在内核参数行里加一句:
ks=http://10.0.0.10/ks/install.ks安装器会去拉取这个kickstart文件,并根据里面的分区、包组、脚本指令执行安装。如果答案是yes,安装过程基本不需要人工干预。这一步是整个自动化安装中“自动化”三个字的真正核心。
2.5 固件差异:Legacy BIOS、UEFI和iPXE
不同固件对PXE的支持方式不太一样。老式Legacy BIOS环境下,引导文件通常是pxelinux.0;UEFI环境则更倾向于使用grubx64.efi或者ipxe.efi。UEFI的PXE网络堆栈和Secure Boot也会影响引导流程,如果配置不对,机器可能卡在PXE-E11这类错误,或者直接跳过网络引导进了本地磁盘。
这里给一个经验判断方法:如果你看到PXE启动报错再跳到硬盘,八成是引导文件名不对或PXE服务和固件不匹配。可以先在UEFI设置里把Secure Boot关掉,再试试UEFI模式下专用的引导器。如果固件对PXE支持较差,也可以用iPXE链来完成二次引导,但这会稍微增加配置复杂度,新手阶段不必立刻上。
3. 实操搭建:从零拉起一台能用的PXE安装环境
3.1 环境清单与网络规划
开始动手前,先明确角色和IP规划。我习惯用一个独立的管理网段做装机,避免影响其他业务。下面是一个典型的规划示例:
| 角色 | 说明 |
|---|---|
| PXE安装服务器 | 同时承担DHCP、TFTP、HTTP服务,IP地址10.0.0.10 |
| 安装目标机 | 网卡接入管理网段,假设MAC为aa:bb:cc:dd:ee:11 |
| 操作系统介质 | 以RHEL系ISO为例 |
| 客户端IP池 | 10.0.0.100到10.0.0.200 |
服务器本身配两块网卡也不奇怪:一块连接稳定业务网络用于管理,另一块专用于装机网络。多网卡情况下,dnsmasq监听接口必须写对,否则客户端发的广播包根本到不了DHCP服务。
镜像文件建议提前解压或者挂载到仓库目录。如果只想快速验证,直接挂载ISO也行,但生产环境我更推荐把ISO解压到独立目录,这样后面不管是更新补丁包还是做定制化,都更灵活。
3.2 基于dnsmasq的DHCP+TFTP配置
我先把核心的dnsmasq配置贴出来,然后逐行解释:
# /etc/dnsmasq.conf interface=eth1 bind-interfaces dhcp-range=10.0.0.100,10.0.0.200,255.255.255.0,12h dhcp-option=3,10.0.0.1 dhcp-option=6,10.0.0.10 dhcp-boot=pxelinux.0,pxeserver,10.0.0.10 enable-tftp tftp-root=/var/lib/tftpbootinterface=eth1严格控制服务只跑在装机网卡上。bind-interfaces在部分多网卡机器上是必须的,但也要注意,如果地址被其他服务占用,dnsmasq会直接启动失败。dhcp-range给出客户端地址池,租期12小时足够一次完整安装。dhcp-option=3指定网关,dhcp-option=6指定DNS,这两项会让安装后的系统网络配置更干净。dhcp-boot里的三个参数分别是引导文件名、服务器名、服务器IP,我这里为了兼容性直接写了IP。最后打开内置TFTP并设置根目录。
启动之后可以检查一下进程状态和端口监听:
systemctl restart dnsmasq ss -ulnp | grep -E ":53|:67|:69"如果看到DNS和DHCP都在监听,说明服务已经就绪。这里有个容易踩的坑:dnsmasq默认同时管理DNS缓存,如果你不想让它接管DNS,记得在配置里写port=0或者单独关闭DNS功能。
3.3 准备引导文件与内核initrd
DHCP和TFTP跑起来后,接下来准备TFTP根目录里的内容。以RHEL系为例:
mkdir -p /var/lib/tftpboot/pxelinux.cfg cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/ mount -o loop rhel-9.iso /mnt/ cp /mnt/images/pxeboot/vmlinuz /var/lib/tftpboot/ cp /mnt/images/pxeboot/initrd.img /var/lib/tftpboot/Syslinux的引导器一般随发行版安装包提供,也可以从ISO的isolinux目录里提取。这里要注意文件权限,默认配置下TFTP服务以特定用户运行,如果文件权限不足会导致客户端下载失败。
接着写默认菜单/var/lib/tftpboot/pxelinux.cfg/default:
default linux label linux kernel vmlinuz append initrd=initrd.img inst.repo=http://10.0.0.10/repo/rhel9 ks=http://10.0.0.10/ks/install.ksappend行里的inst.repo不能省,否则安装器最后会在“选择安装源”这里卡住。ks指向应答文件,如果这一项没写,安装器会进入交互式提问流程,自动化就算白搭了。
如果网络里有UEFI的机器,还需要另外准备grub引导器,并在DHCP配置里根据架构返回不同文件名。这里的判断逻辑略微复杂,但做生产系统时必须要覆盖到。
3.4 Nginx发布安装源和应答文件
仓库服务和应答文件服务可以放在同一个nginx里。直接看配置:
server { listen 80; server_name 10.0.0.10; root /data/pxe; autoindex on; location /ks/ { alias /data/pxe/ks/; } }然后把ISO内容拷贝到仓库目录:
mkdir -p /data/pxe/repo/rhel9 cp -a /mnt/* /data/pxe/repo/rhel9/ mkdir -p /data/pxe/ks cp /data/ks/install.ks /data/pxe/ks/install.ks chmod -R 644 /data/pxe/ks/* chmod 755 /data/pxe /data/pxe/ks /data/pxe/repo权限设置是这里的隐形坑。目录如果没有执行权限,nginx会返回403,安装器访问仓库时报错信息又不够直观,容易让人绕进死胡同。调试时可以直接在另一台机器用curl验证:
curl -I http://10.0.0.10/repo/rhel9/ curl -I http://10.0.0.10/ks/install.ks两个请求都返回200,说明HTTP侧已经就绪。
3.5 编写一份可复用的Kickstart
Kickstart文件是自动安装的核心策略。下面给一份最小可用版本,并解释关键命令:
install url --url="http://10.0.0.10/repo/rhel9" lang en_US.UTF-8 keyboard us rootpw --iscrypted $6$xxxxxx timezone Asia/Shanghai network --bootproto=dhcp --device=link --activate zerombr clearpart --all --initlabel autopart --type=lvm bootloader --location=mbr services --enabled=NetworkManager reboot %packages @core %end %post --log=/root/install.log echo "基础装机完成" >> /root/install.log %end逐项说一下:url指定安装源;rootpw必须是加密哈希,不要明文写密码;network --device=link是关键,建议不要绑死eth0,因为不同服务器网卡命名可能不同,用link参数让安装器选择有链路的那个网卡;clearpart --all会清空磁盘所有分区,如果你对数据盘有要求,这里的写法要更谨慎;autopart会自动采用LVM分区,适合快速验证,生产环境还是建议手动写清分区逻辑。
%packages段定义了要装的软件包组,@core相当于最小系统的核心包组。生产环境一般在这基础上增加一些必备工具。%post段用于装完系统后执行脚本,比如注册监控、下发基础配置、执行初始化脚本等。这个文件在实际使用中一定会补充很多业务项,但核心骨架就是上面这些。
3.6 客户端引导与端到端验证
一切配置就绪后,把目标服务器网络启动设为第一启动项,接上装机网线,开机。如果你用的是Legacy BIOS,应该能在屏幕上看到SYSLINUX菜单;如果走UEFI,则可能是GRUB菜单。菜单出现后,选择对应的label回车,紧接着能看到内核启动日志。
想确认网络请求走得对不对,在PXE服务器上抓包最直观:
tcpdump -i eth1 port 69 or port 80 -nn正常流程应该依次出现DHCP交互、TFTP的RRQ请求、HTTP GET仓库和ks文件的记录。根据这些请求的落点,基本能判断出故障是出在引导阶段还是仓库阶段。
我在第一次完整跑通时,从按下电源键到SSH能连上大概花了十几分钟,大部分时间都在等软件包安装。这个速度对于批量装机来说是非常可接受的。
4. 常见问题与排查技巧实录
4.1 高频故障对照表
把PXE搭建中常见的问题整理成了一张表,按“现象、原因、处理”三条来列:
| 故障现象 | 常见原因 | 处理方法 |
|---|---|---|
| 开机提示PXE-E61 Media test failure | 网线没插好或PXE服务器不可达 | 先检查链路,再确认DHCP服务所在网段 |
| 客户端拿到IP但反复请求TFTP | filename不对或TFTP根目录缺文件 | 核对dhcp-boot里的文件路径,检查TFTP目录权限 |
| 内核加载后卡在安装源界面 | inst.repo地址不可达或仓库权限不对 | 用curl验证仓库路径,检查nginx日志 |
| 提示Kickstart文件未找到 | ks参数写错或HTTP返回403 | 单独访问ks文件,确认目录执行权限 |
| UEFI机型反复重启或黑屏 | Secure Boot拦截引导 | 先临时关闭Secure Boot,或换用签名引导器 |
| 安装完成后重启进不了系统 | bootloader参数与磁盘类型不匹配 | 检查安装日志,确认磁盘是sda还是nvme0n1 |
这张表覆盖面很广,基本涵盖了从零搭建时可能碰到的大多数问题。遇到问题时,先对照这张表定位大致方向,再结合日志做进一步排查。
4.2 一次真实踩坑:TFTP反复超时
某次批量扩容,DHCP分配IP正常,客户端也能拿到地址,但TFTP下载一直超时。我检查了dnsmasq日志和tftp根目录,都没发现问题。后来仔细看网络拓扑才发现,客户端和PXE服务器中间隔着三层交换机,交换机上的DHCP Snooping功能没有放行UDP 69端口,导致TFTP包被静默丢弃。
这个案例让我养成了一个习惯:遇到协议层面异常,先不要死盯服务器配置,先想想中间网络设备有没有做干扰过滤。用tcpdump在服务器上看不到TFTP请求到达,基本就能判断问题出在链路中间,而不是服务本身。
4.3 多网卡与设备命名不一致的处理
裸金属服务器通常有多个网卡,管理网口不一定是厂商默认的第一张卡。很多人会把系统和PXE引导用的网卡搞混,导致开机后PXE请求根本发不到管理网段。
处理办法有三个方向。第一步,在BIOS里确认PXE接口对应的MAC地址,把它记录下来。第二步,在dnsmasq里给这个MAC设置固定租约,例如:
dhcp-host=aa:bb:cc:dd:ee:11,10.0.0.101这样每回重启安装都能拿到同一个IP,后续排查日志会很省事。第三步,在pxelinux.cfg里用MAC命名的配置文件来固定安装参数,保证这台机器无论从哪个版本菜单进去,最终走的都是预期流程。
4.4 在目标机上快速收集安装日志
安装过程出现问题,PXE服务器上的日志只能帮你定位到文件传输阶段,更深层的安装器错误还得看目标机。RHEL系的anaconda安装器支持从终端切换控制台,按Ctrl+Alt+F1可以看到详细日志。如果无法物理接触服务器,可以在kickstart或内核参数里配置远程日志服务。
另外,安装器崩溃时往往会在屏幕最后保留一段错误堆栈,拍照记录比口头描述靠谱得多。我处理远程装机问题时,通常会让机房同事把屏幕照片发过来,再配合nginx访问日志判断卡在哪个步骤,效率比自己盲猜高得多。
5. 让整套方案更进一步的一些个人经验
5.1 用模板渲染来管理Kickstart
静态Kickstart文件一旦多了,维护就是灾难。我现在会把一个ks文件拆成公共头、软件包段、%post段,再用模板方式渲染。比如用Shell和sed做轻量替换:
# build_ks.sh cp install.ks.template /tmp/install.ks sed -i "s/__HOSTNAME__/${HOSTNAME}/g" /tmp/install.ks sed -i "s/__ROOTPW_HASH__/${ROOTPW_HASH}/g" /tmp/install.ks这样做的好处是每次变更只改一个变量,不用维护几十份只有主机名或密码不同的静态文件。再进一步,还可以把模板接入前端页面,让操作人员在网页上填一个表单,点击后自动生成ks文件。团队协作时这样做的效率提升非常明显。
5.2 利用DHCP租约与指纹区分装机路径
同一台PXE服务器可能同时服务测试环境、开发环境、生产环境。怎么让不同机器自动走进对应菜单?我的做法是用DHCP租约规则区分:为不同MAC段分配不同的固定地址,再按IP十六进制命名的pxelinux.cfg文件控制菜单。也有一些场景用vendor-class选项来区分固件架构,把BIOS和UEFI机器分流到不同引导文件。
在一个实际项目里,我甚至通过接入交换机的circuit-id区分机柜位置,再自动生成主机名和Ansible分组。机器开机自动装系统,装完直接加入对应集群,整个过程无需人工指定角色。这套机制虽然初期配置麻烦,但一旦跑通,后续扩缩容的自动化程度会非常高。
5.3 在%post阶段注入初始化动作
只把系统装完不算交付,还要把初始化做完。%post段是这里的主战场。我通常会在%post里完成几件事:设置Yum源、关闭不必要的服务、下发监控agent、执行基础加固脚本、拉取Ansible playbook并运行。
%post --interpreter=/bin/bash curl -s http://10.0.0.10/bootstrap/init.sh | bash %end使用%post时有几个细节。默认情况下%post会在chroot环境中执行,如果你需要访问安装阶段生成的物理信息,可能要加--nochroot。另外%post里的脚本要考虑到幂等性,因为同一份脚本可能被重复执行,如果每次都做“追加写配置”这类操作,容易造成重复内容。
5.4 安全边界设计
PXE链路本身没有加密,TFTP也是明文协议,所以必须把它限制在可信网络内。我的原则是:PXE服务只监听专用管理网段,绝不与业务网共用;DNS和IPv6相关的DHCP选项在做网络隔离时都要一起检查;netfilter只放行TFTP和HTTP端口给指定子网。
还有一个经常被忽略的点:ks文件里不要写明文密码或敏感令牌。如果确实需要注入凭据,建议在%post脚本里从一个受控的API动态获取,而不是直接把秘密写死在应答文件里。整体安全设计做扎实之后,这套装机系统就既能享受自动化效率,又不会成为一个容易被人利用的通道。
回看我搭这套环境的经历,最有价值的可能不是某一个配置命令,而是把整条链路串起来以后,对服务器的启动、引导和安装流程建立了完整认知。建议你也找两台机器亲手试一次,哪怕是最小的环境,也一定会踩到文档里没写的问题。把这个过程走完,后面做再复杂的自动化交付,心里都有底。