RPM包管理从入门到实践:命令、依赖与rpmbuild打包 拿到一台新的 Linux 服务器尤其是 CentOS、Rocky 或者 RedHat 这类基于 RPM 体系的系统你总要跟rpm这个命令打交道。不管是装个 MySQL、部署个 Java 环境还是排查某个文件到底属于哪个软件包都绕不开它。但说实话很多人对 rpm 的认知停留在装包工具这个层面出了问题就靠--force和--nodeps硬上最后把系统搞到依赖一团乱麻不得不重装。这篇文章我就把 RPM 这套体系从头到尾拆开来讲——它到底是怎么工作的、日常命令怎么用、依赖问题为什么会产生、以及更进阶的怎么自己打包一个 rpm 文件。内容会尽量贴近实际运维场景不是那种随手查得到的 man 手册翻译版。1. RPM 包内部是什么一个自带说明书和安装脚本的加密压缩包RPM 全称是 RPM Package Manager虽然叫 Package Manager但本质上它是一套文件打包 安装记录 脚本执行的完整规范。一个.rpm文件并不是简单的 tar.gz 压缩一下再改个后缀它内部有严格的二进制结构包含文件载荷、元数据、安装卸载脚本、签名信息等多个分区。1.1 一张图看懂 rpm 包的文件结构想象一下你收到一个快递盒子。盒子外面贴着快递单元数据里面有你要买的商品程序文件还附带一张安装说明书安装脚本和一张售后卡签名信息。RPM 包就是这个盒子的标准化版本Lead 区包含 magic number 和版本信息用来识别这是一个 rpm 文件Signature 区存放 SHA-256 等签名值用于校验包完整性和来源可信度Header 区这是整个包的大脑记录了软件名称、版本号、Release 号、依赖关系Requires、提供的能力Provides、安装卸载前后的脚本等Payload 区实际要安装的文件本身通常用 zstd 或 gzip 压缩后存放。这些数据综合保证了一个 rpm 包安装到系统里不只是把文件复制过去还要更新系统的软件记账数据库即 RPM 数据库记录哪些文件被装到了哪个位置、属于哪个包、版本是多少。1.2 为什么 RPM 能解决源码安装五步走的痛点早年间 Linux 装软件的主流方式是源码安装./configure、make、make install听起来很极客但实际用起来非常痛苦。比如你装了一个依赖库 A 的软件 B几十个文件散落到/usr/local各个目录里想卸载只能一个个找升级的时候文件被新版本覆盖但老版本的配置残留可能引发玄学 bug。RPM 把这个流程彻底规范化了。它保证安装前检查依赖是否满足不满足直接拒绝安装并明确告诉你缺什么安装时把文件清单登记到数据库每个文件都能追溯来源卸载时可以干净地反悔把装进去的文件全部撤掉升级时知道哪些配置文件是用户改过的不会盲目覆盖。这就是为什么企业级 Linux 发行版RHEL、CentOS、Rocky、openEuler、银河麒麟等都采用 RPM 体系——它让软件安装从手工爆破变成了可审计的标准化操作。1.3 RPM 数据库系统里那本看不见的账本你用rpm命令做的每一个查询本质上都是在访问系统里的 RPM 数据库。这个数据库位于/var/lib/rpm目录在较新的发行版上rpm 4.16 之后默认使用 sqlite 后端文件通常叫rpmdb.sqlite老一点的则是一组 BDBBerkeleyDB文件。数据库里记录了三类关键信息已安装包列表每个 rpm 包的名称、版本、Release、安装时间文件清单每个包安装的所有文件路径、权限、类型依赖关系每个包 requires 什么能力、provides 什么能力。你在执行rpm -qa时其实就是在翻阅账本。这也带来一个常见的坑如果系统异常断电导致 RPM 数据库文件损坏那么所有 rpm 查询和安装操作都会报错不过这个坑我在第 4 节会专门讲恢复方案。2. 日常用得最多的 rpm 操作查询、安装、升级、卸载的完整命令拆解rpm 的命令语法不复杂但参数多且容易混。核心要点是记住动作字母-q查询、-i安装、-U升级、-e卸载、-V校验然后加上辅助参数组合使用。2.1 查询类命令决定你是不是个老手的分水岭很多人对 rpm 的查询参数不熟悉遇到问题只会rpm -qa | grep。实际上组合查询是日常排查效率最高的动作。命令组合作用实用场景rpm -qa列出系统所有已安装的 rpm 包想确认某软件装没装rpm -q 包名查询指定包是否安装及版本只确认单个包rpm -ql 包名列出该包安装的所有文件路径想知道软件装哪了rpm -qf /路径/文件查询某个文件归属于哪个包排查文件被谁动了rpm -qi 包名查看包的详细信息含安装时间需要包的全量元数据rpm -qc 包名列出该包的配置文件快速找配置位置rpm -qd 包名列出该包的文档文件找帮助文档举个例子你发现/etc/my.cnf不知道被谁改坏了想找到它属于哪个包rpm -qf /etc/my.cnf输出会是mariadb-connector-c-config-3.2.5-1.el9.noarch之类的包名。这就是把从文件反查包的能力用起来了非常实用。再举一个典型场景你想知道 Nginx 装完后把文件都放哪了。rpm -ql nginx输出会列出/etc/nginx、/usr/sbin/nginx、/usr/share/nginx等一长串路径。通过这个方式你能快速掌握一个陌生服务的安装布局。2.2 安装和升级-ivh、-Uvh、-Fvh 这三兄弟的区别新手最容易混淆的三个参数。先说结论再讲原理rpm -ivh 包名.rpm安装一个新包如果这个包已经存在会报package already installedrpm -Uvh 包名.rpm升级包如果包没装过它会直接安装相当于 installrpm -Fvh 包名.rpm只升级已存在的包如果包没装过直接跳过不装。-i、-U、-F后面通常搭配-v显示详细过程和-h显示安装进度条就组成了-ivh、-Uvh、-Fvh这三兄弟。实操中最常见的是在已有老版本的情况下想换成指定版本rpm -Uvh mysql-community-server-8.0.36-1.el7.x86_64.rpm如果老版本是 8.0 系列它会自动覆盖升级注意这里我要提醒一句跨大版本升级比如 5.7 升 8.0一定先备份数据RPM 层面的文件覆盖不会帮你做数据库兼容处理。还有个小技巧某些场景下你想强制降级downgrade但rpm -Uvh默认不允许装比当前更旧的版本会报 older version installed。这时需要加--oldpackage参数rpm -Uvh --oldpackage mysql-community-server-5.7.44-1.el7.x86_64.rpm2.3 卸载-e 的正确姿势与两个后悔药参数卸载用rpm -e但这个命令最有讲究的是它默认不允许卸载被其他包依赖的包。比如你执行rpm -e httpd如果系统里有别的包依赖 httpd 的某些文件你会看到error: Failed dependencies: httpd is needed by (installed) php-xxx这时候千万不要头脑一热加--nodeps强删。正确的做法是先卸载依赖它的上层应用或者用rpm -e --test做一次试删观察会不会报依赖错误rpm -e --test httpd--test 是个很少人用但非常安全的参数它会模拟整个卸载流程告诉你如果真卸载会发生什么但不会真的删文件。我在处理环境清理类需求时总是先--test再动手等于给操作上了一份保险。2.4 校验功能-V 参数检查哪些文件被篡改过rpm -V是检查已安装包文件完整性的命令。它会把系统里现有文件的属性、内容、权限等与 RPM 数据库里记录的初始值比对输出差异。rpm -V openssh-server输出结果中如果每一行都以点号.开头说明文件完好无损如果出现字母就表示对应属性变了S文件大小变了M权限/文件类型变了5文件内容MD5/SHA256哈希变了D设备号变了L符号链接目标变了U/G属主/属组变了T修改时间变了遇到5标记要特别警惕这往往是文件被手动改动过或者中招的征兆。这个命令在系统加固和安全排查时很有价值。3. 依赖地狱为什么 rpm 单独装包总会失败以及 yum/dnf 如何帮你收拾残局几乎每个从新手期走来的人都经历过这种报错error: Failed dependencies: libcrypto.so.10()(64bit) is needed by mysql-community-xxx这就是 RPM 最经典的依赖地狱问题——一个包依赖的共享库或工具必须预先存在否则安装会被拒绝。RPM 的设计哲学是安全优先缺依赖就不装这是保护机制不是 bug。3.1 依赖是硬编码在包里的能力声明每个 rpm 包在构建时会扫描出自己依赖哪些共享库、哪些命令把它们以Requires字段写进包元数据里。同时每个包也会声明自己提供哪些能力以Provides字段记录。举个例子说明流程。你下载了一个mysql-community-server的 rpm执行安装时rpm 会读取它的 Requires 列表找到libaio.so.1、libnuma.so.1等依赖扫描系统 RPM 数据库里所有已安装包的 Provides 列表如果发现某个依赖没有任何包提供直接报错终止。所以--nodeps这个参数的本质是跳过依赖检查强行装它绕过的是安全门禁不是在帮你解除依赖——装完照样缺依赖运行起来照样崩。3.2 yum/dnf 的高明之处自动从仓库里补齐依赖正因 rpm 单独装包会卡在依赖上现代 Linux 发行版都标配了 yumCentOS 7 及以前或 dnfCentOS 8 及以后、Fedora。yum/dnf 的定位是rpm 的前端工具它们做的事情可以概括为从配置好的软件仓库repo下载 rpm 包自动解析依赖关系按顺序把所有需要的包全部装齐建立本地缓存下次装相同包不用重复下载。这就是为什么我强烈建议能用 yum/dnf 装的就不要手动 rpm -ivh。尤其在生产环境里手动装一个 rpm 往往会牵扯出一连串手工装依赖的操作得不偿失。一条命令装完 MySQL 的典型姿势yum install -y mysql-community-server如果本地没有合适仓库你也能用yum localinstall让它同时处理本地 rpm 和线上依赖yum localinstall mysql-community-server-8.0.36-1.el7.x86_64.rpm3.3 最小化安装系统后先配 dnf 源再谈其他很多初学者装完 CentOS/Rocky 后第一件事就是yum install xxx结果提示找不到软件包。排查思路是三步确认网络通不通curl -I https://mirrors.xxx.com或你配置的源地址查看仓库列表是否正常dnf repolist清除旧缓存重新生成dnf clean all dnf makecache。在配置软件源的时候国内生产环境通常建议配置可用的内网镜像源或企业自建源加速效果明显。改源时注意备份原文件结构大致是/etc/yum.repos.d/下的.repo配置文件。配好之后日常运维绝大多数软件安装都能通过dnf install解决rpm 手动命令要保留给安装本地离线包和查询系统信息这两类场景。4. 那些年踩过的 rpm 坑命令找不到、数据库损坏、强删依赖这部分我讲几个真实发生过的坑每个都是反复出现的问题希望你能避雷。4.1 没找到 rpm 命令是真没装还是环境变量破了有些轻量容器镜像、嵌入式 Linux 环境真的没有 rpm 命令。但也有些场景下 rpm 命令存在却提示bash: rpm: command not found这多半是 PATH 环境变量被改坏了。判断方式很简单which rpm /usr/bin/rpm如果 which 能找到但 bash 执行不了就是你的 PATH 里没有/usr/bin。这种情况不用急着重装直接export PATH/usr/bin:/bin:/usr/sbin:/sbin:$PATH就能临时恢复。不过在高危发行版上比如某些用 dnf 替代 rpm 的衍生系统也可能真的没有 rpm 前端那就通过dnf install rpm装回来。另外一个高频场景是 Debian/Ubuntu 系系统里有人习惯性敲rpm命令会得到 command not found。正确的是用 dpkg 和 apt严格来说 RPM 体系只适用于 Red Hat 系和 OpenSUSE 等这属于选错工具而不是系统坏了。4.2 RPM 数据库损坏了怎么救我在实际运维中遇到过不止一次某台机器断电重启后任何 rpm 查询都报这样的错rpm: fatal error: ... rpmdb: damaged或者error: db5 error(11) from dbenv-open: Resource temporarily unavailable这种问题大概率是/var/lib/rpm目录下的数据库文件异常导致的。恢复思路依赖你使用的后端现代 rpm 默认是 sqlite 后端先尝试重建索引rm -f /var/lib/rpm/__db.* rpm --rebuilddb如果是老版本 Berkleley DB 后端上面的清理方式同样适用。重建后执行rpm -qa | head验证一下是否能正常输出。这里必须强调一个原则处理数据库问题之前先备份别上来就删。文件是系统的账本删错了会更麻烦。推荐顺序是cp -a /var/lib/rpm /var/lib/rpm.bak.$(date %F) rm -f /var/lib/rpm/__db.* rpm --rebuilddb4.3 --force 和 --nodeps 是核武器不是常规工具有些教程动不动就让人rpm -ivh --force --nodeps xxx.rpm我非常反对这种习惯。--force的本质是忽略文件冲突、强制覆盖--nodeps是跳过依赖检查。两者叠加等于告诉系统别管合理性直接塞进去。短时间可能装上并跑起来了但系统里的 RPM 数据库已经说谎——它记录了一个缺依赖的包后续任何更新合并都可能失败。什么情况下可以合理使用--nodeps只在确认系统里其实有等价能力比如自己编译的库只是 rpm 数据库不认账时用--force只在覆盖重装某个文件被误删的包时用比如rpm -ivh --force openssh-server-xxx.rpm但记住用完这两个参数后应该立刻用yum/dnf做一次check或重装校验确认系统依赖关系是正常的善后工作比操作本身更重要。5. 从使用者到打包者手写 spec 文件给自己工具打一个 rpm 包作为运维/后端工程师除了会用还要会打。比如内网要分发一个自己编译的二进制工具官方没有 rpm 包手工复制到每台机器显得很原始打成一个 rpm 包走 yum 仓库分发才是规范做法。这一节用一个小例子完整演示打包流程。5.1 rpmbuild 环境准备与目录结构首先安装打包工具yum install -y rpm-build rpmdevtools然后使用rpmdev-setuptree帮你生成标准工作目录rpmdev-setuptree这个命令会在你的 HOME 下生成rpmbuild目录内部结构是BUILD构建源码时用的临时目录BUILDROOT打包时模拟安装的根目录装出来的东西先进这里RPMS最终的二进制 rpm 包输出目录按架构分 x86_64、noarch 等;SOURCES存放源码包、补丁等原材料SPECSspec 文件所在地这是打包的核心控制文件SRPMS源码 rpm 包输出目录。5.2 一个最小可用的 spec 文件逐行拆解以打包一个非常简单的 hello 命令为例源码就一个 C 文件hello.c编译后输出可执行文件/usr/bin/hello。把hello.c打成 tar.gz 放进 SOURCEStar czvf hello-1.0.tar.gz hello.c。然后写 SPECS 下的hello.specName: hello Version: 1.0 Release: 1%{?dist} Summary: A simple hello world rpm package License: MIT URL: https://example.com/hello Source0: %{name}-%{version}.tar.gz BuildRequires: gcc Requires: glibc %description A minimal example rpm package built for demonstration purposes. %prep %setup -q %build gcc -o hello hello.c %install install -D -m 0755 hello %{buildroot}/usr/bin/hello %files /usr/bin/hello %changelog * Wed Jul 10 2024 Your Name youexample.com - 1.0-1 - Initial package build几个关键字段解释Name/Version/Release这是包唯一名的三要素组合起来就是hello-1.0-1.el9.x86_64.rpmSource0告诉 rpmbuild 原材料 tar 包在哪变量%{name}-%{version}自动展开成hello-1.0BuildRequires构建期依赖比如编译需要 gccRequires运行期依赖这个二进制程序依赖 glibc%prep准备阶段通常写%setup -q作用是解开 tar 包并进入目录%build执行编译命令%install把编出来的文件安装到%{buildroot}对应的目录里。这里-D是自动创建父目录-m 0755设权限%files声明最终 rpm 包里要包含哪些文件路径注意这里写的是系统安装后的路径不是 buildroot 里的路径%changelog版本变更记录写规范了后面追溯很有用。5.3 完整打包与常见报错处理执行打包命令rpmbuild -ba rpmbuild/SPECS/hello.spec如果一切正常你会在rpmbuild/RPMS/x86_64/下看到hello-1.0-1.el9.x86_64.rpm。这时可以拿它去别的机器上安装验证rpm -ivh hello-1.0-1.el9.x86_64.rpm然后执行hello输出Hello, World!就说明打包成功了。几个新手高发报错和原因error: File not found: /root/rpmbuild/BUILDROOT/hello-1.0-1.el9.x86_64/usr/bin/hello说明%install阶段没有把文件准确放到%{buildroot}下对应的路径里。检查install命令的路径与 spec 里是否一致Exec failed: No such file or directory往往是%build阶段缺少编译依赖给 BuildRequires 补上对应工具即可Package already exists: ...rpmbuild 的输出目录里已经有同名 rpm清掉旧文件或者提高 Release 版本号。进阶一点如果需要打包的东西是 Python 脚本或纯配置文件可以把%build留空、%install直接拷贝文件如果软件有多个不同架构版本还可以用 spec 里的条件判断来控制编译逻辑。6. 进阶实践用 rpmbuild 处理 OpenSSH 这类自己定制版本的打包需求很多人搜索centos8 openssh 打包rpm本质是想把一个编译版 OpenSSH 维护进系统的 RPM 体系里避免和 yum/dnf 管理的官方包冲突。实际操作步骤可以总结为准备源码下载目标版本 OpenSSH 源码包放到 SOURCES编写 spec参考系统自带 openssh 的 spec 改版本号。查看系统自带 spec 可以用yumdownloader --source openssh rpm -ivh openssh-*.src.rpm # 然后查看 ~/rpmbuild/SPECS/openssh.spec改版本号把Version改为目标版本Release改成自定义值比如1.custom执行打包rpmbuild -ba SPECS/openssh.spec本地安装验证yum localinstall RPMS/x86_64/openssh-*.rpm安装到测试机验证连接、权限、PAM 兼容性等。这种做法的好处是最终的服务仍然由 RPM 数据库管理卸载可以干净回滚后续rpm -V也能校验完整性。另一个重点自定义打包时严禁把 Release 设成和官方包相同的值否则后续 yum 更新时会分不清谁新谁旧导致覆盖混乱。OpenSSH 因为涉及 PAM、systemd、selinux 等联动组件打包复杂度远高于 hello 程序但思路是通用的——先解压官方 src.rpm 获得 spec 基础再改版本号和 patch最后重新打包。我处理过几次这类需求最深的感受是永远在构建环境里测试不要在生产机器上直接 rpmbuild因为打包过程会缺 build 依赖而且 rpmbuild 的%buildroot如果配置不当会有越界安装的风险。7. 一些我压箱底的经验rpm 使用的习惯与意识最后分享几个我这些年实际工作中总结出的习惯谈不上系统理论但真的能少走不少弯路。第一个经验是能查就查别瞎试。所有关于 rpm 的灵异问题第一动作永远是rpm -qa | grep确认装的版本然后rpm -ql确认文件位置再rpm -V确认文件有没有被动过。三步走完80% 的疑问都能自己解开。第二个经验是参数从短到长责任从小到大。安装时先rpm -ivh --test没问题再正式装卸载时先rpm -e --test校验先rpm -V。把看似多余的试运行变成肌肉记忆就不会动不动把系统搞坏。第三个经验是RPM 数据库是系统安全审计的重要线索。生产服务器如果发现某个系统二进制文件被替换rpm -V报5要立刻排查入侵痕迹。这个我在一次应急响应中被验证过——通过rpm -V定位到sshd和ls两个命令的文件哈希异常才顺藤摸瓜确认了设备被黑。第四个经验是特殊情况下RPM 也能装进非标准目录。比如内网环境没法用 root 权限安装某些依赖库你可以用rpm -i --prefix$HOME/opt把包装到指定前缀目录。注意这要求包本身支持 relocatablespec 里定义了Prefix:字段很多包不支持所以这个方法有局限性但了解一下没坏处。需要提一句的是这种方式装的文件不会完整记录到系统 RPM 数据库严格说会记录但路径计算逻辑不同生产环境慎用。这些年看下来RPM 相关的问题90% 不是命令不会而是没理解包管理的基本逻辑。当你明白了包是清单文件脚本的组合体数据库是账本依赖是硬性契约这三件事绝大多数坑都能自己绕过去。剩下的 10%靠rpm -V、rpm --rebuilddb、--test这几个工具也就够用了。如果这篇文章对你有点帮助欢迎留言分享你踩过的 rpm 相关的坑或者你有更刁钻的 rpm 用法也欢迎交流。