Linux软件安装三方式:RPM、解压与YUM选型排错

发布时间:2026/10/26 15:25:30
Linux软件安装三方式:RPM、解压与YUM选型排错

1. 先搞清楚三种安装方式到底在解决什么问题

装软件这件事,看起来是 Linux 入门第一课,实际上很多人用了两三年,遇到依赖冲突、命令找不到、装完卸载不干净的时候还是会卡住。我把这几种方式在心里过了一遍:RPM 安装、解压缩安装、YUM 在线安装,它们并不是三个并列的"选项",而是三种不同层次的取舍——一种是把包硬塞进系统,一种是把程序自己摆到某个目录里,还有一种是把"找包、下包、解依赖"这堆活交给仓库管家。搞清楚它们各自的边界,比背命令重要得多。

这篇内容适合两类人:一类是刚接触 Linux、被rpm -ivh和tar -zxvf搞得一头雾水的同学;另一类是用了一段时间、只会yum install,一旦遇到内网无外网、或者某个包仓库里根本没有的时候就抓瞎的朋友。我会把三种方式的原理、完整操作流程、参数选择理由、以及我自己踩过的坑都摊开讲,尽量做到你看完能直接照着做,而不是只知道"有这么个命令"。

先给一个总览,后面每一节再展开。三者的本质区别在于:谁来负责把软件放到正确的位置,以及谁来负责处理依赖关系。RPM 把前者做了一半、后者基本不管;解压缩安装两个都不管,全靠你自己;YUM 两个都管,但前提是仓库里有这个包、而且你能连上仓库。理解了这句话,后面所有问题基本都能自己推导出来。

1.1 软件在 Linux 上到底是怎么"装"上去的

很多人对"安装"的理解是"复制文件",这个理解不算错,但漏了后面几件事。一个软件真正能用起来,通常要完成四件事:把可执行文件、库文件、配置文件、文档分别放到约定好的目录(比如/usr/bin、/usr/lib64、/etc、/usr/share/doc);在包数据库里登记"我装了什么、装了哪些文件",方便以后查询和卸载;处理它依赖的其他库和程序;必要时注册服务、创建用户、更新动态链接库缓存。

RPM 安装做的事情,是把一个已经按上述规则打包好的.rpm文件解开并登记,所以它快、可控、能查、能卸,但它不会帮你去找缺失的依赖,缺什么它就报错停下。解压缩安装则是把打包者准备好的文件树整个丢到你指定的目录,系统包数据库里什么都没有,卸载就是删目录,依赖全靠你手动补。YUM 是在 RPM 之上加了一层"仓库 + 依赖求解",它自己会去仓库元数据里算出需要哪些包,然后一次性按顺序装完。

我用一个生活化的类比:RPM 像你从厂家直接买了一套散件家具,图纸齐全、螺丝编号清楚,但少一颗螺丝它不会自己变出来;解压缩安装像你去建材市场拉了一车木板回家,想做成什么全凭手艺,做坏了也只能自己拆;YUM 像你在电商下单,平台会自动把配套的五金件一起打包发过来,你只需要等收货。三种方式没有绝对优劣,只有场景匹配。

1.2 三种方式的定位对比与选型依据

先把对比表放出来,这张表我在实际带人的时候经常直接甩给对方,能省掉很多解释。

对比维度RPM 安装解压缩安装YUM 在线安装
包来源本地.rpm文件.tar.gz/.tar.xz/.zip等远程或本地仓库
依赖处理不处理,缺依赖直接报错完全不处理自动求解并安装
安装位置按包内规则,通常是系统目录你指定的--prefix或直接解压目录按包内规则,通常是系统目录
是否登记包数据库是否是
卸载方式rpm -e,相对干净删目录,可能残留配置yum remove,相对干净
版本控制手动管理,易冲突天然隔离,可多版本共存仓库统一管理,支持升降级
典型场景内网离线、仓库里没有的包源码编译、绿色版工具、特定版本需求日常绝大多数安装需求
主要风险依赖地狱、覆盖系统文件编译失败、库路径混乱仓库不可达、版本被仓库锁死

选型我的习惯是这样:能 YUM 就 YUM,因为依赖处理和后续升级最省心;仓库里没有、或者版本要求很特殊,才考虑 RPM 本地包;只有当需要改编译参数(比如开启某个默认没编进去的模块)、或者官方只提供压缩包、或者需要多版本共存时,才走解压缩安装这条路。这个顺序能挡掉八成以上的麻烦。

有一点要提前说清楚:这三种方式不是互斥的。你完全可以用 YUM 装编译工具链和依赖库,再解压缩编译一个特定版本的软件;也可以把 RPM 包放进自建仓库,用 YUM 来统一管理。真正的高手不是背命令,而是知道什么时候把哪一层工具叠上去。

