initramfs 与 dracut 引导故障排查实战:从 Waiting for root device 到内核升级后起不来的完整救援

initramfs 是什么:内核启动的"临时轮子"

服务器按开机键之后,从 BIOS/UEFI 到系统登录界面之间,有一条大多数站长从没认真看过的启动链。直观的分工是:GRUB 加载内核,内核挂载根文件系统,然后启动 init 进程。但这里有个鸡生蛋的问题——内核要挂载根文件系统,往往需要先加载根文件系统所在的驱动,而这些驱动本身又住在根文件系统里。比如根分区在 LVM 卷上、在 RAID 阵列上、在加密的 LUKS 容器里、或所在的磁盘控制器需要额外驱动,内核光靠自己那点内置驱动根本认不出根在哪。

initramfs(initial RAM file system)就是解决这个悖论的东西。它是一个压缩过的内存文件系统镜像,由 GRUB 加载到内存里,内核启动后先把它解压出来当成临时的根。这个临时根里装了挂载真正根文件系统所需的全部工具和驱动模块:LVM 的 lvm 命令、mdadm 阵列组装工具、cryptsetup 解锁工具、各种存储驱动的 .ko 模块。initramfs 里的 init 脚本会一步步把真正的根找出来、挂上、切换过去(switch_root),然后才把控制权交给真正的 init(systemd)。

一句话概括:initramfs 是给内核临时用的启动辅助盘,它唯一的使命就是把真实根文件系统挂起来。理解这一点,后面所有的故障排查都有了方向——只要引导卡在 switch_root 之前,问题必然出在 initramfs 找不到或挂不上根。

dracut 与 update-initramfs:两大阵营

