☰
CentOS 8.5修改initrd.img实战:备份、解包、重建与排错
2026/10/16 4:01:17 网站建设 项目流程

CentOS 8.5修改initrd.img,这活儿看着冷门,但真到要动手的时候,基本都是在“系统起不来”或者“再不弄就快起不来”的边缘。我这几回折腾它,一类是给新硬件塞驱动,让机器换了根盘或换了阵列卡之后还能正常挂载根分区;一类是往镜像里注入自定义的启动脚本和救援逻辑;还有一类是排查启动阶段不明原因卡死。不管哪种,核心都绕不开三件事:搞清楚这个文件在启动链路里到底扮演什么角色、用什么方式把它“改”成自己想要的样子、以及改完怎么验证怎么回滚。这篇文章不绕弯子,直接把我从备份、解包、重建到排错的全过程写出来,给同样在 CentOS 8.5 上动过这个文件、或者正准备动手的人做个参考。

1. initrd.img在启动链条中的角色:为什么有人要动它

1.1 它不是一个普通文件,而是一套微型根文件系统

很多人第一次看到/boot/initramfs-4.18.0-348.el8.x86_64.img这种文件,以为它和 Windows 下的引导镜像差不多,就是一段被加载到内存里的二进制数据。实际上它是一套封装的 cpio 归档,解包出来之后你会看到完整的目录结构,里面有usr/bin、usr/lib/modules、etc/udev、sbin/init等等,本质是一个完整的、运行在内存里的微型 Linux 根文件系统。

系统加电之后,固件层先加载 GRUB2 引导程序,GRUB2 再把内核vmlinuz和这个 initrd.img 一起读进内存。内核先跑起来,把 initrd.img 解包成自己的临时根目录,然后执行里面的初始化程序,加载硬件驱动、识别根文件系统、组装磁盘阵列或 LVM 逻辑卷,最终通过switch_root切换到真正的根分区。也就是说,内核本身只提供最基础的代码执行能力,真正让内核“找得到硬盘、挂得上根分区”的,全靠这个临时根文件系统里的模块和脚本在撑场子。

这个设计看着繁琐,但好处很明显:内核可以保持精简,把大量驱动和初始化逻辑外置到 initrd 里按需加载。代价就是——当硬件驱动、磁盘布局、文件系统类型发生变化,而 initrd 里的内容没有跟上时,系统启动就会卡在“挂载根分区”这一步,屏幕上出现的往往就是冷冰冰的Failed to mount /sysroot,甚至直接停在dracut的紧急 shell 里。

1.2 哪些场景会逼着你去改它

我在实际工作中遇到最多的三类需求,基本覆盖了 95% 的修改理由:

  1. 更换或新增存储控制器。比如一台老机器原本用 SATA 盘,你给它换成了 NVMe SSD,内核里虽然有 nvme 驱动模块,但 initrd 是按旧硬件生成的,里面根本没带nvme这个模块,开机自然找不到根设备。这种情况只要把对应驱动模块塞进 initrd 重新生成一次就好。

  2. 定制早期启动行为。比如要在挂载根分区前执行某些固件升级脚本、注入特殊的网络配置、调整 udev 规则,这些逻辑放在 initrd 里最合适,因为它执行时机早于根分区挂载。

  3. 调整文件系统或多路径策略。涉及 XFS 扩展属性、LVM 缓存、多路径设备别名等,如果 initrd 里缺少对应工具或规则,重启后轻则丢盘,重则直接进入 emergency mode。

理解了它的角色,再来看“修改”这件事就清晰了:你要么通过 dracut 重新生成一套新的 initrd,要么直接拆开这个归档,改完里面的文件再打包回去。两种方式各有适用场景,后面第 3、4 两节详细拆。

2. 动手前的三个准备:备份、环境检查与版本对齐

2.1 先搞清楚当前系统的镜像格式和文件路径

CentOS 8.5 的/boot目录下,实际存在的文件名通常是initramfs-$(uname -r).img,比如initramfs-4.18.0-348.el8.x86_64.img。标题里写的 initrd.img 更多是历史叫法,在 CentOS 8 时代正式名称就是 initramfs,两者指代同一个东西。

操作前先确认三点:

uname -r # 当前内核版本 ls -lh /boot/initramfs-*.img # 实际镜像文件和大小 grubby --info=ALL | grep -E "kernel|initrd" # 查看各引导条目实际读取的文件

grubby输出里会明确写着每个内核条目对应的 initrd 路径,这步很重要。有时候你的/boot下有多份 initramfs,但 GRUB2 菜单真正读取的只是其中一条。改错文件等于白改。