2. RPM 安装:离线、可控,但依赖是绕不开的坎

RPM 是我在内网环境里用得最多的方式。原因很现实:很多生产环境不能连外网,仓库也未必齐全,这时候手上一堆.rpm文件,能不能装上、装完会不会把系统搞坏,全靠你对这个工具的理解深度。很多人对 RPM 的印象停留在rpm -ivh一把梭,结果报一堆Failed dependencies就懵了,其实它的报错信息写得很清楚,只是没耐心读。

这一节我会把包名规则、常用参数、依赖判断、以及几个容易翻车的地方讲透。你先建立一个认知:RPM 是一个"执行者",不是"决策者"。它只负责把包里的内容按规则落地并登记,至于这个包该不该装、和现有系统冲不冲突,它只在很有限的范围内做检查(比如文件冲突、依赖缺失),剩下的事得你来判断。

2.1 RPM 包名不是随便起的,读懂它少走一半弯路

一个标准 RPM 包名长这样:包名-版本号-发布号.架构.rpm。拿一个常见的例子拆开看,httpd-tools-2.4.6-97.el7.x86_64.rpm,httpd-tools是包名,2.4.6是上游版本,97.el7是打包者的发布号(el7通常表示面向某个企业级发行版第 7 代),x86_64是架构。这个结构不是装饰,它直接影响你安装时的判断。

架构这一位最容易被忽略。x86_64和i686是两个不同的包,noarch表示与架构无关(比如纯文档、纯脚本)。你在 64 位系统上装了一个i686的包,很多时候不会报错,但运行起来会去找 32 位的库,然后报"找不到共享库",这个坑我见过不止一次。还有一种情况是包名里带el7但你系统是el8,装上去可能因为 glibc 版本不匹配直接起不来。

版本号和发布号的区别也值得说一下。版本号是软件本身的版本,发布号是打包方针对这个版本做的第几次修订(打了补丁、改了编译选项都算)。所以2.4.6-97和2.4.6-98是同一个上游版本的两个不同构建,升级时经常需要关注发布号的变化。你在排查"为什么两台机器同一个软件行为不一样"的时候,rpm -q 包名输出的完整版本串就是第一手线索。

注意:不要用文件名去猜包内容。有些包的命名并不完全规范,尤其是第三方打包的。安装前用rpm -qpi xxx.rpm看一眼描述和依赖,比事后救火便宜得多。

2.2 常用参数与一套完整的安装动作

rpm的参数分几大类:安装/升级、查询、卸载、校验。日常高频的其实就十几个,我按使用频率列一下。

安装和升级相关的:

# 安装一个新包,显示进度 rpm -ivh package.rpm # 升级:如果已安装旧版则升级,没装过则安装 rpm -Uvh package.rpm # 仅升级,没装过就报错,适合不想误装新包时 rpm -Fvh package.rpm # 只做测试,不真正写入,用来预判冲突 rpm -ivh --test package.rpm # 忽略依赖强制安装(慎用) rpm -ivh --nodeps package.rpm # 覆盖已存在的文件 rpm -ivh --replacefiles package.rpm # 重装同版本,用于修复文件被误删 rpm -ivh --replacepkgs package.rpm

这里重点讲-i和-U的区别,因为这是新手最容易搞混的地方。-i是纯安装,如果系统里已经有同名包(不管版本),它会报package xxx is already installed然后退出;-U是升级语义,已装则升级、未装则安装,而且它会在升级后把旧版本的文件清理掉(标记为not installed)。实际操作里我基本用-Uvh,因为它的行为更符合"我要这个版本"的意图。-Fvh用得少但有用,它只升级已装的包,适合批量刷新补丁,不会给你意外引入新软件。

--test这个参数我要单独强调。它会把整个安装过程走一遍但不动磁盘,冲突、依赖问题都会提前暴露。内网环境里我养成的习惯是:先--test,再正式装。多花三秒钟,能避免装到一半失败留下的半残状态。

查询相关的参数用得非常频繁,尤其是排查问题的时候:

# 查某个包是否已安装、版本是多少 rpm -q httpd # 列出系统上所有已安装的包 rpm -qa | grep -i nginx # 查某个包装了哪些文件 rpm -ql httpd # 反查某个文件属于哪个包 rpm -qf /usr/sbin/httpd # 查一个还没安装的 rpm 文件的信息 rpm -qpi package.rpm rpm -qpl package.rpm # 查这个包依赖了什么 rpm -qpR package.rpm # 查这个包提供了什么能力 rpm -qp --provides package.rpm # 看包自带的安装脚本(pre/post install) rpm -qp --scripts package.rpm

rpm -qf这个反查是我最常用的命令之一。系统里某个配置文件不知道哪来的、某个二进制不知道谁装的,一条命令就能定位。rpm -qp --scripts也很有价值,很多包在安装前后会执行脚本(创建用户、初始化数据库、启动服务),你在离线环境装第三方包之前看一眼脚本内容,能提前知道它会不会动系统的关键配置。