不同发行版生成 initramfs 的工具不一样,但内核要的都是同一种东西。认清你的工具属于哪个阵营很重要。

  • dracut——RHEL/CentOS/Fedora/Rocky/Alma 的默认工具,也是 openEuler 等国产发行版沿用的方案。特点是"模块化组装":它不预先生成一个巨大的镜像,而是根据你的系统实际配置(有没有 LVM、有没有 RAID、根在不在加密盘上)动态挑出需要的模块塞进去。配置文件在 /etc/dracut.conf 和 /etc/dracut.conf.d/*.conf。
  • update-initramfs——Debian/Ubuntu 的默认工具,底层其实是 mkinitramfs。它读取 /etc/initramfs-tools/ 下的配置,钩子脚本在 /etc/initramfs-tools/hooks/。特点是结构更简单粗暴,但可定制性稍弱。

手动重建的命令分别是:

dracut -f /boot/initramfs-$(uname -r).img $(uname -r)
update-initramfs -u -k all

dracut -f 的 -f 是强制覆盖已存在的镜像,不加它会因为文件已存在而拒绝生成,这是新手第一个绊脚石。Debian 侧 -u 是更新、-k all 是给所有已装内核都重建(不加就只处理当前运行的内核)。

什么时候必须重建 initramfs

绝大多数情况下你根本不用管 initramfs,它会随内核安装/升级自动重建。但下面这些场景,initramfs 不会自动更新,或者更新后反而坏了,必须手动介入:

  • 把根分区从普通分区迁到 LVM 逻辑卷上(initramfs 里得加 LVM 支持,否则找不到根)。
  • 把根放到了 软件 RAID(mdadm)或 LUKS 加密盘上(得加对应的组装和解锁工具)。
  • 新增了根分区所在磁盘的控制器驱动,特别是从虚拟化环境迁移到物理机、或换了 RAID 卡。
  • 修改了 /etc/fstab 里根分区的 UUID。
  • 安装了专有显卡驱动、网卡驱动,需要在早期启动阶段就加载模块(比如要在 initramfs 里做 iSCSI 网络引导)。

判断 initramfs 里到底有哪些模块,可以拆开看:

lsinitramfs /boot/initrd.img-$(uname -r) | head -40
lsinitrd /boot/initramfs-$(uname -r).img | head -40

lsinitramfs 是 Debian 侧、lsinitrd 是 RHEL 侧。如果根在 LVM 上但输出里搜不到 lvm 相关文件,那这块机器一旦重启就再也起不来了——这是必须在重启前就检查的关键动作。

经典故障一:卡在 "Waiting for root device"

这是 initramfs 相关故障里最常见的一种。现象是:GRUB 正常、内核正常开始启动、日志刷了几屏,最后停在一行 Waiting for root device /dev/mapper/vg0-root...,然后反复重试直到超时进入 emergency shell。

这个报错的意思非常明确:initramfs 跑起来了,但它找不到 fstab 里写的那块根设备。排查顺序如下。

第一步,确认设备到底存不存在。在 emergency shell 里执行:

cat /proc/cmdline
ls /dev/mapper/
lvm vgscan
lvm vgchange -ay

/proc/cmdline 里 root= 后面的值就是内核被告知要去挂载的目标。如果它写的是 /dev/mapper/vg0-root 而 /dev/mapper/ 下空空如也,说明 LVM 卷根本没被激活——手动跑 lvm vgchange -ay 激活后再 exit,系统往往就能继续启动。这能证明"LVM 工具在 initramfs 里可用,只是没自动激活",进而指向另一个问题(比如 /etc/lvm/lvm.conf 里的设备过滤规则把这块盘排除了)。

第二步,如果设备根本不存在,检查 UUID 是否变了。现代发行版默认在 fstab 和内核参数里用 UUID 而不是设备名,因为设备名(sda/sdb)会因盘序变化而漂移。但如果你克隆过磁盘、重装过系统、或改过分区表,UUID 就可能和 fstab 里记的对不上。用 blkid 列出实际 UUID,和 cat /etc/fstab 里的对照:

blkid
cat /etc/fstab

UUID 不一致时,要么改 fstab 匹配实际值,要么用 tune2fs -U 把分区 UUID 改回 fstab 记的值。改完一定要重新生成 initramfs,因为它内部可能缓存了旧的根设备信息。

第三步,检查是不是缺了驱动。如果 ls /dev/disk/by-id/ 下什么都看不到,或 lspci 显示的存储控制器没有对应驱动,那就是内核/initramfs 里缺驱动。这种情况在"把系统盘从一台机器挪到另一台机器"时高发——新机器的 RAID 卡或 NVMe 控制器型号不同,驱动没编进 initramfs。解法是回到能启动的环境(救援盘),用 dracut 的 --add-drivers 显式加上,或加 --force-drivers 强制加载:

dracut -f --add-drivers "megaraid_sas" /boot/initramfs-$(uname -r).img $(uname -r)

经典故障二:GRUB 找不到 initramfs

另一种报错是启动还没进内核就失败了,屏幕上出现 error: file '/initramfs-xxx.img' not found 或 you need to load the kernel first。这说明 GRUB 配置里引用的 initramfs 文件不存在或路径错了。

常见原因是 /boot 分区被写满,导致内核升级时新的 initramfs 生成失败(dracut 因为没空间而中断,留下一个残缺或零字节的镜像)。先确认空间:

df -h /boot
ls -lh /boot/

如果看到 /boot 使用率 100%,且有一个明显偏小(几 KB)或大小异常的 initramfs 文件,那就是它了。清理掉旧内核腾出空间,然后重建:

rm -f /boot/initramfs-旧版本.img
rm -f /boot/vmlinuz-旧版本
dracut -f /boot/initramfs-$(uname -r).img $(uname -r)

RHEL 系可以用 package-cleanup --oldkernels --count=2 保留最近两个内核、清掉更老的,比手删安全。Debian 系用 apt autoremove --purge。清理完再跑一次 update-grub 或 grub2-mkconfig -o /boot/grub2/grub.cfg 让 GRUB 重新扫描菜单项。

实战救援:用 GRUB 命令行备份出数据

有一类最棘手的情况是:initramfs 彻底坏了,但里面的数据还在,服务器又进不去系统,而我们只想把数据拷出来。这时候不要急着重装,用 GRUB 命令行手动引导一次救援盘或直接手工挂根。

在 GRUB 菜单界面按 c 进入命令行模式,先找到可用的分区:

ls
ls (hd0,gpt2)/

GRUB 会列出它识别到的磁盘和分区,逐个 ls 看哪个分区里有 /boot。找到后手动指定内核和 initramfs 加载:

set root=(hd0,gpt2)
linux /vmlinuz root=/dev/mapper/vg0-root
initrd /initramfs.img
boot

如果连这一步都失败,最稳妥的做法是从云控制台挂载一个救援 ISO 进去,从救援系统里 vgchange -ay 激活 LVM、mount 上原来的根分区,把数据 tar 出来或者直接修复原系统的 initramfs。救援盘里可以 chroot 进原系统再跑 dracut / update-initramfs,效果和在正常系统里一样。

真实复盘:一次内核升级后的"随机不启动"

我管过一台 CentOS 7 的物理机,它开始出现一个诡异现象:重启后有时候能进系统,有时候卡在 Waiting for root device,概率大概三七开。反复重启几次又能进去,进去后一切正常。这种"间歇性"故障最让人头疼,因为它不符合"配置错了就必然失败"的规律。

排查的突破口是:故障只在冷启动时出现,热重启几乎必成功。这提示我们问题可能和 initramfs 的模块加载顺序有关。逐个查看发现,这台机器的根分区在一块 mdadm 软件 RAID 上,而 initramfs 里同时存在较新的 md_mod 模块和一处旧的、被某个自定义 hook 塞进去的重复模块,两者加载顺序不确定——冷启动时如果先加载了旧模块、磁盘枚举慢了一点,组装阵列就会超时放弃。

根因是之前有人为了加驱动,在一个自定义的 dracut 配置里写了 force_drivers,把一个早就过时的模块强塞进了 initramfs,和系统自带的模块打了架。删掉那个自定义配置、用 dracut -f 干净重建一次之后,连续冷启动十次全部正常。

这给的教训是:initramfs 是手工痕迹最容易残留的地方。每次排查 initramfs 问题,都要先看看 /etc/dracut.conf.d/、/etc/initramfs-tools/hooks/、/etc/initramfs-tools/conf.d/ 里有没有前人留下的自定义内容。这些东西平时不生效、不报错,只在启动的关键路径上埋雷。

五个必须记住的要点

  • 改根存储结构后必须重建 initramfs:LVM、RAID、LUKS、换控制器驱动,任何一项动了都要 dracut -f 或 update-initramfs -u,否则下次重启起不来。
  • 重建前先 lsinitramfs | grep 确认关键工具在,根在 LVM 上就搜 LVM 工具、在加密盘上就搜 cryptsetup,缺了绝对不能重启。
  • /boot 空间是隐形炸弹:内核升级静默失败大多是因为 /boot 满了,定期清理旧内核而不是等出事。
  • UUID 漂移是克隆/迁移后的头号杀手:用 blkid 对比 fstab,改完必重建 initramfs。
  • 改引导相关配置前保留一条后路:改 initramfs 前确认机器有带外管理或能挂救援盘,最好是先备份一份能启动的旧 initramfs 文件到 /boot 之外的地方。

总结

initramfs 平时藏在启动流程里无人问津,但它一旦出问题,服务器就直接变成一块砖——而且往往是在你最不能停机的时刻。理解它"临时根、只为挂真根"的本质,掌握 dracut 与 update-initramfs 两条工具链,记住"改根存储必重建、重建前必核对工具、重启前必留后路"这三条纪律,就能把这类故障从"灾难"降级成"计划内的维护动作"。对个人站长来说,这些知识不会天天用到,但用到的那一天,就是保住整台服务器的那一天。

Last modification:October 14th, 2026 at 12:25 pm

Leave a Comment