Linux软件安装三方式:RPM、YUM与源码编译选择与避坑 1. 先把选择逻辑摆正三种安装方式各自的适用边界很多人第一次在 Linux 上装软件卡住的地方不是命令打错了而是压根不知道有几种打法、什么时候该用哪一种。软件安装这件事在 Linux 上通常有三条路RPM 安装、解压缩安装多数情况是源码编译、YUM 在线安装。三个名字听着都像装软件但它们解决的问题层次完全不同RPM 管的是单个安装包怎么落到系统里解压缩安装管的是那些没人替你打包、必须自己从源码构建的软件YUM 管的是从一个仓库里把包和它的依赖一起搬下来。搞不清这三者的分工你就会陷入报错了就换一种方式试试的循环装十次崩八次。这篇文章面向的读者很具体刚接手一台 Linux 服务器、被安排装个中间件或者跑个工具手上有一个.rpm文件、一个.tar.gz包还有一台能连内网的机器却不知道从哪个开始动手的人。我会把三条路各自的机制、命令、报错、坑逐条拆开尤其是那些文档上不会写、只有踩过才知道的细节。装软件这事本身不难难的是出错之后你得知道错误长什么样、为什么长这样。提示下文出现的发行版一律指 RPM 系的企业级发行版DEB 系的包管理命令不做展开但底层思路是通的。1.1 一个真实的选择困境手上三个文件先装哪个假设这么个场景你要在一台内网服务器上部署一个日志分析工具运维给了你三样东西——一个官方提供的xxx-x.x.x.rpm一个xxx-x.x.x.tar.gz源码包还有一条yum install xxx的说明。三条路都在你面前选错一条可能就要重装系统。判断标准其实只有四条能不能连外网仓库、要不要指定版本、要不要改编译参数、以后谁来维护。能连仓库且版本无所谓直接走 YUM它会把依赖一起解决卸载也干净机器完全离线、但有人已经把 RPM 包和它的依赖包一起打好给你走 RPM离线、没人打包、或者你必须开启某个默认关闭的编译特性比如自定义 SSL 路径、开启某个模块只能走解压编译。第四点最关键编译安装的软件系统包管理器不认它以后升级、排查冲突都得你自己记着这是个长期的维护负担。我见过太多人为了图快明明能连仓库也硬要手工传 RPM 上去装结果依赖一个接一个地报缺失最后折腾两小时。也见过人为了省事用 YUM 装了个最新版结果业务代码只兼容旧版本回退的时候又因为 YUM 不保留旧包而抓瞎。这两种翻车都不是技术问题是一开始没把版本可控和依赖可控这两件事分开想。1.2 从包管理者视角理解三条路的区别把系统想象成一个仓库管理员他手里有一本账本记录着每个软件装了哪些文件、属于哪个包、依赖谁。RPM 安装相当于你自己抱着一个箱子来找他这是货你登记一下。他会检查箱子上的封条签名、检查货单依赖缺东西就拒收。YUM 在线安装相当于你把需求告诉管理员他自己去上游仓库把货和所有配套零件一起拉回来登记账本记得清清楚楚。解压缩安装则是你绕开管理员自己把东西搬进仓库某个角落摆好账本上压根没这一笔。这个类比解释了很多现象。为什么编译安装的软件rpm -qa查不到因为没登记。为什么yum remove删不掉编译装的软件因为它不归管理员管。为什么编译安装的软件有时会和系统自带的库打架因为两套东西放在了一起谁先被加载取决于环境变量和库搜索路径。理解了账本这个概念后面所有的报错你都能自己推断出根因而不是靠搜索引擎碰运气。还有一层区别在文件落点上。RPM 和 YUM 装的包文件位置是打包时写死的二进制基本落在/usr/bin配置落在/etc库落在/usr/lib或/usr/lib64。编译安装默认落在/usr/local下面这是一条约定俗成的分界线目的就是让手工装的东西和系统包管理的东西物理隔离。这个约定值得尊重后面讲 prefix 的时候会详细说为什么不要随便改成/usr。2. RPM 安装把离线安装包吃透RPM 安装是整个体系的地基。哪怕你天天用 YUMYUM 底层调的也是 RPM报错最后也还是 RPM 的报错。所以先把rpm这一套命令练熟收益最大。它最大的特点是不解决依赖——缺什么它直接告诉你缺什么然后躺平剩下的你自己想办法。这既是缺点也是优点在完全离线的环境里你反而需要这种你别自作主张我自己来的确定性。2.1 rpm 命令的四个动作查、装、升、删装、删、查、验这四类操作覆盖了日常九成场景参数要吃透。# 安装-i 安装-v 显示过程-h 显示进度条 rpm -ivh package-name-1.0.0-1.x86_64.rpm # 升级-U 升级若未安装则直接安装 rpm -Uvh package-name-1.0.1-1.x86_64.rpm # 卸载 rpm -e package-name # 查询是否已安装 rpm -qa | grep package-name # 查询某个包装了哪些文件 rpm -ql package-name # 查询某个文件属于哪个包 rpm -qf /usr/bin/some-command-ivh里的v和h看着像装饰实际上非常有用。-v会把安装过程中写入的每一个文件路径打出来出问题时你能一眼看到卡在哪一步-h是进度条大包安装时不至于以为卡死。装一个几十兆的包没有进度条你会反复按 CtrlC一按就留下半装状态反而更麻烦。-U和-i的差别值得单独说。-i遇到同名包已安装会直接报错退出-U则先卸载旧版再装新版严格说是升级配置文件会保留为.rpmsave。日常升级用-Uvh就对了但跨大版本升级要谨慎有些包在升级脚本里做数据结构迁移中途失败会留下既不是旧版也不是新版的中间状态那种情况下-U和-i都救不回来只能先rpm -e --nodeps强制清掉再重装。2.2 依赖报错的成因与处理顺序RPM 最经典的报错长这样error: Failed dependencies: libxxx.so.1()(64bit) is needed by package-name-1.0.0-1.x86_64 some-lib 2.0 is needed by package-name-1.0.0-1.x86_64看到这个别慌先分清依赖的两种形态。库文件依赖libxxx.so.1是运行时动态链接库缺了程序根本跑不起来包依赖some-lib 2.0是打包时声明的版本约束纯粹是元数据。前者必须把库文件放到位后者必须装对应的 RPM 包让它登记到账本上。处理顺序我建议这样先确认这台机器能不能连内网仓库能连就直接yum localinstall package.rpm让它自动补齐依赖——注意这个命令虽然是 YUM但装的是本地文件等于借 YUM 的依赖解析能力来干 RPM 的活这是离线场景下最省事的一招。如果不能连仓库那就得手工把依赖包一个个找齐顺序很重要先装底层库再装上层应用因为上层包安装时 RPM 会实时检查依赖是否已满足顺序反了就要反复重试。找依赖包有个技巧报错里给的是包名加版本约束用yum provides */libxxx.so.1反查哪个包提供了这个库能连仓库的话这招一步到位。完全离线时可以在任意一台同版本、同架构的联网机器上用yumdownloader --resolve --destdir./pkgs package-name把整套依赖下载下来再拷进内网。--resolve是关键参数不加它只下主包加了才是完整的依赖树。2.3 rpm 的签名校验与文件归属查询从外部拿到的 RPM 包装之前应该验一下签名。这不是形式主义包在传输过程中损坏或者被人替换过装上去轻则解包失败重则把系统组件覆盖成有问题的版本。# 查看包的签名信息 rpm -qpi package-name.rpm # 校验包完整性会显示 OK 或具体哪一项校验失败 rpm -K package-name.rpm # 已安装包的完整性校验对比安装记录和当前文件状态 rpm -V package-namerpm -V的输出很有意思它会对每个文件返回一串九位标记比如S.5....T.分别对应文件大小、MD5、权限、类型、属主、属组、修改时间。出现5说明内容被改过出现S说明大小变了。有人改了配置文件导致服务起不来用这个命令一查就知道哪个文件被动过比翻日志快得多。注意配置文件被合法修改也会报5所以看到标记不等于出问题得结合业务判断。文件归属查询是我用得最多的功能。看到一个陌生的可执行文件rpm -qf /usr/bin/xxx立刻告诉你它属于哪个包反过来rpm -ql package-name列出某个包的全部文件。排查这个文件哪来的、删了这个会不会影响别的服务这类问题这两条命令几乎是无敌的。有一次一个服务启动失败日志说找不到某个配置我用rpm -ql一看配置文件根本不归这个服务包管是另一个基础库提供的装了库就恢复了——这种事靠猜永远猜不准。3. 解压缩安装源码编译那条最长也最自由的路.tar.gz结尾的包打开以后可能是三种东西已编译好的二进制、需要编译的源码、或者某个运行时环境的安装目录比如 JDK、Node 的二进制发行版。这三种处理方式完全不同很多人一看到 tar 包就无脑./configure make结果发现里面压根没有 configure 脚本白折腾。所以拿到压缩包的第一步永远是tar -tzf先看目录结构别急着解。3.1 为什么有些软件只能编译安装根源在于预编译二进制包解决不了所有组合。一个 RPM 包是针对特定发行版、特定架构、特定库版本编译出来的成品它把编译时的所有选择都固化进去了。如果你的环境里某个库的路径不一样、版本不一样或者你需要开启某个打包者默认关闭的特性比如启用特定的加密算法、绑定特定的 CPU 指令集、关掉不需要的模块减小体积预编译包就无能为力了。源码编译本质上是把编译决策重新交回给你。./configure阶段就是在做决策收集它在你的机器上跑一堆探测脚本检查编译器版本、检查头文件在不在、检查库能不能链接然后把结果写进Makefile。探测不过就报错报错信息里通常直接告诉你缺什么。这个阶段失败一点都不用紧张它恰恰是编译安装最友好的部分——把缺的东西装上再跑一遍就行不像运行时崩溃那样难查。编译安装还有个常被忽视的优势性能调优的余地。默认的 RPM 包为了兼容性通常不会开启激进的优化选项。如果你跑的是计算密集型服务用./configure CFLAGS-O2 -marchnative让编译器针对本机 CPU 生成指令理论上能榨出一些额外性能。不过实话说这个收益在多数业务场景下感知不到别为了这点提升把编译参数搞得太复杂-marchnative编出来的二进制还不能跨机器拷贝运维上是个负担。3.2 configure / make / make install 三段的真实分工这三段的分工可以用做饭打比方configure是采购和备菜检查食材齐不齐、厨具有没有make是真正开火炒make install是把菜装盘端到指定位置。# 解包 tar -xzf package-1.0.0.tar.gz cd package-1.0.0 # 配置指定安装路径和编译选项 ./configure --prefix/usr/local/package-1.0.0 \ --with-ssl/usr/local/openssl \ --enable-some-feature # 编译-j 用多核加速nproc 取核数 make -j$(nproc) # 安装写入目标目录通常需要 root make install--prefix是这里最重要的参数没有之一。它决定软件最终装到哪个目录默认是/usr/local。我强烈建议带上版本号做目录隔离比如/usr/local/package-1.0.0。原因很实际同一条路径下只能存在一个版本将来要升级或者并存两个版本时不带版本号的路径会让你陷入先删旧的才能装新的删完发现新的装不上旧的也回不来了的窘境。带版本号以后升级就是新装一个目录改个软链或者改个环境变量的事回滚也一样简单。make -j$(nproc)用满所有核能显著缩短编译时间但要注意内存。四核机器编大型项目时每个编译进程可能吃掉几百兆内存-j开太大会直接 OOM进程被系统杀掉报错信息还特别隐晦通常只显示internal compiler error或者干脆没有输出。稳妥做法是-j不超过核数内存紧张时改用-j2。3.3 prefix 与环境变量的配合以及怎么干净地卸载装到/usr/local/xxx之后系统默认不一定能找到它。PATH里通常包含/usr/local/bin但不包含/usr/local/package-1.0.0/bin。这时需要改环境变量。# 临时生效当前 shell export PATH/usr/local/package-1.0.0/bin:$PATH export LD_LIBRARY_PATH/usr/local/package-1.0.0/lib:$LD_LIBRARY_PATH # 永久生效写进 /etc/profile.d/ 下单独一个文件 echo export PATH/usr/local/package-1.0.0/bin:$PATH /etc/profile.d/package.sh echo export LD_LIBRARY_PATH/usr/local/package-1.0.0/lib:$LD_LIBRARY_PATH /etc/profile.d/package.sh放在/etc/profile.d/下而不是直接改/etc/profile好处是卸载时直接删文件不留尾巴。这是个很小的习惯但维护久了差别很大。关于LD_LIBRARY_PATH得提醒一句这东西是把双刃剑。它会覆盖系统默认的库搜索顺序设错了会让系统自带的程序加载到错误版本的库症状是各种莫名其妙的symbol lookup error。能用/etc/ld.so.conf.d/加配置文件再跑ldconfig的方式就别用环境变量前者对全局更可控后者只对当前会话生效但容易误伤。卸载这件事是编译安装最大的坑。没有make uninstall的项目太多了有些项目即使提供了这个目标也只删它自己装的那几个文件依赖目录里的东西一律不动。所以装之前留个底是最省事的做法make install前后各生成一次文件清单差集就是这软件装进去的东西。# 安装前留底 find /usr/local/package-1.0.0 -type f /tmp/before.txt 2/dev/null make install # 安装后对比 find /usr/local/package-1.0.0 -type f /tmp/after.txt comm -13 /tmp/before.txt /tmp/after.txt /tmp/installed-files.txt有了这个清单将来卸载就是xargs rm -f /tmp/installed-files.txt再删掉空目录和环境变量文件干干净净。注意目录层面不建议用这个清单删只删文件目录手工清理更安全避免误删别的软件放在同一路径下的东西。4. YUM 在线安装把仓库机制搞明白才算真会用YUM 是三条路里最省心的前提是你真的理解它在干什么。很多人的用法停留在yum install xxx一出问题就只会yum clean all这跟生病了只会喝热水差不多。YUM 的核心概念只有三个仓库repo、元数据metadata、依赖解析dependency resolution。仓库是货源元数据是货单依赖解析是调度。搞清楚这三样绝大部分找不到包、依赖冲突、版本不对都能自己定位。4.1 一次 yum install 背后发生了什么你敲下yum install nginx之后实际发生的事比想象中多。第一步是读取/etc/yum.repos.d/下的所有.repo文件拿到每个仓库的地址和启用状态第二步是拉取每个仓库的元数据如果本地缓存过期元数据里记录了每个包的文件名、版本、依赖关系、提供的能力第三步是在所有启用的仓库里搜索满足条件的包如果有多个版本按优先级和版本号选出最优的那个第四步是递归解析依赖生成一个完整的安装事务第五步才是真正调用 RPM 逐个安装。理解了这个流程很多现象就解释得通了。为什么第一次装特别慢、后面就快了因为第一次要下载元数据后面走本地缓存。为什么明明仓库里有这个包却报No package available多半是元数据过期了本地缓存里还是旧货单。为什么会提示Nothing to do说明包已经装了或者名称拼错了——注意 YUM 匹配的是包名而不是文件名你从网页上抄下来的包名可能和实际包名差一个连字符。缓存相关的三个命令要分清用途# 清空所有缓存元数据 包 yum clean all # 只清元数据缓存强制下次重新拉货单 yum clean metadata # 手动构建元数据缓存配合 clean all 使用避免下次安装时卡顿 yum makecacheyum clean all我一般用在两类场景改了仓库地址之后或者报错信息明显指向元数据损坏。日常没事别清清了下次安装又要重新下一遍货单浪费时间。反过来如果换过镜像源或者调整过仓库配置yum clean all yum makecache这一套连招能避免大量配置改了但行为没变的困惑。4.2 仓库配置文件与缓存的实操细节.repo文件的格式很朴素一个文件里可以写多个仓库每个仓库用[]开头。[myrepo] nameMy Internal Repository baseurlhttp://repo.example.internal/centos/$releasever/$basearch/ enabled1 gpgcheck0 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-myrepobaseurl里的$releasever和$basearch是变量分别代表系统版本号和架构这么写的好处是一份配置能在多个版本上用。enabled0表示只注册不启用需要时用yum --enablerepomyrepo install xxx临时启用适合那种平时不用、偶尔查一下的仓库。gpgcheck控制是否校验签名内网自建仓库为了方便常见做法是关掉但公网仓库强烈建议开着——签名校验是防篡改的最后一道防线关掉等于把安全交给传输协议。priority这个字段容易被忽略但很关键。当多个仓库都提供同一个包、版本还不一样时YUM 默认按版本号选最高的而priority数值越小优先级越高能强制让它从指定仓库取包。我踩过这样的坑默认仓库和某个第三方仓库都提供了同一个工具第三方仓库版本更高但编译参数不同装完行为不一致。加上priority1到默认仓库问题立刻消失。这类问题排查起来极其耗时因为现象是功能不对而不是装不上很容易往业务代码方向查过去。还有exclude字段用来把某些包排除在安装和升级之外。系统核心组件比如内核、glibc在大规模升级时最容易被带上去一旦出问题影响面极大。在/etc/yum.conf里加上excludekernel* glibc*等于给自己加了一道保险升级时不会顺手把内核也换了。4.3 用 provides / groupinstall / localinstall 解决具体问题这三个子命令是 YUM 真正拉开差距的地方用熟了效率翻倍。yum provides用来反查能力。你只知道自己缺/usr/bin/somecmd或者libxxx.so.2不知道属于哪个包它就派上用场了。yum provides */somecmd yum provides */libxxx.so.2它查的是元数据里的 provides 字段所以有时同一个库会被多个包声明提供这时要结合包名和仓库判断该装哪个。yum groupinstall用来装一组包比如开发工具这一整组编译环境一条命令把 gcc、make、autoconf 全装上省得一个个查。yum grouplist yum groupinstall Development Tools注意组名带空格必须加引号且组名的确切拼写因发行版版本而异grouplist里看到什么就照抄什么。yum localinstall是离线场景的救命命令前面提过。它装的是本地 RPM 文件但依赖解析走仓库等于用 YUM 的能力补 RPM 的短板。较新的版本里yum install ./package.rpm和它等价但老版本不认路径参数还是老老实实用localinstall。yum localinstall -y ./package-1.0.0.rpm另外yum history是我最推荐的功能之一它记录了每一次事务可以整体回滚。yum history list yum history info 10 yum history undo 10装了一堆东西把环境搞乱、想退回去的时候yum history undo比手工一个个remove靠谱得多。不过要提醒它只回滚 YUM 记录过的事务编译安装和手工 RPM 装的包不在其列且若被依赖的包已经被别的软件需要回滚会失败。5. 三种方式混用时的坑从现象到根因的排查链路单独用哪一种其实都不难真正让人崩溃的是混着用。系统里同时存在 YUM 装的、RPM 装的、编译装的同名软件然后某个服务启动失败日志里只有一句找不到符号。这类问题没有捷径必须按链路一步步走。5.1 同一个软件装了两份命令走的却是旧版本现象很典型which nginx显示/usr/bin/nginx但你明明刚编译装了新版到/usr/local/nginx/bin而且PATH也改了。原因是PATH里的顺序问题或者 shell 有命令哈希缓存。# 确认实际执行的路径 which -a nginx type -a nginx # 清掉 shell 的哈希缓存 hash -rwhich -a会列出所有能匹配到的路径按 PATH 顺序排列第一个就是实际执行的。如果发现新路径排在后面检查/etc/profile.d/里的脚本是否在系统默认 PATH 设置之前被加载。hash -r解决的是另一个坑bash 会把命令路径缓存起来你改了 PATH 但同一个会话里它还是走老路径新手经常在这里怀疑人生。排查版本还要看动态库加载的是哪一份这比命令路径更隐蔽。# 查看程序的动态库依赖及实际加载路径 ldd $(which nginx) | grep -i ssl # 查看运行中进程实际映射的库 cat /proc/$(pgrep -f nginx | head -1)/maps | grep sslldd的输出里库路径直接决定它加载哪个版本。如果指向/usr/lib64而不是你装的/usr/local/openssl/lib那说明LD_LIBRARY_PATH没生效或者被覆盖了。还有一种可能是程序编译时用了-rpath把库路径硬编码进去了这种情况下改环境变量完全无效只能重新编译或者用patchelf改二进制。5.2 卸载残留与依赖断裂卸载的坑比安装多。yum remove会连带删掉不再被依赖的包这个行为在共享库上尤其危险——它可能顺手把别的服务依赖的库也一起卸了。# 先看这次卸载会波及什么不加 -y 就是干跑 yum remove package-name # 查看谁依赖这个包 yum deplist package-name repoquery --whatrequires package-name养成卸载前先看波及范围的习惯能省掉大量事后救火。我就吃过亏卸一个不再用的工具它顺带把某版本的 OpenSSL 库带走了另一个跑着的服务重启后直接起不来查了半天才发现是库没了。RPM 卸载也类似rpm -e会因为依赖被占用而拒绝-e --nodeps强制卸会留下依赖断裂。强制卸载后要立刻检查受影响的服务# 检查系统里是否有断裂的依赖需要 yum-utils package-cleanup --problems package-cleanup --dupes--dupes能查出重复安装的包这在混用多种安装方式的环境里特别有用——同一个包被装了两个版本症状往往就是随机性的行为异常不查根本想不到。编译安装的残留则更隐蔽。删目录不会删掉/etc/profile.d/里的环境变量也不会删掉/etc/ld.so.conf.d/里的库路径配置。这些残留会在几个月后某个新服务启动时突然跳出来捣乱。所以卸载编译安装的软件清单应该是三样目录、环境变量文件、库配置三样都清干净再跑一次ldconfig。5.3 离线环境下的组合打法内网离线环境是最考验功力的场景。我的做法是把三种方式串成一条流水线先在一台联网的同版本机器上用yumdownloader --resolve把主包和全部依赖下载下来组成一个本地目录把这堆 RPM 拷进内网用createrepo把它们做成一个本地仓库写成.repo文件指向file://路径然后在内网机器上所有安装都走yum install等于把在线安装的体验搬到了离线环境。# 联网机器上下载主包与全部依赖 yumdownloader --resolve --destdir./pkgs nginx # 内网机器上把目录做成仓库 createrepo ./pkgs # 配置本地仓库 cat /etc/yum.repos.d/local.repo EOF [local] nameLocal Offline Repo baseurlfile:///opt/pkgs enabled1 gpgcheck0 EOF yum clean all yum makecache yum install nginx这套打法的价值在于内网机器上不需要再手工解决依赖YUM 帮你算好版本完全可控因为包是你自己下的将来要装别的软件往pkgs目录里加包、跑一次createrepo --update就行仓库越用越完整。唯一的成本是初期搭建搭好之后能一直用。对于实在没人打包的软件才走解压编译。这时候我会把 prefix 统一到一个约定路径下比如/opt/soft/每个软件一个带版本号的子目录配一个current软链指向当前使用的版本。升级时新装目录、切软链、重启服务回滚就切回去。这套约定本身没什么技术含量但坚持用下来一台机器跑几年也不会变成一团乱麻。真正让环境失控的从来不是哪一次安装出错而是每次出错都用一种新方式补救。