卸载就一条rpm -e 包名,注意这里用的是包名不是文件名,也不是全路径。有个常见误区是rpm -e httpd-2.4.6-97.el7.x86_64.rpm,这样会报"package not installed",因为它把整串当成了包名。正确的写法是rpm -e httpd,如果存在多个版本需要指定完整版本串rpm -e httpd-2.4.6-97.el7。

2.3 依赖报错怎么读,怎么一步步补齐

依赖报错是 RPM 最大的门槛,但它的信息其实很结构化。典型输出长这样:

error: Failed dependencies: libapr-1.so.0()(64bit) is needed by httpd-tools-2.4.6-97.el7.x86_64 /etc/mime.types is needed by httpd-tools-2.4.6-97.el7.x86_64

注意看,依赖有两种形态:一种是共享库依赖,写成libxxx.so.N()(64bit);另一种是文件依赖,直接写一个路径。这两种的处理思路不一样。

共享库依赖,你要先搞清楚这个.so文件在系统里归哪个包管。有两种查法:如果系统已经装了提供这个库的包,rpm -qf /usr/lib64/libapr-1.so.0能直接反查;如果没装,就得靠仓库来查,yum provides "*/libapr-1.so.0"或者yum whatprovides libapr-1.so.0会告诉你哪个包提供了它。文件依赖同理,yum provides /etc/mime.types就能定位。

这里有个很实用的技巧:当你手上有一堆互相依赖的 RPM 包时,不要一个个试。把所有包放到同一个目录,先执行rpm -ivh *.rpm --test,它会一次性把所有缺失依赖列全;然后缺的包用yum provides逐个查、逐个补,补完再--test一次。循环两三轮基本就齐了。这个笨办法在离线环境里比任何脚本都可靠。

还有一种情况是循环依赖,A 依赖 B,B 又依赖 A。RPM 单条命令没法处理这种,必须用rpm -ivh A.rpm B.rpm一次性把两个包同时传入,让它在同一事务里解决。如果循环链更长,就把整条链上的包一起传进去。

注意:--nodeps和--force是两个"后悔药",不是"常用药"。--nodeps强行跳过依赖检查,装完很可能运行时才报找不到库,那时候排查成本高得多;--force会覆盖冲突文件,可能把系统里另一个包的文件改掉,导致那个包行为异常。我用这两个参数的次数,一年不超过三次,而且每次都会在笔记里记下装了啥、为什么这么干。

2.4 RPM 安装的实操心得与几个隐蔽的坑

先说一个我觉得最值得分享的:装之前先看包里会不会覆盖系统文件。rpm -qpl package.rpm列出文件清单后,你挑几个关键路径(比如/etc/下的、/usr/bin/下的)用rpm -qf反查一下是否已被别的包占用。如果占用,安装时就会报file /xxx from install of A conflicts with file from package B。这个冲突有两种处理:确认新版确实要覆盖就加--replacefiles,但事后要检查被覆盖的那个包是否还能正常工作;如果是误装,就换一个不同--prefix的包或者干脆走解压缩安装。

第二个坑是安装脚本失败导致包处于半装状态。RPM 的安装是分阶段的:先解包写文件,再跑%post脚本,脚本失败的话包会标记为安装完成但实际不可用。这时候rpm -q显示已装,服务却起不来。排查方法是看/var/log/messages或者直接手动执行%post里的命令复现。修复手段通常是rpm -e卸载后重装,实在卸不掉就rpm -e --noscripts跳过脚本卸载。

第三个是升级后配置文件被改名。RPM 对标记为配置文件的路径(%config)有特殊处理:升级时如果本地改过,新文件会存成.rpmnew,旧文件保留;如果本地没改过,直接覆盖,旧文件存成.rpmorig。所以升级完发现配置"没生效",先去看看目录里有没有.rpmnew,这个细节我在生产环境踩过一次,排查了半小时。

还有一个容易被忽略的点:RPM 数据库本身可能损坏。如果你执行rpm -qa报error: rpmdb: BDB0113 Thread/process failed,说明数据库出问题了。修复流程是先把/var/lib/rpm备份,然后rm -f /var/lib/rpm/__db.*,再rpm --rebuilddb重建。这个操作有风险,务必先备份,我一般会把整个目录打包留档再动手。

3. 解压缩安装:源码编译与二进制包两条路

解压缩安装这个词其实涵盖了两种完全不同的东西,很多人混为一谈。一种是源码包,解压出来是一堆.c、.h和configure脚本,需要你自己编译;另一种是预编译的二进制包,解压出来直接就是可执行文件,改改权限就能跑(常见于一些绿色版工具、JDK、Node 之类)。前者复杂、耗时长、依赖多,后者简单、几乎零成本,但两者在"不登记包数据库"这一点上是相同的。

