Ubuntu 升级中途失败如何恢复?修复 dpkg 与系统引导
Ubuntu 24.04 升级 26.04 失败导致系统中断?本文教你如何通过 screen 会话恢复进程、修复 dpkg 锁定状态、修正软件源配置,以及在无法启动时判断何时应直接恢复快照。
Ubuntu 升级中途失败:先确认症状
Ubuntu 从 24.04 升级到 26.04 失败后,服务器会处于四种状态之一,每种状态的修复方法各不相同。升级程序可能仍在某个你已失去连接的 screen 会话中运行。dpkg 可能因软件包配置到一半被终止,导致 apt 拒绝执行任何安装、删除或升级操作,尽管 apt update 和 apt show 等只读命令仍可运行。apt 软件源可能已指向 26.04,但已安装的软件包仍为 24.04 版本。或者,服务器可能完全无法启动。在输入任何命令前,请先确定你处于哪种状态,因为针对一种状态的修复操作可能会使另一种状态恶化。
有一条规则适用于所有四种情况。在确认 dpkg 的状态之前,请勿重启。在软件包替换过程中重启,会将可修复的 dpkg 中断转变为本指南末尾提到的无法启动的情况。此外,在第一个 apt 或 dpkg 进程可能仍在运行时,不要启动第二个进程,因为两个进程同时写入软件包数据库会导致数据库损坏。
本指南假设你已按照 24.04 到 26.04 升级指南 的要求,并在开始前创建了快照。如果你未创建快照,请在阅读启动故障章节时注意这一点,因为快照是应对最坏情况的方案。
升级程序还在运行吗?
许多被报告为失败的升级实际上仍在运行。SSH 会话断开,终端变为空白,而升级程序在没有您参与的情况下继续运行。
do-release-upgrade 正是为此设计的。当它在服务器默认的文本界面下运行时,会自动封装在一个 GNU screen 会话中,因此即使启动它的终端丢失,升级过程依然能够存活。此外,当它检测到是从 SSH 会话启动时,会主动提供在另一个端口(默认 1022)启动第二个 sshd 的选项,以便在软件包替换期间主 SSH 守护进程崩溃时,您仍能登录。这两个事实在此刻至关重要。
重新通过 SSH 连接并查找 screen 会话。升级程序在 sudo 下运行,因此其 screen 会话归 root 用户所有,以普通用户身份执行 screen -ls 将无法列出该会话。
sudo screen -lsscreen -ls 会标记每个会话是已连接(attached)还是已断开(detached)。如果列出了会话,请重新连接到它。如果由于死掉的 SSH 连接未释放而显示为已连接,请先使用 -d 强制断开该过期的连接。
sudo screen -d -r如果列出了多个会话,请在 -r 后加上 screen -ls 中的会话名称。不要指望 sudo do-release-upgrade 能自动为您重新连接。查找现有 screen 会话的代码位于升级程序下载的脚本中,而在下载任何内容之前,如果发现有待更新的软件包(分阶段更新除外),do-release-upgrade 会直接退出并提示“请在升级前安装当前版本的所有可用更新”,这正是系统处于软件包替换中途时的状态。重新连接后,您将回到正在运行的升级进程中,它通常正停留在关于配置文件变更或服务重启的提示符处。回答提示并让其完成即可。
如果您按照升级指南的建议在 tmux 中启动了升级,请先使用 tmux attach 重新连接 tmux。screen 会话运行在该 tmux 面板内,因此您可以直接看到升级进度。如果面板中仅显示 shell 提示符,说明升级程序已不在那里运行,此时应执行 sudo screen -ls 进行下一步检查。
如果主 SSH 端口拒绝连接,请尝试备用端口:ssh -p 1022 user@host。该守护进程仅在升级期间存在,因此如果两个端口都无法进入,请使用服务商提供的控制台。在控制台中,sudo ss -ltnp 可以显示 sshd 进程正在监听哪些端口,sudo ufw status 可以确认防火墙是否放行了备用端口。
当不存在 screen 会话且控制台也没有任何等待响应的提示时,说明升级确实已经停止。在操作软件包数据库之前,请确认没有任何进程仍在运行:
ps -eo pid,etime,cmd | grep -iE '[u]pgrade|[d]pkg|[a]pt'如果结果为空,说明 dpkg 处于空闲状态,您可以继续进行修复。如果 dpkg 或 apt 进程运行时间很长且没有可重新连接的 screen 会话,说明它已挂起。请等待几分钟,检查控制台是否有无人响应的 debconf 问题,确认后再将其终止。在进程持有锁文件时,切勿删除 /var/lib/dpkg/ 或 /var/lib/apt/lists/ 下的锁文件。该锁是防止两个写入进程同时破坏软件包数据库的唯一机制。
dpkg 中断,apt 拒绝运行
如果在解压软件包和运行其配置脚本之间终止 dpkg,该软件包会被标记为未完成状态,并在 /var/lib/dpkg/updates/ 下的日志中留下未完成的条目。所有会获取 dpkg 锁的 apt 命令,也就是安装、删除或升级软件包的命令,都会先检查该日志,然后以 dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. 停止。不获取该锁的命令,例如 apt update、apt list、apt show 和 apt-cache,仍可正常运行,因此您仍然可以查看系统状态。无论 apt 还输出什么内容,第一步都相同。
sudo dpkg --configure -a这会完成所有已解压但尚未配置的软件包的配置步骤。该命令会按依赖顺序运行维护者脚本。在部分升级的系统上,这可能需要很长时间,因此在命令返回前不要中断它。如果命令在某个软件包处停止,它会输出软件包名称和失败的脚本。记下该名称。它就是阻止升级的软件包,下一节将说明如何读取其错误信息。
然后让 apt 修复中断留下的未满足依赖,因为部分软件包已升级,但它们所依赖的软件包尚未升级。
sudo apt --fix-broken install然后完成升级程序正在执行的升级:
sudo apt update
sudo apt full-upgrade请使用 full-upgrade,而不是 upgrade,因为发行版升级会删除软件包,而普通的 upgrade 会拒绝删除任何内容。确认前先阅读 apt 输出的摘要。删除少量软件包通常是正常的。但如果列表中包含 ubuntu-server、systemd、openssh-server 或内核软件包,则不正常。此时应选择否,并先查明 apt 为什么要删除这些软件包。
在执行其他操作前检查结果:
sudo dpkg --audit
sudo apt-get checkdpkg --audit 会列出所有仍处于损坏状态的软件包,apt-get check 会报告未满足的依赖。两者都应不输出任何内容。如果有输出,请运行 sudo apt autoremove,删除当前不再需要的自动安装依赖(标记为手动安装的软件包会保留,即使没有任何软件包使用它们),然后使用 cat /etc/os-release 确认发行版,再运行 sudo update-initramfs -u -k all 和 sudo update-grub,最后才能重启。
如何读取 /var/log/dist-upgrade 以找出阻止升级的软件包
升级程序会将所有内容写入 /var/log/dist-upgrade/。如果运行过多次,它会将之前尝试的日志移入以时间戳命名的子目录,因此请先检查 ls -la /var/log/dist-upgrade/,然后读取与失败运行相对应的目录。
main.log 是升级程序自己的日志。它记录运行所处的阶段,以及升级程序如何处理软件源。如果升级程序本身崩溃,Python 回溯也会记录在这里。请从末尾开始读取:最后几行会告诉您它停止时所处的阶段。如果末尾有回溯,说明失败的是工具,而不是软件包。
apt.log 保存依赖解析器的判断过程。内容较多,但当 apt 根本拒绝计算升级方案时很有用,因为这表示在处理任何软件包前就已经失败。如果升级已经进行到安装软件包的阶段,通常可以跳过此文件。
apt-term.log 是排查软件包失败时需要查看的文件。它会记录升级期间 dpkg 的终端输出,也就是原本会滚过屏幕的相同内容。文件末尾之前最后提到的软件包,就是升级停止时正在处理的软件包。如果维护者脚本失败,dpkg 对该脚本的报错,以及脚本上方紧邻的脚本自身错误,都会记录在这里。
sudo tail -n 60 /var/log/dist-upgrade/apt-term.log
sudo grep -in 'error' /var/log/dist-upgrade/apt-term.log | tail请与 /var/log/dpkg.log 交叉核对。该文件会记录 dpkg 执行的状态更改和操作,并为每项操作标记时间戳。tail -n 30 /var/log/dpkg.log 显示 dpkg 最后处理的软件包,以及当时对它执行的操作。只有在失败后没有再次运行 dpkg 时,这才是来自第二个来源的相同结论。运行 dpkg --configure -a 或 apt --fix-broken install 后,它们的条目会出现在末尾,因此请向上滚动,查找升级停止时对应时间戳的行。
服务器在此阶段发生的多数失败都来自少数几类原因。软件包会在 postinst 中重启某项服务,但该服务因您自定义的配置文件而无法启动;此时,该服务的 systemctl status 和 journalctl -xeu 会指出它不接受的配置行。磁盘已满,最常见的是 /boot 被旧内核占满,或 /var 被 apt 的软件包缓存占满;df -h / /boot /var 会显示相关信息。即使 apt 处于其他故障状态,sudo apt clean 仍可清空缓存;du 显示未满但磁盘报告已满 的情况另有解释。升级程序禁用的第三方软件源中的某个软件包,依赖一个 26.04 不再发布的库。被保持的软件包(apt-mark showhold)阻止了某项依赖升级。修复原因后,再次运行 sudo dpkg --configure -a。它会从停止的位置继续。
如果无论如何都无法配置某个软件包,且没有重要软件包依赖它,请将其删除,并在升级完成后重新安装:
sudo dpkg --remove --force-remove-reinstreq package-name
sudo dpkg --configure -a只能对您能够明确说明名称和问题的软件包使用此命令。不要对库或 ubuntu-server 依赖链中的任何内容使用它,因为强制删除会跳过检查,而这些检查本可以告知您还会破坏哪些内容。
源已切换,但软件包未切换
升级程序会在下载任何软件包前,先重写 apt 源。若此时中断,源会指向 26.04,而已安装的软件包仍处于混合状态。这种混合状态会使相关工具产生误判,也是 do-release-upgrade 现在可能坚持认为没有新版本的原因。
比较以下两个位置,确认系统当前指向哪个版本。/etc/apt/sources.list.d/ubuntu.sources 是 24.04 引入的 deb822 源文件,其中的 Suites: 行包含发行版代号。/etc/os-release 由 base-files 软件包写入,用于说明实际安装的发行版。
grep -E '^(Suites|Components):' /etc/apt/sources.list.d/ubuntu.sources
grep -E '^(VERSION_ID|VERSION_CODENAME)=' /etc/os-release
apt-cache policy base-files需要关注以下三种组合。源仍指向 24.04(代号为 noble),且 os-release 仍显示 24.04:升级程序尚未通过检查。读取 main.log 了解停止原因后,可以再次运行 sudo do-release-upgrade。只要仍有 24.04 更新待处理,或 /var/run/reboot-required.pkgs 指向 libc6、linux-base 或某个 linux-image 软件包,升级程序就不会启动。因此请运行 sudo apt full-upgrade;如果该文件要求先重启,请执行重启。源指向 26.04 的代号,但 os-release 仍显示 24.04:软件包替换已经开始但被中断。上一节介绍的 dpkg 修复流程,以 apt full-upgrade 结束,是完成升级的方法。在此状态下不要重新运行升级程序;由于仍有 26.04 软件包待安装,它会停止并要求先安装所有可用更新。源指向 26.04,且 os-release 显示 26.04:base-files 是已完成安装的软件包之一。系统现在会将自身标记为 26.04,即使大多数软件包实际上尚未完成升级。
最后一种组合最容易造成误判。do-release-upgrade 根据与 os-release 相同的信息判断当前发行版。如果该文件已经显示 26.04,工具就会查找高于 26.04 的版本;由于找不到更高版本,它会报告没有可用的新版本。工具回答的是关于 os-release 的问题,而 os-release 的内容是错误的。不要继续运行升级程序,改用 apt 完成升级:先运行 sudo apt update,再运行 sudo apt full-upgrade。后者会升级仍处于 24.04 版本的软件包,但会跳过被 hold 的软件包(apt-mark showhold 可列出这些软件包),并且可能改为删除某些软件包。因此请先阅读其摘要,然后运行 sudo apt autoremove。如果 os-release 仍显示 24.04,还应排除 do-release-upgrade 报告没有新版本的其他原因,例如 LTS 提示正在等待首个点版本发布。
升级程序还会禁用 /etc/apt/sources.list.d/ 下的第三方源,并为每个修改过的文件保留备份副本,在原文件名后添加后缀。运行 ls -la /etc/apt/sources.list.d/ 和 diff,分别将每个原文件与其备份进行比较,即可准确查看它执行了哪些修改。在 Ubuntu 软件包状态一致前,不要重新启用第三方源;之后也只能在确认供应商已发布适用于 26.04 的软件包后,逐个重新启用。如果 apt update 报告某个源被配置了多次,说明旧的 sources.list 条目和新的 ubuntu.sources 条目描述了同一个套件;deb822 重复源错误会说明应删除哪一个。
升级后服务器无法启动
重启时,未完成的升级往往会暴露问题。在 VPS 上,常见原因包括:安装内核时未生成 initramfs、从未重新生成 GRUB 配置、某个启动时单元依赖的软件包处于未完成配置状态,或者 dpkg 写入数据时磁盘已满。
在进行其他操作前,先打开服务商的控制台。控制台会显示启动停止的位置:GRUB 菜单、内核崩溃、等待回答的文件系统检查,或要求输入 root 密码的 systemd emergency shell。这个判断决定下一步操作。
如果出现 GRUB,请从高级选项子菜单启动之前的 24.04 内核。旧内核通常会一直保留,直到 autoremove 运行。使用旧内核启动系统后,执行 sudo dpkg --configure -a,并按照上一节完成其余修复;然后执行 sudo update-initramfs -u -k all 和 sudo update-grub,再重新尝试启动新内核。修复内核更新后无法启动的 VPS详细介绍了 GRUB 和 initramfs 相关操作。
如果进入 emergency shell,root 文件系统通常以只读方式挂载。重新挂载后执行相同的修复操作:
mount -o remount,rw /
dpkg --configure -a如果系统完全无法进入 shell,请启动服务商的救援镜像,挂载 VPS 磁盘,然后从 chroot 中修复。使用 lsblk 查找 root 分区,不要凭猜测确定分区名称。
lsblk
mount /dev/<root-partition> /mnt
for d in dev proc sys run; do mount --bind /$d /mnt/$d; done
chroot /mnt /bin/bash
dpkg --configure -a
apt --fix-broken install
update-initramfs -u -k all
update-grub
exit
reboot在 chroot 中耗费一小时之前,先将其与快照恢复进行比较。你在开始操作前已经创建了快照,而大多数服务商只需几分钟即可完成恢复。之后重新运行升级;在 VPS 上通常不到一小时。这次你已经知道需要提前修复哪个软件包。满足以下任一条件时,恢复快照通常更快:无法访问控制台或救援镜像、无法确定导致升级停止的软件包、有多个软件包卡在未完成状态,或者服务器上运行着有人正在等待使用的服务。只有在你明确知道发生故障的唯一原因,并且修复只需执行一条命令时,手动修补才更快。
恢复前,将 /var/log/dist-upgrade/ 从服务器复制出来;如果只能通过救援镜像访问服务器,就从救援镜像中复制。恢复操作会删除这些日志。如果你没有找出第一次失败的原因,第二次尝试会完全重复同样的失败。依赖快照前,值得阅读快照可以恢复和无法恢复的内容。快照会将整个磁盘回滚,包括创建快照后写入的所有数据。升级过程中这样做通常没有问题,但一周后再这样做就可能造成数据丢失。
应改为重建系统的三个迹象
有些升级不值得继续修复。恢复快照并重新执行升级的成本很低,但如果故障源于系统本身的状态,第二次执行也会失败。此时,正确的做法是使用全新的 26.04 镜像,再从备份恢复数据。出现以下三个迹象时,就说明应当这样处理。
第一,dpkg 自身的数据库已损坏。如果 dpkg --audit 或 apt-get check 完全无法读取 /var/lib/dpkg/status,而不是报告其中存在损坏的软件包,那么已安装软件包的记录就丢失了。Ubuntu 会在 /var/backups/(ls -la /var/backups/dpkg.status*)下保存每日副本,将最新的可用副本替换到原位置有时可以解决问题。但如果该副本与磁盘上的实际状态不一致,后续操作就只能依靠猜测,而每次 apt 执行都会继续建立在这个猜测之上。
第二,修复系统所需的工具本身已损坏。如果 apt 或 dpkg 因共享库被删除或部分替换而无法启动,或者 systemd 因自身的软件包处于半配置状态而无法启动单元,那么系统中就没有可用的软件包管理器来修复软件包管理器了。ldd /usr/bin/apt 可用于确认 apt 所需的库是否全部存在。有时可以从救援镜像中的 chroot 环境启动修复,但这通常比重建系统耗时更长。
第三,损坏软件包的列表没有减少。如果您循环运行 dpkg --configure -a 和 apt --fix-broken install 超过一小时,而每次执行都会发现新的软件包,却无法清除上一次发现的问题,说明系统在升级前就已带有升级过程未造成的问题:/usr 下的文件曾被手动修改,软件包被固定或锁定,第三方软件源替换了核心库,或者之前的升级本身从未完成。全新的镜像不存在这些问题,将数据恢复到新系统所需的时间少于逐一查找这些问题。
只有当数据存储在系统之外时,重建才真正省时。这就是快照与备份的区别,也是升级指南要求同时准备二者的原因。
FAQ
中断后可以直接再次运行 do-release-upgrade 吗?
不应先这样做。如果升级程序仍在其 screen 会话中运行,请使用 sudo screen -d -r 重新连接;第二次运行 do-release-upgrade 会在进入自身的重新连接代码前停止,因为只要有任何软件包等待升级,它就拒绝启动。如果升级程序已退出,请先运行 sudo dpkg --configure -a 和 sudo apt --fix-broken install。如果软件源中已经写入 26.04,请使用 sudo apt full-upgrade 完成升级,而不要再次运行升级程序。如果软件源中仍写入 24.04,请运行 sudo apt full-upgrade;如果 /var/run/reboot-required.pkgs 列出 libc6、linux-base 或内核,请重启,然后再次启动升级程序。如果 Prompt=never 在 /etc/update-manager/release-upgrades 中设置,或者 Ubuntu 的服务器报告“当前无法进行版本升级”,升级程序也会拒绝运行。
为什么升级失败后,do-release-upgrade 会提示没有新版本?
因为写入 /etc/os-release 的软件包 base-files 是中断前已完成升级的软件包之一。现在该工具读取此文件,认为系统已经运行 26.04,因此找不到可提供的新版本。比较 grep VERSION_ID /etc/os-release 和 grep Suites /etc/apt/sources.list.d/ubuntu.sources,然后使用 sudo apt update && sudo apt full-upgrade 完成升级。
半升级状态的 Ubuntu 服务器可以安全重启吗?
在 sudo dpkg --audit 返回空结果前,不要重启。如果内核已解包但尚未配置,或 GRUB 尚未重新生成,此时重启最容易使原本 10 分钟内可以修复的升级问题变成需要使用救援镜像处理的问题。完成 dpkg 修复并运行 apt full-upgrade,再运行 update-initramfs -u -k all 和 update-grub,最后才能重启。
如何找出导致升级停止的软件包?
查看 /var/log/dist-upgrade/apt-term.log 的末尾,其中包含 dpkg 的终端输出。日志结束前最后一个出现的软件包就是当时正在处理的软件包;如果维护者脚本失败,其错误会直接显示在 dpkg 报错上方。/var/log/dpkg.log 是第二个信息来源:其中升级停止时带时间戳的行会指出同一个软件包,但此后通过 dpkg 执行的操作也会追加到该日志中,因此日志末尾可能不再是该软件包。如果 main.log 以 Python traceback 结尾,则表示升级程序自身崩溃,没有软件包导致该问题。
应恢复快照,还是继续修复?
在无法确定失败的软件包、多个软件包卡住、无法访问控制台,或服务器必须尽快恢复时,应恢复快照。只有在明确知道唯一的故障点,且修复只需执行一条命令时,才应继续修复。恢复前请将 /var/log/dist-upgrade/ 复制到服务器外部,否则第二次尝试仍会以相同方式失败。