1. 为什么我从手动装机转向Kickstart自动化
先说个我自己的真实场景。去年在准备一套基于DoraOS朵拉云的资源池环境时,手头有十来台物理服务器要装系统。第一次我按照传统流程,一台一台插U盘、选语言、分区、设密码、等进度条,一台机器差不多四十分钟起步,装完还得手工做网络配置、改主机名、重启验证。刚开始还能忍受,装到第三台的时候我已经开始犯低级错误——把两台机器的IP配重了,还有一台的分区表没按规划走,后面改起来更痛苦。
后来同事提醒我,其实每次手工安装结束,系统都会在 /root/ 下留一个 anaconda-ks.cfg,那是安装程序根据你刚才的选项自动生成的安装应答文件。换句话说,你手工点的每一步,都已经有办法变成一份"剧本",下次直接照着剧本自动演完。这个想法让我彻底告别U盘插拔的日子,也把DoraOS朵拉云这类环境的前期准备效率拉高了一个量级。
Kickstart解决的正是这种"重复、机械、容易出错"的裸机交付问题:它把Linux安装过程中所有的交互问答,全部固化到一个文本文件里,安装程序 anaconda 在启动时读到这个文件,就不再询问用户,按文件里的指令逐条执行。云操作系统场景下,一个资源池动辄十几台、几十台节点,每台节点其实只分为管理节点和计算节点两种角色,配置高度相似,这几乎就是为Kickstart量身定做的场景。
这篇文章不会只讲理论,我会把我自己从"手动装机"到"Kickstart + PXE批量部署DoraOS朵拉云节点"的完整过程写下来,包括ks文件怎么写、PXE环境怎么搭、部署中踩了哪些坑、最后的排查思路是什么样的。适合正在做云平台交付、机房批量装机,或者刚接触Kickstart想少走弯路的人参考。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Kickstart的接管逻辑:安装程序是如何"按剧本执行"的
2.1 从anaconda到ks.cfg:安装流程的控制权转移
要理解Kickstart,先得知道Linux安装程序 anaconda 的默认行为。不管是CentOS、RHEL还是其他基于Red Hat体系的发行版,启动安装介质后,anaconda会启动一个交互界面,从语言、时区、键盘布局一路问到root密码、磁盘分区、软件包选择。Kickstart做的事,就是在内核启动参数里追加一个 inst.ks=路径,告诉anaconda"别问了,直接去这个位置读配置"。
这份配置文件的生效时机有两个关键点:
- anaconda刚启动时就会去拉取ks文件,所以网络型安装(
ks=http://、ks=nfs://)需要在系统盘真正开始装之前,网络就已经能通到配置服务器。这也是为什么PXE环境里ks地址往往和安装源(repo)放在同一个HTTP服务上,少一层依赖就少一个故障点。 - ks文件只是接管交互,不接管内核本身,内核和initrd还是通过引导介质或PXE加载。理解了这条链路,后面排障时就能把问题快速分成"引导阶段"和"anaconda执行阶段"两段来查。
我第一次看ks文件时觉得它像一份"配置清单",其实更准确地说,它是一套带执行顺序的指令集。anaconda会按固定顺序处理:先执行 %pre 里的脚本(此时系统还未格式化磁盘,环境极其有限),然后处理主配置段(安装源、语言、分区、网络等),再执行 %packages 里的软件包选择,最后进入 %post 执行装机后的脚本——这时系统已经切到新安装的根目录环境,可以干很多"善后"的活。这个顺序决定了你会把哪些逻辑放进哪个段,放错地方就会遇到"变量找不到""网络不通""目录不存在"之类的问题。
2.2 ks文件核心区块逐个拆解
一个完整的ks文件通常包含以下区块,我按实际常用程度排序:
| 区块 | 作用 | 常见命令/字段 |
|---|---|---|
| 主配置段 | 基础安装选项 | install、url、lang、keyboard、timezone、rootpw、network、bootloader、clearpart、part、selinux、firewall、services |
%packages |
软件包选择 | @^minimal、@core、包名、--exclude |
%pre |
安装前脚本 | 动态生成后续配置、检测硬件、写临时文件 |
%post |
安装后脚本 | 初始化服务、装驱动、改配置、通知上层平台 |
%addon/%include |
复用与扩展 | 引入其他配置片段 |
主配置段是"骨架", %post 是"灵魂"。以我部署DoraOS朵拉云节点的经验来说,真正体现项目差异的往往不是分区那几个命令,而是 %post 里写的节点角色注册、资源池初始化、监控Agent安装这些逻辑。ks文件的核心价值不只是"把系统装完",而是"把系统装成你需要的样子"。
2.3 一条能跑通的最简ks.cfg长什么样
先给一份最小可用的示例,跑通它,后面再谈定制:
bash复制# 最小可用ks.cfg
install
url --url="http://192.168.1.10/centos/8/" # 安装源
lang en_US.UTF-8
keyboard us
timezone Asia/Shanghai --isUtc
rootpw --iscrypted $6$xxxxxxxxxxxx # 用grub-crypt或openssl生成
# 或者直接:rootpw 你的密码(明文,仅限测试环境)
selinux --enforcing
firewall --disabled
services --enabled=sshd,NetworkManager
zerombr
clearpart --all --initlabel
part /boot --fstype=xfs --size=1024
part pv.01 --size=1 --grow # 剩余空间全给LVM
volgroup vg_sys pv.01
logvol / --fstype=xfs --vgname=vg_sys --name=root --size=20480
logvol swap --fstype=swap --vgname=vg_sys --name=swap --size=8192
logvol /var --fstype=xfs --vgname=vg_sys --name=var --size=40960
network --bootproto=dhcp --device=ens192 --activate
bootloader --location=mbr --append="crashkernel=auto"
%packages
@^minimal
@core
openssh-server
vim
%end
%post
echo "node01" > /etc/hostname
systemctl enable sshd
%end
这份文件看起来简单,但有几个细节值得说明。clearpart --all --initlabel 会无警告清空磁盘所有分区表,这台机器上如果有旧数据要提前备份,否则一条命令全没。part pv.01 --size=1 --grow 配合LVM是我在云节点场景的习惯——你不确定每台服务器磁盘多大,用"分一个至少1MB、允许扩展到剩余全部空间"的PV,再在LVM里灵活划分逻辑卷,比死板地固定 / 和 /var 要鲁棒得多。%post 里第一行设置主机名看起来多余,但在批量装机时这往往是后面所有服务能否正确注册的关键,我后面会专门讲。
3. 面向DoraOS朵拉云的ks定制:网络、存储与后续初始化
3.1 云OS节点对分区和存储的特殊要求
Kickstart通用模板很好找,但真要拿来做DoraOS朵拉云这类云操作系统的节点部署,有几个点必须要单独调。
云OS节点说白了是要把物理服务器变成资源池里的"被管理对象",上面至少要跑虚拟化或容器相关组件,数据面和管理面最好物理隔离。这就带来两个存储层面的要求:
第一,系统盘和数据盘要分开规划。DoraOS这类平台通常会把系统装在系统盘上,而把虚拟机镜像、容器存储、备份数据放到独立的数据盘或存储阵列上。我在ks里会这样处理:
bash复制# 检测是否存在第二个磁盘
DISK2=$(lsblk -d -o NAME,TYPE | awk '$2=="disk" && $1!="sda" {print $1}' | head -n 1)
if [ -n "$DISK2" ]; then
echo "part pv.data --ondisk=$DISK2 --size=1 --grow" > /tmp/disk2.cfg
echo "volgroup vg_data $DISK2" >> /tmp/disk2.cfg
echo "logvol /data --fstype=xfs --vgname=vg_data --name=data --size=1 --grow" >> /tmp/disk2.cfg
fi
这段写在 %pre 里,利用 %pre 阶段能动态生成ks片段并 %include 的特性,把"有没有第二块盘"这个变量解耦出来。%pre 阶段虽然环境受限,但 lsblk、awk 这些基础工具是可用的,检测硬件绰绰有余。
第二,swap和系统日志要有兜底。云平台节点上某个VM异常导致内存吃满是常见事,swap给少了会出现内核OOM把关键服务杀了。我习惯按"物理内存的一半、最低不低于8G"来设swap逻辑卷,并在 %post 里配置 vm.swappiness=10,让系统只在真正需要时才用swap。
3.2 多网卡与IP规划如何写进ks脚本
云环境里最烦的网络问题是多网卡命名和IP分配。现在的服务器动辄四口千兆、双口万兆,eth0 这种名字在较新的系统里变成了 ens192、enp3s0f0 这种带PCI位置的名称,同一型号的机器还好,不同型号混了就很容易写错。
我的做法是:不在ks主配置段里写死网卡名,而是在 %pre 里按MAC地址识别网卡角色,生成对应的network指令。比如管理网卡约定使用某个MAC前缀,存储网卡是另一个:
bash复制# %pre 片段:按MAC前缀识别角色
for nic in $(ls /sys/class/net/ | grep -v lo); do
mac=$(cat /sys/class/net/$nic/address)
case "$mac" in
00:16:3e:*) # 管理网卡特征
echo "network --device=$nic --bootproto=static --ip=192.168.1.$HOSTID --netmask=255.255.255.0 --gateway=192.168.1.1 --nameserver=192.168.1.2 --activate --onboot=yes" > /tmp/ks-network.cfg
;;
00:50:56:*) # 存储网卡特征
echo "network --device=$nic --bootproto=static --ip=192.168.50.$HOSTID --netmask=255.255.255.0 --activate --onboot=yes" >> /tmp/ks-network.cfg
;;
esac
done
配合 %include /tmp/ks-network.cfg,就能做到"脚本不分机型,机器自动认角色"。$HOSTID 怎么来的?我通常会在PXE引导菜单里通过内核参数传进去,下面章节会展开。
3.3 %post阶段:装机完成后的"最后一公里"
系统装完不等于节点交付完成。DoraOS朵拉云节点要真正加入资源池,%post 阶段通常要做这几件事:
- 调整内核参数:云OS依赖的系统参数,如
net.ipv4.ip_forward=1、fs.file-max调大、vm.max_map_count等,写进/etc/sysctl.d/99-dora.conf并执行sysctl --system。 - 安装驱动和依赖:新装系统默认内核不一定带全网卡驱动、RAID卡驱动、GPU驱动,
%post里可以调用厂商驱动仓库进行安装。GPU服务器尤其要注意,驱动和内核版本不匹配会在后续初始化虚拟化环境时报一堆诡异错误。 - 注册节点身份:把主机名、IP、硬件指纹等信息写到指定配置文件,或者直接调用云平台的注册接口。这一步几乎总是需要网络,所以
%post里必须在真正执行注册前确认网络已经就绪。
这里有个非常容易踩的坑:%post 在anaconda刚切换根目录后执行,网络服务可能还没起来。此时你执行 curl 调用注册接口,十有八九是失败的。我习惯在调用网络前加一个等待循环:
bash复制%post --log=/root/ks-post.log
for i in $(seq 1 30); do
if ping -c1 -W2 192.168.1.2 > /dev/null 2>&1; then
break
fi
sleep 2
done
# 网络通了再做后续操作
%end
不要嫌这个循环丑,它帮我解决过至少三次"看起来随机出现、实际上就是网络初始化时序"的诡异问题。
4. PXE+Kickstart批量部署环境搭建实录
4.1 服务端组件与目录结构
ks文件写好之后,怎么把它和安装介质结合?单台的话可以用U盘加引导参数,一次装一台也够用,但批量部署我强烈建议直接上PXE。一台服务器既当DHCP又当TFTP又当HTTP,半小时就能搭好,换来的是"插上网线、开机、走人、半小时后系统已经就绪"的体验。
我用的组件和目录结构如下:
| 组件 | 用途 | 备注 |
|---|---|---|
dnsmasq |
DHCP + TFTP | 也可以用 dhcpd + tftp-server 分开搭 |
syslinux |
提供 pxelinux.0 引导文件 |
装完 syslinux 后拷到TFTP根目录 |
httpd/nginx |
提供安装源和ks文件 | 目录用软链或直接挂载ISO |
| 安装ISO | 系统安装源 | 解压或挂载到HTTP可访问路径 |
目录大概长这样:
bash复制/tftpboot/
├── pxelinux.0
├── ldlinux.c32
├── menu.c32
├── pxelinux.cfg/
│ └── default
├── vmlinuz
└── initrd.img
/var/www/html/
├── os/
│ └── 8/ # CentOS 8安装树,ISO解压出来的
└── ks/
├── dora-mgmt.ks
└── dora-compute.ks
4.2 PXE引导链路的配置细节
PXE的整个引导链路是:客户端网卡发出DHCP请求 → DHCP服务器回应IP地址,同时告诉它TFTP服务器的地址和要下载的引导文件名 → 客户端通过TFTP下载 pxelinux.0 → pxelinux.0 读取 /pxelinux.cfg/default 配置文件(或者MAC地址对应的配置文件) → 根据配置下载 vmlinuz 和 initrd.img → 内核启动,anaconda开始工作。
dnsmasq 的配置核心就三行:
bash复制dhcp-range=192.168.1.100,192.168.1.199,12h
dhcp-boot=pxelinux.0
pxelinux.cfg/default 则是真正的"菜单":
bash复制default menu.c32
prompt 0
timeout 100
LABEL dora-compute
MENU LABEL Install DoraOS Compute Node
KERNEL vmlinuz
APPEND initrd=initrd.img inst.repo=http://192.168.1.10/os/8/ inst.ks=http://192.168.1.10/ks/dora-compute.ks hostid=103 netboot=pxe
LABEL dora-mgmt
MENU LABEL Install DoraOS Management Node
KERNEL vmlinuz
APPEND initrd=initrd.img inst.repo=http://192.168.1.10/os/8/ inst.ks=http://192.168.1.10/ks/dora-mgmt.ks hostid=101 netboot=pxe
LABEL local
MENU LABEL Boot from local disk
LOCALBOOT 0
注意我在APPEND里加了 hostid=103 这样的自定义内核参数。它的作用可以在 %pre 里通过读取 /proc/cmdline 拿到,从而动态决定这台机器装完后的IP尾号、主机名后缀。一台机器只要在BIOS里设好PXE启动优先,再在菜单里选一次角色,剩下的全部自动完成。几十台机器这么做,效率比手工至少高一个数量级。
4.3 不同硬件节点共用ks时的差异化处理
不同批次采购的服务器往往配置不同:有的磁盘是SATA SSD,有的是NVMe,有的带RAID卡,有的直通。同一个ks文件直接砸上去,很可能出现"这台装成功了,那台分区报错"的情况。
我的处理思路是:ks文件里只写"稳定不变"的部分,"会变的部分"全部丢给 %pre 动态生成。
比如NVMe磁盘和SATA磁盘在 part 命令里指定的方式不同,NVMe是 /dev/nvme0n1,SATA是 /dev/sda。提前做一轮硬件识别,动态生成对应的 clearpart 和 part 指令:
bash复制# %pre
DISK=$(lsblk -d -o NAME,TYPE,ROTA | awk '$2=="disk" && $3==0 {print $1}' | head -n1)
# 优先选SSD作为系统盘
if [ -z "$DISK" ]; then
DISK=$(lsblk -d -o NAME,TYPE | awk '$2=="disk" {print $1}' | head -n1)
fi
cat > /tmp/ks-part.cfg <<EOF
clearpart --all --drives=$DISK --initlabel
part /boot --fstype=xfs --size=1024 --ondisk=$DISK
part pv.01 --size=1 --grow --ondisk=$DISK
volgroup vg_sys pv.01
logvol / --fstype=xfs --vgname=vg_sys --name=root --size=20480
logvol swap --fstype=swap --vgname=vg_sys --name=swap --size=8192
logvol /var --fstype=xfs --vgname=vg_sys --name=var --size=40960
EOF
再配合主配置文件里的 %include /tmp/ks-part.cfg,就可以做到"一个入口文件,适配多种硬件"。这套"识别-生成-复用"的思路,是Kickstart从demo走向生产环境的关键分水岭。
5. 部署过程中的排障记录:那些ks脚本引发的连锁问题
5.1 %post阶段网络等待不够导致的初始化失败
第一次用PXE批量装DoraOS朵拉云计算节点时,我遇到了一个很诡异的现场:十台机器同时装,八台成功,两台在 %post 阶段任务执行到一半就停了,重启后系统能进,但云平台的Agent服务没有起来。手动执行Agent启动脚本,又一切正常。
问题定位过程是这样的。第一次排障我先看了 /root/ks-post.log(这是 %post --log 参数指定的日志文件),发现失败的两台机器日志全部断在"调用平台注册接口"这一步,前面识别网卡、写配置的动作都完成了。这就说明 %post 本身执行到了网络请求环节,但请求失败。
进一步看,失败机器的 /var/log/messages 里,%post 执行那个时间点,网络管理服务 NetworkManager 也确实在同时做DHCP连接的激活。两台机器之间竞争,网络管理服务完成IP获取的时机比 %post 里的首次网络请求晚了几秒钟。我原来的等待循环只ping网关三秒,运气好就过了,运气差就断开。把等待时间拉长到30秒,并把"ping网关"改成"TCP建连到管理平台端口",问题彻底消失。
这个坑让我明白:%post 里的网络操作不能假设网络一定就绪,脚本必须自带重试和超时机制。这也直接印证了我在3.3节里写那个 for 循环的必要性——不是防御式过度设计,是真实踩出来的教训。
5.2 分区脚本在不同容量磁盘上的边界处理
还有一个更隐蔽的坑出现在分区上。我们的管理节点用了两块960G SSD做RAID1,但其中一台后来换成了两块480G的盘。ks文件里我把 /var 分区设置成固定40G,这本没什么问题,问题出在 / 分区我用了"固定20G",在960G盘上剩余空间要给LVM PV,part pv.01 --size=1 --grow 会把剩余全部吃掉,所以没问题。但在480G盘上,20G的 / 加上8G swap、40G /var 占掉68G,剩下的LVM PV空间依然充足,也没问题。结果真正翻车的是另一台数据盘。
我在 %pre 里动态检测第二块磁盘并生成数据盘分区,逻辑是"找到第二块非系统盘就全部分给 vg_data"。有一台机器我漏看了,它第二块盘其实是个老旧的小容量SATA盘(500G),而真正的大容量NVMe盘因为排序在它后面,被当成了"第三块盘"没被识别到。结果数据盘LVM卷组建在了那块老旧小盘上,容量直接成了瓶颈。
这个问题的本质是**"第二块磁盘"这个判断条件太脆弱**。后来我改成按磁盘容量排序取最大的一块作为数据盘,并把判断条件写成:
bash复制DISK2=$(lsblk -d -o NAME,SIZE | sort -k2 -h | tail -n1 | awk '{print $1}')
同时加了一道保险:如果检测到数据盘的可用容量低于200G,就直接在 %pre 里报错中止安装,不把问题留给运维去猜。自动化脚本里早失败比晚失败好,一个明确的报错日志远比一个藏着问题的系统省时间。
5.3 SELinux与云服务启动冲突的定位过程
第三次比较深的排障是SELinux相关的。ks主配置段里我写的是 selinux --enforcing,安装没问题,但DoraOS朵拉云平台的核心服务起来之后,始终报权限相关错误。看audit日志发现一堆 avc: denied 记录,全是平台服务进程访问某些目录被拦。
我的第一反应是直接把 selinux 改成 --disabled 重装,这确实能通,但不是一个好习惯。云平台多节点环境里,各节点的安全配置应该尽量一致,一个节点关了SELinux另一个开了,后面审计和排障会非常痛苦。
更稳妥的做法是保留 --enforcing,在 %post 里通过 ausearch 和 audit2allow 生成针对性的SELinux策略模块。当时的操作步骤大致是:
bash复制grep "avc: denied" /var/log/audit/audit.log | audit2allow -M dora-platform
semodule -i dora-platform.pp
加载策略模块后,平台服务不再报权限错误。说实话第一次做的时候我依赖的是"关SELinux"这条捷径,但在批量交付场景里必须把它当成一个正式的安全基线去处理,而不是因为麻烦就整体关闭。
5.4 日志定位的思路
总结这几次排障,我形成了一套固定的排查顺序:
- 先看
%pre和%post对应的日志文件(通过--log参数指定),判断是脚本逻辑问题还是系统服务问题; - 再看
/root/install.log和/var/log/anaconda/下的安装日志,确认ks文件本身有没有被执行、执行到哪一步; - 最后查目标系统自己的日志(
/var/log/messages、journalctl),把"安装过程"和"安装后启动"两个阶段分开看。
这套顺序帮我把排障时间从"瞎猜一晚上"压缩到"半小时内定位"。一个额外的经验:给每台机器在 %post 最后留一个"安装完成标记文件",比如 touch /root/dora-node-deployed,后期盘点时一条命令就能确认哪些机器走通了完整流程,哪些半路出了问题。这对几十台规模的交付特别有用。
6. 从一次性脚本到可复用交付模板的进阶玩法
6.1 用模板参数区分管理节点与计算节点
跑通批量装机之后,我开始思考怎么把两套ks文件(管理节点、计算节点)收敛成一套可复用的模板。
最简单的做法是用PXE菜单里传入的自定义参数区分角色。比如在APPEND里加 role=mgmt 或 role=compute,然后在 %pre 中写成:
bash复制ROLE=$(grep -o 'role=[^ ]*' /proc/cmdline | cut -d= -f2)
case "$ROLE" in
mgmt) PART_FILE=/tmp/ks-part-mgmt.cfg ;;
compute) PART_FILE=/tmp/ks-part-compute.cfg ;;
esac
按角色选择分区方案和软件包列表,之后在 %post 里同样按角色执行"管理节点初始化"或"计算节点-加入资源池"的逻辑。这样维护成本大幅下降,以后调策略只需要改一份模板文件,而不是在多个ks副本里同步修改。
更进一步,可以把模板文件里的变量抽出来,用 company 这类脚本在HTTP服务端预生成最终ks文件。比如管理平台IP、NTP服务器地址、DNS地址这些"整个环境统一"的配置,我习惯放在一个 common.inc 片段里,用 %include 引入,避免在每台机器上重复维护。
6.2 装机完成后的安全基线自动加固
批量装机天然适合做安全基线。我在 %post 里固定做以下几件事:
- SSH配置加固:关闭root密码登录,仅允许密钥登录;限制允许登录的用户白名单;修改默认SSH端口(如果内网规范允许)。
- 系统更新与补丁:装机时如果内网有镜像源,在
%post里执行dnf update -y --security打安全补丁,避免新装系统带一堆已知CVE上线。 - 审计配置:安装
auditd并启用关键目录审计规则;配置日志的本地保留策略和远程转发地址,这一步对云平台这种多节点环境尤其重要,出了问题能快速追溯哪台机器做了什么操作。 - 密码策略与账户:通过
chage设置密码过期策略,创建专用的运维账户而不是直接裸用root。
这些内容写成模板后,每次新节点上线都会自动执行,相比"装完系统再逐台手工加固",省下的时间在一二十台规模下就能明显感觉到。
6.3 把装机流程融入整体的自动化交付体系
最后想聊一个更宏观的思路。Kickstart解决的是"单机系统交付",但这通常只是整个自动化链路里的一环。在我参与的实际项目中,完整的交付流是这样的:
text复制配置管理平台记录新节点信息 → 触发PXE装机(分配IP、指定角色) → 系统安装完成 → %post调用平台API注册资源 → 平台自动完成资源池的扩容或纳管 → 监控系统自动接入新节点
每一步之间通过API或消息通知衔接,Kickstart的 %post 阶段就是连接"裸机阶段"和"云平台纳管阶段"的桥。所以我在写 %post 时,会有意识地遵循一个原则:尽量只做"节点的本职工作",把"全局性的编排"留给上层平台。比如计算节点要加入负载均衡池、要分配业务网段、要设置监控告警阈值,这些我都不放进ks脚本,而是由平台在节点注册后续自动下发或通过CMDB接口获取。这样ks脚本保持精简,职责单一,后续维护和排障也更清晰。
说白了,Kickstart最终的价值不只是"免去手工点鼠标",而是把"装出一台机器"这件小事,变成一个"随时可重复、可审计、可对接上层"的标准化动作。有了它,不管你是装DoraOS朵拉云节点,还是开一套内部的批量测试环境,都能把交付时间从"小时"压缩到"分钟",把出错概率从"靠运气"变成"靠设计"。我后来回过头看,当初那台插U盘装了四十分钟的机器,其实早就埋着一条更聪明的路,只是一开始没意识到而已。