我为什么还要用这种方式?三个真实理由:第一,仓库里的版本太老,业务需要某个新特性或者安全补丁;第二,需要自定义编译参数,比如让某个组件支持特定格式、关掉不需要的模块减小体积;第三,同一个软件要在同一台机器上跑多个版本,系统包管理做不到,解压到不同目录最干净。

这一节我会把源码编译的完整链路走一遍,包括每一步在干什么、参数怎么选、编译失败怎么定位,以及装完之后怎么让系统"认识"这个软件。

3.1 压缩包的两副面孔:先判断你拿到的是什么

拿到一个.tar.gz,第一件事不是急着解压,而是先看看里面是什么。tar -tzf package.tar.gz | head -30能列出前几十个条目,从目录结构就能看出端倪。

如果里面是configure、Makefile.in、src/、include/这类东西,那就是源码包,需要编译。如果里面是bin/、lib/、conf/加上一堆可执行文件,那就是预编译二进制包,解压即用。还有一种混合情况:提供了configure但也提供了bin/下的预编译产物,这种通常可以直接用,编译是可选的。

预编译包的处理非常简单:

# 解压到指定目录 tar -zxvf package.tar.gz -C /opt/ # 或者先解压再看 tar -zxvf package.tar.gz mv package-1.2.3 /opt/package # 给可执行文件加权限 chmod +x /opt/package/bin/* # 建立软链接方便调用 ln -s /opt/package/bin/tool /usr/local/bin/tool

这里有个细节:压缩格式不同,参数也不同。.tar.gz用-z,.tar.bz2用-j,.tar.xz用-J,.zip得用unzip。用错参数会报gzip: stdin: not in gzip format,这不是包坏了,是你参数选错了。我一般直接用tar -xvf让 tar 自己探测格式,省心。

源码包的解压同样简单,但接下来的动作就复杂了。解压后先别急着./configure,花两分钟看一眼目录里的README、INSTALL、configure --help的输出。这三个东西能告诉你:需要哪些依赖、有哪些编译开关、推荐的安装路径是什么。跳过这一步直接编译,后面大概率要为缺依赖返工。

3.2 源码编译三步走:configure、make、make install

源码编译的经典流程是三步,但每一步背后都有讲究。我按顺序拆开讲,顺便说清楚失败时怎么定位。

第一步:./configure。这个脚本的作用是探测你的系统环境(编译器版本、库文件位置、头文件是否存在),然后根据你给的参数生成Makefile。它不是在编译,是在"出图纸"。常用参数有这么几个:

./configure \ --prefix=/usr/local/myapp \ --with-ssl=/usr/local/openssl \ --enable-shared \ --disable-static

--prefix决定安装路径,不指定的话默认是/usr/local。我强烈建议给每个自编译软件单独指定一个前缀,比如/usr/local/myapp,理由是卸载时直接rm -rf就干净了,不会和系统其他文件混在一起。--with-xxx用来指定某个依赖的位置,当你的依赖不是装在系统默认路径时必用。--enable-shared/--disable-static控制生成动态库还是静态库,一般服务器环境用动态库更省内存。

configure失败是最常见的卡点,报错通常是checking for xxx... no后面跟着error: xxx not found。这时候不要瞎猜,直接看config.log文件,里面有完整的探测过程和失败原因。绝大多数情况是缺开发包——注意是-devel包,不是运行时包。比如报openssl headers not found,你要装的是openssl-devel而不是openssl。这个区别我见过太多人搞错。

第二步:make。根据Makefile把源码编译成目标文件。这一步最耗时间,几分钟到几十分钟都正常。加速的方法是make -j$(nproc),用满所有 CPU 核心并行编译。但要注意,某些老项目的 Makefile 对并行支持不好,-j开太大反而会报奇怪的错误,遇到这种情况就退回单线程make。

make报错通常是代码层面的问题:缺少头文件、编译器版本不兼容、某个 API 在当前系统上不存在。定位方法是看报错的第一行(不是最后一行),那里通常写着哪个源文件的哪一行出了问题。如果报的是undefined reference to xxx,说明链接阶段找不到库,需要检查--with-xxx参数或者LDFLAGS环境变量。

第三步:make install。把编译好的产物复制到--prefix指定的目录。这一步需要写权限,所以通常要sudo。有个很实用的做法是先用make install DESTDIR=/tmp/stage装到一个临时目录,看看都装了什么文件,确认无误再真正安装。这个技巧在打包或者做灰度部署的时候特别好用。

注意:make install不会覆盖你之前手动改过的配置文件(大部分项目会检测已存在则跳过),但它会覆盖同名的二进制和库文件。如果你在同一个前缀下升级版本,建议先备份配置目录。

