PXE + Kickstart 无人值守批量部署系统

第一部分:原理

1.1 先认识两台机器

整个部署过程涉及两台机器

角色 它是谁 它有什么
PXE 服务器 我们搭建的那台 CentOS 7,IP 固定为 192.168.145.21 上面跑了三个服务(DHCP、TFTP、HTTP),还存着 CentOS 的 ISO 安装镜像和一份 ks.cfg 应答文件
客户机 需要被安装系统的那台空白机器 什么都没有------没有系统、没有 IP。只有一块网卡,支持从网络启动

1.2 PXE 服务器上的三个服务分别干什么

PXE 服务器本质上是把**一张 CentOS 安装光盘(ISO)**通过网络喂给客户机。这个过程中用到了三个服务:

服务 端口 它提供给客户机的东西 类比
DHCP UDP 67 分配给客户机的 IP 地址 + 告诉客户机"引导文件在 TFTP 那里" 前台,告诉你工位在哪、去哪领资料
TFTP UDP 69 启动内核 vmlinuz + 初始化内存盘 initrd.img + PXE 引导程序 pxelinux.0 传达室,你领走第一个包裹
HTTP TCP 80 完整安装镜像(ISO 里的全部 RPM 包)+ 无人值守应答文件 ks.cfg 仓库,你后续大量物资都从这拿

DHCP 和 TFTP 只负责启动阶段 ,传的文件很小(内核几十 MB)。HTTP 负责安装阶段,传的文件很大(几 GB 的 RPM 包)。

1.3 客户机从开机到装完的完整过程

以下是客户机从一片空白到安装完成的全部对话过程。每一步都标注了"谁跟谁说话"、"说了什么":


第 1 步:客户机向 DHCP 要一个 IP

客户机开机,BIOS 选择网络启动。客户机的网卡在局域网里广播一条消息:

"有人在吗?我是 00:0c:29:16:b6:37,请给我一个 IP 地址和启动文件!"

PXE 服务器上的 DHCP 服务 收到后,回复:

"给你 IP 192.168.145.100,你要的引导文件叫 pxelinux.0,它在 TFTP 服务器 192.168.145.21 上。"

至此客户机有了自己的 IP,还知道了下一步该找谁、要什么文件。


第 2 步:客户机通过 TFTP 下载引导程序和内核

客户机拿着 DHCP 给的信息,向 PXE 服务器的 TFTP 服务发起请求,依次下载三个文件:

文件 是什么
pxelinux.0 引导程序,约 27KB。它负责在屏幕上显示"Install PXE+Kickstart"菜单,然后启动后面的内核
vmlinuz Linux 内核,约 6MB。这就是最终要装到客户机上的 CentOS 内核
initrd.img 初始化内存盘,约 40MB。内核启动时需要的一个临时小文件系统,里面装着驱动(网卡驱动、硬盘驱动等)和安装引导工具

这三个文件下载完成后,pxelinux.0 把控制权交给 vmlinuz(内核),并把 PXE 菜单里 append 行上写的所有参数一起传给内核。


第 3 步:内核启动,dracut 解析内核参数

内核 vmlinuz 在客户机内存中启动后,第一个运行的程序叫 dracut

dracut 的工作是:在这个临时的小系统里,把网卡驱动起来,连上网,然后去下载真正的安装程序。

dracut 会读取 PXE 菜单 append 行上的参数。其中有两条最关键:

复制代码
inst.repo=http://192.168.145.21/pub/
ks=http://192.168.145.21/ks/ks.cfg
  • inst.repo= → dracut 自己用。它告诉 dracut:"安装源在 HTTP 服务器 192.168.145.21/pub/ 目录下,去那里下载 LiveOS/squashfs.img。"
  • ks= → dracut 先记下来,不自己用,留给后面的 Anaconda。
    dracut 根据 inst.repo= 的指示,通过 HTTP 下载 LiveOS/squashfs.img(约 400MB)。这个文件叫 stage2 镜像,里面就是 Anaconda 安装程序本体

第 4 步:Anaconda 启动,解析 ks.cfg

stage2 下载完成后,Anaconda(CentOS 的安装程序)启动。

Anaconda 读取 dracut 留给它的 ks= 参数,通过 HTTP 向 http://192.168.145.21/ks/ks.cfg 发起请求,下载应答文件。

