1. 为什么还要聊 rpm 和 yum 这两个老家伙
在容器化、声明式部署满天飞的今天,很多人一听到 rpm 和 yum 就觉得是上古时代的产物,觉得有 Docker 和各类编排工具就够了。但只要你真正在生产环境里摸爬滚打过几年,就会发现一个事实:大量企业的核心业务依然跑在传统的 Linux 服务器上,而这些服务器的软件生命周期管理,绕不开 rpm 和 yum 这两套机制。你可以不用它,但你绝对不能不懂它。
我见过太多这样的情况:一台线上机器缺了个依赖库,有人直接下载源码编译,编译到一半报错,折腾两小时;而懂行的人一条 yum 命令三十秒搞定。差距不在智商,在于对包管理体系的底层理解。rpm 是红帽系 Linux 的包管理基石,yum 则是建立在 rpm 之上的依赖解决与仓库管理工具。两者一个管“单个包怎么装、怎么查、怎么卸”,一个管“从哪拿包、依赖怎么自动补齐”。搞清楚了它们的分工,你在服务器上装软件这件事就从“碰运气”变成了“可预期”。
这篇内容适合谁看?如果你是刚接触 Linux 运维的新手,它能帮你建立一套完整的包管理认知框架,少走弯路;如果你是有一定经验的开发者,它能帮你补齐那些“平时能用但说不清原理”的知识盲区,让你在排查依赖冲突、离线安装、版本锁定这些场景下不再抓瞎。我会从设计思路讲到实操细节,再到踩坑经验,尽量把每个“为什么”都讲透。
2. rpm 与 yum 的分工与底层逻辑拆解
2.1 rpm 到底解决了什么问题
要理解 rpm,得先回到它诞生之前的年代。早期 Linux 上装软件基本靠源码编译,./configure && make && make install三连,听起来很极客,但问题一大堆:装了什么文件散落在系统各处、卸载时根本删不干净、版本信息无处可查、多个软件之间的依赖关系全靠人脑记忆。软件一多,系统就变成一团乱麻。
rpm 的全称是 RPM Package Manager,它做的事情本质上是把“一个软件安装所需的所有信息”打包成一个文件,这个文件里包含了几样关键东西:编译好的二进制文件、配置文件、文档,以及最重要的——元数据。元数据里记录了这个包的名称、版本号、架构、依赖了哪些其他包、被哪些包依赖、安装后会往系统哪些路径写文件、卸载时应该删除哪些文件。有了这些信息,安装和卸载就变成了数据库操作,而不是“凭记忆删文件”。
rpm 包的文件名有固定的命名规范,比如nginx-1.24.0-1.el9.x86_64.rpm,拆开看就是:包名 nginx、版本号 1.24.0、发布号 1.el9、架构 x86_64。这个命名规则不是随便定的,它让你在拿到一个包的时候,一眼就能判断版本和适用平台。我刚开始学的时候经常忽略架构后缀,拿着 aarch64 的包装到 x86 机器上,报错信息又不直观,白白浪费时间。后来养成了习惯,拿到包先rpm -qpi看一眼详细信息,确认架构和依赖再动手。
rpm 的数据库存放在/var/lib/rpm目录下(新版本可能在/usr/lib/sysimage/rpm),所有已安装包的信息都记录在这里。这意味着你可以随时查询系统上装了什么、某个文件属于哪个包、某个包依赖了什么。这个数据库是 rpm 体系的核心,一旦损坏,整个包管理就会出问题。我遇到过因为磁盘写满导致 rpm 数据库损坏的情况,修复起来相当麻烦,所以后来养成了一个习惯:服务器上永远留够磁盘空间,尤其是/var分区。
2.2 yum 为什么必须存在
rpm 虽然解决了单个包的管理问题,但它有个致命短板:不自动解决依赖。你装 A 包,A 依赖 B,B 又依赖 C,rpm 只会告诉你“缺少 B”,然后你就得手动去找 B,装 B 的时候又发现缺 C,如此往复。在依赖链短的时候还能忍,一旦遇到几十个依赖的软件,手动解决简直是噩梦。
yum 就是来解决这个问题的。它的全称是 Yellowdog Updater Modified,核心能力是:从配置好的仓库(repository)里自动查找软件包,分析依赖关系,计算出正确的安装顺序,然后一次性把所有需要的包都装好。你只需要告诉它“我要装 nginx”,它自己去仓库里找 nginx 以及 nginx 依赖的所有包,按顺序装完。
yum 的工作流程大致是这样的:首先读取/etc/yum.repos.d/目录下的仓库配置文件,这些文件定义了从哪里获取包(通常是 HTTP 或 HTTPS 地址);然后根据你的命令去仓库的元数据里查找包和依赖信息;接着在本地已安装的 rpm 数据库里比对,算出需要新增哪些包、升级哪些包;最后从仓库下载这些包,调用 rpm 逐个安装。整个过程对用户透明,你只看到它在刷屏输出,实际上背后做了大量依赖计算工作。
这里有个关键点很多人不知道:yum 本身也是通过 rpm 来安装包的,它只是帮你做了“找包”和“算依赖”这两件事。所以 rpm 数据库的状态直接影响 yum 的行为。如果 rpm 数据库里记录的信息和实际文件不一致,yum 就会做出错误判断。这也是为什么有时候yum install会报一些莫名其妙的错,根源往往在 rpm 数据库层面。
2.3 仓库配置:yum 的“软件源”从哪来
yum 能自动找包,前提是你告诉它去哪里找。这个“哪里”就是仓库。仓库本质上是一个目录结构,里面放着 rpm 包文件和一份描述所有包信息的元数据(repodata)。元数据里记录了每个包的名称、版本、依赖关系、文件列表等信息,yum 就是靠读这份元数据来做依赖计算的。
仓库配置文件放在/etc/yum.repos.d/目录下,文件名通常以.repo结尾。一个典型的配置文件长这样:
[baseos] name=BaseOS Repository baseurl=http://mirror.example.com/centos/9/BaseOS/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=http://mirror.example.com/centos/9/BaseOS/x86_64/os/RPM-GPG-KEY-CentOS-9逐行解释一下:[baseos]是仓库的唯一标识,不能和其他仓库重名;name是给人看的描述;baseurl是仓库地址,可以是 HTTP、HTTPS、FTP 或本地文件路径;enabled=1表示启用这个仓库;gpgcheck=1表示要校验包的签名,gpgkey指定公钥地址。GPG 校验这一步非常重要,它确保你下载的包没有被篡改。我强烈建议生产环境永远开启 gpgcheck,不要为了图省事关掉,否则你就是在裸奔。
仓库地址的写法有几种常见形式。baseurl直接指向仓库根目录,适合固定地址的场景。也可以用mirrorlist,指向一个返回镜像列表的地址,yum 会从列表里挑一个可用的。还有一种是用变量,比如$releasever和$basearch,这样配置文件可以跨版本复用。我个人的习惯是内网环境用baseurl指向本地镜像服务器,稳定可控;外网环境用mirrorlist,能自动选到速度快的镜像。
3. rpm 命令实操:从查询到安装的完整流程
3.1 查询:动手之前先摸清情况
我个人的习惯是,拿到任何一台机器,先不急着装东西,而是先查。查清楚系统上已经有什么、版本是多少、某个文件属于哪个包,这些信息能帮你避免很多冲突。rpm 的查询功能非常强大,用好了能省下大量排查时间。
最常用的查询命令是rpm -q,后面跟包名。比如rpm -q nginx会告诉你 nginx 是否安装、装的是什么版本。如果没装,它会明确说package nginx is not installed。这个命令看起来简单,但有个细节要注意:包名是精确匹配的,不支持模糊搜索。你想查所有和 nginx 相关的包,得用rpm -qa | grep nginx,先列出所有已安装的包,再过滤。
rpm -qa会列出系统上所有已安装的包,输出可能有好几百行。直接看肯定不现实,通常配合 grep 使用。我经常用rpm -qa | wc -l来快速了解一台机器的软件规模,正常的最小化安装大概三四百个包,装了各种服务的可能上千。这个数字本身没什么意义,但如果你发现某台机器包数量异常少或异常多,可能意味着系统被裁剪过或者装了太多东西,值得留意。
查文件归属是另一个高频操作。rpm -qf /usr/sbin/nginx会告诉你这个文件属于哪个包。这个命令在排查“这个文件是哪来的”这类问题时特别好用。反过来,rpm -ql nginx会列出 nginx 这个包安装的所有文件路径。我经常用这个来确认配置文件在哪、二进制在哪、日志目录在哪,比翻文档快多了。
还有一个很实用的查询是rpm -qc nginx,只列出配置文件。当你需要修改某个服务的配置但不确定配置文件路径时,这个命令能直接告诉你。对应的rpm -qd列出文档文件,rpm -qi显示包的详细信息(版本、安装时间、大小、描述等)。这些查询命令不需要 root 权限,普通用户就能执行,日常排查很方便。
3.2 安装与升级:参数选择背后的考量
rpm 安装包的基本命令是rpm -ivh package.rpm。-i是 install,-v是 verbose 显示详细信息,-h是 hash 显示进度条。这三个参数组合几乎是标配,我很少见到有人不加-vh的,因为不加的话你完全不知道安装进度,大包安装时干等着很焦虑。
但-ivh有个问题:如果这个包已经装了,它会报错说已经安装。这时候你有两个选择:rpm -Uvh升级安装,或者rpm -Fvhfreshen 安装。-U的行为是“如果已装就升级,没装就安装”,-F的行为是“只有已装才升级,没装就跳过”。这两个参数的区别很关键:批量升级时用-F更安全,因为它不会意外安装你不想装的新包;单独装一个新包时用-U更方便。
安装时最常见的报错是依赖缺失,比如error: Failed dependencies: libxxx.so.1 is needed by package.rpm。这时候你有几个选择:一是用 yum 来装(如果包在仓库里),让 yum 自动解决依赖;二是手动找到缺失的依赖包,先装依赖再装目标包;三是用--nodeps强制忽略依赖。最后这个选项我要特别警告:除非你非常清楚自己在做什么,否则不要用--nodeps。忽略依赖装上去的包,运行时大概率会因为找不到库而崩溃,到时候排查起来更麻烦。
还有一个参数--force也经常被误用。它的作用是强制安装,即使包已经装了或者文件冲突。有些人遇到报错就加--force,这相当于用锤子修手表。正确的做法是先看清楚报错信息,理解冲突的原因,再决定怎么处理。我见过有人用--force把系统关键库覆盖了,导致整个系统命令都跑不起来,最后只能重装系统。
3.3 卸载与校验:善后工作同样重要
卸载用rpm -e package_name,注意这里跟的是包名,不是 rpm 文件名。比如你装的是nginx-1.24.0-1.el9.x86_64.rpm,卸载时用rpm -e nginx就行,不需要带版本号和架构。这个细节新手经常搞混,报错说找不到包,其实就是名字写多了。
卸载时也会遇到依赖问题:如果别的包依赖你要卸载的包,rpm 会拒绝卸载并告诉你谁在依赖它。这时候你得先处理依赖关系,要么把依赖它的包也卸了,要么就别卸。同样,--nodeps可以强制卸载,但后果是依赖它的软件可能无法运行。我个人的原则是:卸载前先用rpm -q --whatrequires package_name查一下谁依赖它,心里有数再动手。
rpm 还提供了校验功能,rpm -V package_name会检查包安装的文件是否被修改过。输出里每个字符代表一种校验结果:S表示文件大小变了,M表示权限变了,5表示 MD5 校验和变了,D表示设备号变了,L表示链接变了,U表示用户变了,G表示组变了,T表示修改时间变了。如果输出为空,说明文件完好无损。这个功能在排查“配置文件被谁改了”或者“二进制是否被篡改”时非常有用。我习惯在系统交付前跑一遍关键包的校验,留个基线,后续出问题时有对比依据。
4. yum 实战:依赖自动解决与仓库管理
4.1 日常操作:安装、更新、卸载的标准姿势
yum 最常用的命令就是yum install package_name。执行后它会先刷新仓库元数据(如果缓存过期),然后计算依赖,列出将要安装的包清单,等你确认后开始下载安装。这个确认步骤在交互式操作时没问题,但在脚本里就会卡住。所以脚本里通常加-y参数自动确认,比如yum install -y nginx。我建议在脚本里永远加-y,否则脚本跑到一半等你输入,在无人值守的场景下就是灾难。
更新操作有两个命令容易混淆:yum update和yum upgrade。在大多数场景下它们行为一致,都是更新所有可更新的包。但有个历史遗留差异:yum upgrade会连带处理过时的包(obsoletes),而yum update默认不处理。现代 yum 版本里这个差异已经很小了,但为了保险,我通常用yum update,需要处理过时包时再加--obsoletes参数。另外,yum update package_name可以只更新指定的包,这在只想升级某个服务而不动整个系统时很有用。
卸载用yum remove package_name。和 rpm 不同的是,yum 会自动处理依赖:如果卸载某个包会导致其他包依赖缺失,yum 会把这些依赖它的包也一并卸载。这个行为有时候是好事,有时候是坑。比如你卸载一个库,结果发现一堆依赖它的软件都被卸了,系统功能受影响。所以yum remove执行前一定要看清楚它列出的将要卸载的包清单,确认没有误伤再继续。我个人的习惯是,卸载前先yum remove --assumeno package_name预演一遍,看看会牵连哪些包,确认没问题再去掉--assumeno真正执行。
4.2 仓库管理:多仓库共存时的优先级策略
实际生产环境里,一台机器往往配置了多个仓库:系统基础仓库、EPEL 扩展仓库、内部私有仓库、第三方软件仓库等。多个仓库里可能有同名但不同版本的包,yum 该选哪个?这就涉及到优先级问题。
默认情况下,yum 会选择版本号最高的包,不管它来自哪个仓库。但这不一定是你想要的。比如内部私有仓库里的包是经过测试的稳定版本,而第三方仓库里有更新的版本,你希望优先用内部的。这时候就需要配置仓库优先级。有两种方式:一是安装yum-plugin-priorities插件,在 repo 文件里用priority=1这样的数字指定优先级,数字越小优先级越高;二是用includepkgs和excludepkgs精确控制某个仓库只提供哪些包、排除哪些包。
我个人的经验是,内部仓库优先级设为 1,基础仓库设为 10,第三方仓库设为 20。这样默认优先用内部包,内部没有的才去基础仓库找,最后才考虑第三方。同时用excludepkgs把第三方仓库里容易和系统包冲突的包排除掉,比如内核相关、glibc 相关。这套组合拳下来,基本能避免大部分仓库冲突问题。
仓库缓存管理也值得一说。yum 会把仓库元数据缓存在本地,默认在/var/cache/yum下。缓存的好处是加快后续操作速度,坏处是可能拿到过期的信息。当你确认仓库里明明有新包但 yum 就是看不到时,先试试yum clean all清缓存再yum makecache重建。我遇到过好几次“仓库更新了但 yum 死活找不到新包”的情况,都是缓存没刷新导致的。养成习惯:改完仓库配置或者确认仓库有更新后,先清缓存再操作。
4.3 包组与历史记录:批量操作和回滚
yum 有个很实用的功能叫包组(group),它把一组相关的包打包在一起,方便一次性安装。比如yum groupinstall "Development Tools"会安装编译开发所需的一整套工具链,省得你一个个装。yum grouplist可以列出所有可用的包组。这个功能在初始化新服务器时特别好用,几条 groupinstall 就能把常用环境搭好。
但包组也有个坑:不同版本的系统里,包组的名称可能不一样。比如某个包组在旧版本叫 “Development Tools”,新版本可能拆成了 “Development Tools” 和 “Additional Development”。所以脚本里用 groupinstall 时,最好先yum grouplist确认一下名称。我吃过这个亏,脚本在测试环境跑得好好的,到生产环境因为系统小版本不同,包组名对不上,直接报错中断。
yum 的历史记录功能是我最喜欢的功能之一。每次 yum 操作都会被记录在案,yum history可以查看所有历史操作,每条记录有编号、操作类型、时间、影响的包数量。yum history info 编号查看某次操作的详细信息,yum history undo 编号可以回滚某次操作。这个功能在误操作后简直是救命稻草。我有一次手滑用yum remove卸了一堆包,发现后立刻yum history找到刚才的操作编号,yum history undo一键回滚,前后不到一分钟就恢复了。如果没有这个功能,可能得花半小时一个个装回来。
不过要注意,yum history undo不是万能的。它只能回滚 yum 记录的操作,如果你在两次 yum 操作之间手动改了配置文件或者用 rpm 直接装了包,回滚后状态可能不一致。所以回滚后最好再检查一下关键服务是否正常。另外,历史记录默认保留一段时间,太久的记录可能被清理,重要操作后建议手动记录一下操作编号。
5. 离线环境与特殊场景的应对方案
5.1 离线安装:没有网络时怎么搞定依赖
生产环境里经常遇到没有外网的情况,尤其是内网隔离的服务器。这时候 yum 没法从远程仓库拉包,但你又需要装软件,怎么办?核心思路是:在一台有网的机器上把包和依赖都下载好,然后传到目标机器上离线安装。
具体做法是,在有网的机器上用yumdownloader工具下载包及其依赖。yumdownloader默认只下载指定的包,加--resolve参数会连带下载所有依赖。比如yumdownloader --resolve --destdir=/tmp/packages nginx会把 nginx 和它依赖的所有包下载到/tmp/packages目录。然后把这个目录打包传到目标机器,在目标机器上进入目录执行yum localinstall *.rpm或者rpm -ivh *.rpm。用 yum localinstall 的好处是它会自动处理本地包之间的依赖关系,比直接用 rpm 逐个装省心。
但这里有个细节要注意:yumdownloader --resolve下载的依赖是基于当前机器的已装包状态计算的。如果目标机器上已经装了一些依赖,下载的包可能有多余的;如果目标机器上缺一些当前机器有的包,下载的依赖可能不全。所以最稳妥的做法是在一台和目标机器系统版本、已装包尽可能接近的机器上下载。我通常用最小化安装的虚拟机来做这个事,保证下载的依赖集是最小完整的。
还有一个更省事的方案:如果内网有足够的磁盘空间,可以搭建一个本地仓库。把下载好的所有 rpm 包放到一个目录里,用createrepo命令生成元数据,然后把这个目录通过 HTTP 服务暴露出去,在目标机器的 repo 文件里指向这个地址。这样目标机器就能像用外网仓库一样用本地仓库了,后续装其他软件也方便。这个方案前期投入大一点,但长期来看一劳永逸,适合有大量内网机器的场景。
5.2 版本锁定:防止意外升级带来的灾难
生产环境最怕什么?最怕你不想升级的东西被意外升级了。比如某个关键服务依赖特定版本的库,结果某次yum update把库升级了,服务起不来了。这种事故我见过不止一次,每次都是血泪教训。
防止意外升级有几种手段。第一种是用yum versionlock插件锁定特定包的版本。安装yum-plugin-versionlock后,用yum versionlock add package_name把包加入锁定列表,之后yum update就会跳过这些包。需要升级时先yum versionlock delete package_name解锁,升级完再锁上。这个方案适合少量关键包。
第二种是在 repo 文件里用exclude排除特定包。比如exclude=kernel*可以阻止内核被升级。这个方案适合批量排除某一类包。但 exclude 是全局的,一旦配置,所有操作都会忽略这些包,包括你主动想装的时候。所以用 exclude 要谨慎,最好配合yum --disableexcludes=all install package_name在需要时临时绕过。
第三种是精确指定版本安装。yum install package_name-1.2.3-1.el9这样带完整版本号,yum 就会装这个特定版本而不是最新版。但前提是仓库里得有这个版本。如果仓库只保留最新版,这个方案就行不通。所以关键包的旧版本 rpm 文件最好在本地留一份,以备不时之需。
我个人的做法是组合使用:内核和 glibc 这类底层包用 exclude 永久排除升级,业务相关的关键包用 versionlock 锁定,普通包正常更新。同时定期检查yum check-update的输出,看看有哪些包可以升级,评估后再决定是否解锁升级。这样既保证了稳定性,又不至于让系统长期不更新积累安全风险。
5.3 依赖冲突排查:当 yum 也搞不定的时候
yum 虽然能自动解决大部分依赖问题,但有些复杂场景它也会束手无策。最常见的报错是 “Error: Package X requires Y, but none of the providers can be installed” 或者 “conflicting requests”。这类问题通常源于几个原因:多个仓库提供了同名但不同版本的包、某个包的依赖链里存在版本冲突、或者本地 rpm 数据库状态异常。
排查这类问题的第一步是看清楚完整的报错信息。yum 的报错通常会告诉你哪个包需要什么、哪个仓库提供了什么、为什么冲突。不要只看最后一行,往上翻几屏,关键信息往往在前面。我见过有人一看到报错就到处搜,其实报错信息里已经写得很清楚了,只是没耐心看。
第二步是用yum deplist package_name查看包的完整依赖树。这个命令会列出包直接依赖和间接依赖的所有库和包,以及每个依赖由哪个仓库的哪个包提供。通过这个输出,你能清楚地看到依赖链在哪里断了或者冲突了。
第三步是尝试用--skip-broken参数跳过有问题的包。这个参数让 yum 忽略无法解决的依赖,继续安装其他能装的包。但要注意,跳过的包可能正是你需要的,所以用之前先确认跳过了哪些。我通常先用--skip-broken跑一遍看看哪些包被跳过了,如果跳过的包不重要就继续,重要的话就得另想办法。
如果以上都不行,最后的办法是手动介入:用yumdownloader把冲突的包和依赖都下载下来,手动分析依赖关系,用 rpm 逐个安装合适的版本。这个过程比较繁琐,但能解决 99% 的依赖问题。我遇到过一个极端案例:某个第三方仓库的包依赖了一个旧版本的库,而系统基础仓库里只有新版本的库,两者不兼容。最后是从第三方仓库下载了旧版库的 rpm,用rpm -ivh --oldpackage强制装了旧版,才把问题解决。这种操作有风险,装之前一定要确认旧版库不会影响其他软件。
6. 常见问题速查与避坑经验
6.1 高频报错与对应处理
下面这张表整理了我这些年遇到的高频报错和处理方法,基本覆盖了日常 90% 的场景:
| 报错信息 | 常见原因 | 处理方法 |
|---|---|---|
Failed dependencies: xxx is needed by yyy | 缺少依赖包 | 用 yum 安装让 yum 自动解决,或手动下载依赖包先装 |
package xxx is already installed | 包已安装 | 用rpm -Uvh升级安装,或先卸载再装 |
conflicts with file from package xxx | 文件冲突 | 查清冲突文件属于哪个包,决定是否覆盖或先卸载冲突包 |
Error: Nothing to do | 包已在最新版或仓库里没有 | 检查包名拼写,yum clean all清缓存后重试 |
Cannot retrieve metalink for repository | 仓库地址不可达 | 检查网络和仓库地址,确认 DNS 解析正常 |
GPG key retrieval failed | GPG 公钥获取失败 | 检查 gpgkey 地址,或手动导入公钥rpm --import |
rpmdb: BDB0113 Thread/process failed | rpm 数据库损坏 | 备份后重建数据库,或从备份恢复 |
Transaction check error: file /xxx conflicts | 文件冲突 | 用yum provides查文件归属,决定处理方式 |
这张表里的每一条我都在实际工作中遇到过,其中 rpm 数据库损坏那条最麻烦。那次是因为/var分区写满,rpm 数据库写入中断导致损坏。修复过程是:先备份/var/lib/rpm目录,然后删除__db.*文件,再用rpm --rebuilddb重建。重建后部分包的记录丢失了,只能手动补装。从那以后我养成了两个习惯:一是监控/var分区使用率,超过 80% 就告警;二是定期用rpm -qa > /root/rpm_list.txt备份已装包列表,万一数据库出问题,至少有份清单可以对照恢复。
6.2 那些文档里不会写的实操心得
第一条心得:装包之前先yum check-update看一眼。这个命令会列出所有可更新的包,但不实际执行更新。养成这个习惯的好处是,你能提前知道系统有哪些更新待处理,避免在装新包的时候被一堆更新干扰。我通常每周跑一次yum check-update,评估哪些更新需要打、哪些可以缓一缓,做到心里有数。
第二条心得:yum install和yum localinstall的区别要记牢。前者从仓库装,后者从本地 rpm 文件装。但yum localinstall在较新版本里已经被yum install取代了——直接yum install ./package.rpm就能装本地包,而且会自动解决依赖。这个变化很多人不知道,还在用老命令。我建议统一用yum install,不管是仓库包还是本地包,语法一致,省得记两套。
第三条心得:善用yum provides反查文件归属。当你遇到“某个命令找不到”或者“某个库缺失”时,yum provides */command_name或yum provides */libxxx.so能告诉你这个文件属于哪个包,然后直接yum install那个包就行。这个技巧在排查编译错误时特别有用,编译器报缺某个头文件,用 provides 一查就知道该装哪个开发包。
第四条心得:yum history是后悔药,但别依赖它。虽然yum history undo能回滚,但它回滚的是 yum 操作,不包含你手动做的修改。而且如果回滚涉及大量包,执行时间可能很长,期间系统状态不稳定。所以最好的策略还是操作前确认清楚,而不是事后回滚。我现在的习惯是,执行任何yum remove或大规模yum update之前,先yum history记下当前的操作编号,万一出问题知道从哪回滚。
第五条心得:仓库配置里的gpgcheck永远不要关。我见过有人为了省事把 gpgcheck 设为 0,结果装了一个被篡改的包,系统被植入了后门。虽然内网环境相对安全,但谁能保证内网就没有风险?GPG 校验的开销很小,换来的是包完整性的保证,这笔账怎么算都划算。如果遇到 GPG 校验失败,正确的做法是检查公钥是否正确导入,而不是关掉校验。
6.3 性能优化:让 yum 跑得更快
yum 操作慢是很多人的痛点,尤其是仓库多、网络差的时候。有几个优化手段可以显著提升速度。第一个是启用 fastestmirror 插件,它会自动测试各个镜像的速度,选最快的那个。安装yum-plugin-fastestmirror后在 repo 文件里加fastestmirror=1即可。这个插件在配置了多个镜像地址时效果明显,能省不少下载时间。
第二个是合理设置缓存。/etc/yum.conf里的keepcache=1可以让 yum 保留下载的包文件,下次装同样的包时直接用缓存,不用重新下载。但要注意磁盘空间,缓存多了会占地方。我通常设keepcache=1同时定期清理/var/cache/yum里的旧文件。另外metadata_expire参数控制元数据缓存的有效期,默认是 6 小时。如果仓库更新频繁,可以调短一些;如果仓库很稳定,调长一些能减少元数据刷新次数。
第三个是并行下载。较新版本的 yum 支持max_parallel_downloads参数,在/etc/yum.conf里设置max_parallel_downloads=5可以同时下载多个包,充分利用带宽。这个参数在装大量包时效果显著,能把安装时间缩短一半以上。但也不是越大越好,设太大反而会因为磁盘 IO 竞争导致整体变慢。我一般设 3 到 5 之间,根据实际网络和磁盘情况调整。
第四个是本地仓库。如果内网机器多,搭建一个本地仓库是最彻底的优化方案。所有包从本地拉,速度是外网的几十倍,而且不受外网波动影响。搭建方法前面讲过,用createrepo生成元数据,用 HTTP 服务暴露出去。维护成本主要是定期同步外网仓库的更新,可以写个定时脚本自动同步。
7. 从 rpm 到 yum 再到现代包管理的演进思考
聊完了 rpm 和 yum 的具体用法,我想再往深一层说说这套体系的设计哲学,以及它在今天的定位。rpm 和 yum 的分层设计其实非常经典:底层 rpm 负责单个包的原子操作和数据库记录,上层 yum 负责仓库管理和依赖解析。这种分层让每一层都可以独立演进——rpm 的包格式可以升级,yum 的依赖算法可以改进,互不影响。后来出现的 dnf 就是 yum 的下一代实现,它用 libsolv 做依赖求解,速度比 yum 快很多,但命令和概念基本兼容。你在 yum 上学到的知识,迁移到 dnf 上几乎零成本。
这套体系的核心价值在于“可追溯”和“可复现”。每个包有明确的版本、来源、依赖关系,每次操作有历史记录,整个系统的软件状态是透明可控的。这和现在流行的容器镜像理念其实是一致的——容器镜像本质上也是一种包管理,只是粒度更大、隔离性更强。理解了 rpm 和 yum,你再去看容器镜像的分层、标签、仓库这些概念,会发现很多设计思路是相通的。
当然,rpm 和 yum 也有它的局限。依赖解析在复杂场景下仍然可能失败,多仓库共存时的冲突处理不够优雅,离线场景的支持也比较笨重。但这些局限并不妨碍它成为传统 Linux 服务器上最可靠的软件管理方案。我个人的看法是:新技术有新技术的好,老工具有老工具的稳。在生产环境里,稳定可靠比时髦重要得多。你可以用容器跑新业务,但底层宿主机的软件管理,rpm 和 yum 依然是绕不开的基本功。
最后分享一个我自己的习惯:每台新服务器交付时,我都会跑一遍rpm -qa | sort > /root/.rpm_baseline,把初始的包列表存下来。后续如果怀疑系统被改动过,用rpm -qa | sort | diff /root/.rpm_baseline -一对比,就知道多了哪些包、少了哪些包。这个基线文件在安全审计和故障排查时特别有用,成本几乎为零,但关键时刻能帮你快速定位问题。这个习惯我坚持了好几年,推荐你也试试。