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 /boot、logvol /...)

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 服务器自身的安全策略。

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 名。

挂载 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 服务器自身------这些包装在服务器上,后面几步才配置它们。

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 都没有,整个流程无法开始。

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-server 由 xinetd 管理,默认是关闭的。这一步把 /etc/xinetd.d/tftp 里的 disable = yes 改成 disable = no,然后启动 xinetd------xinetd 会代为监听 UDP 69 端口,有请求时自动启动 TFTP 服务。

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

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

启用 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 端口开着也没东西可传。

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 包。

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_LANG、KS_KEYBOARD... 基本配置 语言、键盘、时区、root 密码、是否自动重启

KS_INSTALL_METHOD、KS_HTTP_SERVER... 安装方式 从 HTTP/FTP/NFS/CDROM 安装

KS_BOOTLOADER_* 引导器 GRUB 装在哪、内核参数

KS_DISK、KS_PART_* 分区 目标磁盘名、各分区大小

KS_ETH、KS_HOSTNAME... 网络 网卡名、DHCP/静态IP、主机名

KS_AUTH、KS_AUTH_PASSALGO 认证 密码加密算法

KS_FIREWALL、KS_SELINUX 安全 防火墙和 SELinux 开关

KS_X11 显示 装完用文本还是图形界面

KS_PACKAGES 数组 软件包 要装哪些 RPM 包和包组

KS_PRE_SCRIPT 安装前脚本 分区完成后、装包前执行的命令

KS_POST_SCRIPT 安装后脚本 装完系统后、重启前执行的命令(chroot 环境)

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

#!/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"(dirname "(dirname"OUTPUT_FILE")"

构建条件行

-n "$KS_ROOTPW_ENCRYPTED" && ROOTPW_LINE="rootpw --iscrypted $KS_ROOTPW_ENCRYPTED"

|| ROOTPW_LINE="rootpw --plaintext KSROOTPW""KS_ROOTPW" \[ "KSROOTPW"\["KS_REBOOT" = "yes" && REBOOT_LINE="reboot" || REBOOT_LINE=""

-n "$KS_X11" && X11_LINE="xconfig $KS_X11" || X11_LINE=""

构建安装方式

case "KSINSTALLMETHOD"inhttp)INSTALLLINE="url−−url=h¨ttp://KS_INSTALL_METHOD" in http) INSTALL_LINE="url --url=\"http://KSINSTALLMETHOD"inhttp)INSTALLLINE="url−−url=h¨ttp://{KS_HTTP_SERVER}KSHTTPDIR"¨;;ftp)INSTALLLINE="url−−url=f¨tp://{KS_HTTP_DIR}\"" ;; ftp) INSTALL_LINE="url --url=\"ftp://KSHTTPDIR"¨;;ftp)INSTALLLINE="url−−url=f¨tp://{KS_HTTP_SERVER}KSHTTPDIR"¨;;nfs)INSTALLLINE="nfs−−server={KS_HTTP_DIR}\"" ;; nfs) INSTALL_LINE="nfs --server=KSHTTPDIR"¨;;nfs)INSTALLLINE="nfs−−server={KS_HTTP_SERVER} --dir=${KS_HTTP_DIR}" ;;

cdrom) INSTALL_LINE="cdrom" ;;

esac

cat > "KaTeX parse error: Expected 'EOF', got '#' at position 23: ...FILE" << KSEOF #̲version=DEVEL a...{KS_AUTH} --passalgo=${KS_AUTH_PASSALGO}

INSTALLLINEfirstboot−−enableignoredisk−−only−use={INSTALL_LINE} firstboot --enable ignoredisk --only-use=INSTALLLINEfirstboot−−enableignoredisk−−only−use={KS_DISK}

keyboard --vckeymap=KSKEYBOARD−−xlayouts=′{KS_KEYBOARD} --xlayouts='KSKEYBOARD−−xlayouts=′{KS_KEYBOARD}'

lang KSLANGnetwork−−bootproto={KS_LANG} network --bootproto=KSLANGnetwork−−bootproto={KS_NET_BOOTPROTO} --device=KSETH−−onboot=yes−−ipv6=auto−−activatenetwork−−hostname={KS_ETH} --onboot=yes --ipv6=auto --activate network --hostname=KSETH−−onboot=yes−−ipv6=auto−−activatenetwork−−hostname={KS_HOSTNAME}

${ROOTPW_LINE}

services --enabled="chronyd"

timezone ${KS_TIMEZONE} --isUtc

REBOOTLINEbootloader−−append="{REBOOT_LINE} bootloader --append="REBOOTLINEbootloader−−append="{KS_BOOTLOADER_APPEND}" --location=KSBOOTLOADERLOCATION−−boot−drive={KS_BOOTLOADER_LOCATION} --boot-drive=KSBOOTLOADERLOCATION−−boot−drive={KS_DISK}

