1. 为什么还要折腾 PXE:从一次机房装机的真实困境说起
如果你管理过超过十台服务器的机房,大概率经历过这样的场景:一台一台插 U 盘、进 BIOS 改启动顺序、手动分区、选软件包、设密码,装完一台再换下一台。十台机器能耗掉一整天,而且每台机器的配置还可能因为手抖而不一致。更麻烦的是,有些机器根本没有光驱,U 盘插上去还挑盘,BIOS 里改来改去容易改错别的参数。
PXE(Preboot Execution Environment)就是冲着这个痛点来的。它让机器在网卡层面就能从网络引导,配合 TFTP 传内核、HTTP 或 FTP 传安装源,再叠加 Kickstart 自动应答文件,最终实现"开机即装、装完即用"的无人值守批量部署。整套方案的核心价值在于:把重复劳动变成一次性配置,把人工操作变成自动化流程。
这篇文章面向的是有一定 Linux 基础、需要批量部署物理机的运维人员或实验室管理员。我会从 PXE 的工作链路讲起,把 DHCP、TFTP、HTTP、Kickstart 四个环节的配置逻辑拆开揉碎,再给出可直接复现的配置步骤和我自己踩过的坑。整套方案在局域网内运行,不涉及任何外部网络依赖,适合内网机房、实验室、教学环境等场景。
需要提前说明的是,PXE 本身只是一个引导规范,真正干活的是它背后的一整套服务组合。很多人第一次配 PXE 失败,不是因为 PXE 难,而是因为没搞清楚"谁在什么时候把什么文件交给谁"。所以下面我先讲清楚这条链路,再动手配。
2. PXE 引导链路拆解:数据包到底在机器之间怎么跑
2.1 从网卡通电到内核启动的完整时序
一台裸机按下电源后,网卡固件(PXE Client)会先发一个 DHCP Discover 广播包,包里带着自己的 MAC 地址和一个特殊标识,告诉网络"我需要网络引导"。DHCP 服务器收到后,除了分配 IP,还要通过next-server字段告诉客户端 TFTP 服务器的地址,通过filename字段告诉客户端去下载哪个引导文件。
客户端拿到这些信息后,通过 TFTP 去下载filename指定的文件。传统 BIOS 环境下这个文件通常是pxelinux.0,UEFI 环境下则是grubx64.efi或shimx64.efi。这个引导文件本身很小,它的作用是加载一个配置文件(如pxelinux.cfg/default),配置文件里写着内核路径、initrd 路径和启动参数。
接着客户端再通过 TFTP 下载内核(vmlinuz)和初始内存盘(initrd.img),把内核加载起来。内核启动后,安装程序(Anaconda)会去inst.repo参数指定的地址拉取完整的安装源,这个地址通常走 HTTP 或 FTP,因为安装源动辄几个 GB,用 TFTP 传会慢到怀疑人生。
最后,安装程序读取 Kickstart 文件(ks.cfg),按照里面定义的分区方案、软件包列表、网络配置、root 密码等自动完成安装,装完自动重启,一台机器就交付了。
2.2 为什么 TFTP 只传引导文件,安装源必须换协议
这是新手最容易困惑的点。TFTP 基于 UDP,协议极简,没有认证、没有目录列表、块大小默认只有 512 字节,传输大文件时效率极低。但它胜在实现简单,网卡固件里就能内置一个精简的 TFTP 客户端,不需要完整操作系统支持。所以 TFTP 只适合传几百 KB 到几 MB 的引导文件。
安装源动辄 4-10 GB,用 TFTP 传会慢到无法接受,而且 TFTP 对丢包的处理很粗糙。HTTP 或 FTP 基于 TCP,有重传机制、支持大文件、还能配合 Web 服务器做并发,所以安装源走 HTTP/FTP 是标准做法。理解这一点,你就明白为什么 PXE 服务器上通常要同时跑 DHCP、TFTP、HTTP 三个服务。
2.3 BIOS 与 UEFI 两条引导路径的差异
现在机房里的机器新旧混杂,BIOS 和 UEFI 两种固件都可能遇到,它们的引导文件完全不同:
| 固件类型 | 引导文件 | 配置文件路径 | 说明 |
|---|---|---|---|
| Legacy BIOS | pxelinux.0 | pxelinux.cfg/default | 由 syslinux 提供 |
| UEFI | grubx64.efi / shimx64.efi | grub.cfg | 由 grub2 提供 |
如果只配了 BIOS 的引导文件,UEFI 机器会卡在 PXE 阶段反复重试;反之亦然。稳妥的做法是两套都配好,让 DHCP 根据客户端架构(Client System Architecture)返回不同的filename。这个细节后面在 DHCP 配置里会具体展开。
3. 服务端环境搭建:DHCP、TFTP、HTTP 三件套的配置逻辑
3.1 基础环境与软件包准备
我习惯用一台配置中等的机器做 PXE 服务器,2 核 4G、100G 磁盘起步,因为要存放安装源。系统用主流的服务器发行版即可。先把三个核心服务装上:
# 以常见的 RPM 系发行版为例 dnf install -y dhcp-server tftp-server httpd syslinux grub2-efi-x64 shim-x64这里解释一下每个包的作用。dhcp-server负责分配 IP 和引导信息;tftp-server提供引导文件下载;httpd提供安装源;syslinux提供 BIOS 环境下的pxelinux.0及其模块;grub2-efi-x64和shim-x64提供 UEFI 环境下的引导文件。少装任何一个,对应环节就会断。
防火墙和 SELinux 是绕不开的坎。测试阶段我建议先把防火墙放行相关端口,SELinux 设为 permissive 观察,确认链路通了再收紧策略。生产环境则要精确放行 DHCP 的 67/68 端口、TFTP 的 69 端口、HTTP 的 80 端口。
3.2 DHCP 配置:next-server 与 filename 的配合
DHCP 配置是整个 PXE 的"指路牌",写错了后面全白搭。核心配置片段如下:
# /etc/dhcp/dhcpd.conf subnet 192.168.100.0 netmask 255.255.255.0 { range 192.168.100.100 192.168.100.200; option routers 192.168.100.1; option domain-name-servers 192.168.100.1; next-server 192.168.100.10; # TFTP 服务器地址 filename "pxelinux.0"; # 默认引导文件 } # 针对 UEFI 客户端返回不同的引导文件 class "pxeclients" { match if substring (option vendor-class-identifier, 0, 9) = "PXEClient"; if option arch = 00:07 { filename "grubx64.efi"; # UEFI x64 } elsif option arch = 00:00 { filename "pxelinux.0"; # BIOS } }next-server告诉客户端 TFTP 服务器在哪,filename告诉它下载哪个文件。option arch是 DHCP 的一个选项,客户端会在请求里带上自己的架构类型,00:07代表 UEFI x64,00:00代表 Legacy BIOS。用这个判断就能让同一台 DHCP 服务器同时服务两种固件的机器。
注意:
next-server必须写 TFTP 服务器的 IP,不能写主机名,因为此时客户端还没有 DNS 解析能力。这个坑我见过不止一个人踩。
3.3 TFTP 根目录的目录结构规划
TFTP 的根目录通常在/var/lib/tftpboot,所有引导相关文件都放在这里。我建议按下面的结构组织,清晰且不易乱:
/var/lib/tftpboot/ ├── pxelinux.0 # BIOS 引导文件 ├── ldlinux.c32 # syslinux 模块 ├── menu.c32 # 菜单模块 ├── grubx64.efi # UEFI 引导文件 ├── shimx64.efi # UEFI 安全启动用 ├── pxelinux.cfg/ │ └── default # BIOS 引导菜单配置 ├── grub.cfg # UEFI 引导菜单配置 └── images/ ├── vmlinuz # 内核 └── initrd.img # 初始内存盘把内核和 initrd 统一放在images目录下,引导菜单里引用相对路径即可。这样以后换发行版或升级内核,只改images目录里的文件,菜单配置基本不用动。
3.4 引导菜单配置:让装机的人能选
BIOS 环境下编辑pxelinux.cfg/default:
default menu.c32 prompt 0 timeout 60 label auto-install menu label ^Auto Install (Kickstart) kernel images/vmlinuz append initrd=images/initrd.img inst.repo=http://192.168.100.10/iso ks=http://192.168.100.10/ks/ks.cfg label manual-install menu label ^Manual Install kernel images/vmlinuz append initrd=images/initrd.img inst.repo=http://192.168.100.10/isoinst.repo指向 HTTP 上的安装源,ks指向 Kickstart 应答文件。timeout 60表示 60 秒无操作自动选默认项,配合prompt 0就能实现完全无人值守——机器开机后什么都不用按,自动进入自动安装。
UEFI 环境下编辑grub.cfg,逻辑类似,只是语法换成 grub 的写法:
set timeout=60 set default=0 menuentry 'Auto Install (Kickstart)' { linuxefi images/vmlinuz inst.repo=http://192.168.100.10/iso ks=http://192.168.100.10/ks/ks.cfg initrdefi images/initrd.img }3.5 HTTP 服务:安装源与 Kickstart 文件的托管
把发行版 ISO 挂载后,内容复制到 HTTP 根目录下的某个路径,比如/var/www/html/iso。Kickstart 文件放在/var/www/html/ks/ks.cfg。这样inst.repo和ks两个地址都能通过 HTTP 访问到。
mount -o loop /path/to/distro.iso /mnt cp -r /mnt/* /var/www/html/iso/ chmod -R 755 /var/www/html/iso复制完记得检查repodata目录是否存在,这是安装程序识别安装源的关键。有些精简版 ISO 不带完整 repodata,需要自己用createrepo生成,否则安装程序会报"无法读取安装源"。
4. Kickstart 应答文件:无人值守的真正核心
4.1 ks.cfg 的生成方式与结构
Kickstart 文件决定了机器装成什么样。最省事的生成方式是先用图形界面装一台样板机,安装程序会自动把这次安装的选择记录到/root/anaconda-ks.cfg,把它拷出来改改就能用。但更可控的方式是手写,因为自动生成的往往带一堆你不需要的东西。
一个精简的 ks.cfg 结构大致分几块:命令段(系统配置)、软件包段(装什么)、脚本段(装完做什么)。下面给一个可直接改用的模板:
#version=DEVEL # 系统语言与时区 lang en_US.UTF-8 keyboard us timezone Asia/Shanghai --utc # 网络配置,用 DHCP 或静态都行 network --bootproto=dhcp --device=link --activate # 静态写法示例: # network --bootproto=static --ip=192.168.100.50 --netmask=255.255.255.0 --gateway=192.168.100.1 --nameserver=192.168.100.1 # root 密码,这里用加密串 rootpw --iscrypted $6$xxxxxxxxxxxxxxxx$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 分区方案 zerombr clearpart --all --initlabel part /boot --fstype=xfs --size=1024 part / --fstype=xfs --size=20480 part swap --size=4096 part /data --fstype=xfs --size=1 --grow # 关闭防火墙和 SELinux(按需) firewall --disabled selinux --disabled # 安装完成后重启 reboot %packages @^minimal-environment vim net-tools %end %post echo "Provisioned at $(date)" >> /etc/motd %end4.2 分区策略:为什么 clearpart 和 zerombr 必须写
zerombr的作用是清除磁盘上的无效分区表,clearpart --all --initlabel则是清空所有分区并重新初始化磁盘标签。这两条在批量装机里几乎是必写的,因为待装的机器可能残留着旧系统的分区表,不清理干净安装程序会报错或装到一半失败。
分区大小要根据实际用途定。上面模板里/给 20G,/data用--grow吃掉剩余空间,这是比较通用的做法。如果是数据库机器,/data可能要单独规划;如果是纯计算节点,/给大一点也无妨。关键是--grow只能有一个分区用,否则安装程序不知道该把剩余空间给谁。
4.3 root 密码的加密处理
ks.cfg 里明文写密码是大忌,因为文件放在 HTTP 上谁都能访问。正确做法是用openssl passwd -6生成 SHA-512 加密串:
openssl passwd -6 'YourPassword'把输出的加密串填到rootpw --iscrypted后面。这样即使 ks.cfg 被看到,也拿不到明文密码。更进一步的做法是安装后通过%post脚本从内部配置管理系统拉取密码或注入 SSH 公钥,彻底避免密码出现在应答文件里。
4.4 %post 脚本:装完机器后自动做的那些事
%post段是 Kickstart 最灵活的部分,它在系统装完、首次重启前执行,可以用来做各种初始化。常见的用法包括:注册到监控系统、配置内部软件源、注入 SSH 公钥、设置主机名、拉取配置管理 Agent。
%post --log=/root/ks-post.log # 注入 SSH 公钥 mkdir -p /root/.ssh cat > /root/.ssh/authorized_keys <<'EOF' ssh-rsa AAAA... your-key-here EOF chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys # 配置内部软件源 cat > /etc/yum.repos.d/internal.repo <<'EOF' [internal] name=Internal Repo baseurl=http://192.168.100.10/repo enabled=1 gpgcheck=0 EOF %end注意:
%post默认在 chroot 环境下执行,如果要访问网络或操作宿主机层面的事情,需要加--nochroot。这个区别很多人第一次会搞混,导致脚本里网络请求失败。
5. 联调与排错:链路不通时按这个顺序查
5.1 从 DHCP 抓包开始定位断点
PXE 出问题,第一步永远是抓包。在服务器上跑:
tcpdump -i eth0 -n port 67 or port 68 or port 69如果连 DHCP Discover 都看不到,说明客户端根本没发起 PXE 请求,问题在客户端 BIOS 设置或网线。如果看到了 Discover 但服务器没回应,检查 DHCP 服务是否监听在正确的网卡上、地址池是否耗尽。如果 DHCP 回应了但客户端没去下 TFTP 文件,检查next-server和filename字段是否正确。
5.2 TFTP 下载失败的常见原因
TFTP 报错通常很隐晦,客户端只显示一个超时或"file not found"。排查时先在服务器本地自测:
tftp 192.168.100.10 tftp> get pxelinux.0如果本地都下不下来,问题在 TFTP 服务配置或文件权限。常见原因有三个:一是 TFTP 根目录权限不对,需要确保目录和文件对tftp用户可读;二是 SELinux 拦截,用ausearch -m avc查;三是防火墙没放行 69 端口。这三个原因我几乎每次配新环境都会遇到其中一个。
5.3 安装源读取失败的排查
如果内核起来了、安装程序也启动了,但卡在"读取安装源"或报repo相关错误,问题多半在 HTTP 服务或inst.repo路径。先在浏览器或curl里访问inst.repo那个地址,确认能列出目录。如果 403,是权限问题;如果 404,是路径写错;如果能访问但安装程序还报错,检查repodata目录是否完整。
5.4 Kickstart 文件解析错误的定位
Kickstart 语法错误会导致安装中断,错误信息会显示在安装界面上,同时记录在/tmp/anaconda.log和/tmp/ks-script-*.log里。常见的语法错误包括:分区大小写成负数、%packages段里包名拼错、%post段缺少%end。建议先用ksvalidator工具校验一遍:
ksvalidator /var/www/html/ks/ks.cfg这个工具能提前发现大部分语法问题,省得装到一半才发现。
6. 几个让我印象深刻的坑与对应的经验
6.1 网卡多口导致 DHCP 拿错地址
有些服务器有四个网口,PXE 引导时可能从错误的网口发起请求,拿到一个不通的网段地址。解决办法是在 BIOS 里明确指定 PXE 使用的网口,或者在 DHCP 配置里按 MAC 地址做绑定。我遇到过一台机器四个口都插了网线,结果 PXE 从管理口发起,怎么都装不上,最后在 BIOS 里把 PXE 网口锁定才解决。
6.2 UEFI 安全启动拦截未签名引导文件
开了 Secure Boot 的机器,会拒绝加载未签名的grubx64.efi。这时候要么在 BIOS 里关掉 Secure Boot,要么用签过名的shimx64.efi配合grubx64.efi。后者是更规范的做法,shim作为第一级引导,验证通过后再加载 grub。配置时filename要指向shimx64.efi,grub 的配置文件路径也要对应调整。
6.3 安装源路径里的软链接陷阱
有次我把 ISO 内容复制到 HTTP 目录,为了省事用了软链接指向挂载点。结果安装程序通过 HTTP 访问时,Web 服务器默认不跟随软链接,直接 403。后来改成实际复制文件,或者给 Web 服务器开FollowSymLinks才解决。这个坑的教训是:PXE 链路上任何一环的"小聪明"都可能变成大麻烦,老老实实复制文件最稳。
6.4 大批量并发装机时的性能瓶颈
一次给几十台机器同时装机,HTTP 服务和 TFTP 服务都可能成为瓶颈。TFTP 是单线程的,并发下载引导文件时会排队;HTTP 虽然能并发,但磁盘 IO 和网络带宽是硬限制。我的经验是:引导文件阶段用 TFTP 问题不大,因为文件小;安装源阶段如果并发超过 20 台,最好把安装源放到独立的存储或做多副本,避免所有机器挤一个磁盘。
7. 把这套方案用顺之后的几点体会
整套 PXE + Kickstart 方案配通之后,批量装机的效率提升是数量级的。以前装十台机器要一整天,现在开机、等着、装完,中间该干嘛干嘛。但我想强调的是,这套方案的价值不只是"快",更在于"一致"——每台机器装出来的分区、软件包、配置完全相同,消除了人工操作带来的差异,这对后续的批量运维太重要了。
我在实际使用中最大的体会是:Kickstart 文件要当成代码来管理。把它放进版本控制,每次改动都有记录,出问题能回滚。同时给不同用途的机器准备不同的 ks 文件,比如计算节点、存储节点、管理节点各一份,引导菜单里让装机的人选,或者干脆按 MAC 地址自动匹配。这样一套 PXE 服务器就能服务整个机房的多种机型。
最后分享一个小技巧:在%post脚本里加一行,把装机时间和机器序列号写到一个中央日志服务器。这样你随时能知道哪台机器什么时候装的、装的哪个版本,排查问题时特别有用。这个习惯我坚持了好几年,省了无数次翻记录的时间。