ks.cfg 里写了:

  • 安装源在哪(url --url=http://192.168.145.21/pub/)--- 从这里下载所有 RPM 包
  • 要清除哪块磁盘(clearpart)--- 对 nvme0n1 操作
  • 怎么分区(part /bootlogvol /...)
  • root 密码是什么(rootpw --plaintext 1
  • 装哪些软件(%packages 段落)
  • 装完要不要重启(reboot

Anaconda 逐行执行 ks.cfg 里的指令:清磁盘 → 分区 → 从 HTTP 下载 RPM 包 → 安装 → 设置 root 密码 → 重启


第 5 步:重启,安装完成

Anaconda 执行完所有步骤后,客户机自动重启。此时客户机的硬盘上已经有了一套完整的 CentOS 7 系统,root 密码是 1,root 可以直接登录。


1.4 最容易踩的坑:inst.repo 和 ks.cfg 里的 url --url 不是一回事

很多人认为:只要 ks.cfg 里写了 url --url=http://...,dracut 就知道从哪下载 stage2。这是错的。

原因很简单------看时间顺序:

复制代码
第 3 步:dracut 需要 inst.repo= 来找安装源下载 stage2
         ↑
         此时 Anaconda 还没启动!ks.cfg 文件还没被读取!
         ↑
第 4 步:Anaconda 启动后,才解析 ks.cfg 里的 url --url=

dracut 阶段 :Anaconda 不存在。内核参数是唯一的信息来源。所以必须在 PXE 菜单的 append 行写上 inst.repo=

Anaconda 阶段 :Anaconda 已经启动了,它会解析 ks.cfg,这时候 url --url= 才生效。

参数 谁读 什么时候读 用来干什么 写在哪
inst.repo= dracut 内核刚启动时(第 3 步) 下载 stage2 安装程序镜像 LiveOS/squashfs.img PXE 菜单 append
ks= Anaconda 安装程序启动后(第 4 步) 获取 ks.cfg 应答文件 PXE 菜单 append
url --url= Anaconda ks.cfg 被解析后(第 4 步内部) 获取 RPM 安装包 ks.cfg 文件内

两者都必须写,各管各的阶段,不可互相替代。


第二部分:搭建步骤

环境

项目
PXE 服务器 IP 192.168.145.21
网段 192.168.145.0/24 (VMware NAT)
操作系统 CentOS 7.9.2009
ISO 路径 /home/pxe/iso/CentOS-7-x86_64-DVD-2009.iso

步骤 1:关闭防火墙和 SELinux

这一步在干什么:CentOS 默认开启的防火墙和 SELinux 会拦截外来网络请求。PXE 服务器需要对外提供 DHCP、TFTP、HTTP 三个服务,客户机会主动连接这些端口。如果不关,客户机的 DHCP 请求、TFTP 下载、HTTP 访问都可能被拦截,导致 PXE 启动流程在第一步就失败。

影响谁:PXE 服务器自身的安全策略。

bash 复制代码
systemctl stop firewalld
systemctl disable firewalld
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

步骤 2:挂载 ISO 并配置本地 yum 源

这一步在干什么 :ISO 文件就像一个压缩包,只有"挂载"之后里面的文件才能被系统访问。挂载后 ISO 的内容出现在 /mnt/cdrom 目录下,CentOS 安装光盘里的所有 RPM 包都在这里。然后配置 yum 源,告诉 yum:"安装软件的时候从这个本地目录找,不要上网找。"因为这台 PXE 服务器可能连不上外网,如果不禁用 base/extras/updates 三个联网源,yum 会反复尝试连接外网镜像站然后超时,导致后续安装 DHCP/TFTP/HTTP 时每步都卡很久。

影响谁:PXE 服务器自己装软件(后面步骤 3 需要 yum 安装 dhcp、tftp-server、syslinux、httpd)。

注意 :yum repo 的 [development] 这个名字不能乱改------如果用 system-config-kickstart GUI 工具,它硬编码了只认这个 repo 名。

bash 复制代码
# 挂载 ISO
mkdir -p /mnt/cdrom
mount /home/pxe/iso/CentOS-7-x86_64-DVD-2009.iso /mnt/cdrom

# 配置 yum 源(注意:repo id 必须是 [development])
cat > /etc/yum.repos.d/dvd.repo << 'EOF'
[development]
name=CentOS Local DVD
baseurl=file:///mnt/cdrom
gpgcheck=0
enabled=1
EOF

# 禁用外网源(连不上外网会导致 yum 超时)
yum-config-manager --disable base extras updates

# 验证
yum clean all
yum repolist
# 应显示: development  CentOS Local DVD  4,070

步骤 3:安装所需服务(一次性安装)

这一步在干什么:从刚刚配好的本地 yum 源,一次性安装 PXE 服务器需要的所有软件包。

包名 提供的东西 对应原理中的哪个服务
dhcp DHCP 服务程序 原理第 1 步:给客户机分配 IP
tftp-server TFTP 服务程序 原理第 2 步:传 pxelinux.0 / vmlinuz / initrd.img 给客户机
xinetd 超级守护进程,管理 TFTP TFTP 不自带服务管理,由 xinetd 代为监听 UDP 69 端口
syslinux PXE 引导程序 pxelinux.0 原理第 2 步:客户机下载的第一个引导文件
httpd Apache HTTP 服务程序 原理第 3/4 步:供客户机下载 stage2 镜像和全部 RPM 包

影响谁:PXE 服务器自身------这些包装在服务器上,后面几步才配置它们。

bash 复制代码
yum install dhcp tftp-server xinetd syslinux httpd -y

步骤 4:配置 DHCP

这一步在干什么 :告诉 DHCP 服务:"当有客户机请求 IP 时,给它们分配 192.168.145.100~199 范围内的地址,同时告诉它们引导文件名是 pxelinux.0,去 192.168.145.21 的 TFTP 服务上找。"

每一行的作用:

配置行 含义
subnet 192.168.145.0 netmask 255.255.255.0 这个 DHCP 服务于 192.168.145.0/24 网段
range 192.168.145.100 192.168.145.199 从 100 到 199 里挑一个空闲 IP 分配给客户机
option routers 192.168.145.1 客户机的网关(VMware NAT 网关)
next-server 192.168.145.21 告诉客户机:TFTP 服务器的地址是 192.168.145.21
filename "/pxelinux.0" 告诉客户机:你要的引导文件叫这个名字

影响谁:客户机------客户机开机时通过 DHCP 拿到 IP 和下一步方向。没有 DHCP 客户机连 IP 都没有,整个流程无法开始。

bash 复制代码
cat > /etc/dhcp/dhcpd.conf << 'EOF'
subnet 192.168.145.0 netmask 255.255.255.0 {
    range 192.168.145.100 192.168.145.199;
    option subnet-mask 255.255.255.0;
    option routers 192.168.145.1;
    default-lease-time 21600;
    max-lease-time 43200;
    next-server 192.168.145.21;
    filename "/pxelinux.0";
}
EOF

systemctl start dhcpd
systemctl enable dhcpd

步骤 5:配置 TFTP

这一步在干什么 :TFTP(简单文件传输协议)是客户机从服务器下载第一个引导文件的通道。安装的 tftp-serverxinetd 管理,默认是关闭的。这一步把 /etc/xinetd.d/tftp 里的 disable = yes 改成 disable = no,然后启动 xinetd------xinetd 会代为监听 UDP 69 端口,有请求时自动启动 TFTP 服务。

为什么是 TFTP 而不是 HTTP? 因为客户机网卡的 PXE 固件只支持 TFTP 协议。TFTP 极其简单,没有认证、没有目录列表,专用于这种"我只下载一个文件"的场景。后续大量数据传输才走 HTTP。

影响谁 :客户机------客户机拿到 DHCP 信息后,必须通过 TFTP 下载 pxelinux.0

bash 复制代码
# 启用 TFTP(xinetd 管理)
sed -i 's/disable.*=.*yes/disable = no/' /etc/xinetd.d/tftp

systemctl start xinetd
systemctl enable xinetd

# 验证 UDP 69 端口
netstat -ntulp | grep 69

步骤 6:部署 PXE 引导文件

这一步在干什么 :把客户机 TFTP 阶段需要的文件部署到 /var/lib/tftpboot/ 目录(TFTP 服务的根目录)。共有三样东西:

文件 来源 是什么 大小
pxelinux.0 syslinux 包(/usr/share/syslinux/ PXE 引导程序,BIOS 网卡固件加载的第一个程序 ~27KB
vmlinuz ISO 里(/mnt/cdrom/isolinux/ CentOS 7 安装内核 ~6MB
initrd.img ISO 里(/mnt/cdrom/isolinux/ 初始化内存盘,含网卡/磁盘驱动和 dracut 工具 ~40MB

目录创建的位置有讲究:pxelinux.0 被加载后,它会自动在同目录下找 pxelinux.cfg/default 文件作为启动菜单。

影响谁:客户机------客户机通过 TFTP 下载的就是这个目录里的文件。没有这些文件,TFTP 端口开着也没东西可传。

bash 复制代码
# syslinux 提供 pxelinux.0
cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/

# 从 ISO 复制内核和初始化内存盘
cp /mnt/cdrom/isolinux/* /var/lib/tftpboot/

# 创建 PXE 启动菜单目录
mkdir -p /var/lib/tftpboot/pxelinux.cfg

# 验证文件
ls /var/lib/tftpboot/
# 应包含: pxelinux.0  vmlinuz  initrd.img  pxelinux.cfg/

步骤 7:配置 HTTP 安装源

这一步在干什么:客户机前几步通过 TFTP 只能拿到几十 MB 的启动文件。真正的 CentOS 系统有几千个 RPM 包,加起来几 GB,必须通过 HTTP 传输------因为 HTTP 比 TFTP 快得多,而且支持断点续传和大文件传输。

/mnt/cdrom 是步骤 2 挂载的 ISO 内容(所有 RPM 包都在里面)。用 mount --bind 把同一个内容绑定到 httpd 的网页目录 /var/www/html/pub/,这样客户机访问 http://192.168.145.21/pub/ 就等于直接访问 ISO 里的文件。

同时创建 /var/www/html/ks/ 目录,用于存放步骤 8 生成的 ks.cfg 应答文件。

为什么用 bind mount 而不是复制:ISO 有 4.4GB,复制一份浪费磁盘。bind mount 不占额外空间。

影响谁 :客户机------dracut 阶段通过 inst.repo=http://... 从这里下载 stage2 镜像,Anaconda 阶段通过 url --url=http://... 从这里下载全部 RPM 包。

bash 复制代码
mkdir -p /var/www/html/pub
mkdir -p /var/www/html/ks

# 把 ISO 内容通过 HTTP 发布(bind mount,不占额外磁盘)
mount --bind /mnt/cdrom /var/www/html/pub

systemctl start httpd
systemctl enable httpd

# 验证
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/pub/
# 应返回: 200

步骤 8:编写 Kickstart 配置脚本

这一步在干什么 :安装源(RPM 包)已经有了,但 Anaconda 还需要知道怎么装 ------装到哪块磁盘、怎么分区、root 密码是什么、装哪些软件包。这些全部写在 ks.cfg 文件里。下面的脚本 /root/gen_kscfg.sh 把所有配置项变成开头的变量,改完变量执行脚本就能自动生成 ks.cfg,避免手写。

脚本里的变量按 GUI 工具的 11 个配置页分组:

变量组 对应 GUI 页面 控制的内容
KS_LANGKS_KEYBOARD... 基本配置 语言、键盘、时区、root 密码、是否自动重启
KS_INSTALL_METHODKS_HTTP_SERVER... 安装方式 从 HTTP/FTP/NFS/CDROM 安装
KS_BOOTLOADER_* 引导器 GRUB 装在哪、内核参数
KS_DISKKS_PART_* 分区 目标磁盘名、各分区大小
KS_ETHKS_HOSTNAME... 网络 网卡名、DHCP/静态IP、主机名
KS_AUTHKS_AUTH_PASSALGO 认证 密码加密算法
KS_FIREWALLKS_SELINUX 安全 防火墙和 SELinux 开关
KS_X11 显示 装完用文本还是图形界面
KS_PACKAGES 数组 软件包 要装哪些 RPM 包和包组
KS_PRE_SCRIPT 安装前脚本 分区完成后、装包前执行的命令
KS_POST_SCRIPT 安装后脚本 装完系统后、重启前执行的命令(chroot 环境)

影响谁 :客户机------Anaconda 读取这个文件来决定安装过程中的所有操作。每次改完 gen_kscfg.sh 必须执行一次,才会更新 /var/www/html/ks/ks.cfg

bash 复制代码
#!/bin/bash
# ============================================================
# Kickstart 配置生成脚本
# 修改下面变量后执行: sh /root/gen_kscfg.sh
# ============================================================

# ---- 步骤 1: Basic Configuration (基本配置) ----
KS_LANG="en_US.UTF-8"              # 语言
KS_KEYBOARD="us"                    # 键盘
KS_TIMEZONE="Asia/Shanghai"         # 时区
KS_ROOTPW="1"                       # root 密码 (明文)
KS_REBOOT="yes"                     # 安装后自动重启

# ---- 步骤 2: Installation Method (安装方式) ----
KS_INSTALL_METHOD="http"            # http | ftp | nfs | cdrom
KS_HTTP_SERVER="192.168.145.21"
KS_HTTP_DIR="/pub/"

# ---- 步骤 3: Boot Loader (引导器) ----
KS_BOOTLOADER_LOCATION="mbr"        # mbr | partition | none
KS_BOOTLOADER_APPEND="crashkernel=auto"

# ---- 步骤 4: Partition (分区) ----
KS_DISK="nvme0n1"                   # 目标磁盘 (nvme0n1 / sda / vda)
KS_PART_BOOT=500                    # /boot (MB)
KS_PART_ROOT=10240                  # / (MB)
KS_PART_SWAP=2048                   # swap (MB)
KS_PART_HOME=2048                   # /home (MB)
KS_USE_LVM="yes"
KS_VG_NAME="centos"

# ---- 步骤 5: Network (网络) ----
KS_ETH="eth0"
KS_NET_BOOTPROTO="dhcp"
KS_HOSTNAME="localhost.localdomain"

# ---- 步骤 6: Authentication (认证) ----
KS_AUTH="enableshadow"
KS_AUTH_PASSALGO="sha512"

# ---- 步骤 7: Firewall & SELinux (安全) ----
KS_FIREWALL="disabled"              # enabled | disabled
KS_SELINUX="disabled"               # enforcing | permissive | disabled

# ---- 步骤 8: Display (显示) ----
KS_X11="--defaultdesktop=GNOME"     # 留空 = 文本模式

# ---- 步骤 9: Packages (软件包) ----
KS_PACKAGES=(
    "@^minimal"
    "@core"
    "chrony"
    "kexec-tools"
    "net-tools"
    "vim"
    "wget"
    "bash-completion"
)

# ---- 步骤 10: Pre-Install Script (安装前脚本) ----
KS_PRE_SCRIPT=""

# ---- 步骤 11: Post-Install Script (安装后脚本) ----
KS_POST_SCRIPT=""
# KS_POST_SCRIPT='
# echo "nameserver 8.8.8.8" >> /etc/resolv.conf
# useradd -m -s /bin/bash admin
# echo "admin:123456" | chpasswd
# '

# ============================================================
# 以下为生成逻辑,不需要修改
# ============================================================

OUTPUT_FILE="/var/www/html/ks/ks.cfg"
mkdir -p "$(dirname "$OUTPUT_FILE")"

# 构建条件行
[ -n "$KS_ROOTPW_ENCRYPTED" ] && ROOTPW_LINE="rootpw --iscrypted $KS_ROOTPW_ENCRYPTED" \
  || ROOTPW_LINE="rootpw --plaintext $KS_ROOTPW"
[ "$KS_REBOOT" = "yes" ] && REBOOT_LINE="reboot" || REBOOT_LINE=""
[ -n "$KS_X11" ] && X11_LINE="xconfig $KS_X11" || X11_LINE=""

# 构建安装方式
case "$KS_INSTALL_METHOD" in
  http) INSTALL_LINE="url --url=\"http://${KS_HTTP_SERVER}${KS_HTTP_DIR}\"" ;;
  ftp)  INSTALL_LINE="url --url=\"ftp://${KS_HTTP_SERVER}${KS_HTTP_DIR}\"" ;;
  nfs)  INSTALL_LINE="nfs --server=${KS_HTTP_SERVER} --dir=${KS_HTTP_DIR}" ;;
  cdrom) INSTALL_LINE="cdrom" ;;
esac

cat > "$OUTPUT_FILE" << KSEOF
#version=DEVEL
auth --${KS_AUTH} --passalgo=${KS_AUTH_PASSALGO}
${INSTALL_LINE}
firstboot --enable
ignoredisk --only-use=${KS_DISK}
keyboard --vckeymap=${KS_KEYBOARD} --xlayouts='${KS_KEYBOARD}'
lang ${KS_LANG}
network --bootproto=${KS_NET_BOOTPROTO} --device=${KS_ETH} --onboot=yes --ipv6=auto --activate
network --hostname=${KS_HOSTNAME}
${ROOTPW_LINE}
services --enabled="chronyd"
timezone ${KS_TIMEZONE} --isUtc
${REBOOT_LINE}
bootloader --append="${KS_BOOTLOADER_APPEND}" --location=${KS_BOOTLOADER_LOCATION} --boot-drive=${KS_DISK}
clearpart --all --initlabel --drives=${KS_DISK}
part /boot --fstype="xfs" --ondisk=${KS_DISK} --size=${KS_PART_BOOT}
part pv.01 --fstype="lvmpv" --ondisk=${KS_DISK} --size=1 --grow
volgroup ${KS_VG_NAME} --pesize=4096 pv.01
logvol / --fstype="xfs" --size=${KS_PART_ROOT} --name=root --vgname=${KS_VG_NAME}
logvol swap --fstype="swap" --size=${KS_PART_SWAP} --name=swap --vgname=${KS_VG_NAME}
logvol /home --fstype="xfs" --size=${KS_PART_HOME} --name=home --vgname=${KS_VG_NAME}
firewall --${KS_FIREWALL}
selinux --${KS_SELINUX}
${X11_LINE}
KSEOF

# 追加软件包(数组在 heredoc 里处理不了,单独写)
cat >> "$OUTPUT_FILE" << PKGEOF
%packages
$(for pkg in "${KS_PACKAGES[@]}"; do echo "$pkg"; done)
%end

%addon com_redhat_kdump --enable --reserve-mb=auto
%end
PKGEOF

[ -n "$KS_PRE_SCRIPT" ] && { echo "%pre"; echo "$KS_PRE_SCRIPT"; } >> "$OUTPUT_FILE"

cat >> "$OUTPUT_FILE" << ANACEOOF
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end
ANACEOOF

[ -n "$KS_POST_SCRIPT" ] && { echo "%post"; echo "$KS_POST_SCRIPT"; } >> "$OUTPUT_FILE"

echo "=========================================="
echo "  ks.cfg 生成完毕: $OUTPUT_FILE"
echo "  大小: $(wc -c < "$OUTPUT_FILE") bytes"
echo "  访问: http://${KS_HTTP_SERVER}/ks/ks.cfg"
echo "=========================================="
echo "  磁盘: ${KS_DISK} 分区: /boot=${KS_PART_BOOT}M /=${KS_PART_ROOT}M"
echo "  网络: ${KS_ETH} dhcp 主机: ${KS_HOSTNAME}"
echo "  软件包: ${KS_PACKAGES[*]}"

部署并执行:

bash 复制代码
# 将上述脚本保存为 /root/gen_kscfg.sh
chmod +x /root/gen_kscfg.sh
sh /root/gen_kscfg.sh

步骤 9:编写 PXE 启动菜单

这一步在干什么pxelinux.0 加载后会读取 pxelinux.cfg/default 来决定显示什么菜单、用哪个内核启动、传什么参数。这个文件的 append 行是整个 PXE 流程中最关键的一行------内核和 dracut 收到的所有指令都在这里。

逐行说明:

配置行 含义
default linux 默认选中 label linux 菜单项,超时后自动执行
timeout 60 菜单等待 6 秒,无操作则自动启动
kernel vmlinuz 告诉 pxelinux.0:用 TFTP 目录下的 vmlinuz 作为内核
append initrd=initrd.img ... 传给内核的参数,每个都用空格分隔

append 行上每个参数的作用(结合原理第 3/4 步):

参数 谁读 作用
initrd=initrd.img Linux 内核 指定初始化内存盘文件
inst.repo=http://192.168.145.21/pub/ dracut 去哪下载 stage2 安装程序镜像。不能省略
ks=http://192.168.145.21/ks/ks.cfg Anaconda 应答文件的位置
ksdevice=eth0 dracut 用哪块网卡下载
net.ifnames=0 biosdevname=0 内核 强制网卡命名为 eth0 而非 ens33/enp0s3

影响谁 :客户机------pxelinux.0 读取这个菜单,决定加载哪个内核、传什么参数。参数里 inst.repo=ks= 缺一不可。

bash 复制代码
cat > /var/lib/tftpboot/pxelinux.cfg/default << 'EOF'
default linux
timeout 60
display boot.msg

label linux
  menu label ^Install PXE+Kickstart_RedHat7
  kernel vmlinuz
  append initrd=initrd.img inst.repo=http://192.168.145.21/pub/ ks=http://192.168.145.21/ks/ks.cfg ksdevice=eth0 net.ifnames=0 biosdevname=0
EOF

inst.repo= 不能省略! 它是 dracut 阶段获取 stage2 镜像的唯一来源。ks.cfg 里的 url --url= 只在 Anaconda 阶段生效。


步骤 10:全面验证

这一步在干什么:搭建完成后做一次完整检查,逐项确认每个服务都在运行、每个文件都在正确的位置、HTTP 安装源和 ks.cfg 都能正常访问。三项检查分别对应客户机 PXE 流程的三个阶段:

验证项 对应原理阶段 通过标准
DHCP 服务 running 第 1 步:客户机获取 IP systemctl is-active dhcpd → active
TFTP 端口 69 监听 + boot 文件存在 第 2 步:客户机下载引导文件 pxelinux.0vmlinuzinitrd.img 都在 /var/lib/tftpboot/
HTTP pub 和 ks.cfg 返回 200 第 3/4 步:dracut 下载 stage2 + Anaconda 下载应答文件 curl 返回 HTTP 200
bash 复制代码
# 验证服务
systemctl is-active dhcpd   && echo "DHCP OK"    || echo "DHCP FAIL"
systemctl is-active httpd   && echo "HTTP OK"    || echo "HTTP FAIL"
systemctl is-active xinetd  && echo "TFTP OK"    || echo "TFTP FAIL"
netstat -ntulp | grep -q 69 && echo "Port 69 OK" || echo "Port 69 FAIL"

# 验证 HTTP 可访问
curl -s -o /dev/null -w "pub: %{http_code}\n" http://127.0.0.1/pub/
curl -s -o /dev/null -w "ks:  %{http_code}\n" http://127.0.0.1/ks/ks.cfg

# 验证 TFTP 文件
ls /var/lib/tftpboot/pxelinux.0 /var/lib/tftpboot/vmlinuz /var/lib/tftpboot/initrd.img

第三部分:生产环境部署清单

从测试环境搬到生产环境,需要修改以下三处配置。

清单 1:DHCP 配置 --- /etc/dhcp/dhcpd.conf

复制代码
subnet 192.168.145.0 netmask 255.255.255.0 {    # ★ 改成生产网段
    range 192.168.145.100 192.168.145.199;      # ★ 改 IP 池, 决定同时装多少台
    option subnet-mask 255.255.255.0;            # ★ 子网掩码
    option routers 192.168.145.1;                # ★ 生产网关 IP
    default-lease-time 21600;
    max-lease-time 43200;
    next-server 192.168.145.21;                  # ★ PXE 服务器 IP
    filename "/pxelinux.0";
}
参数 作用 怎么改
subnet ... netmask ... 声明网段 改成生产网络的网段,必须和 PXE 服务器 IP 在同一网段
range ... 可分配的 IP 范围 范围大小 = 同时 PXE 启动的最大台数。生产建议 200-500 个
option routers 客户机网关 改成生产网络的网关 IP
next-server TFTP 服务器地址 必须和 PXE 服务器 IP 一致
filename 引导文件名 固定 /pxelinux.0,不用改

清单 2:PXE 启动菜单 --- /var/lib/tftpboot/pxelinux.cfg/default

复制代码
default linux
timeout 60                                     # ★ 菜单等待秒数, 批量部署改成 1
display boot.msg

label linux
  menu label ^Install PXE+Kickstart_RedHat7
  kernel vmlinuz
  append initrd=initrd.img inst.repo=http://192.168.145.21/pub/ ks=http://192.168.145.21/ks/ks.cfg ksdevice=eth0 net.ifnames=0 biosdevname=0
  #                              ★ 把 IP 换成 PXE 服务器 IP    ★ 同上
参数 作用 怎么改
timeout 菜单等待秒数后自动启动 批量部署改 1(1秒自动进安装,不等人);调试阶段改 600(10分钟,留时间排查)
inst.repo= dracut 下载 stage2 的地址 IP 换成 PXE 服务器实际 IP ,路径 /pub/ 不变
ks= Anaconda 下载 ks.cfg 的地址 IP 同上,路径 /ks/ks.cfg 不变
ksdevice=eth0 强制用 eth0 网卡 如果有双网卡且不是 eth0,改成实际卡名,或者删掉让系统自动选择
net.ifnames=0 biosdevname=0 强制网卡命名为 eth0 生产物理机如果有多网卡建议保留此参数,保证一致性

清单 3:Kickstart 脚本 --- /root/gen_kscfg.sh

脚本顶部有 ★ 标记的全是生产需要改的参数,这里挑最重要的列出来:

参数 作用 测试环境值 生产环境应改成
KS_HTTP_SERVER PXE 服务器 IP(用于 url --url= 下载 RPM 包) 192.168.145.21 生产 PXE 服务器的实际 IP
KS_DISK 目标磁盘设备名 nvme0n1 先装一台测试机进 shell 敲 lsblk 确认(物理机多数是 sda)
KS_PART_ROOT / 根分区大小 (MB) 10240 生产至少 50G (51200),按实际需求
KS_ROOTPW root 密码(明文) 1 生产必须用强密码 ,或用 KS_ROOTPW_ENCRYPTED 填加密后的密码
KS_HOSTNAME 主机名 localhost.localdomain 建议留空或用前缀(如 node-),配合 DHCP/DNS
KS_FIREWALL 防火墙 disabled 生产改 enabled
KS_SELINUX SELinux disabled 生产改 enforcing
KS_X11 图形界面 --defaultdesktop=GNOME 服务器留空(文本模式即可)
KS_PACKAGES 要装的软件包 见默认值 openssh-clientsntp、监控 agent、公司要求的包
KS_POST_SCRIPT 装完后执行的脚本 注入 SSH 公钥、配 DNS、加监控、初始化脚本等

生产部署前自检清单

bash 复制代码
# 1. 确认 PXE 服务器 IP
ip a | grep inet

# 2. DHCP 配置的网段和 IP 池是否正确
grep -E "subnet|range|routers" /etc/dhcp/dhcpd.conf

# 3. PXE 菜单里两个 URL 的 IP 是否正确
grep "inst.repo\|ks=" /var/lib/tftpboot/pxelinux.cfg/default

# 4. gen_kscfg.sh 里的 KS_HTTP_SERVER 是否正确
grep "^KS_HTTP_SERVER" /root/gen_kscfg.sh

# 5. 三个服务都在跑
systemctl is-active dhcpd httpd xinetd

# 6. ks.cfg 可以从 HTTP 访问
curl -sf http://127.0.0.1/ks/ks.cfg > /dev/null && echo "ks OK" || echo "ks FAIL"

第四部分:问题解决

问题 1:dracut-initqueue timeout → /dev/root does not exist

现象(dracut 紧急 shell):

复制代码
dracut-initqueue[730]: Warning: dracut-initqueue timeout
dracut-initqueue[730]: Warning: Could not boot.
dracut-initqueue[730]: Warning: /dev/root does not exist

原因 :PXE 菜单缺少 inst.repo=

dracut 加载完内核和 initrd 后,需要从网络下载 LiveOS/squashfs.img(stage2 安装程序镜像)。没有 inst.repo= 它就不知道该去哪获取,反复超时后放弃。

解决 :检查 /var/lib/tftpboot/pxelinux.cfg/defaultappend 行:

复制代码
append initrd=initrd.img inst.repo=http://192.168.145.21/pub/ ks=http://...

验证方法:在 dracut 紧急 shell 中执行:

bash 复制代码
ip a          # 检查客户端是否拿到 IP
ping -c 2 192.168.145.21   # 检查到 PXE 服务器的连通性
curl -I http://192.168.145.21/pub/   # 检查 HTTP 安装源是否可达

如果三个都正常但还报这个错,就是缺 inst.repo=


问题 2:No space left on device(dracut 阶段)

现象(dracut 紧急 shell):

复制代码
OSError: [Errno 28] No space left on device
/lib/anaconda-lib.sh: line 217: /tmp/ks.info: No such file or directory

原因:客户机内存不足

CentOS 7.3+ 的 Kickstart 要求客户机至少 2GB 内存 。dracut 的 /tmp 运行在 tmpfs(内存)上,内存不够时解析 ks.cfg 就会写爆。

解决 :VMware 中将客户机内存调到 ≥ 2048 MB


问题 3:Disk "xxx" given in ignoredisk command does not exist

现象(Anaconda 报错界面):

复制代码
The following problem occurred on line 12 of the kickstart file:
Disk "sda" given in ignoredisk command does not exist.

原因 :客户机磁盘设备名不是 sda

不同虚拟化平台的磁盘命名:

平台/接口 磁盘名
VMware SATA/SCSI sda
NVMe nvme0n1
VirtIO (KVM) vda
Xen xvda

解决 :在 Anaconda Shell(Alt+F2)中执行 lsblk 确认实际磁盘名,然后修改 /root/gen_kscfg.sh 中的 KS_DISK 变量:

bash 复制代码
# 方式一:改脚本变量后重新生成(推荐)
KS_DISK="nvme0n1"    # 改为实际磁盘名
sh /root/gen_kscfg.sh

# 方式二:直接全局替换(临时)
sed -i 's/sda/nvme0n1/g' /var/www/html/ks/ks.cfg

问题 4:yum repolist 显示 0(Package Selection 空白)

现象system-config-kickstart 的 Package Selection 页面空白,或 yum repolist 显示 0

原因:yum 缓存被连不上外网的基础源污染

解决

bash 复制代码
yum clean all
yum --disablerepo=base,extras,updates repolist
# 应显示: development  CentOS Local DVD  4,070

说明system-config-kickstart 的 Package Selection 在 CentOS 7.9 上有 comps.xml 解析 bug,即使 yum 正常也可能不显示。不建议依赖 GUI ,直接编辑 /root/gen_kscfg.sh 中的 KS_PACKAGES 数组更可靠。


问题 5:重启后 ISO 挂载丢失

现象curl http://192.168.145.21/pub/ 返回空或 404

原因mount 命令不会持久化,重启后失效

解决

bash 复制代码
mount /home/pxe/iso/CentOS-7-x86_64-DVD-2009.iso /mnt/cdrom
mount --bind /mnt/cdrom /var/www/html/pub
systemctl restart httpd

如需永久生效,写入 /etc/fstab

复制代码
/home/pxe/iso/CentOS-7-x86_64-DVD-2009.iso  /mnt/cdrom  iso9660  loop,ro  0 0
/mnt/cdrom  /var/www/html/pub  none  bind  0 0

问题 6:客户机 IP 不在 DHCP 池范围内

现象:客户机 PXE 启动时 DHCP 拿不到 IP

原因:客户机网段和 DHCP 配置的 subnet 不一致

检查

bash 复制代码
# 在 PXE 服务器上查看 DHCP 日志
journalctl -u dhcpd -f

# 确认 DHCP 配置中的 subnet 和你 VMware NAT 网段一致
grep subnet /etc/dhcp/dhcpd.conf
# 应输出: subnet 192.168.145.0 netmask 255.255.255.0 {

客户端和 PXE 服务器必须在同一 VMware NAT 网络中。


附:关键文件速查

文件 作用
/etc/dhcp/dhcpd.conf DHCP 配置:IP池 + TFTP地址 + 引导文件名
/etc/xinetd.d/tftp TFTP 启用开关 (disable = no)
/var/lib/tftpboot/pxelinux.0 PXE 引导程序
/var/lib/tftpboot/vmlinuz 安装内核
/var/lib/tftpboot/initrd.img 初始化内存盘
/var/lib/tftpboot/pxelinux.cfg/default PXE 启动菜单(含 inst.repo=
/var/www/html/pub/ HTTP 安装源(ISO bind mount)
/var/www/html/ks/ks.cfg Kickstart 应答文件(由脚本生成)
/root/gen_kscfg.sh ks.cfg 生成脚本(手动修改的唯一入口)