然后用file命令看一眼镜像的压缩格式:

file /boot/initramfs-4.18.0-348.el8.x86_64.img

正常会看到类似gzip compressed data或者XZ compressed data的提示,也可能因为包含微码部分而显示为混合内容。这一步记录在案,后面解包和重打包要用到,别跳过。

2.2 备份与回滚预案比操作本身更重要

改 initrd 这件事最大的风险不是改坏,而是改坏之后没有退路。我的固定操作是,动手之前先做两份备份:

mkdir -p /root/initrd_backup cp /boot/initramfs-$(uname -r).img /root/initrd_backup/initramfs-$(uname -r).img.orig cp /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak cp /boot/grub2/grub.cfg /root/initrd_backup/grub.cfg.orig

一份放在/root/initrd_backup下,目的是不被/boot空间限制;另一份放在/boot下命名为.bak,目的是万一系统起不来,可以在 GRUB 菜单界面手动把加载路径改成这个备份文件,先把系统救回来再说。备份 grub.cfg 是因为很多改坏的情况会连着引导配置一起动,有备份兜底会安心很多。

还有一个容易被忽略的准备:检查/boot分区剩余空间。

df -h /boot

dracut 重新生成的镜像通常比原来的大,因为可能新增了驱动模块、固件文件,甚至换了更高的压缩级别也没法抵消内容增量。如果/boot分区剩余只有几十 MB,重建进行到一半就会报No space left on device,这下系统启动时会直接读到一个被截断的镜像,故障面一下子扩大。我见过的翻车现场里有不少就是栽在这上面的,所以低于 200MB 剩余空间,先清理旧内核或者扩容,再继续。

2.3 内核版本和 initramfs 必须严格匹配

这个匹配关系不是“差不多就行”,是必须完全一致。initramfs 里的usr/lib/modules/目录路径里就带着内核版本号,驱动模块的.ko文件也只能被对应版本的内核加载。如果你用dracut /boot/initramfs-4.18.0-348.el8.x86_64.img 4.18.0-999.el8.x86_64 --force这种命令强行指定一个不存在的版本号,生成出来的镜像大概率是残缺的,启动时要么报Initramfs unpacking failed,要么加载驱动全部失败。

所以我在运行系统里重建,永远是用$(uname -r)让命令自动取当前版本,而不是手敲一串字符串:

dracut /boot/initramfs-$(uname -r).img $(uname -r) --force

但注意,进入 chroot 急救模式后这个规则就不成立了。因为 chroot 环境里uname -r拿到的是急救系统(通常是安装 U 盘里的内存环境)的内核版本,不是目标系统根分区里那个内核。此时必须根据/boot/vmlinuz-*实际文件来确认版本号,手动传给 dracut。第 3 节里我会专门写这一段,因为这是离盘重建最容易翻车的地方。

3. 重新生成initramfs:dracut的正确打开方式

3.1 dracut 命令格式与参数拆解

CentOS 8.5 上重建 initramfs 的主力工具是 dracut。基本格式就一行:

dracut /boot/initramfs-$(uname -r).img $(uname -r) --force
  • 第一个参数是输出文件路径;
  • 第二个参数是目标内核版本;
  • --force是覆盖已存在的同名镜像,不加的话遇到同名文件会报错退出。

如果你想边看输出边确认它在做什么,加-v:

dracut -v /boot/initramfs-$(uname -r).img $(uname -r) --force

-dracut 会列出一串模块名,比如95resume、90lvm、99base、99fs-lib,这些都是 dracut 的“模块”,不是内核模块。每个 dracut 模块负责往 initrd 里塞一类东西:90lvm负责 LVM 工具和规则,95resume负责休眠恢复逻辑,99base负责最基础的 systemd 与目录骨架。

这里插一个容易混淆的概念:dracut 的--add-drivers和--add是两个完全不同的操作。前者是往 initrd 里强制加内核驱动模块,比如 nvme、virtio_blk;后者是追加 dracut 功能模块,比如--add "dmraid"可以加软 RAID 识别能力。日常需求里,绝大多数场景只需要--add-drivers,不要因为嫌麻烦就顺手--add一堆东西,因为每加一个模块,镜像体积和启动时间都会往上走一点,而且引入无关组件反而增加排错面。

3.2 如何让自定义驱动进入新镜像

最直接的方式是命令行里指定驱动:

dracut /boot/initramfs-$(uname -r).img $(uname -r) --force --add-drivers "nvme virtio_blk"

