Rocky AlmaLinux 9 如何回滚错误的 dnf 更新
Rocky Linux 9 和 AlmaLinux 9 使用 dnf 4。本文演示如何用 dnf history 查找事务、执行 undo 或 rollback,并锁定软件包,避免问题版本再次安装。
回滚错误的 dnf 更新:使用最少步骤
要回滚错误的 dnf 更新,请先找到更改软件包的事务 ID,查看该事务执行的操作,然后将其反向执行。每个 dnf 操作都会记录一个编号,因此恢复过程是查找,而不是猜测。以下命令针对 Rocky Linux 9 和 AlmaLinux 9 编写,这两个系统随附 dnf 4。
sudo dnf history list
sudo dnf history info 42
sudo dnf history undo 42将 42 替换为第一个命令显示的 ID。这是最短的处理路径,但只有在旧版本软件包仍可供下载时才有效。本指南的大部分内容针对旧软件包已无法下载的情况,因为快速解答通常会遗漏这种情况。
每个 dnf 操作都有一个事务 ID
dnf 会将每次安装、升级、降级和删除分别写入历史数据库。dnf 4 将该数据库保存在 /var/lib/dnf/history.sqlite。它与 /var/lib/rpm 下的 RPM 数据库相互独立,后者只记录当前已安装的内容。历史记录会保存变更顺序,而任何回滚都依赖这个顺序。
sudo dnf history list
sudo dnf history list --reverse
sudo dnf history list httpd
sudo dnf history info last
sudo dnf history info 42dnf history list httpd 可将列表限制为涉及某个软件包的事务,因此无需查看全部记录,就能找到导致某个服务故障的操作。dnf history info 用于描述单个事务:哪些软件包发生了变更、变更方向是什么,以及启动该事务的命令行。撤销任何操作前都应先查看这些信息,因为撤销会反转整个事务,而不只是反转你怀疑有问题的软件包。
两个日志文件保存更底层的详细信息。/var/log/dnf.log 记录 dnf 执行的操作,/var/log/dnf.rpm.log 记录 rpm 应用的软件包变更。事务可能在你休息时运行,因此这两个文件都很重要。
使用 dnf history undo 撤销一次事务
dnf history undo 会对某个事务中的每个操作执行相反操作。它升级的软件包会降级回原版本。它降级的软件包会再次升级。它安装的软件包会被删除。它删除的软件包会重新安装。当您确定某次运行导致系统故障,且之后执行的操作都不重要时,可以使用此命令。
sudo dnf history undo 42
sudo dnf history undo last
sudo dnf history undo --assumeno 42dnf 4 没有 --dry-run,因此安全的做法是先不使用 -y 运行命令,并在确认前阅读它显示的执行计划。--assumeno 会自动回答否,因此只会显示计划,不会进行任何更改。
撤销操作本身也是一个事务。它会获得自己的 ID,并显示在 dnf history list 中,因此如果您后悔执行撤销操作,还可以再次撤销该操作。这也是系统状态回退时事务 ID 仍持续递增的原因。
使用 dnf history rollback 回滚到指定时间点
dnf history rollback 会撤销指定事务之后的所有事务,使软件包集合恢复为该事务完成时的状态。当服务最后一次正常工作到现在之间执行过多次操作,且无法确定是哪次操作导致问题时,可以使用此命令。
sudo dnf history list --reverse
sudo dnf history rollback 40首先需要了解两点。传入的 ID 是要保留的最后一个事务,而不是要移除的第一个事务。如果当前已安装的软件包集合与历史记录预期的状态不一致,回滚可能会拒绝执行。例如,在 dnf 之外直接使用 rpm 安装或移除了软件包,就可能出现这种情况。dnf 不会在这种情况下自行推断,因此应阅读它输出的消息,然后改为撤销单个事务,或降级某个软件包。
跨越次版本发布点的回滚可能会一次性变更多达数百个软件包。确认操作前,先检查计划中的变更规模。
降级单个软件包
有时您不希望回滚整个事务。您只需要将一个软件包恢复到之前正常工作的版本,同时保留其余更新。
sudo dnf list --showduplicates httpd
sudo dnf downgrade httpd
sudo dnf downgrade httpd-2.4.62-4.el9先运行第一个命令。--showduplicates 会列出启用的软件仓库仍可提供的该软件包所有版本。如果列出的唯一版本就是当前已安装的版本,则无法从现有软件仓库执行降级;请参阅后续章节。
dnf downgrade httpd 会切换到低于当前已安装版本的最高可安装版本。最好指定确切版本,这样可以自行选择目标版本,而不是接受候选列表中的下一个版本。完成后,使用 rpm -q httpd 和 systemctl status httpd 检查服务是否已恢复,并从服务器外部确认端口已重新处于监听状态。
撤销操作为何经常无法执行
撤销操作不是恢复快照。dnf 不会保留被替换软件包的副本。它会读取历史记录,确定需要恢复的版本,然后从仍提供这些版本的软件仓库下载对应的 RPM 文件。如果没有任何仓库提供这些文件,事务会在 dnf 解析依赖时失败,磁盘上的内容尚未发生变化。
小版本发布后,仓库目录发生了变化。 Rocky 和 Alma 9 仓库提供当前的小版本。下一个小版本发布后,之前的构建会离开活动仓库并进入归档仓库。历史记录仍会记录旧版本,但启用的镜像中已没有提供该版本的镜像。
第三方仓库重新构建了软件包。 社区仓库通常每个软件包只保留一个当前构建版本,因此昨天的版本已经不存在。这种情况最常见于 来自 EPEL 或 CRB 的软件包,而不是来自基础仓库的软件包。
仓库已禁用或已不存在。 如果软件包来自后来删除的仓库,就无法从该仓库恢复。同样,如果软件包是从本地文件安装的,也无法通过仓库恢复。
小版本已结束生命周期。 该版本不再发布任何内容,因此没有任何版本可供下载。
sudo dnf list --showduplicates 可用一行回答单个软件包的问题。没有命令可以用相同方式预先检查整个事务,因此即使你认为已经制定了计划,撤销操作仍可能在依赖解析阶段失败。
从归档库获取旧软件包
两个发行版都保留了旧次要版本的软件包归档。将其中一个归档添加为禁用的仓库,下载所需的单个软件包,然后降级到该文件。保持仓库禁用很重要,因为启用的归档仓库可能会使整台机器停留在旧次要版本,并且静默地停止安全更新。
对于 Rocky Linux,在 /etc/yum.repos.d/rocky-vault.repo 中:
[rocky-9-5-baseos-vault]
name=Rocky Linux 9.5 BaseOS vault
baseurl=https://dl.rockylinux.org/vault/rocky/9.5/BaseOS/x86_64/os/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
enabled=0AlmaLinux 将归档发布在另一台主机上,在 /etc/yum.repos.d/alma-vault.repo 中:
[alma-9-5-baseos-vault]
name=AlmaLinux 9.5 BaseOS vault
baseurl=https://vault.almalinux.org/9.5/BaseOS/x86_64/os/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
enabled=0将 9.5 更改为你正在运行的次要版本;当软件包位于该位置时,将 BaseOS 更改为 AppStream。使用 ls /etc/pki/rpm-gpg/ 确认系统中实际存在的密钥文件名,因为该文件由 release 软件包放置,而不是由你手动创建。上述基础 URL 是 2026 年 9 月发布的目录结构。如果下载失败,请在浏览器中打开基础 URL:地址栏中的 404 比 dnf 解析错误更容易读取。这两个发行版在此类细节上有所不同,粘贴任一文件之前,最好先了解 Rocky Linux 和 AlmaLinux 之间的实际差异。
然后下载软件包并安装该文件:
sudo dnf install -y dnf-plugins-core
sudo dnf --enablerepo=rocky-9-5-baseos-vault download --destdir=/root/rpm-rollback httpd
sudo dnf downgrade /root/rpm-rollback/httpd-2.4.62-4.el9.x86_64.rpmdnf download 来自 dnf-plugins-core 软件包,不属于基础命令集,因此应先安装该软件包。将该文件传递给 dnf downgrade,而不是 rpm -U,因为这样可以将依赖项解析保留在 dnf 中;当旧软件包还需要旧版本的库时,这一点很重要。
保留一份旧 RPM 的副本
最省成本的保障是停止删除这些软件包。在 /etc/dnf/dnf.conf 中设置 keepcache:
[main]
keepcache=1下载的 RPM 随后会保存在 /var/cache/dnf/ 下,并按软件仓库名称分目录。找到软件包后,可直接安装:
sudo find /var/cache/dnf -name 'httpd-*.rpm'
sudo dnf downgrade /var/cache/dnf/appstream-1ec8a0bfaa5e5ff9/packages/httpd-2.4.62-4.el9.x86_64.rpm代价是占用磁盘空间。每次更新都会使缓存增长,而且不会自动清理,因此小型 VPS 上的 /var 最终会被占满。sudo du -sh /var/cache/dnf 可显示缓存占用的空间,sudo dnf clean packages 可清空缓存。第二条命令还会删除用于回滚的文件,因此在排查更新问题的同一周内不要运行它。应将缓存检查加入 按计划执行的维护清单。
另一个习惯是在升级前先下载软件包。这样只需多花几秒钟,就能保留所需软件包的确切文件:
sudo dnf download --destdir=/root/rpm-rollback httpd
sudo dnf upgrade没有内容需要下载时
内核是一个例外,而且是有用的例外。Enterprise Linux 9 会并行安装多个内核,而不是替换现有内核;installonly_limit 中的 /etc/dnf/dnf.conf 用于设置保留的内核数量。因此,即使内核更新失败,磁盘上通常仍有一个可正常工作的内核。
rpm -q kernel
grep installonly /etc/dnf/dnf.conf
sudo grubby --default-kernel
sudo grubby --info=ALL
sudo grubby --set-default=/boot/vmlinuz-5.14.0-503.14.1.el9_5.x86_64
sudo reboot请使用 grubby --info=ALL 在您的机器上输出的确切文件名,不要使用上面的文件名。在重启前设置默认内核非常重要,因为 VPS 无法通过 SSH 访问启动菜单;要访问该菜单,必须打开服务商的控制台。重启后,uname -r 会显示实际启动的内核;读取 VPS 上的内核日志可以了解故障内核报告了哪些问题。请先运行 rpm -q kernel,因为之前的清理操作可能只留下了损坏的内核。然后,可以从上面的 vault 仓库安装旧版本内核作为回退方案。
对于非内核组件,回退方案是在更新前创建的快照。Rocky 和 Alma 默认使用 XFS,而 XFS 本身不提供快照功能;大多数云镜像会在一个分区上使用一个 XFS 文件系统,且没有配置 LVM。请使用 lsblk 和 sudo vgs 检查您的系统。实际操作中,回退点通常是更新开始前创建的整块磁盘服务商快照;确认机器已稳定运行一天后,再删除该快照。如果您希望将回退作为操作系统的内置属性,而不是依赖记忆执行的操作,那么这属于另一种设计:基于镜像的服务器发行版会将整个系统作为一个单元进行版本管理。
阻止损坏的软件包再次自动安装
撤销操作不会修改软件包仓库,因此刚刚移除的版本仍是仓库中的最新版本。下一次 dnf upgrade 会再次安装它。在离开前,先锁定该软件包的版本。
sudo dnf install -y python3-dnf-plugin-versionlock
sudo dnf versionlock add httpd
sudo dnf versionlock list
sudo dnf versionlock delete httpdversionlock add httpd 会锁定当时已安装的版本,并将规则写入 /etc/dnf/plugins/versionlock.list。在删除该规则前,dnf 不会更新这个软件包。另一种方法是在 dnf.conf 中使用 excludepkgs=httpd,或在单条命令中使用 --exclude=httpd,但这会让 dnf 完全隐藏该软件包;当其他软件包依赖它时,可能导致无关的事务无法解决。对于已经安装的软件包,优先使用 versionlock。
锁定也意味着该软件包将不再接收安全更新。锁定文件接受 # 注释,因此应将日期和原因写入其中,并在你确实会查看的位置设置提醒。忘记解除锁定,可能导致机器上的易受攻击 Web 服务器持续运行一年。
dnf 4 与 dnf 5 不是同一个工具
Rocky Linux 9 和 AlmaLinux 9 随附 dnf 4,本篇中的所有命令都针对 dnf 4 编写。Fedora 和较新的发行版随附 dnf 5,它是一次重写版本。dnf 5 记录了相同的 history 子命令,但对某些选项的处理方式不同。在依赖某个选项之前,请先确认系统使用的版本,并检查当前构建版本接受哪些参数:
dnf --version
dnf history --helpdnf history --help 才是当前机器的实际情况。如果您还运行 Debian 或 Ubuntu 服务器,apt 与 dnf 命令的对应关系也涵盖相同的操作;在这些系统上,对应操作是先执行 apt install <package>=<version>,再执行 apt-mark hold,因为 apt 会将运行记录到 /var/log/apt/history.log,但没有用于撤销单次操作的命令。
为什么自动更新的机器仍需要了解这些
学习历史命令的原因,是因为您允许服务器自行更新。对于没有人每天登录的机器,无人值守更新是合理的默认设置。代价是,变更会在无人监控时生效。了解事务模型后,您可以将“某处出问题了,但不知道是什么时候”转化为带有时间信息的编号列表。
systemctl list-timers 'dnf-*'
sudo dnf history list
sudo dnf history info last第一个命令会显示自动运行是否已启用,以及下一次运行的时间。请尽早检查,因为如果今晚的定时器会撤销回滚结果,就没有必要花费一小时执行回滚。应像 在 Rocky 和 Alma 上配置 dnf-automatic 中所述,有计划地设置这些定时器,并锁定已回滚的内容。
在将问题归咎于软件包之前,再执行一项检查。更新可能已正确安装,但系统仍表现异常,因为某个进程仍在运行旧代码,并且没有重新启动。撤销任何更改前,请先查看 更新后哪些服务仍需要重新启动。对于新机器,更新策略应与 保护新的 Rocky Linux 服务器 中的其他事项一样,在首次配置时一并确定。
值得提前运行一次的演练
今天在一台无关紧要的机器上运行此演练,此时不要等到出现故障。
sudo dnf install -y tree
sudo dnf history list tree
sudo dnf history info last
sudo dnf history undo last
rpm -q tree最后一条命令会告诉您撤销操作是否确实移除了软件包。然后使用 sudo dnf list --showduplicates chrony,或对另一个软件包运行相同的命令,找出有多个可用版本的软件包,将其降级,再撤销这次操作。在备用 VPS 上花 10 分钟进行演练,比任何检查清单都更有价值,因为下次执行时服务可能已经中断,而计时已经开始。
FAQ
如何查看最近一次 dnf 更新更改了什么?
sudo dnf history info last 描述最近一次事务,sudo dnf history list 输出带编号的列表,以便选择更早的事务。若要跟踪单个软件包,请运行 sudo dnf history list httpd。更底层的记录位于 /var/log/dnf.log 和 /var/log/dnf.rpm.log 中。如果事务在夜间运行,而您需要查看具体时间,应检查这些记录。
dnf history undo 和 dnf history rollback 有什么区别?
undo 撤销一个事务,并保留该事务之后的所有更改。rollback 撤销指定 ID 之后的所有事务,使软件包集合恢复为该事务完成时的状态。如果您知道是哪次运行导致系统故障,请使用 undo。如果已知正常状态之后运行过多次事务,且无法区分具体原因,请使用 rollback。
为什么在发布小版本后 dnf history undo 会失败?
因为撤销操作需要重新下载旧软件包,而 dnf 不会保留这些软件包的副本。基础仓库只提供当前小版本,因此 9.5 变为 9.6 后,之前的构建版本已移至归档库。使用 sudo dnf list --showduplicates 和软件包名称检查。如果未列出旧版本,请将归档库添加为已禁用的软件仓库,使用 dnf download 从中下载软件包,然后将生成的文件传递给 sudo dnf downgrade。
可以在 Rocky 或 AlmaLinux 上回滚内核更新吗?
可以。这是最简单的情况,因为 Enterprise Linux 9 会保留多个已安装内核,而不是直接替换它们。rpm -q kernel 列出磁盘上的内核,sudo grubby --info=ALL 列出引导项,sudo grubby --set-default=/boot/vmlinuz-<version> 选择下一次要引导的内核。请在重启前设置默认项,因为从引导菜单选择项目需要使用供应商的控制台,而不能通过 SSH 完成。
如何阻止同一个有问题的更新今晚再次安装?
锁定该软件包。安装 python3-dnf-plugin-versionlock,然后使用软件包名称运行 sudo dnf versionlock add,这样会将已安装版本记录到 /etc/dnf/plugins/versionlock.list 中。如果不设置锁定,刚刚移除的版本仍然是仓库中的最新版本,因此下一次升级会直接将其重新安装。请将日期和原因写入锁定文件,因为该锁定也会阻止此软件包的安全更新。