clearpart --all --initlabel --drives=KSDISKpart/boot−−fstype="xfs"−−ondisk={KS_DISK} part /boot --fstype="xfs" --ondisk=KSDISKpart/boot−−fstype="xfs"−−ondisk={KS_DISK} --size=KSPARTBOOTpartpv.01−−fstype="lvmpv"−−ondisk={KS_PART_BOOT} part pv.01 --fstype="lvmpv" --ondisk=KSPARTBOOTpartpv.01−−fstype="lvmpv"−−ondisk={KS_DISK} --size=1 --grow

volgroup KSVGNAME−−pesize=4096pv.01logvol/−−fstype="xfs"−−size={KS_VG_NAME} --pesize=4096 pv.01 logvol / --fstype="xfs" --size=KSVGNAME−−pesize=4096pv.01logvol/−−fstype="xfs"−−size={KS_PART_ROOT} --name=root --vgname=KSVGNAMElogvolswap−−fstype="swap"−−size={KS_VG_NAME} logvol swap --fstype="swap" --size=KSVGNAMElogvolswap−−fstype="swap"−−size={KS_PART_SWAP} --name=swap --vgname=KSVGNAMElogvol/home−−fstype="xfs"−−size={KS_VG_NAME} logvol /home --fstype="xfs" --size=KSVGNAMElogvol/home−−fstype="xfs"−−size={KS_PART_HOME} --name=home --vgname=KSVGNAMEfirewall−−{KS_VG_NAME} firewall --KSVGNAMEfirewall−−{KS_FIREWALL}

selinux --${KS_SELINUX}

${X11_LINE}

KSEOF

追加软件包(数组在 heredoc 里处理不了,单独写)

cat >> "$OUTPUT_FILE" << PKGEOF

%packages

(forpkgin"(for pkg in "(forpkgin"{KS_PACKAGES@}"; do echo "$pkg"; done)

%end

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

%end

PKGEOF

-n "KaTeX parse error: Expected 'EOF', got '\&' at position 18: ..._PRE_SCRIPT" &̲& { echo "%pre"...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 "KaTeX parse error: Expected 'EOF', got '\&' at position 19: ...POST_SCRIPT" &̲& { echo "%post...KS_POST_SCRIPT"; } >> "$OUTPUT_FILE"

echo ""
echo " ks.cfg 生成完毕: OUTPUT_FILE" echo " 大小: (wc−c\<"(wc -c \< "(wc−c\<"OUTPUT_FILE") bytes" echo " 访问: http://{KS_HTTP_SERVER}/ks/ks.cfg"
echo ""

echo " 磁盘: KSDISK分区:/boot={KS_DISK} 分区: /boot=KSDISK分区:/boot={KS_PART_BOOT}M /=${KS_PART_ROOT}M"

echo " 网络: {KS_ETH} dhcp 主机: {KS_HOSTNAME}"

echo " 软件包: ${KS_PACKAGES\*}"

部署并执行:

将上述脚本保存为 /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= 缺一不可。

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.0、vmlinuz、initrd.img 都在 /var/lib/tftpboot/

HTTP pub 和 ks.cfg 返回 200 第 3/4 步:dracut 下载 stage2 + Anaconda 下载应答文件 curl 返回 HTTP 200

验证服务

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-clients、ntp、监控 agent、公司要求的包

KS_POST_SCRIPT 装完后执行的脚本 空 注入 SSH 公钥、配 DNS、加监控、初始化脚本等

生产部署前自检清单

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-initqueue730: Warning: dracut-initqueue timeout

dracut-initqueue730: Warning: Could not boot.

dracut-initqueue730: Warning: /dev/root does not exist

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

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

解决:检查 /var/lib/tftpboot/pxelinux.cfg/default 的 append 行:

append initrd=initrd.img inst.repo=http://192.168.145.21/pub/ ks=http://...

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

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 变量:

方式一:改脚本变量后重新生成(推荐)

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 缓存被连不上外网的基础源污染

解决:

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 命令不会持久化,重启后失效

解决:

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 不一致

检查:

在 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 {

相关推荐
清秋2 小时前
全网最全 ECMAScript 攻略( 更新至 ES2026)
前端·javascript·编程语言
Imagine Miracle2 小时前
【WSL】让WSL2后台持久运行不自动关闭的解决方案
linux·windows·wsl
兔C2 小时前
Linux 命令行入门学习资料 day_2
linux·运维·服务器
Hilaku2 小时前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
eggcode2 小时前
Linux命令基础与操作技巧
linux
二宝哥3 小时前
VMware Workstation 实战:CentOS 7.9 安装、桥接网络配置与克隆管理详解
linux·centos·vmware
AutumnWind04204 小时前
【4种打开Ubuntu(WSL)的方法】
linux·ubuntu
不简说5 小时前
拖拽DIY表格插件来了!sv-print 七月更新:这次真的能自己拼表格了
前端·javascript·前端框架
辞旧 lekkk5 小时前
【Qt系统相关】鼠标事件
linux·开发语言·qt·学习·计算机外设·萌新