
晚上九点机柜旁边新到的一批机器在 DHCP 里拿到了地址PXE 菜单也正常弹出来了然后屏幕停在“Starting installer, one moment...”一停就是半小时。这种场景我蹲过不止一次最后十有八九发现是 Kickstart 文件根本没被取到或者取到了但仓库地址不对或者分区规则里写了个根本不存在磁盘名。Kickstart 无人值守安装的报错看起来五花八门实际上只要按安装阶段拆开看大部分都能在几分钟内定位到根因。这篇就把我在 RHEL 系发行版无人值守安装里实际碰到、以及从同行那边收集到的高频报错整理成一套“错题集”。我不打算按文档目录罗列而是按照一次无人值守安装真正经历的顺序来讲从 Kickstart 文件获取、仓库连接、磁盘分区、bootloader、%post 脚本到最后怎么靠日志快速定位。适合刚开始搭 PXE 安装环境的运维也适合那些已经跑通自动装机、但偶尔会被某台特殊硬件卡住的老手。1. 卡在“Starting installer”Kickstart 文件都还没进来的那半小时1.1 先分清三个环节initrd、stage2、Kickstart 文件很多人一看到 PXE 菜单正常、内核也加载了就默认安装器已经把一切拉起来了其实从 PXE 启动到真正读入 Kickstart 文件中间隔着至少三个独立环节initrd 里的 dracut 环境、stage2安装器本体镜像、以及 Kickstart 文件本身。这三个环节只要有一个没打通表现都是“Starting installer”之后长时间无响应或者直接报错。dracut 初始化阶段负责把网卡驱动和网络配置拉起然后根据inst.stage2指定的位置去加载安装器镜像最后才轮到inst.ks指向的 Kickstart 文件。新版 Anaconda 里这两个参数可以都指向 HTTP 服务器但它们的获取是分开的。所以当你看到卡在开始画面时先别急着怀疑 Kickstart 文件内容优先确认 stage2 是否真的到了本地。inst.stage2http://install-srv/ks-station/os inst.kshttp://install-srv/ks-station/kickstart/ks.cfg同一台 HTTP 服务器同一个网络很多时候 stage2 能拉下来Kickstart 文件却取不到——因为我是用短路 URL 测试的搭服务器的人把实际路径换了安安静静地返回 404。1.2 取不到 Kickstart 文件的七大常见原因我把这些年遇到的“Kickstart 文件没进来”的情况拢了拢排在前面的基本都是下面这些。报错表现常见根因定位方式FAILED to fetch kickstartHTTP 路径少了个目录、文件名大小写不对先在安装环境用 curl 验证再看 /tmp/anaconda.log停在 starting installer 永远不动stage2 拉到但 ks 一直重试切到安装器文字控制台看网络请求ks 文件能 curl 下载但安装器拿不到防火墙只放行了部分客户端 IP在服务端看 access log收到 401/403HTTP 目录权限问题、启用认证给 ks 目录加可读权限或用短链临时去掉认证在 UEFI 网络启动时莫名拿不到grub.cfg 里 kernel 参数被换行截断检查引导配置里的续行符HTTP URL 带查询参数时直接挂被当成内核参数分隔符避免在 ks URL 里带查询串网络初始化失败导致无法解析服务器DHCP 拿到的 DNS 不对或没配 ip加 ipdhcp 参数其中 404 和 DNS 这两个坑最阴险因为你在本机浏览器里 curl 一点问题没有在安装环境的 dracut 里却可能连解析都失败。我自己的习惯是先用一个极简的/var/www/html/ks/ks.cfg路径带个测试账号密码直接 HTTP 明文验证确认能取到后再替换成生产环境路径。1.3 一个更隐蔽的坑URL 里的 被命令行吃掉如果厂商的交付文档里给了类似inst.kshttps://install-srv/ks/api.php?nameprodenvtest这样的地址进入 PXE 配置之后十有八九会翻车。因为内核命令行把当作参数分隔符后面那半截直接变成了另一个内核参数Kickstart 会拿到一个残废的 URL 然后 404。遇到带查询参数的接口式 Kickstart 服务我的建议是不要让安装器直接吃这个 URL。更稳的做法是在 HTTP 服务端做一个短链或者 rewrite把动态参数变成固定路径例如/ks/prod-test.cfg。这个方案我搭过几套后续维护也方便因为动态接口出问题的时候你至少可以先用 curl 单独把文件抓出来看内容。2. 网络与仓库报错baseurl 404 背后不只是拼写问题2.1 repo 配置报错的三种典型文案进入安装界面之后最常碰到的英文报错就那几句Failed to set up repositories、Could not find a valid baseurl for repo: AppStream、Unable to read install media。这三句话看上去是三个问题其实背后往往同一个根源——安装器在inst.repo指定的地方找不到有效的 yum 仓库元数据。注意我在这里说的不是 Kickstart 文件里的url --url那一行。很多人会忽略inst.repo这个内核参数它除了告诉 Anaconda 从哪里加载安装源还同时决定了 stage2 和安装介质的位置。如果你用inst.repohttp://server/os那么 Anaconda 会先去检查这个目录下的.treeinfo文件再根据里面的路径去找images/squashfs.img和repodata/repomd.xml。任何一个文件缺失都会先报出上面那种迷惑性的错误。2.2 网络基础没配好repo 报错全是障眼法有一次我在新环境里搭无人值守安装DHCP 能分到地址TFTP 也通了但安装器始终报仓库 404。我查了一下午 HTTP 服务器权限最后发现是长时间放行的客户端 IP 列表过旧新装机网段根本不在白名单里。也就是说门卫把路给封了客人再怎么敲门都没用。所以我一遇到 repo 报错第一步不是去翻 Kickstart 分区规则而是先确认三条链路安装机能否解析仓库域名从安装机能 telnet/curl 到仓库端口HTTP 服务端 access log 里是否出现了安装机的请求记录。在 Linux 发行版里可以用curl -I http://install-srv/path/repodata/repomd.xml本地验证但如果 PXE 安装机连不上这个方法帮不上忙。更实用的是在安装环境里启用inst.sshd登录进 dracut 环境手动用 curl 测一下再退出。这个参数遇到疑难杂症时几乎百试百灵。2.3 同一 URLks 能下载但 stage2 404 的玄机还有一类情况是 Kickstart 文件能从同一个 HTTP 目录下载安装器也进入配置阶段了但仓库死活起不来。这时要重点检查.treeinfo指向的镜像目录和repodata是否完整尤其当你用的是裁剪过的定制 ISO。我见过有人在装系统时图省事把一个大 ISO 里的几个文件拷出来放到某目录就当作安安装源结果缺了images/install.img或者repodata里的repomd.xml。安装器拉不到对应的 stage2表面就会显示仓库相关的怪错。这时候把 ISO 完整解压、保留目录结构往往问题就没了。另外如果你用自建镜像源务必要保证和系统大版本一致混用不同大版本的 repodata 经常引发这种“绕不过去”的损坏。3. 磁盘与分区报错sda 显示不存在时先怀疑硬件抽象3.1 “没有可用磁盘”很多时候是 ignoredisk 写砸了无人值守到分区阶段最狠的报错就是那句No valid disks或The disk could not be found。新手第一反应是硬件 raid 驱动没加载资深一点的会先打开 /tmp/storage.log但如果你在 Kickstart 里写了ignoredisk --only-usesda问题很可能就出在这。--only-use的意思是“除了这个盘其他盘全部忽略”不是“如果存在这个盘就用它”。当安装机里的虚拟磁盘因为光纤卡识别顺序变化实际主盘变成了 sdb而你只允许 sda结果就是所有盘都被忽略最后给你一个没有可用磁盘的结论。我现在的做法是尽量不依赖单一设备名。如果硬件是同一批次设备名相对稳定我会在分区段里同时写clearpart --drivessda,sdb并保持分区规则宽松。如果是异构批次干脆在 %pre 阶段根据 wwid 或者厂商型号把盘名重写成变量再拼进后续分区命令。3.2 sda 换了马甲内核设备名在 KS 里并不可靠另一个很磨人的问题是clearpart --drivesdasda写对了但等到真正跑分区那一步内核把设备顺序又换了一次。固态盘、NVMe、USB 启动盘混在同一台机器上时尤其常见。USB 安装盘插在哪个口就会影响到 sda 到底是不是系统盘。这个坑我在加了 NVMe 盘的新服务器上栽过一回。Kickstart 里定义part / --ondisksda结果系统盘实际是nvme0n1安装器报找不到分区。后来我改成在 %pre 里读取/sys/block/*/device/model挑出那个厂商型号对应的设备名再把生成的磁盘名写入到一个临时文件后面的分区段用%include引入这样才算稳定。# %pre 里生成磁盘变量示例 for disk in /sys/block/*/device/model; do model$(cat $disk) case $model in VIRTUAL-DISK*) echo DISK/dev/$(basename $(dirname $disk)) /tmp/disk.env ;; esac done注意 %pre 默认跑在安装环境里写入 /tmp 的文件在后续阶段还能读到这是 Kickstart 自带的一种传参方式比我之前见过的各种诡奇方案都可靠。3.3 GPT 分区表、biosboot 和 2TB 上限的纠葛磁盘分区报错的另一大类来自分区表类型和引导方式的错位。比如某台老服务器用 BIOS 引导但磁盘超过 2TB你在 Kickstart 里没有显式让 anaconda 使用 GPT安装器会直接告诉你不能在这个盘上创建这么大的分区或者干脆在 bootloader 阶段失败。正确做法是同时保留part /boot --fstypeext4 --size1024part biosboot --fstypebiosboot --size1clearpart --disklabelgptbiosboot分区在 BIOSGPT 组合下是引导记录的家很多人只记得 UEFI 要efi分区忘了 BIOS 也要一个专门放 grub core 的 1MB 分区。少了它分区阶段不会报错但 bootloader 阶段会突然挂掉而且错误信息看得人一头雾水。看到 grub 安装失败时先回头检查一下自己的分区表设计十次里有三四次是这里的问题。UEFI 方向则要注意另外两点/boot/efi必须用vfat文件系统并且分区要标上 ESP 引导标志。新版 Anaconda 一般会自动处理但手写分区表时如果漏了/boot/efi报错会一直针对 bootloader不会明说“你少了 EFI 分区”。3.4 LVM 路径问题与 /dev/mapper 造成的历史遗留分区规则的最后一个雷区来自 LVM 历史遗留名字。part pv.01 --size102400这样的写法配合volgroup vg_system pv.01只要名称不冲突基本没问题。真正麻烦的是你引用了/dev/mapper/vg01-lv_root而实际 VG 名称是vg_system安装器会报 “Could not find the LV” 之类的错。遇到这种情况别去硬编码名称统一用逻辑卷定义的方式part pv.01 --size1 --grow volgroup vg01 pv.01 logvol / --vgnamevg01 --namelv_root --size40960 --fstypeext4 logvol swap --vgnamevg01 --namelv_swap --size8192这样安装器自己生成设备映射换机器、换盘符都不容易翻车。另外还要记得别把一个已经属于某个 VG 的 PV 在另一个分区段里重复引用那种报错会显示成设备被占用日志里能看到 PV 重复声明的记录。4. Bootloader 失败与 UEFI/BIOS 错位grub 装不上的常见根因4.1 BootLoaderError 出现在 BIOS 与 UEFI 的不同解法分区全过了安装包也拷完了结果来一句BootLoaderError: install of bootloader failed这时候最考验人。BIOS 和 UEFI 两套引导方式看到这句报错的原因基本是两种截然不同的故事。BIOS 下最常见的是前面说的 biosboot 分区缺失或者是--locationmbr指定的磁盘无法写入引导记录。硬件 raid 卡上如果还挂着老的引导配置grub2-install 可能因为无法识别目标盘的既有文件系统而拒绝写入。UEFI 下则多半是 ESP 分区没挂上或者efibootmgr写入 nvram 失败。虚拟机里常见的是不需要 nvram 但 grub.cfg 里的 UUID 对不上。排查这类问题我直接翻/tmp/program.log。里面会记录 grub2-install 实际执行到的命令错误信息往往比安装界面弹窗详细得多。注意program.log会更接近命令行输出而不是安装器的封装提示。4.2 ESP 分区与 /boot/efi 缺失时 grub 的直接报错在一个真正 UEFI 引导的机器上如果 Kickstart 忘了写/boot/efi安装器也不会那么体贴地提醒你缺了一个什么分区。它大概率会给你install of bootloader failed或者更隐晦的unable to identify a filesystem。我的part /boot/efi --fstypeefi --size512几乎成了所有 UEFI 模板的标准开头。--fstypeefi是 Anaconda 识别 ESP 的固定写法虽然它最终会格式化成 vfat但别图省事写成--fstypevfat一些版本会不认导致最终引导器装不上。默认模板里也要确保/boot分区足够大至少 1024MB不然装了多个内核之后第一次重启就可能满盘。4.3 PXE 引导文件与 KS 无关的误导性报错还有一种 bootloader 相关报错其实根本不是 Anaconda 能处理的它发生在 PXE 阶段本身。比如ldlinux.c32缺失、Invalid or unsupported executable format、Could not find kernel image这些。这些跟 Kickstart 文件没关系但很多人会误判成无人值守配置问题。我见过有人把一个架构为 x86_64 的 vmlinuz 塞进 arm64 机器的 PXE 菜单报错一直说是内核格式问题最后折腾一整天才发现是引导镜像选错了。无人值守排错时建议把 PXE 引导文件、grub.cfg 路径、以及启动参数这三样单独做版本校验别让它们混进 Kickstart 的排错循环里。5. %post 脚本的“安装成功但系统坏了”chroot 与退出码陷阱5.1 %pre 与 %post 运行环境的差异决定写法和测试方式很多人第一次写 %post 脚本以为它跟普通 Linux 启动脚本没区别实际上它运行在安装器临时环境里而且默认是处于 chroot 状态的。所谓 chroot就是脚本的根目录已经切换到了新装系统的文件系统。你在 %post 里执行cp /tmp/a /root/a是对的但如果你写cp /mnt/sysimage/root/a /root/a反而会找不到路径。%pre 则跑得更早它运行在 dracut 的 initramfs 环境里没有完整的发行版环境可能连某些常用命令都没有。我吃过一次亏在 %pre 里依赖curl去拉一个配置文件结果安装环境里没有这个命令脚本直接静默失败。后来统一改成只在 %pre 里做最简单的变量探测和磁盘名判断把需要复杂依赖的逻辑全部挪到 %post用系统装完后的环境执行。5.2 退出码是内鬼脚本在装完系统后悄悄失败%post 脚本最常见的内鬼是退出码。默认情况下 Anaconda 并不会因为某个 %post 命令返回非零而中断安装它只是写进日志。问题是你以为库里安装成功了重启一看需要的包根本没装上或者配置文件还是默认值而日志又被淹没在成千上万行输出里。我现在的规范是每个 %post 脚本第一行就重定向所有输出%post exec /root/kickstart-post.log 21 set e # 你的命令 %end这样不管脚本里发生什么安装完成后可以在新系统的/root/kickstart-post.log看到完整执行记录。遇到需要严格判断的步骤我还会主动加set -e并在关键命令后做退出码检查宁可让安装停在那里让值守的人立刻发现也别留着半残系统给业务方。5.3 日志重定向和临时文件目录的固定套路%post 里另一件容易被忽略的事是网络状态。虽然安装阶段网络是通的但 %post 执行时系统的/etc/resolv.conf可能还没有被最终网络配置覆盖导致yum install报 DNS 解析失败。如果脚本里要在线装包我通常改成在系统首次开机后由 systemd unit 执行而不是在 %post 里硬拼。SELinux 也是个老问题。如果你在 %post 里改了系统文件属性重启后大概率进不去图形界面。最简单的兜底是在 %post 最后执行touch /.autorelabel让首次启动自动重打文件上下文。这招会拖慢一次开机时间但比系统文件权限错乱安全得多。我一般只在脚本动了系统目录、改了服务配置时才加这行。6. 别急着重装用这一套日志定位任何报错6.1 安装现场的六份日志与两个 TTY我至今没见过谁能保证 Kickstart 无人值守从第一次就完全成功。关键在于一次失败之后你能在几分钟内从日志里找到原因。Anaconda 在安装过程中会把不同类型的日志写在不同的文件里这是它最有价值的排错入口。日志文件主要记录内容/tmp/anaconda.log安装器主流程、阶段切换、Kickstart 解析信息/tmp/program.log外部命令执行过程比如 grub、mkfs、yum 的原始输出/tmp/storage.log磁盘检测、分区、LVM 操作的详细过程/tmp/ifcfg.log网络配置和 DHCP 交互细节/tmp/syslog系统内核和 dracut 的常规日志/tmp/packaging.log包安装阶段依赖、事务提交记录安装当时你未必能把这些文件拷出来但 Anaconda 通常提供了多个 TTY 控制台。遇到卡住时先切到文本界面看正在执行的阶段再切到 shell 去 tail 对应的日志。如果环境允许我给 PXE 模板加一个inst.sshd参数安装过程中就能直接 SSH 进去日志查起来比对着屏幕打字舒服得多。6.2 排查四步法从网络、文件、分区、到逐步叠加网上报错五花八门我自己总结了一套固定的排查顺序能覆盖绝大多数场景先确定卡在哪个安装阶段通过 TTY 或日志头部时间戳判断是 ks 获取、仓库配置、分区、bootloader还是脚本执行网络问题先验证连通性再看 HTTP 服务端日志最后才怀疑配置内容文件问题用 curl 或对照目录树确认.treeinfo、repodata、images是否完整分区和脚本问题从最小模板开始逐步叠加每次只加一个变量。这套流程的核心思想是不要把几个错误混在一起查。比如仓库 404 和分区失败同时出现时先修网络否则你改半天分区规则安装器根本走不到那一步。最小模板是我长期保留的一个含 autopart、不跑任何 %post 的 ks.cfg任何环境出问题我都先用它排除自身引入的复杂度。6.3 建立自己的“报错速查表”最后分享一个比较个人的经验把踩过的每个坑按“报错原文 / 安装阶段 / 关键日志 / 根因 / 修复方式”整理成一张速查表存在自己文档库里。每次无人值守失败先拿报错去匹配这张表能匹配上就直接修复匹配不上再开新条目。这样积累半年之后绝大多数报错都能在五分钟内给出结论。所谓“无人值守常见报错集锦”说到底不是一份能背完的清单而是一个你愿意不断更新、不断在实战中验证的问题库。我自己回头翻以前记的条目第一年记的是路径写错这种低级问题后面几年记的则都是 NVRAM 写入失败、软件 RAID 元数据残留这类更接近系统底层的细节——这个过程本身就是运维经验在涨的证明。