上周机房到了二十多台新机器,要在一个晚上全部装好系统并且能直接跑业务。一开始我老老实实拿U盘逐台装,装到第六台的时候人已经很崩溃了——每台都要插U盘、选启动项、分区、等安装进度、再设主机名和IP,中间稍不留神选错分区就直接重来。后来我把自己以前的PXE网络装机方案翻出来,半小时内把它重新部署好,剩下的机器基本不用管,泡杯茶的工夫就已经装完大半。这篇文章就围绕“PXE高效批量网络装机”这套方案,把原理、服务端搭建、无人值守配置和批量并发的坑一次说清楚。适合机房运维、实验室管理员、以及所有需要经常批量交付系统的朋友参考。
1. 为什么放着U盘不用,偏要搭一套网络装机环境
1.1 单台手工装机的真实时间账
我遇到过不少新人觉得U盘装系统挺快的,毕竟单个安装包也就几个G,拷贝几分钟,装起来二三十分钟。但放到批量场景里,这套流程的问题就暴露了。先不说U盘反复插拔对接口的磨损,单是“人工干预”这件事就足够致命。每台机器安装过程中几乎都要回答问题:语言、键盘布局、安装位置、分区方式、root密码、网络配置。哪怕你只设置一次,后续每台重复做一遍,时间也是成倍往上翻的。
我按真实情况算过一笔账:两台配置接近的机器,用U盘加手动分区的办法,熟练的人大概每台需要15到20分钟的有效操作时间。注意,这里说的是“有效操作时间”,还不算等待安装进度条的时间。如果中间遇到个别机型网卡固件不认U盘、或者U盘启动引导被安全策略拦下来,那时间直接翻倍。二十台机器全部搞定,一个人基本要搭进去四五个小时,而且全程没法离开人。
改用PXE之后,流程变成这样:服务器上把安装源、自动应答文件、引导文件准备好,机器插上网线开机,网卡固件自己会去网络上找引导程序,然后拉取安装内核,再通过网络访问安装源,按照预设的应答文件自动完成分区、软件包安装、系统配置。整个过程除了开机之外不需要人在旁边盯着。同样是二十台机器,你只需要把网络和启动顺序调对,剩下的就是等待。
1.2 网络装机真正解决的是“一致性”和“无人值守”
很多人以为PXE的价值只是“快”,其实这理解偏了。单台安装的分区、软件包选择、系统参数,在不同人手底下做出来很可能是两套结果。有的人多勾了图形管理工具,有的人忘了加SSH服务,还有人把swap分区大小设得千奇百怪。这种差异在后期统一维护时会变成大小不一的坑。
PXE配合Kickstart之类的自动应答文件,最大的价值是让每一台机器拿到的是完全一样的标准配置。分区表、软件包集合、内核参数、安全加固脚本全部固化在同一个模板里,交付质量是稳定的。再加上可以无人值守,夜里挂机批量装,第二天早上直接进入验收阶段,这对交付节奏来说完全是两种体验。
自然,PXE也不是银弹。它要求有一个可控的内网环境,要求DHCP服务至少能管理到预装网段,还要求服务端能稳定提供TFTP和HTTP服务。这些前置条件在正规机房、实验室甚至办公室内网里通常都满足。如果你手头就一台破笔记本,想给两台旧电脑装系统,那老老实实U盘反而省事。PXE的适用场景,是“一旦数量上来,就必须把重复劳动自动化”。
2. PXE启动链路拆解:四个服务怎么配合起来
2.1 从客户机加电到安装内核加载完成
要真正用好PXE,最好先把它的启动链彻底搞明白。我见过不少同学照着网上的教程搭出了环境,但一旦报错就完全不知道在哪一步,原因就是没理解这台机器开机之后到底发生了什么。
客户机加电后,CPU先自检,然后BIOS或者UEFI把控制权交给网卡固件。网卡固件会向局域网内发送一个DHCP发现报文,这是整个PXE流程的起点。接下来服务器上的DHCP服务会回应这个请求,不仅给客户机分配一个临时IP,还会在报文里带一个“启动文件”字段。这个字段告诉网卡固件:“你要去哪个TFTP服务器,下载哪个文件来当启动引导。”网卡拿到这个信息之后,会通过TFTP协议下载引导程序,通常是pxelinux.0,接着引导程序再去TFTP服务器上下载配置文件,以及系统安装要用到的内核vmlinuz和内存文件系统initrd.img。
到这一步,客户机的“预启动环境”就加载完成了。内核启动后,会按照配置里的参数继续寻找安装源,这些安装源通常放在HTTP或NFS服务上,内核和initrd里带的安装程序会真正开始安装操作系统。如果需要无人值守,还会额外通过参数指定一个应答文件的网络地址,安装程序自动读取并执行。
链路看起来长,但拆开其实就是:DHCP分配参数、TFTP传小文件、内核接管、HTTP传大文件和安装源。每一段各司其职。
2.2 TFTP、DHCP、HTTP各自承担什么角色
我经常把PXE链路比作点外卖:DHCP负责告诉你“餐厅地址和取餐口令”,TFTP是“把菜单和餐前小吃先给你送上”,HTTP则是“真正把主菜和配菜一个文件一个文件递过来”。
DHCP承担的是最前面的“寻址引导”。它不光要分配IP,还要把next-server和filename这两个关键参数塞进DHCP响应里。有的新手只配置了IP地址池,忘了配置引导参数,结果客户机成功拿到IP却不知道该去下载什么,最后界面会一直停留在“PXE-E53: no boot filename received”的报错,整条链路就等于没开始。
TFTP是一个极其简单的UDP文件传输协议,默认每个数据块只有512字节,虽然实际可以用blksize选项调大一点,但整体吞吐仍然远不如TCP协议。PXE标准之所以选它,是因为早期设备性能有限、协议简单可靠。但也正因为它弱,我们只让它承担“引导小文件”的角色,pxelinux.0、配置文件、vmlinuz、initrd.img这些加起来一般也就几十MB,网络差一点也没关系。
HTTP则是安装主力。现代安装程序普遍支持通过HTTP直接访问安装源,这样每个软件包都能用TCP协议传输,速度稳、抗丢包能力强,而且服务端配置也简单,一个nginx目录就能搞定。
2.3 为什么安装源不放在TFTP上而是走HTTP
这个问题是很多初学者会问的:反正TFTP也能传文件,为什么安装源不直接放TFTP上?我可以给你讲一个真实教训。第二年接触PXE的时候,我确实偷懒把所有文件都塞进TFTP目录,结果三台机器同时安装,TFTP服务直接卡死,表现为部分机器反复出现超时重试,安装进度走走停停。原因很简单,TFTP基于UDP,没有TCP那些窗口流控和拥塞控制机制,并发一上来就是灾难。
HTTP则完全没这个问题。nginx这类服务对并发连接处理得非常好,二十个安装进程同时拉取软件包也没有太大压力。再加上HTTP还支持断点续传、缓存、压缩这些实用特性,给解决批量装机时的网络瓶颈留足了空间。所以我的建议很明确:TFTP只管启动引导阶段,安装源一律走HTTP或NFS。
3. 服务端环境搭建与最小路径验证
3.1 软件选型:用dnsmasq还是独立DHCP+TFTP
搭建PXE服务端,第一步是选软件。我常用的方案有两种:一种是dnsmasq一把梭,DHCP、TFTP、DNS全用它一个人扛起来;另一种是isc-dhcp-server做DHCP、tftpd-hpa做TFTP、nginx做HTTP,三项分离开。这两种我都长期跑过,适合的场景不太一样。
dnsmasq胜在轻量、配置集中,测试环境和小规模机房用起来非常舒服。我个人的运维习惯是,网络里已经有正式DHCP服务的情况下去做PXE,其实是有风险的,因为多了一个DHCP服务如果配置不当,可能把正常办公网络的IP分配搅乱。这时候dnsmasq可以直接禁用DHCP端口、只做TFTP和HTTP转发,反而灵活。而如果整个网段就是专门的装机网络,那dnsmasq直接把DHCP承担下来更省事。
分离方案更接近生产环境的稳妥路线。DHCP、TFTP、HTTP各自独立,出问题时排查边界清晰,哪个服务挂了看哪个日志就行。但需要处理的配置文件也更多。
我推荐你从dnsmasq开始,等摸清楚整套链路原理再迁移到分离方案。下面给的示例也以dnsmasq为主,但配置思路完全可以平移到独立服务上。
3.2 配置文件示例与目录结构
以老牌的CentOS系发行版为例,完整环境一般包含这几个部分:一个dnsmasq做DHCP+TFTP、一个nginx做HTTP安装源、还准备一个存放引导文件和安装镜像的目录。先说dnsmasq,假设装机网段是192.168.88.0/24,PXE服务器地址192.168.88.10,客户机启动后会从100到200的池子里拿IP。
# /etc/dnsmasq.conf port=0 interface=eth0 bind-interfaces dhcp-range=192.168.88.100,192.168.88.200,12h dhcp-option=3,192.168.88.1 dhcp-option=6,192.168.88.1 dhcp-boot=pxelinux.0,pxeserver,192.168.88.10 enable-tftp tftp-root=/var/lib/tftpboot log-dhcpport=0表示关闭dnsmasq自带的DNS功能,避免和现有DNS冲突。bind-interfaces让dnsmasq只在eth0这个网卡上监听,防止别的网卡把请求也收进来。dhcp-boot后面三个值的含义分别是:启动文件名、启动物理服务器名、引导服务器地址。其中pxeserver这个主机名可以省略,但保留地址是最稳妥的。
tftp-root指向的/var/lib/tftpboot目录是TFTP服务的根目录,我们把启动文件全放这里。pxelinux.0文件在发行版的syslinux软件包里,找到后复制过来:
mkdir -p /var/lib/tftpboot/pxelinux.cfg cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/ cp /usr/share/syslinux/vesamenu.c32 /var/lib/tftpboot/接下来设置HTTP安装源。我一般把nginx的站点根目录放在/var/www/html,把安装镜像完整解压进/var/www/html/install。这样网络安装源地址就是http://192.168.88.10/install/。然后配置启动菜单:
# /var/lib/tftpboot/pxelinux.cfg/default default vesamenu.c32 prompt 0 timeout 300 label linux menu label ^Install System kernel vmlinuz append initrd=initrd.img inst.repo=http://192.168.88.10/install/ inst.ks=http://192.168.88.10/ks/ks.cfgvmlinuz和initrd.img同样从镜像的isolinux目录里复制到/var/lib/tftpboot下。这里有个细节:pxelinux.cfg目录不直接放文件,而是通过命名规则来区分不同客户机。PXE引导时固件会按顺序查找多个名字,最前面的是以客户机MAC地址命名的文件,找不到就找IP对应的十六进制文件名,最后找default。这个机制给后面做不同机器差异化配置留了空间。
3.3 用一个空机器验证的最小步骤
配置写完之后,最重要的就是先拿一台机器走通最小链路。我建议不要一上来就上Kickstart,先手动验证网络引导能成功进入安装界面,再加自动应答。
验证步骤大概是:先在客户端网卡固件里把启动顺序改成网卡优先,确认网卡PXE功能开启;然后开机观察,应该能看到DHCP获取地址的过程,以及从192.168.88.10下载引导程序的进度条。如果卡在这一步,回到服务端看dnsmasq日志:
journalctl -u dnsmasq -f日志里如果出现了“DHCPDISCOVER”和“DHCPACK”,说明DHCP这层是通的;如果出现了“sent bootfile pxelinux.0”,说明引导参数也下发成功。接着TFTP阶段有没有问题,直接看客户端界面有没有报TFTP open timeout。这些信息虽然简陋,但足够定位是大方向还是细节问题。
顺带提一句防火墙。通常装完系统服务之后,最容易忘记放行的就是69端口和后续的HTTP端口。有的客户机卡在TFTP下载那里,反复报错,最后发现是服务端防火墙把UDP 69丢包了,而DHCP用的是67/68 UDP,不一定会被拦,现象就是这样错位。
4. Kickstart无人值守参数:自动分区、软件包与后置任务
4.1 自动应答文件的核心段解读
手动走通安装之后,就可以把整个部署流程自动化了。Kickstart文件(通常叫ks.cfg)本质上就是预先回答安装程序所有交互问题的“答卷”。我最早接触它时总觉得内容神秘,其实拆开看就是几个大段落。
先看基础信息段,包括语言、键盘、时区和root密码。root密码如果直接明文写会被人一眼看穿,可以用iscrypted参数配合加密后的哈希值。接着是安装源,需要指定一个url指向HTTP安装目录,这里的路径要和服务端的目录结构一一对上。
再往下就是存储和引导配置,这是最容易出错的地方。
一个典型的ks.cfg开头长这样:
#version=RHEL lang en_US.UTF-8 keyboard us timezone Asia/Shanghai --isUtc rootpw --iscrypted $6$jcI...加密串 url --url=http://192.168.88.10/install/ text skipx zerombr clearpart --all --initlabel autopart --type=lvm bootloader --location=mbr %packages @core @development vim-enhanced %endzerombr的意思是清空硬盘的MBR,clearpart --all是把所有分区清掉,autopart让安装程序自动规划分区,配合--type=lvm之后会默认生成LVM逻辑卷布局。这些参数写在一起,就实现了“装完机器就是一个干净标准盘”的效果。要修改分区逻辑,比如给/var单独分一块、把swap控制在某个大小,就在autopart之外用part命令手动指定。
4.2 分区与网卡命名的常见坑
ks.cfg里最值得展开讲的坑,是分区和网卡命名。先说分区。如果你面对的是混合机型,这块机器是SATA盘,那块是NVMe盘,设备名可能是/dev/sda也可能是/dev/nvme0n1。你如果在ks.cfg里硬编码“part /boot --onfirstdisk /dev/sda”,装到只有NVMe盘的机器上很可能直接失败。稳妥做法是尽量依赖clearpart和autopart这套自动逻辑,它会让安装程序自己找出合适的第一块磁盘。如果你确实需要按盘符区分,也建议结合磁盘大小或厂商信息去判断,而不是只看sda这种排列顺序。
网卡命名也有类似的坑。较早的发行版默认用eth0,后来很多改成基于固件位置的命名方式,比如eno1、ens3f0。如果在ks.cfg里硬写ifcfg-eth0,新机器上根本对不上。我的习惯是在ks.cfg里不纠结网卡名,只写网络方式为DHCP,装完系统之后再用后置脚本去统一重命名网卡和配置静态IP。这个思路虽然多一两个步骤,但兼容性高得多。
4.3 %post脚本里的批量运维动作
Kickstart另一个强大的地方是%post段,也就是系统安装完成之后自动执行的一段脚本。这个地方能干的事情非常多:安装额外软件、配置内部软件源、写入主机名规则、设置SSH免密登录、下发监控Agent,甚至把系统直接加入某个集群。
下面这段是常用的后置脚本骨架:
%post --interpreter=/bin/bash --log=/root/ks-post.log echo "post install start" >> /root/ks-post.log # 设置主机名前缀,按IP尾号生成 ip_last=$(ip route | grep 192.168.88 | awk '{print $9}' | awk -F. '{print $4}') hostnamectl set-hostname nginx-${ip_last} # 写入内网DNS配置 cat > /etc/resolv.conf <<EOF nameserver 192.168.88.1 EOF # 把装机脚本放进去统一管理 curl -fsSL http://192.168.88.10/scripts/init.sh -o /usr/local/bin/init.sh chmod +x /usr/local/bin/init.sh /usr/local/bin/init.sh %end这段脚本做到了典型的“批量机器差异化”:每台机器安装完成后,根据自己从DHCP拿到的IP自动生成主机名,这样几十台机器不需要手动逐台改名。后置脚本里还可以继续执行安全基线配置、修改sudo权限、部署日志转发等等。实际上,你可以把50%以上的系统初始化工作都塞进%post里,PXE安装结束的那一刻,机器就已经处于“可直接交付”的状态。
5. 批量装机的高效化:并发、带宽与启动风暴控制
5.1 并行装机时的服务端瓶颈在哪
多台机器同时通过PXE引导,整个系统最吃力的是哪个环节?我最初的判断是CPU或者磁盘IO,但实测下来这两个往往都不是瓶颈。因为安装程序主要从HTTP服务顺序读取软件包,服务端磁盘性能正常的机器,靠一两个千兆网卡就能撑住十几二十台客户端同时安装。
真正的瓶颈一般出现在两个地方:一个是交换机端口带宽,一个是网络里的全局广播报文。很典型的情况是,客户机同时开机后,整个网段的DHCP请求、ARP请求、TFTP请求突然暴增,交换机如果开启了广播风暴抑制或者防环策略,可能会把这些正常请求误判成攻击流量直接丢弃,结果就是一部分机器能正常引导,另一部分始终拿不到IP。另一个隐性瓶颈在TFTP的并发能力上。TFTP是靠UDP单包确认的,单客户端一次只能在一个会话里传一个块,客户端多了之后服务端的socket数会迅速膨胀,稍有不慎就会超时。
5.2 让启动阶段不成为瓶颈的几个设置
针对上面说的启动阶段拥塞,有几个非常有效的调整手段。
第一,尽量让客户端拿到IP之后分开时间段进入PXE。这个听起来像废话,但做起来很简单:要么手动分组开机,要么在DHCP配置里按MAC地址区分租约优先级。其实大多数情况下,一个简单的DHCP配置就能避免最严重的“全网同时启动”。当然,很多业务需求就是要“一键全量上线”,那更靠谱的方案是靠服务端限速或交换机端口限速,把PXE阶段的总带宽控制在交换机能力以内。
第二,调大TFTP的块大小。TFTP默认的blksize只有512字节,但PXE规范里早就允许通过选项协商调大,比如把blksize调成1468字节或更大。在dnsmasq的tftp-root选项旁边,可以单独加:
tftp-blksize=1468 tftp-max-connections=200不过要注意,部分老网卡固件不支持大的块大小协商,如果调大后反而出现传输中断,就得退回默认值。这个属于典型的“按环境实测决定”的参数,不要照抄。
第三,把大文件尽早切换到HTTP。pxelinux.0、配置文件这些必须在TFTP上跑,但vmlinuz和initrd.img也可以直接通过HTTP下载,只需在配置菜单里用“kernel http://192.168.88.10/pxe/vmlinuz”这种写法。这样可以把启动阶段的流量彻底分流到TCP协议上,TFTP只需要承担一个几百KB的引导文件,压力几乎可以忽略。
5.3 实测二十台并发的体验
我实际测试过用一个普通配置的服务器带二十台客户机同时安装:DHCP用dnsmasq,安装源走nginx,没有做任何限速,千兆交换机下,启动阶段因为大家都同时拉TFTP文件确实出现了半分钟左右的“挤车”,但随后进入HTTP安装阶段后,整体速度很快就稳定下来,大约十五分钟全部完成。服务端CPU占用不到百分之五,内存占用也低,真正吃满的是网络带宽。跑完之后我专门查看了交换机端口统计,正常的一个千兆口要供五六台机器分摊,单看每个客户机的实际传输速率肯定是降了,但总吞吐量是够用的。
从这次实测得到的结论是:对于中小规模批量装机,服务端性能压根不是你需要焦虑的点,真正要花心思的是如何控制启动风暴的峰值,以及怎么在交换机、客户机之间做好流量编排。
6. 装机现场最常见的故障与排查链路
6.1 故障一:卡在PXE启动界面反复重试
现场最容易遇到的诡异现象,是客户机启动后出现一行“PXE-E51: No DHCP or proxyDHCP offers were received”,然后卡住不动。这个报错的意思很直白,全网段没有任何设备响应DHCP请求。问题大概率出在几个地方:服务端dnsmasq是不是只监听在别的网卡上;交换机的端口VLAN和预期不符;或者防火墙把UDP 67/68给拦了。
排查时不要急着翻配置文件,先看客户机插的是哪个口、是否处于正确VLAN,再用服务端盯日志:
tail -f /var/log/messages如果日志里连DHCPDISCOVER都没出现,那问题在二层链路;如果出现了DISCOVER但服务端没有发ACK,多半是地址池满了或配置里的网段和实际不符。
还有一种更隐蔽的情况:网络里已经有另一个DHCP服务在运行,它先回应了客户机,而且这个DHCP服务没配置PXE引导参数。客户机收到一个没有filename的响应之后,要么报错,要么直接尝试从硬盘启动,看起来就像“PXE没生效”。解决手段是把客户端所在网段改成专门的装机VLAN,或者在dnsmasq里用dhcp-ignore标记把非PXE的请求忽略掉。
6.2 故障二:装到一半拉到安装源超时
另一个高频故障是引导成功、安装界面也出来了,但走到“Setting up installation source”那一步就反复报网络超时。这种问题几乎都是HTTP安装源的问题,但又和“网络通不通”不完全等价。最常见的坑是目录权限和文件路径大小写。比如服务端目录放在/var/www/html/install,但ks.cfg里url写的是/Install,Linux路径大小写敏感,一旦写错,安装程序在拉head.img时就404了。另一种情况是防火墙只放行了TFTP和DHCP,没放行80端口,结果PXE引导很顺利,一进安装程序就抓瞎。这个问题的迷惑点就在于前面几步都很正常,新手很容易怀疑是客户端网卡驱动有问题,其实服务端一条iptables规则就能解决。
我在排查这类问题时的习惯,是先在服务端看nginx访问日志,确认有没有来自客户机的HTTP请求。如果日志里有大量的404记录,那基本就是路径问题;如果日志里完全没有请求,就要回头查网络连通性和防火墙。
6.3 判断PXE链路状态的抓包思路
当问题复杂到日志解释不清时,最有效的手段就是在服务端网卡上抓包。不用抓太多,抓关键端口就够了。
tcpdump -i eth0 -n -vv -s0 'port 67 or port 68 or port 69 or port 80'这条命令能同时看到DHCP的DISCOVER/OFFER/REQUEST/ACK四个阶段、TFTP的读写请求、HTTP的GET请求。以前有一次客户机反复在TFTP阶段超时,我用tcpdump抓包发现TFTP的读写请求一直在发,但服务端回包后客户端不认,进一步观察发现是客户机要求tftp-blksize为8192,而服务端回绝了。这种细节问题靠看日志基本看不出来,但抓包里一目了然。
抓包还能帮你确认PXE是否被“别的DHCP服务截胡”。如果抓包看到offer报文的server identifier不是你的dnsmasq地址,那结论就很清晰了。设备上还可以用短一点的等待时间来做测试,比如把DHCP的超时时间调小,让客户机尽快报错,这样你就能更快地复现问题而不是等它自动跳过。
最后再分享一个我自己一直保持的习惯:在pxelinux.cfg/default里把prompt设为1、timeout设为300,但保留启动菜单的修改入口。这样每次出新批次机器,可以先拿一台进手动模式验证模板,再放心地全量无人值守。PXE这套东西,难的不是某个单独环节,而是整条链路里每个角色、每层协议、每段配置都要咬合在一起。只要把链路拆开理解透,以后遇到再诡异的报错,心里都有一条清晰的排查路线。