RTX 3060 离线工作站 NVIDIA 535 驱动更新与 initrd 启动故障复盘
摘要
本文记录了一台配备 NVIDIA RTX 3060、无法直接连接互联网的 Ubuntu 工作站在离线更新 NVIDIA 535 驱动时遇到的完整故障链。
驱动安装过程中,NVIDIA DKMS 模块已经针对当前内核成功编译,nvidia-smi 和 GPU 驱动在尚未断电的运行环境中也能正常使用。但独立的 /boot 分区只有约 94 MB,放不下当时生成的未压缩 initramfs。为把镜像放进 /boot,后来采用了在其他目录生成 initramfs、手工压缩并复制到 /boot 的办法。
在此之后,物理机所在环境发生了本次事件中唯一一次断电。机器重新上电后无法通过 SSH 连接。将物理机运回检查后,确认此前手工处理的 initrd 无法正常引导系统。结合故障发生顺序,最可能的原因是手工压缩得到的 initramfs 没有形成启动器能够正确识别和解析的有效镜像,或其内部归档本身并不完整。
考虑到原系统已经同时存在 /boot 容量过小、离线软件包依赖混杂以及启动镜像可信度不足等问题,维护方最终选择备份业务文件,重装更新版本的 Ubuntu,并取消独立 /boot 分区,让 /boot 直接使用根文件系统的空间。
机器与任务背景
这台机器是一台离线 Ubuntu 工作站,硬件和当时的软件环境如下:
| 项目 | 信息 |
|---|---|
| GPU | NVIDIA GeForce RTX 3060 |
| 系统 | Ubuntu 24.04 系列环境 |
| 当前内核 | 6.14.0-27-generic |
| 目标驱动 | NVIDIA 535 系列 |
| DKMS 包版本 | 535.288.01-0ubuntu0.24.04.2 |
| 安装方式 | 在联网机器下载 .deb,复制到离线机器安装 |
/boot |
独立分区,容量约 94 MB |
驱动更新的目标,是让离线工作站能够正常识别 GPU 并运行依赖 CUDA/NVIDIA 驱动的程序。
由于机器无法直接联网,安装包先在另一台联网设备上准备,再复制到离线环境。实际安装时采用了较为直接的方式:
sudo dpkg -i *.deb |
同一类操作曾在另一台机器上成功,因此(对方)最初认为可以复用。但两台机器的内核版本、现有软件包版本、图形栈状态和 /boot 剩余空间并不相同,最终结果也完全不同。
故障时间线
将离线包整体交给 dpkg 安装
(对方)复制到离线机器的目录中不只有 NVIDIA 驱动的核心软件包,还混有一部分更广泛的系统组件,例如:
- NVIDIA 用户态库和 Xorg 驱动;
mesa-vulkan-drivers、mesa-libgallium;libdrm2;- Wayland、Vulkan 相关包;
polkitd、pkexec;- 可能影响内核的
linux-headers、linux-image、linux-modules类软件包。
执行 dpkg -i *.deb 后,这些包被同时送入安装流程。由于 dpkg 本身不会像 apt 那样自动解决所有依赖关系,离线环境又无法即时下载缺失版本,系统出现了局部依赖不一致。
当时的错误涉及:
libnvidia-gl-535未完成配置;xserver-xorg-video-nvidia-535未完成配置;libnvidia-fbc1-535未完成配置;- Mesa 组件与
libdrm2版本不匹配; pkexec与polkitd版本不一致。
这使原本的“更新显卡驱动”扩大成了“NVIDIA 驱动、桌面图形栈和部分系统组件的混合升级”。
(其实当时排查到这一步的时候就应该让对方加钱了的,不然也不会那么折磨自己)
NVIDIA DKMS 模块编译成功
尽管用户态软件包的配置过程出现问题,nvidia-dkms-535 实际上已经成功针对当前内核:
6.14.0-27-generic |
编译并安装了 NVIDIA 内核模块,包括:
nvidia.ko.zst |
模块被安装到:
/lib/modules/6.14.0-27-generic/updates/dkms/ |
这一步非常关键。它说明此次故障并不是简单的“驱动与 RTX 3060 不兼容”或“DKMS 无法编译”,而是发生在后续的启动镜像更新阶段。
/boot 空间不足,update-initramfs 失败
DKMS 完成后,软件包安装脚本需要更新 initramfs,将启动阶段可能需要的内核模块和脚本写入:
/boot/initrd.img-6.14.0-27-generic |
但是,这台机器的 /boot 独立分区只有约 94 MB。生成新的 initrd 时不仅需要最终文件的空间,还可能同时保留旧镜像、临时文件和新镜像,因此实际空间需求通常高于最终 initrd 的大小。
安装过程最终出现:
No space left on device |
于是形成了下面的状态:
NVIDIA DKMS 编译成功 |
临时绕过 update-initramfs
由于每次执行:
sudo dpkg --configure -a |
都会再次触发 update-initramfs,而 /boot 的容量问题尚未解决,因此当时曾临时阻止安装脚本自动调用 update-initramfs,希望先让 dpkg 完成其他软件包的配置。
这一做法可以暂时绕过安装脚本,但不能修复启动链。它可能让系统形成一种危险的“半完成状态”:
| 位置 | 可能状态 |
|---|---|
/lib/modules/6.14.0-27-generic/ |
已有新 NVIDIA 模块 |
| dpkg 数据库 | 部分包已经配置,部分包仍异常 |
/boot/initrd.img-* |
仍是旧镜像、不完整镜像或手工处理镜像 |
| 当前运行内核 | 可能可以加载磁盘上的新模块 |
| 下一次启动 | 仍依赖未经可靠验证的 initrd |
断电前,NVIDIA 驱动已经能够正常使用
在机器仍保持运行、尚未发生断电时,NVIDIA 驱动已经能够正常加载:
nvidia-smi |
可以正常输出,GPU 驱动也能正常使用。这说明当时正在运行的内核能够找到并加载 /lib/modules 中的 NVIDIA 模块,用户态 NVIDIA 库也已经达到可用状态。
但这时机器并没有使用后来手工压缩的 initramfs 完成过一次重新启动。换句话说,已经验证的是“当前运行内核中的驱动”,尚未验证的是“下一次开机所需的启动镜像”。
未压缩的 initramfs 放不进 /boot
由于 /boot 容量不足,当时尝试在其他目录生成 initramfs。生成结果没有按预期压缩,未压缩镜像又无法放入约 94 MB 的 /boot,因此随后采用了手工 gzip 压缩、再复制到 /boot 的办法。
检查时,生成文件曾被 file 识别为 ASCII 类型。这里需要说明,initramfs 的内部通常是 cpio 归档,而 cpio newc 使用 ASCII 字段表示文件头。因此:
ASCII cpio archive |
并不必然等于“普通文本”或“已经损坏”,它也可能只是一个尚未经过 gzip、zstd 等算法压缩的 cpio 归档。
真正需要关注的是归档能否被完整解析、是否包含启动所需的模块、外层压缩格式是否符合启动工具的预期,以及复制过程是否完整。手工执行:
gzip initrd.img |
只能在已有归档外增加压缩层。如果内部 cpio 已经截断、缺少文件,或者最终文件名、压缩格式及 GRUB/initramfs 工具链之间不匹配,gzip 本身并不能把它变成可启动镜像。
当时使用过:
lsinitramfs /boot/initrd.img-6.14.0-27-generic |
来检查归档结构,并遇到过归档非正常终止的迹象。
但即便完整解析,也只能证明归档结构大体正常,仍不能完全证明其中包含根文件系统、存储控制器、LVM/RAID、文件系统等启动所需模块。真正的启动验证最终仍需要重启或在受控环境中测试。
使用 MODULES=dep 缩小镜像
为了减小 initrd,还尝试过修改:
/etc/initramfs-tools/initramfs.conf |
把:
MODULES=most |
改为:
MODULES=dep |
dep 是 dependency 的缩写,只收录生成镜像时根据当前硬件推断出的必要模块;most 则会包含更多常见的存储、文件系统和硬件模块。
MODULES=dep 确实可能显著减小镜像,但代价是降低硬件变化、磁盘迁移和异常救援时的兼容性。它可以缓解空间压力,却不能从根本上解决只有 94 MB 的 /boot 带来的长期维护风险。
(这一步也可能是重启出问题的原因之一)
一次断电后,机器无法通过 SSH 连接
在驱动已经正常使用、手工压缩的 initramfs 已经放入 /boot 之后,物理机所在环境发生了一次断电。这是本次事件中唯一一次断电。重新上电后,机器无法通过 SSH 连接。
当时只能观察到远程连接异常,无法立即区分以下情况:
- 机器根本没有完成启动;
- 系统进入 initramfs/emergency shell;
- 根文件系统没有成功挂载;
- 系统已经启动,但网络或
sshd没有运行; - IP 地址或网络路径发生变化。
机器能够 ping 通也不能证明 Ubuntu 已经完整启动,因为 ICMP 应答、SSH 服务和用户空间启动是不同层次的问题;某个 IP 有响应也不能单独证明响应者就是目标系统。
由于现场缺少可用的带外管理入口,最终只能将物理机运回进行本地检查。
本地检查确认手工处理的 initrd 无法启动
将机器运回并接入本地显示和维护环境后,确认此前生成并手工压缩后放入 /boot 的 initrd 无法正常引导系统。
这解释了为什么断电前 nvidia-smi 能够正常工作,而断电后却无法重新启动:
- 驱动运行时验证的是已经启动的内核和根文件系统中的模块;
- 断电后的重新启动验证的是 GRUB、内核、initrd、存储驱动和根文件系统挂载这一整条启动链;
- NVIDIA 驱动已经可用,并不能反向证明 initrd 一定完整、可启动。
这次断电并不是把一个已经验证可启动的系统突然“弄坏”,而是首次迫使机器使用手工压缩后的 initrd 完成完整启动,从而暴露了该镜像不能正常工作的隐患。
由于没有保留下足以唯一确定格式错误位置的完整启动日志,所以更严谨的结论是:手工压缩后的 initrd 已被现场确认无法启动;它没有被启动流程正确识别或解析,是结合时间线得到的最可能解释。
最终选择备份文件并重装系统
理论上仍可以通过 Live USB/DVD 挂载原系统、进入 chroot、修复软件包状态、重新生成 initramfs 并修复 GRUB。但维护方需要同时面对:
/boot独立分区容量过小;- initrd 已经确认无法启动;
- NVIDIA 与部分图形栈软件包版本混杂;
dpkg曾经历临时绕过和未完成配置;- 即使修复当前镜像,未来内核或驱动升级仍可能再次耗尽
/boot; - 离线环境使依赖修复和版本校验更加困难。
综合修复成本、未来维护成本和系统可信度后,对方最终选择:
- 备份原系统中的业务文件和数据;
- 重装更新版本的 Ubuntu;
- 不再创建独立
/boot分区; - 让
/boot作为根文件系统中的普通目录,直接使用根分区的可用空间。
这一选择没有继续保留原系统的包和配置状态,但换来了更清晰的软件包基线和更简单的磁盘布局,也从结构上消除了 94 MB 独立 /boot 对后续内核、initramfs 和 NVIDIA 驱动升级的限制。
根因分析
直接原因:initrd 无法正常启动
机器最后无法在断电后恢复服务,直接原因是此前生成、手工压缩并写入 /boot 的 initrd 不能正常完成启动。结合操作顺序,最可能的问题是该镜像没有被启动流程正确识别或解析,但缺少完整启动日志,无法进一步唯一确定是外层压缩格式、内部 cpio 结构、文件完整性还是启动配置不匹配。
首要基础问题:独立 /boot 容量过小
约 94 MB 的 /boot 已经难以容纳现代 Ubuntu 内核、initrd、GRUB 文件以及更新过程中的临时空间。即使只保留一个内核,也可能在重建镜像时触发空间不足。
放大因素:离线安装包范围失控
使用 dpkg -i *.deb 一次性安装整个目录,使 NVIDIA 驱动更新与 Mesa、Vulkan、Wayland、libdrm、polkit 甚至内核相关包混在一起,扩大了依赖冲突范围,也降低了对实际变更的可控性。
风险因素:绕过标准 initramfs 流程
临时阻止 update-initramfs、在其他目录生成镜像、手工压缩和复制等操作,在应急阶段各有用途,但本次手工压缩后的镜像没有在断电前完成启动验证,最终在唯一一次断电后的重新启动中暴露出无法启动的问题。
验证缺口:把运行时验证当成启动验证
nvidia-smi 正常只能证明当前会话中的驱动栈工作。它不能证明:
- GRUB 指向正确内核和 initrd;
- initrd 归档完整;
- initrd 中包含必要的存储和文件系统模块;
- 根文件系统可以在下一次冷启动时挂载;
- 网络和 SSH 服务能够在重启后恢复。
安装 NVIDIA 驱动为什么会影响启动
安装 NVIDIA 驱动并不必然升级 Linux 内核,但通常会触发:
- 安装 NVIDIA 用户态库;
- 通过 DKMS 为当前内核编译模块;
- 执行
depmod更新模块依赖; - 调用
update-initramfs更新启动镜像; - 某些情况下更新 GRUB 配置。
因此:
不升级内核,不等于不更新
/boot;驱动模块编译成功,也不等于驱动安装和启动链已经完成闭环。
如果离线目录中还包含:
linux-image-* |
那么安装过程还可能真正新增或更换内核。Wayland、Mesa、Vulkan 和 libdrm 等包则可能把一次驱动更新扩大为桌面图形栈升级。
为什么取消独立 /boot 分区
独立 /boot 并不是错误设计。在全盘加密、复杂存储结构、特定固件限制或集中运维规范下,它仍可能是必要的。但这台普通工作站的独立 /boot 只有约 94 MB,已经成为系统升级的固定瓶颈。
取消独立 /boot 后:
/boot仍然存在,但只是根文件系统中的一个目录;- 内核和 initrd 可以使用根分区中更充足的空间;
- 不再需要单独调整
/boot分区大小; - 内核和驱动更新时更不容易因为几十或几百 MB 的固定上限失败;
- 磁盘和挂载关系更简单,降低了后续维护复杂度。
需要强调的是,“取消 /boot 分区”不是删除 /boot 目录,而是不再为它设置单独的文件系统和挂载点。
重新执行推荐的流程
安装前确认系统基线
uname -r |
必须先确认:
- 当前准确的 Ubuntu 版本和架构;
- 正在运行的内核;
/boot的总容量和剩余空间;- 是否已经存在未配置软件包;
- 旧 NVIDIA 驱动和 DKMS 状态。
在相同发行版环境准备离线包
联网机器或容器环境应与离线机器保持相同的:
- Ubuntu 发行版;
- CPU 架构;
- 软件源版本;
- 目标驱动分支。
保留包清单和版本,不要把下载目录中所有历史 .deb 混在一起。安装时也应使用明确的软件包列表,而不是直接执行不受控的:
dpkg -i *.deb |
不随意混入图形栈和内核包
除非依赖关系明确要求,否则不要因为名称与显卡相关,就同时升级:
- Mesa;
- Wayland;
- Vulkan;
- libdrm;
linux-image;linux-modules;- 与当前内核无关的
linux-headers。
为 initramfs 留出充足空间
驱动安装前先检查:
df -h /boot |
需要考虑的不只是最终 initrd 大小,还包括旧镜像、临时文件和包管理器更新过程。对于新装系统,不应再为现代 Ubuntu 配置几十 MB 级别的独立 /boot。
完成标准重建与一致性检查
sudo update-initramfs -u -k "$(uname -r)" |
完整解析 initrd:
lsinitramfs "/boot/initrd.img-$(uname -r)" >/dev/null |
把重启测试纳入验收
驱动更新后的验收不应止于:
nvidia-smi |
还应包括:
- 确认当前内核与目标内核一致;
- 确认 initrd 可以完整解析;
- 确认 GRUB 配置已更新;
- 准备本地控制台、Live USB/DVD 或带外管理;
- 执行一次受控重启;
- 条件允许时执行一次完整关机再上电;
- 验证 SSH、网络、显示管理器和
nvidia-smi; - 确认业务程序实际调用 GPU。
Live 环境能够做什么
如果只是 initrd 或 GRUB 损坏,并不一定必须重装。Live USB/DVD 可以作为外挂维护环境,完成:
- 挂载原根分区和
/boot; - 检查或修复文件系统;
- 绑定
/dev、/proc、/sys、/run; chroot进入原系统;- 修复
dpkg状态; - 重新生成 initramfs;
- 更新或重新安装 GRUB。
典型思路如下,实际设备名必须根据现场分区布局调整:
sudo mount /dev/ROOT_PARTITION /mnt |
本次最终没有继续修复旧系统,是因为重装更新版本 Ubuntu 并重新设计分区,比继续维护一个包状态复杂、/boot 结构先天受限的旧系统更可控。
最终结论
这次事件可以概括为三句话:
- NVIDIA 535 的 DKMS 模块确实编译成功,RTX 3060 也曾在运行中的系统里正常工作。
- 真正没有完成闭环的是手工压缩后的 initramfs 和启动链;唯一一次断电后的重新启动暴露了这个潜伏问题。
- 最终通过备份数据、重装更新版本 Ubuntu 并取消独立
/boot分区,彻底移除了旧系统的结构性风险。
驱动更新不是看到 nvidia-smi 正常就结束。对 Linux 物理机而言,真正的完成标准应当是:软件包状态一致、内核模块正确、initrd 可用、GRUB 正确,并且机器能够在受控重启和完整断电后恢复网络、SSH 与 GPU 业务。