3.3 编译参数怎么选,依赖怎么补

参数选择这件事,我的原则是"只开需要的,不动不确定的"。每多开一个--enable-xxx,就多引入一份依赖和一份潜在的编译失败风险。具体怎么定,先跑./configure --help把所有开关看一遍,重点看三类:默认开启但你不用的(关掉)、默认关闭但你要用的(打开)、以及路径类的(按实际填)。

依赖补充是源码编译最耗时的一环,因为它是"报一个补一个"的串行过程。我的做法是先在README里找到依赖清单,一次性把能装的都装上,减少来回次数。以常见的编译工具链为例:

# 基础编译工具 yum install -y gcc gcc-c++ make autoconf automake libtool # 常见依赖开发包,按需选择 yum install -y zlib-devel openssl-devel pcre-devel \ ncurses-devel readline-devel bzip2-devel

这里有个经验:装-devel包的时候顺手把对应的运行时包也确认一下。有些-devel包的依赖里已经包含了运行时包,会自动带上;但有些不会,导致编译通过、运行时报找不到.so。判断方法很简单,编译完执行ldd 可执行文件,看有没有not found的条目。

还有一种情况是依赖版本不够。比如某个软件要求 OpenSSL 1.1 以上,但系统自带的是 1.0。这时候有两条路:升级系统 OpenSSL(风险高,可能影响一堆已装软件),或者自己编译一个新版本装到独立前缀,然后用--with-ssl=/usr/local/openssl指过去。我一般选后者,虽然多花点时间,但不会动系统的地基。

3.4 装完之后:让系统"认识"你的软件

make install完成不代表就能直接敲命令用了。自编译软件装到/usr/local/myapp/bin之后,系统默认的PATH里通常没有这个路径,所以会报command not found。解决办法有几种,我按推荐程度排序。

第一种,建软链接到/usr/local/bin(这个目录默认在PATH里):

ln -s /usr/local/myapp/bin/mycmd /usr/local/bin/mycmd

第二种,加环境变量。但不要直接改~/.bashrc,因为那只对当前用户生效,服务以其他用户运行时又会找不到。推荐放到/etc/profile.d/下:

# /etc/profile.d/myapp.sh export MYAPP_HOME=/usr/local/myapp export PATH=$MYAPP_HOME/bin:$PATH export LD_LIBRARY_PATH=$MYAPP_HOME/lib:$LD_LIBRARY_PATH

这里必须提醒一句:LD_LIBRARY_PATH是个"核武器",全局设置会覆盖系统默认的库搜索顺序,可能导致其他程序加载到错误版本的库然后崩溃。更稳妥的做法是把库路径写进/etc/ld.so.conf.d/:

echo "/usr/local/myapp/lib" > /etc/ld.so.conf.d/myapp.conf ldconfig

ldconfig会重建动态链接库缓存,之后所有程序都能正常找到这个目录下的库,而且不会打乱搜索优先级。这个区别我在早期不理解,吃过一次亏:全局设了LD_LIBRARY_PATH指向自己编译的 OpenSSL 1.1,结果系统的ssh客户端加载了错误的库版本,直接连不上机器了。从那以后我只用ld.so.conf.d的方式。

4. YUM 在线安装:把依赖交给仓库

如果只让我教一条 Linux 装软件的命令,那一定是yum install。它把"找包、下包、解依赖、装包"这一整条链路自动化了,日常九成以上的安装需求都能覆盖。但用得越久越会发现,YUM 的"省心"是有前提的:仓库得可达、元数据得正确、包得存在。一旦这三个前提有一个不成立,报错信息往往不够直白,这时候需要你对它的工作原理有基本了解。

这一节我讲三块:YUM 是怎么工作的、仓库配置怎么写、以及离线和缓存这两个高频场景怎么处理。命令本身很简单,重点在理解它背后的机制,这样遇到问题时你知道该看哪个文件、查哪个目录。

4.1 YUM 的工作原理与仓库配置

YUM 的核心是仓库元数据。每个仓库里有一个repodata目录,里面存着这个仓库所有包的清单、依赖关系、文件列表。你执行yum install的时候,它先下载(或读取本地缓存的)元数据,在内存里算出"装这个包需要哪些依赖、哪些已装、哪些要新装",然后按顺序把包下下来执行安装。所以第一次执行会慢一点(在下元数据),后面就快了。

仓库配置文件放在/etc/yum.repos.d/下,每个.repo文件可以定义一到多个仓库。一个典型配置长这样:

[myrepo] name=My Internal Repository baseurl=http://repo.example.internal/centos/$releasever/os/$basearch/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-myrepo

逐项解释一下,因为每一项出问题都会导致不同的报错。[myrepo]是仓库 ID,必须唯一,后面所有命令都用这个 ID 来引用。name只是描述,随便写。baseurl是关键,指向仓库根目录,支持http://、https://、ftp://、file://四种协议,其中file://用于本地目录仓库。里面的$releasever和$basearch是变量,会被自动替换成系统版本和架构,这样一份配置能适配多个版本。

