云盘扩容后重启直接卡在 dracut rescue shell 或 grub rescue> 提示符——这不是引导配置错了,是分区表本身被抹掉了。常见于腾讯云/阿里云控制台点完「扩容」就立刻 reboot,没等系统重读分区表,或者扩容后未执行 partprobegrowpart 就强行重启。CentOS 7、Anolis OS 8.8、Rocky Linux 9.3 都踩过这坑,尤其用 LVM 的环境,lsblk 里只显示整块盘(如 /dev/vda),连 /dev/vda1 都没了。

先别急着重装。进急救模式(比如腾讯云「使用救援镜像启动」或阿里云「VNC 进入救援模式」),执行 lsblk。如果输出里没有分区条目,或 fdisk -l无效分区表,基本可断定分区表已损毁。注意:此时 /dev/vda1 可能仍存在但无法挂载,mount 会报 结构需要清理——不是文件系统坏了,是内核压根没识别出这个分区。

数据无价。修复前务必先做磁盘镜像:dd if=/dev/vda of=/mnt/rescue/vda-backup.img bs=1M count=10240(至少备份前 10G,含 MBR/GPT 头和分区表)。别跳这步,gdisk 一敲错,w 一下就真没了。

我第一次遇到这情况时心里真没底。腾讯云控制台里点了扩容,等了不到一分钟就直接重启,结果机器就再也起不来了。那时候还不懂 partprobe 是干嘛用的,只觉得“扩容不应该是云厂商帮我搞定吗?”后来才知道,云控制台的“一键扩容”其实只是修改了磁盘后端的大小,操作系统层面的分区表需要自己刷新——它不会自动帮你跑 growpart。所以下次扩容完,别急着 reboot,至少在重启前执行一下 partprobe 确认内核认到了新边界。

为什么我弃用了 testdisk 而选 gdisk 来重建 GPT

别被名字骗了——testdisk 虽然名气大,但它对 GPT 分区表的恢复逻辑偏保守,尤其在云服务器扩容后扇区偏移错乱时,常把原 /dev/vda1 识别成两个碎片分区,UUID 和起始扇区全对不上。而 gdisk 是为 GPT 原生设计的,它的 r(recovery)模式能扫描整个磁盘,比对备份 GPT 头、LBA 区域和分区项签名,直接重建出原始布局。testdisk 更适合 MBR 时代的物理盘恢复,云硬盘这场景我试过两次都翻了车——一次它把原本 40G 的根分区拆成了两个 20G 的“碎片”,另一次直接把 GPT 备份头标记成了损坏。从那以后我就不再信任它处理云盘扩容后的场景了。

进救援系统后确认盘符(通常是 /dev/vda),执行:

gdisk /dev/vda
Command (? for help): r
Recovery/transformation command (? for help): i
Partition size is 2097152 sectors (1024.0 MiB)
First sector: 2048
Last sector: 419430399
Partition unique GUID: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
...
Recovery/transformation command (? for help): w

i 会显示扫描到的最可信分区结构;w 写入前它会校验 LBA 位置是否与当前磁盘大小兼容——这点在云盘扩容后特别关键。写完立刻 synclsblk 就该看见 vda1 回来了。不是工具越老越好,是得匹配你的分区表类型。GPT 环境下,gdisk 的恢复逻辑更贴近云厂商底层扩盘行为,它知道怎么处理那些“磁盘尾部多了几 GB 但分区表却没更新”的尴尬局面。

Using gdisk recovery mode to rebuild GPT partition table

分区回来了但启动还是崩?chroot 里要干三件事

分区表回来了,lsblk 能看到 vda1 了,但先别急着敲 reboot。这时候重启,十有八九要翻车——initramfs 里存着的磁盘 UUID 还是扩容前的老号码,分区表重建以后内核的设备映射早变了,它根本对不上根文件系统。挂载这事得按顺序来,错一步都不行。先把 /dev/vda1 挂到 /mnt,要是 /boot 是单独分区(用 fdisk -l 看一眼,类型 EF02 或者分区大小才几百 MB 那种),那就得先把 /dev/vda2 挂到 /mnt/boot。完事之后,再把虚拟文件系统绑上去。

mount /dev/vda1 /mnt
mount /dev/vda2 /mnt/boot   # 如果 /boot 是独立分区
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys

有人会漏掉 /run 或 /sys/firmware/efi,但 CentOS 7/8 和 Anolis OS 其实不需要——它们的 dracut 不依赖这些。Arch 系才需要额外 bind /run。chroot 进去之后第一件事:重新生成 initramfs

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

dracut 默认会用当前内核版本号找 /lib/modules/$(uname -r) 下的驱动模块。如果扩容后内核没变,这步不会报错。但它有个坑:如果你之前手动改过 /etc/dracut.conf.d/ 里的 add_drivers,重建时会覆盖自定义配置。我习惯先 cp /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak 再跑 dracut。然后是 GRUB。别信 grub-install 那句“安装成功”——它可能只装了 core.img,没更新 /boot/grub2/grub.cfg。