这种方式适合一次性的临时需求。但如果这个机器后续还会重复重建 initramfs(比如每次升级内核后 dracut 会自动执行),你手动在命令行里注入的驱动是不会被记住的。一升内核,配置又丢干净了,新内核的 initrd 照样没有 nvme 驱动。

更稳妥的做法是在/etc/dracut.conf.d/下新建一个配置文件,比如90-custom.conf:

# 强制加载的驱动 add_drivers+=" nvme virtio_blk " # 压缩方式 compress="xz" # 保留主机特定配置 hostonly="yes"

注意add_drivers+=" "引号里要留空格,这是 dracut 配置解析的一个老规矩。加引号并且带上空格,可以避免多个驱动字符串被错误拼接成一条。写完配置文件后,再执行一次普通的重建命令:

dracut -f -v

它自动读取/etc/dracut.conf.d/*.conf里的设置,把上述参数融合进生成过程。以后每次内核更新触发 dracut 自动重建,自定义驱动也不会丢。这套做法的核心收益是“可重复”,比单纯敲命令行参数更适合生产环境。

还有一点,CentOS 8.5 默认生成的是 hostonly 镜像,也就是只包含当前这台机器已知需要的驱动。如果你的场景是做一个通用救援 U 盘,要把同一个 initramfs 拿去引导不同硬件配置的机器,那重建时要加--no-hostonly:

dracut -f --no-hostonly /boot/initramfs-generic.img

加了这个选项,生成的镜像只依赖内核自带的模块目录,能覆盖绝大多数存储控制器和文件系统,不会再按当前机器的设备列表裁剪内容。代价就是镜像会明显变大,这个要看需求取舍。

3.3 chroot 修复模式下的离盘重建

如果系统已经起不来,没法在运行环境里执行 dracut,那就得从安装 U 盘或者救援盘引导,进入“rescue”模式,或者直接 drop 到 shell,手动挂载根分区后 chroot 进去。这个流程我完整走下来是这样:

  1. 用安装盘引导,选择“Troubleshooting → Rescue a CentOS system”;
  2. 选择挂载根分区到/mnt/sysroot;
  3. 依次执行磁盘挂载和 chroot。

核心动作是这套命令:

chroot /mnt/sysroot /bin/bash # 以下在 chroot 环境内执行 mount -t proc proc /proc mount -t sysfs sys /sys mount --rbind /dev /dev mount --rbind /run /run # 查看目标系统的内核版本 ls /boot/vmlinuz-*

这里的ls /boot/vmlinuz-*输出就是要用的版本号,假设是4.18.0-348.el8.x86_64,接着执行:

dracut /boot/initramfs-4.18.0-348.el8.x86_64.img 4.18.0-348.el8.x86_64 --force

此时千万注意:不能在 chroot 里用$(uname -r),因为此时内核是急救盘的,不是目标系统的。我最初吃过这个亏,chroot 后用uname -r拿到的是安装内核版本,生成的 initramfs 文件名和内容都跟目标系统对不上,启动时 GRUB 按原名加载到的还是旧文件,等于白折腾。

重建完退出 chroot,再执行reboot前,建议顺手在 chroot 里用grub2-mkconfig -o /boot/grub2/grub.cfg刷新一遍 GRUB 配置,确保菜单项指向的 initrd 文件名和我重建的目标一致。

4. 你说的“修改”如果是解包改文件:直接操作initrd.img内部

4.1 查看镜像内文件清单

不是所有需求都要重新生成。有时你只是想在 initrd 里替换一个驱动模块、补一个脚本、调整一个 udev 规则,重新生成整份镜像反而动静太大。这时候直接解包、修改、重打包是更精准的做法。

先用lsinitrd看内容:

lsinitrd /boot/initramfs-$(uname -r).img

输出会列出完整的目录树和文件列表,比如/usr/lib/modules/4.18.0-348.el8.x86_64/kernel/drivers/nvme/nvme.ko.xz。如果想提取某个具体文件出来查看,用-f参数:

lsinitrd -f /etc/ld.so.conf /boot/initramfs-$(uname -r).img

这条命令会把文件内容打印到标准输出,你可以重定向到本地路径保存。这个技巧在做“确认镜像里某个配置文件到底改了没有”的验证时很有用,不用完整解包。

4.2 解包、修改、重打包的完整链路

完整解包分三步走。第一步,建立工作目录并进入:

mkdir -p /tmp/initrd_work && cd /tmp/initrd_work

第二步,解包。CentOS 8 生成的 initramfs 开头通常有一段 CPU 微码数据,需要先用 dracut 自带的工具跳过它:

/usr/lib/dracut/skipcpio /boot/initramfs-$(uname -r).img | gunzip | cpio -id --no-absolute-filenames

这里有三处需要注意。第一,如果file命令之前显示镜像不是 gzip 压缩,而 XZ 压缩,那第二段命令要把gunzip换成xz -d或unxz。第二,cpio -id是在当前目录下释放文件,所以一定要先cd到工作目录再执行,否则文件会散落在/tmp下。第三,--no-absolute-filenames是为了防止归档里万一存在绝对路径时直接写到根文件系统的危险路径去,务必保留。

成功之后,/tmp/initrd_work下就会出现usr/、etc/、sbin/等目录。此时你想替换的驱动模块、想修改的脚本、想追加的配置文件,都在里面按相对路径摆好了,直接编辑就行。比如往里面加一个启动脚本:

mkdir -p usr/local/bin cp /root/custom_check.sh usr/local/bin/custom_check.sh chmod 755 usr/local/bin/custom_check.sh

第三步,重打包。回到/tmp/initrd_work(必须是解包后的根目录),执行:

find . -print0 | cpio --null -ov --format=newc | gzip -9 > /tmp/initramfs-new.img

这个命令的核心是cpio -o,它会把当前目录下所有文件按 newc 格式打包成归档;gzip -9是最高压缩级别,输出文件路径随便起,建议先放在/tmp而不是直接覆盖/boot下的原文件。

重打包完成后,先对比一下新旧镜像大小:

ls -lh /boot/initramfs-$(uname -r).img /tmp/initramfs-new.img

如果新文件比旧的还小很多,先怀疑是不是打包时少了目录层级,回 4.3 节检查一下。确认没问题,再把新文件备份后覆盖到/boot:

cp /boot/initramfs-$(uname -r).img /root/initrd_backup/initramfs-$(uname -r).img.pre_custom cp /tmp/initramfs-new.img /boot/initramfs-$(uname -r).img

4.3 重打包时最容易翻车的四个细节

手动解包重打包这个流程,我用过很多次,也帮朋友排查过几次打出来的镜像起不来的问题。翻车点基本集中在这四类:

第一,打包目录多套了一层。这是最常见的错误。如果你解包后在/tmp/initrd_work下又建了一个子目录,把解出的文件挪进了子目录,然后cd进子目录执行 find 打包,包出来的镜像内容就会整体被套在一个子目录下面。内核解包时发现根目录下没有usr/、etc/这些预期目录,直接Initramfs unpacking failed。正确做法是find .一定要从解包根目录执行,保证归档第一层就是usr/、etc/、sbin/这些标准目录。

第二,压缩格式选错了。initramfs 的内核侧解压逻辑是依据文件头自动识别的,gzip、xz、zstd 都能解。但 grub 加载和内核解析时对“是否采用标准压缩”有一定约定,你重打包时如果原本是 gzip 却用了 xz,而文件扩展名没变,大部分情况能识别,但碰到某些老引导环境会出问题。保守做法是:打包时沿用第一步file命令查到的原始压缩格式,能不换就不换。默认推荐 gzip,兼容性最好。

第三,文件属主和权限丢了。有些 initrd 里的脚本是 root 属主,带特殊执行位。如果你执行 cpio 打包时用的不是 root 账号,或者修改文件时把属主改成了普通用户,启动时执行到某个关键脚本会报 permission denied。规范流程是解包、修改、重打包全程用 root,修改完脚本顺手检查一下权限和属主。

第四,跳过了微码后面导致整体不完整。skipcpio会跳过 initramfs 前面那段 CPU 微码数据,但重打包后的新镜像不会自动带上这段微码。绝大多数 x86 机器上没有微码也能正常启动,但如果你面对的是需要微码修复特定 CPU 漏洞的服务器环境,还是建议保持使用 dracut 重建,而不是纯手工解包重打包,因为 dracut 会自动把微码重新加回来。我这里对“手工解包”的定位是:适合小改动、临时补救、排查问题;日常维护和需要长久保留的变更,还是优先走 dracut 的配置化方案,可重复、可审计。

5. 修改之后:验证、排错与回滚三板斧

5.1 重启前先在现有系统里完成验证

改完 initrad 不验证直接重启,是对生产环境的极大不负责任。好在验证手段都在现有系统里现成可用。

第一步,检查新镜像里是否包含目标内容:

lsinitrd /boot/initramfs-$(uname -r).img | grep nvme

如果目标驱动是 nvme,这行命令应该能抓到nvme.ko.xz字样。如果加的是自定义脚本,同样用lsinitrd能看到对应路径。

第二步,把关键文件导出出来确认内容:

lsinitrd -f /usr/local/bin/custom_check.sh /boot/initramfs-$(uname -r).img

眼睛确认一下脚本内容确实是你写进去的,这一步能拦截掉“改了 A 文件但打包时引用的还是旧路径”之类的低级错误。

第三步,检查引导配置指向。运行:

grubby --info=ALL

逐个条目确认 initrd 字段写的是新生成的文件名,而不是某份旧的.img.bak。这个检查和修改 initrd 本身同样重要,因为引导配置里哪怕只差一个字符,重启后实际加载的还是旧镜像。

最后,记录一下新旧镜像的校验值,方便后续对比确认自己是否成功重启到了新模式:

md5sum /boot/initramfs-$(uname -r).img /root/initrd_backup/initramfs-$(uname -r).img.orig

两份文件的 md5 肯定不同,这一步的意义是给“新镜像确实生效”留一个对比基准。

5.2 启动阶段常见故障和定位方法

即便验证过了,启动阶段仍可能出现问题。我的建议是不要慌,先在 GRUB 界面按e编辑启动条目,在内核命令行末尾加上调试参数:

rd.debug systemd.log_level=debug

然后按 Ctrl-X 启动,屏幕会打印大量 dracut 与内核的早期日志。根据卡住的位置和日志关键行,基本能定位问题类型。我把常见症状和对应修复方向整理成一个表:

故障现象典型日志关键行常见原因修复方向
启动卡在早期阶段Initramfs unpacking failed重打包格式或目录层级错误回滚到备份镜像,检查打包步骤
找不到根分区Failed to mount /sysrootinitramfs 缺少根设备驱动用--add-drivers补对应驱动后重建
进入紧急模式You might want to fix this文件系统或 LVM 元数据异常检查/etc/fstab,在紧急 shell 里手动挂载诊断
引导时找不到镜像文件error: file not foundgrub.cfg 指向的 initrd 文件名不存在用 grubby 修正菜单项或恢复备份文件

这张表不是万能药,但能覆盖我见过的大部分事故类型。另外要说个经验:重启后系统顺利到登录界面,不代表 initrd 没问题。如果它只是多加载了无用驱动,可能表面一切正常,但后续某次磁盘操作时才暴雷。所以重大项目变更后,建议在 24 小时内注意观察/var/log/messages里有没有早期驱动的报错,确认稳定再做清理。

5.3 出现问题如何快速回滚

最坏情况:重启起不来。此时回滚路径有三条,按优先级排。

第一条,GRUB 菜单选旧内核。如果/boot下还有其他可用的内核及配套 initramfs,开机时在 GRUB 菜单里往下翻,选择旧的内核条目直接启动。这是最快的回退方案,前提是平时不要乱删旧内核。

第二条,GRUB 菜单手动指定备份镜像启动。在 GRUB 界面按e编辑当前启动条目,找到initrd那一行,把镜像文件名改成你之前放在/boot下的.bak备份,比如:

initrd /initramfs-4.18.0-348.el8.x86_64.img.bak

然后 Ctrl-X 启动。进入系统后,把正式的 initramfs 文件恢复回去,并重新执行 grub2-mkconfig。

第三条,急救盘 chroot 恢复。如果连 GRUB 菜单都进不去或者备份文件也丢了,就只能用安装 U 盘进 rescue 模式,挂载根分区和/boot,然后把备份拷回去,或者直接按 3.3 节的方式 chroot 后重新生成一份干净的 initramfs。

“回滚三板斧”这套流程,在使用前那个“后备三份备份”的准备动作里就已经规划好了,这也是我在 2.2 节反复强调备份要放两份的原因——一份在/boot里方便 GRUB 读取,一份在/root或外部存储里防止/boot分区本身损坏。

最后说一个我个人的操作习惯。每次改完 initrd,我会顺手记录一下当前内核版本、dracut 的执行命令、新增了哪些驱动模块、镜像大小变化,写成两行注释存在/etc/dracut.conf.d/90-custom.conf的文件头里。这样下次不管是自己接手还是交给同事,看到配置就能还原当时的完整思路。之前就有过一次,改完镜像只更新了引导条目,忽略了自定义驱动列表,过了段时间升级内核后驱动又丢了,重启直接找不到盘,后来就是靠这份注释里的记录快速定位到问题。CentOS 8.5 本身已经进入维护后期,这套方法放到 Rocky Linux、AlmaLinux 或者其他 RHEL 系发行版上依然通用,趁现在样本还在,多留点实操经验总没坏处。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询