enabled=1表示启用,设成 0 就是临时禁用(排查问题时常用,比删文件安全)。gpgcheck=1表示校验包的签名,这个不要关,关了等于放弃了完整性验证。gpgkey指向公钥文件的位置。

常用命令我列一下,按使用频率排:

# 安装(-y 自动确认) yum install -y nginx # 查包信息,装之前看看版本和描述 yum info nginx # 搜索,支持通配 yum search nginx # 查某个文件或库由哪个包提供,排查依赖神器 yum provides "*/libapr-1.so.0" # 列出已安装的包 yum list installed | grep nginx # 列出仓库里可用的版本 yum list available nginx # 升级单个包 / 全系统 yum update nginx yum update # 卸载,会连带清理不再被依赖的包 yum remove nginx # 清理缓存并重建 yum clean all yum makecache

yum provides这个命令我要多说两句。它的参数支持通配符,而且可以查文件路径、也可以查库名。当你遇到Failed dependencies: libxxx.so.2 is needed这种报错时,yum provides "*/libxxx.so.2"一条命令就能告诉你是哪个包提供的。这是我从 RPM 手动补依赖过渡到 YUM 之后最常用的一个命令,效率提升非常明显。

4.2 本地仓库:内网环境的标配做法

很多生产环境不能连外网,但又想享受 YUM 的依赖自动处理,怎么办?搭一个本地仓库。做法不复杂,把一堆 RPM 包放在一个目录里,生成元数据,然后配置一个file://的仓库指向它。

# 1. 把 rpm 包放进目录 mkdir -p /data/localrepo cp /path/to/rpms/*.rpm /data/localrepo/ # 2. 安装 createrepo 工具(如果还没有) yum install -y createrepo # 3. 生成元数据 createrepo /data/localrepo # 4. 配置仓库 cat > /etc/yum.repos.d/local.repo <<'EOF' [local] name=Local Repository baseurl=file:///data/localrepo enabled=1 gpgcheck=0 EOF # 5. 刷新缓存 yum clean all yum makecache

这里有几个实操细节。createrepo每次新增或删除包之后都要重新执行一遍,否则元数据和实际内容对不上,会报"包找不到"。如果包比较多,可以加--update参数只更新变化的部分,速度快很多。gpgcheck=0在纯内网自建仓库里常见,因为自己打的包往往没签名,但如果是引入外部包,建议还是签名并开启校验。

还有一种更省事的做法:把安装光盘挂载成仓库。系统盘里通常带了基础包,应急时非常好用。

mount /dev/cdrom /mnt cat > /etc/yum.repos.d/cdrom.repo <<'EOF' [cdrom] name=CDROM Repository baseurl=file:///mnt enabled=1 gpgcheck=0 EOF

这个方案我在现场救过好几次火——外网断了、本地仓库又缺包,光盘里的基础依赖往往能顶上一阵。

4.3 缓存、锁和那些让人摸不着头脑的报错

YUM 的缓存目录在/var/cache/yum/(新版叫dnf的话是/var/cache/dnf/),元数据和下载的包都放这里。缓存出问题是很多"莫名其妙"报错的根源,而解决方法往往就是一句yum clean all && yum makecache。养成习惯:任何 YUM 报错,先清缓存重试一次,能排除掉一半的偶发问题。

锁文件是另一个高频问题。YUM 运行时会创建一个锁/var/run/yum.pid,防止多个实例同时操作包数据库。如果你上一次yum因为网络卡住被 Ctrl+C 中断,锁可能没释放,下次执行就报Another app is currently holding the yum lock。处理方法是先确认没有其他 yum 进程在跑(ps aux | grep yum),然后删掉锁文件再重试。直接删锁有一定风险,如果真有进程在跑,可能把包数据库写坏,所以确认这一步不能省。

几个典型报错和对应排查方向,我整理成表格,方便对照。

报错信息大概率原因处理方向
Cannot retrieve metalink for repository仓库地址不可达或 DNS 解析失败检查baseurl、网络连通性、DNS 配置
repomd.xml: [Errno 14] HTTP Error 404仓库路径写错或版本变量替换后不存在手动用浏览器或curl访问baseurl验证
repomd.xml signature could not be verifiedGPG 公钥缺失或不匹配导入正确的gpgkey,或临时gpgcheck=0验证是否为签名问题
No package xxx available仓库里确实没有,或仓库未启用yum search换关键词,yum repolist确认仓库状态
Transaction check error: file ... conflicts两个包提供了同一文件确认来源,必要时用--setopt排除冲突包
Error: Package requires xxx依赖在仓库中缺失补仓库,或手动下载依赖包放入本地仓库