grub2-mkconfig 会扫描 /boot 下的内核文件、读取 /etc/default/grub 里的参数、以及分区 UUID。如果分区表重建后 UUID 没变(gdisk 恢复时保留了原 GUID),这步就顺滑。要是变了,就得手动改 /etc/default/grub 里 GRUB_CMDLINE_LINUX 那行的 root=UUID=xxx。exit 退出 chroot,umount 所有挂载点(顺序反了会报 target is busy,得用 umount -l 强制卸载)。重启前最后看一眼:cat /boot/grub2/grub.cfg 里 root 指向的 UUID 是不是 lsblk -f 里 /dev/vda1 的 UUID。对得上,才能安心 reboot。

这里最磨人的不是命令本身,而是你不知道该信谁——dracut 没报错但启动时还是 kernel panic,grub-install 说成功却卡在 grub rescue>。唯一的办法就是逐条排查:initramfs 里有没有硬盘驱动模块(lsinitrd | grep virtio),grub.cfg 里的分区号对不对(hd0,msdos1 还是 hd0,gpt1)。别慌,线性排查,一定能出。我有一回排查了快两个小时,最后发现只是 /etc/fstab 里的 UUID 和实际对不上——一个字母的大小写写错了,偏偏 blkid 输出的是小写,fstab 里是大写。

Mounting root partition and chroot for initramfs rebuild

重启还卡死?三个藏得最深的静默故障点

reboot 后黑屏、卡在 dracut emergency shell,或者直接进 grub rescue>?别急着重装。先插回 LiveCD,chroot 进去——这次不是修 GRUB,是查三个藏得深的“静默故障点”。

UUID 没变?fstab 可不认账

gdisk 重建分区表时保留了原 GUID,blkid 输出的 UUID 看似没动。但 /etc/fstab 里那行 root 分区可能还锁着扩容前旧分区的 UUID(比如你用 dd 备份过 /dev/vda1,恢复时没清缓存)。执行:blkid | grep vda1cat /etc/fstab | grep vda1,两行 UUID 对不上?立刻改 fstab,别信“应该一样”的直觉。这个问题我见过三次,每次都是“我觉得 UUID 没变所以懒得查”——然后多花半小时 debug。

selinux 上下文全乱套了

CentOS 8+/Anolis OS 8+ 默认启用 selinux。chroot 里没重挂 /sys/fs/selinux,restorecon 就会报 无效参数。必须先:

漏掉任何一环,/usr/lib/systemd/systemd 就拿不到 execmem 权限,启动直接卡死在 “Started Setup Virtual Console”——这破提示能骗你折腾半天。之前帮同事排查,他死活咬着 GRUB 坏了,重装了三四遍无果,最后发现只是 selinux 上下文没重置。重启前跑一遍 restorecon,一分钟的事,偏偏最容易忘。

initramfs 里塞的是哪个内核?

dracut 用 uname -r 找模块,但扩容后你可能刚升级过内核,而 /boot/vmlinuz-$(uname -r)/lib/modules/$(uname -r) 实际是旧版残留。运行 ls /boot/vmlinuz* 对比,再手动指定:

dracut --force --kver [实际内核版本号] /boot/initramfs-[实际内核版本号].img

比默认命令多扫一遍模块依赖,尤其是碰到 virtio-scsi 驱动,少扫一次就死活起不来。这三处改完再 reboot,盯着屏幕等它走完——最磨人的不是敲命令那几分钟,是不知道它能不能一路绿灯跑到登录界面。每次看到熟悉的 login 提示符蹦出来,自己就念叨一句:下次扩容前一定老老实实备份分区表,真不想再来一回。

其实扩容前花两分钟备份,比修一晚上划算

聊了这么多怎么救,其实最想说的是——很多分区表损坏,完全可以在扩容前用一条命令预防掉。我自己踩过两次坑之后,现在每次扩容前都会做两件事:快照和分区表备份。快照是云厂商控制台里点一下的事,不花钱买安心?分区表备份更简单,根本不用进系统:

sgdisk --backup=table.backup /dev/vda

这个 文件只有几 KB,存到对象存储或者本地都行。扩容完万一 partprobe 报错,直接 sgdisk --load-backup=table.backup /dev/vda 就能回滚。比你手动算扇区号快一百倍。很多人在云控制台点完“扩容”就直接 reboot,这是最危险的。内核还在用旧的分区表跑,重启时 GRUB 读到一半发现分区边界对不上,直接卡住。正确的做法是先跑:

partprobe /dev/vda
# 或者
partx -a /dev/vda

让内核重新读取分区表。如果报错,立刻停下来查原因,而不是赌一把重启。阿里云、腾讯云、华为云的控制台里其实都有“在线扩容”按钮,走那个流程会自动处理 resize2fs 和分区表刷新,比自己手动操作少很多隐患。备份不是给“可能出事”准备的,是给自己“能安心睡觉”准备的。下回扩容前花两分钟做这件事,比修一晚上 GRUB 划算太多。