RPM 事务和回滚
您有多少次安装了一个很棒的新软件,结果却发现您其实并不想安装它?更糟糕的是,当您安装此软件时,您不得不升级其他几个软件包,并从头开始安装额外的软件包。为了恢复原样,您不得不从多个来源找到升级软件包的早期版本,降级到这些版本,并删除任何新安装的软件包。当然,如果您没有很好地记录实际更改了哪些软件包以及它们的先前版本是什么,情况会变得更糟。如果您可以按下一个按钮或运行一个命令来回滚此升级,那不是很好吗?
在某些环境中,快速回滚升级的能力不仅是理想的,而且是必需的。例如,在升级电信公司的设备时,软件和硬件供应商需要在有限的时间范围内升级设备,这被称为维护窗口。在同一个维护窗口中,他们还必须能够撤销升级所做的任何更改。未能在维护窗口内撤销升级会导致严厉的经济处罚。
尽管 RPM 的自动回滚非常理想,但直到最近,RPM 还不支持此选项。公平地说,RPM 支持降级一组软件包。例如,如果您将某些 RPM foo-1-1 升级到版本 foo-1-2,您可以使用带有 rpm 命令的 --oldpackage 开关来降级到以前的版本;像这样
# rpm -Uvh --oldpackage foo-1-1.i386.rpm Preparing... ################# [100%] Upgrading... 1:foo ################# [100%]
如果升级到 foo-1-2 不需要您升级或安装任何额外的 RPM,则 --oldpackage 开关效果很好。您所要做的就是找到原始的 foo-1-1 RPM,然后您就自由了。另一方面,如果您确实需要安装或升级 foo-1-2 所依赖的其他 RPM,那么您必须在各种位置搜索这些 RPM——在您的安装介质上、在您的发行版的勘误站点上、在各种 RPM 存储库中或在各种项目网站上。
一旦您找到了所有依赖的 RPM,您就需要降级所有已升级的 RPM,并删除您已安装的新 RPM。相反,如果您颠倒了这个顺序,在您降级已升级的 RPM 之前,先删除了您已安装的新 RPM,您将收到来自 RPM 的错误,抱怨这些软件包是 foo-1-2 所必需的。简而言之,回滚一组 RPM 的旧方法是痛苦且充满错误的。
早在 2002 年,RPM 的现任维护者 Jeff Johnson 就开始解决这个回滚问题,当时他将事务性回滚功能包含到 RPM 的 4.0.3 版本中。此功能带来了自动降级一组 RPM 的希望。像许多新功能一样,它很粗糙,并且完全没有文档记录,除了 RPM 邮件列表 (rpm-list@redhat.com) 上的一些电子邮件。在过去的一年半中,事务性回滚稳步成熟。在当前的 RPM 4.2 版本中,Red Hat 9 附带了该版本,事务性回滚非常可用。
在幕后,RPM 将它安装的任何一组 RPM 视为一个离散的事务。当单独安装一个 RPM(一个 RPM 的事务)或同时安装多个 RPM 时,情况都是如此。每个事务都给定一个唯一的事务 ID (TID)。当每个 RPM 被安装或升级时,它在 RPM 数据库中的条目都会标记有安装它的事务的 TID。这允许 RPM 跟踪每个 RPM 在哪个事务中被安装或升级。
为了回滚 RPM 事务集,RPM 必须能够访问事务发生时系统上的一组 RPM。它通过在擦除每个 RPM 之前重新打包它并将这些重新打包的软件包存储在重新打包目录(默认情况下为 /var/spool/repackage)中来解决这个问题。重新打包的软件包包含 RPM 拥有的所有文件,这些文件在擦除时存在于系统上,旧 RPM 的标头以及旧 RPM 附带的所有脚本。
您可能想知道这种设计如何帮助升级。毕竟,如果您升级 RPM,您不会擦除它。但是,您正在擦除它,因为升级 RPM 分为两部分:安装新软件包,并擦除旧软件包。这意味着每次您升级一组软件包时,RPM 首先重新打包所有正在更新的软件包,然后安装所有新软件包,最后擦除所有旧软件包。当 RPM 重新打包旧软件包时,它还会用正在运行的事务的 TID 标记重新打包的软件包。最终结果是您不必在网络、媒体或备份中搜索您更新的 RPM。因为重新打包的软件包包含升级时系统上当前存在的文件,所以消除了从备份恢复配置文件的需要。作为副作用,重新打包的软件包中文件的 md5 校验和很可能是不正确的,因为 RPM 在创建重新打包的软件包时不会重新计算每个校验和。这对于 RPM 回滚事务来说不是问题,但是当您直接操作重新打包的软件包时,您需要使用 --nodigest 选项。
一旦重新打包目录被填充,RPM 只需要一个回滚目标(它要回滚到的日期)即可执行回滚。然后,RPM 通过 TID 确定自回滚目标日期以来已应用于系统的事务。接下来,RPM 获取这组事务,按从最近到最远的顺序对它们进行排序,并为每个事务执行以下操作
查找所有标记有此 TID 的重新打包的软件包。
查找所有当前安装的软件包,这些软件包标记有此 TID,但没有相应的重新打包的软件包。
构建回滚事务。重新打包的软件包作为安装元素添加到此事务中,而没有相应重新打包软件包的已安装软件包作为擦除元素添加。
运行新构建的回滚事务。
通过对每个事务(从最近的事务到最接近或等于目标日期的事务)重复这些操作,RPM 遍历自回滚目标以来发生的所有事务,并撤消它们。
您可能会认为这个过程很复杂,但使用事务性回滚实际上相当容易。作为一个简单的例子,让我们安装一个 RPM 并回滚它。您必须记住的最关键的一点是,每当您进行升级或简单擦除时,您都必须告诉 RPM 在擦除旧软件包之前重新打包它。为此,请使用 --repackage 选项
# rpm -Uvh --repackage foo-1-2.noarch.rpm Preparing... ############################# [100%] Repackaging... 1:foo ############################# [100%] Upgrading... 1:foo ############################# [100%]
RPM 不显示任何擦除输出,但是如果您在擦除后查看重新打包目录,则其中有一个重新打包的软件包。
要回滚此 RPM 事务,请使用 --rollback 选项,后跟回滚目标。回滚目标可以是实际日期,也可以是像一小时前这样的时间(日期说明符允许与 cvs(1) 命令的 -D 选项相同的日期格式)。因此,如果在升级 foo 一小时后,您决定不需要它,您可以键入
# rpm -Uvh --rollback '2 hours ago' Rollback packages (+1/-1) to Thu Jul 31 23:26:52 2003 (0x3f29ddfc): Preparing... ########################### 100%] 1:foo ########################### [ 33%]
事务性回滚仅与您的本地重新打包软件包存储库一样好。使它们失败的一种快速方法是在不使用 --repackage 选项的情况下升级或擦除某些内容。根据我的经验,很容易忘记使用此选项。因此,如果您要使用事务性回滚,您需要配置 RPM 以自动重新打包所有擦除。为此,请设置%_repackage_all_erasures宏为 1 在您的 /etc/rpm/macros 文件中。如果该文件不存在,只需创建它
%_repackage_all_erasures 1
默认情况下,RPM 不会回滚新安装的软件包;也就是说,它不会擦除在升级时系统上不存在的软件包。您可能不希望这是默认行为,因此您需要告诉 RPM 允许这样做。为此,请将宏 %_unsafe_rollbacks 设置为您不希望在回滚时完全擦除 RPM 的日期之后的时间。此日期应以自纪元以来的秒数表示。要将日期转换为自纪元以来的秒数,请使用 date 命令
date --date="8/1/2003" +%s 1059710400
如果您想告诉 RPM 不要完全删除在 2003 年 8 月 1 日(上例中的日期)或之前安装的软件包,您应该将以下内容添加到 /etc/rpm/macros 文件中
%_unsafe_rollbacks 1059710400
您可能需要配置的唯一其他内容是 RPM 将重新打包的软件包放在哪里。这样做的一个原因是确保它们放置在具有足够空间的 分区中。要更改重新打包目录,请将 %_repackage_dir 宏设置为您希望存储重新打包软件包的目录
%_repackage_dir /my_rp_repository
现在您有了一个自动重新打包所有擦除的系统(这样您或其他人就不会忘记),在回滚时擦除新安装的软件包(但不会擦除您的整个系统),并将重新打包的软件包放在您想要存储它们的位置。
在 Red Hat 9 中,up2date 使用 RPM 的事务性回滚机制支持回滚。配置它以支持事务性回滚就像运行up2date-config一样简单,单击“检索/安装”选项卡,然后单击“启用 RPM 回滚”复选框(参见图 1)。您必须按照上一节中的描述配置 RPM 本身。当您使用 up2date 升级系统时,一旦您配置了 RPM 和 up2date,RPM 就会在升级软件包之前创建您正在更新的 RPM 的重新打包的软件包。
要列出不同的已知回滚目标,请键入
up2date --list-rollbacks
您应该收到如下列表
# up2date --list-rollbacks install time: Sun Jul 27 20:49:55 2003 tid:1059353395 [-] goo-1.0-1.0: install time: Tue Jul 29 20:44:25 2003 tid:1059525865 [-] foo-1-2:
即使您实际上没有使用 up2date,此命令也很方便,因为 rpm 命令不提供显示此类信息的方式。
要撤消事务,请使用 --undo 选项,该选项撤消最后安装的事务。只需键入
# up2date --undo
RPM 通常使用尽力而为的策略交付软件包,这意味着如果一个或多个 RPM 安装失败,事务中的其余 RPM 仍然会被安装。这在某些环境中是理想的行为,但在其他环境中,如果 RPM 自动回滚失败的事务会更好。因为我在这样的环境(电信)中工作,所以我编写了一个名为自动回滚补丁的补丁。此补丁允许您配置 RPM,以便如果事务失败,RPM 会自动回滚失败的事务。如果它在 %post 脚本中失败,它确实会留下失败的 RPM;希望这很快就会得到修复(有人打补丁吗?)。
如果您想使用此功能,您可以从 lee.k12.nc.us/~joden/misc/patches/rpm 下载补丁(或已应用补丁的 RPM)。安装了带有自动回滚补丁的 RPM 版本后,您需要配置 RPM 以使用自动回滚功能。为此,请编辑 /etc/rpm/macros 并添加以下宏定义
%_rollback_transaction_on_failure 1
执行此操作后,下次您安装/升级一组 RPM 并且其中一个安装失败时,RPM 会自动回滚失败的事务,除非失败的事务在 %post 脚本中失败。
RPM 事务性回滚提供了一种有效的方法来撤消 RPM 升级。它们还提供了一个坚实的构建块,系统更新程序(如 up2date、yum 和 apt-get)可以在此基础上提供自动回滚功能。但是,事务性回滚并非适合所有人。引用 Jeff Johnson 的话,“--rollback 选项...需要绝对完美的系统管理,并且主要是机制,而不是策略。” 事务性回滚是一项全有或全无的事情。必须注意确保重新打包所有擦除,因为 RPM 回滚事务的能力仅与其重新打包软件包的来源一样好。管理员必须确保为重新打包软件包的存储分配额外的空间。最后,RPM 的事务性回滚功能仍在开发中。话虽如此,RPM 事务性回滚从一开始就取得了长足的进步。如果您想确保可以快速撤消对系统的 RPM 更新,它们可能正是您所需要的。
James Olin Oden (joden@lee.k12.nc.us) 是 Tekelec 的一名软件工程师。他管理 UNIX 类型系统并在其下开发已有十多年。他还是 Tech Tracker (tt.lee.k12.nc.us) 的创建者,Tech Tracker 是一个基于 Web 的 IT 跟踪系统。