yum history这个功能值得单独提一下,很多人不知道。它记录了每一次 YUM 事务的详情,包括装了什么、卸了什么、事务 ID。出问题时用yum history list找到对应事务,yum history info <ID>看详情,yum history undo <ID>可以回滚这次操作。这个功能在误装了一批包之后特别有用,比手动一个个卸载靠谱得多。

5. 常见问题排查实录

前面几节讲的是"怎么装",这一节讲"装不上怎么办"。我把这些年遇到的高频问题按类型归了归类,每个都给出排查路径。这些问题的共同点是:报错信息看起来吓人,但只要知道往哪看,解决起来都不复杂。

5.1 依赖冲突与版本回退

依赖冲突最典型的形态是"要装 A,但 A 依赖 B 的新版本,而系统里已有 C 依赖 B 的旧版本"。YUM 这时候会报Transaction check error,把冲突的包和版本都列出来。处理思路有三个层次。

第一层是升级牵连包。如果 C 也能用 B 的新版本,那就让 YUM 一起升级,通常加不加参数它都会自动处理,处理不了说明 C 限制了版本上限。

第二层是排除冲突包。用yum install A --exclude=C或者--setopt=protected_packages=之类的方式,让 YUM 跳过它。这个做法的风险是 C 可能因此不可用,装完要验证。

第三层是回退版本。yum downgrade B可以降回上一个版本,但要注意这会连带影响依赖新版本的包。降级前用yum history看一眼,降级后发现其他东西坏了还能 undo 回来。

还有一种情况是多仓库提供同名包的不同版本。YUM 默认会选版本最高的,但如果你想要低版本,就得用yum install 包名-版本号精确指定,或者临时禁用高版本所在的仓库。yum --disablerepo=xxx install yyy这个组合在排查时很好用。

注意:不要迷信"最新版本一定好"。生产环境里我倾向于跟随仓库的稳定版本,除非有明确的安全补丁或功能需求。手动追新导致的依赖链断裂,修复成本远高于收益。

5.2 装完了但命令找不到

这个问题太常见了,原因通常有四个,我按概率从高到低排。

第一种,命令在非标准路径,PATH里没有。用rpm -ql 包名 | grep bin或者find / -name 命令名 -type f定位实际位置,然后加软链接或改PATH。解压缩安装的软件几乎百分之百是这个问题。

第二种,包装了但没装全。有些包拆成了主包和工具包,你只装了主包。比如某些服务端软件,主包只有守护进程,命令行客户端在单独的-tools包里。

第三种,动态链接库缺失,程序启动即失败。这种情况下which 命令能找到路径,但执行就报error while loading shared libraries: libxxx.so.N: cannot open shared object file。解决方法是ldd $(which 命令)看哪个库not found,然后yum provides找到对应的包装上,或者把库路径加进/etc/ld.so.conf.d/后ldconfig。

第四种,权限问题。文件存在、路径也对,但执行报Permission denied。检查ls -l看是否有执行位,以及所在目录是否有遍历权限。解压出来的二进制包经常丢执行位,chmod +x一下就好。

5.3 磁盘、权限与 SELinux 的干扰

装不上还有一种很隐蔽的原因:磁盘满了。/var分区被日志或者 YUM 缓存占满,安装过程写到一半失败,报错信息可能是No space left on device,也可能是一堆莫名其妙的解包错误。排查用df -h和du -sh /var/cache/yum/*,清理缓存能立刻腾出空间。

权限问题的表现是Permission denied或者Operation not permitted,绝大多数情况是没加sudo。但有一类特殊情况值得提:用sudo执行./configure && make会把编译产生的中间文件搞成 root 所有,后续普通用户再编译就写不进去了。正确做法是用普通用户编译,只在make install时用sudo。

SELinux 是另一个容易被冤枉的因素。它不是安装失败的原因,但会让"装好了却跑不起来"的情况变得难以理解——比如服务起不来、端口绑不上、文件读不了,日志里是一堆avc: denied。排查方法是先看/var/log/audit/audit.log,确认是 SELinux 拦截,然后决定是调整策略还是临时设为permissive验证。我个人的习惯是:先临时setenforce 0验证一下是不是它的问题,确认后再针对性写策略,而不是长期关掉。

5.4 常见问题速查表

把上面这些整理成一张表,出问题时按症状对号入座。

症状优先排查命令/文件
command not foundPATH、软链接、是否装全echo $PATH、rpm -ql、find / -name
cannot open shared object file库缺失、库路径未注册ldd、yum provides、/etc/ld.so.conf.d/
Failed dependencies缺包、缺库rpm -qpR、yum provides
Transaction check error文件冲突、版本冲突yum history、--exclude、--setopt
Another app is currently holding the yum lock残留锁文件ps aux | grep yum、/var/run/yum.pid
No space left on device磁盘满df -h、du -sh、yum clean all
Permission denied权限、SELinuxls -l、audit.log、getenforce
编译报xxx not found缺-devel包config.log、yum install xxx-devel
编译报undefined reference链接阶段缺库LDFLAGS、--with-xxx参数
服务起不来配置、端口、SELinux、日志systemctl status、journalctl -u

6. 选型建议与几个真实踩坑记录

工具本身没有高下,关键看场景。我在不同环境里的默认选择差别很大:能连外网、仓库齐全的机器,yum install是唯一答案,省下来的时间去干别的;纯内网环境,我会提前把常用包同步到本地仓库,用 YUM 管理,只有仓库里真没有的才用 RPM 补;需要特定版本或者自定义编译的,老老实实解压编译,并且一定装到独立前缀、记好编译参数。

这一节我想分享几个具体的坑,都是真金白银换来的经验,比命令手册有用。

6.1 场景化选型:我的默认决策路径

遇到"要装个东西"的需求,我脑子里走的是一条固定的判断链,你可以参考。

第一步,先问能不能用仓库。yum search和yum info各跑一次,确认有没有、版本是多少。有且版本满足要求,直接yum install -y,结束。这一步能覆盖绝大多数情况。

第二步,仓库没有或者版本不满足,问能不能找到 RPM 包。官方或可信来源下载.rpm,先rpm -qpi看信息、rpm -qpR看依赖,--test预演一遍,通过了再正式装。装完用rpm -q和实际运行验证。

第三步,还不行,走解压编译。先看README和--help,确认依赖和参数,装依赖,configure、make、make install三步走,装完配环境变量和库路径,ldd验证一遍。

这条链的价值在于:按成本从低到高排序。YUM 最省心,RPM 次之,编译最费劲。很多人反着来,明明仓库里有,非要自己编译最新版,结果引入一堆依赖问题,最后发现还不如用仓库的。也有反过来的,明明需要某个编译开关,硬用 RPM 包装,功能跑不起来才回头重来。

6.2 几个印象深刻的翻车现场

第一个是在 64 位系统上误装 32 位包。当时急着装一个工具,看到包名差不多就rpm -ivh上去了,安装过程没报错,运行时报找不到libc.so.6。查了半天才发现装的是i686版本,系统里没有 32 位运行库。教训是:装之前一定看包名的架构后缀,x86_64和i686差别很大,noarch才是通用的。

第二个是用--nodeps强装导致系统半瘫。早年为了省事,依赖报错就加--nodeps,装完当时能用,后来发现它依赖的那个库版本不对,导致另一个服务启动失败。排查花了大半天才定位到是那次强装引入的。从那以后我给自己定了规矩:--nodeps只在能明确说清后果的情况下用,并且记录下来。

第三个是源码编译覆盖了系统库。有一次./configure时忘了指定--prefix,默认装到了/usr/local,而某个库路径正好和系统路径重叠,ldconfig之后系统命令开始报错。恢复过程很痛苦,最后是从备份里把库文件捞回来的。这个坑让我养成两个习惯:编译必指定独立前缀,make install前先用DESTDIR装到临时目录检查文件清单。

第四个是YUM 缓存导致的"包明明有却装不上"。现象是yum install报No package available,但仓库里确实有。清了缓存重建元数据就好了。这个问题的根源是仓库更新了元数据,本地缓存还是旧的。现在我给所有服务器的运维脚本里都加了一条定期yum clean all && yum makecache的定时任务,省了不少事。

6.3 给不同阶段读者的实用建议

如果你刚入门,我建议先把yum install、yum provides、yum history这三个命令用熟,它们能解决你八成的问题。遇到报错别急着搜,先把报错信息完整读一遍,YUM 和 RPM 的报错写得其实很清楚,只是信息量大。

如果你已经用了一段时间,建议刻意练习两件事:一是离线环境的包管理,学会搭本地仓库、用createrepo维护元数据;二是源码编译的完整链路,至少完整编译一遍你常用的某个软件,把configure参数、依赖关系、装完后的环境配置都走通一次。这两项能力在关键时刻能救命。

如果你在带团队,我建议把选型规则写进内部文档,明确"什么情况用哪种方式",并且要求所有自编译软件必须记录编译参数和安装路径。我见过太多"前人编译了个东西装在某个角落,后人不敢动也不知道怎么升级"的情况,本质上是缺少记录。

最后分享一个我个人一直在用的小习惯:每装一个非仓库来源的软件,就在/root/install-notes/下留一个文本,写清楚来源、版本、安装方式、依赖、安装路径、验证命令。这个习惯看着麻烦,但每次系统迁移或者故障恢复的时候,它能帮你省下几个小时。装软件这件事,装上去只是开始,能管得住、卸得掉、复现得了,才算真的会。