1. 从RPM到Yum:为什么需要软件仓库
很多刚接触Linux运维的朋友,第一次在服务器上装软件时都会经历这样一个过程:下载一个.rpm包,然后敲下rpm -ivh package.rpm,结果屏幕上弹出一堆红色的依赖报错——error: Failed dependencies。你只好去网上搜那个缺失的依赖包,装完又发现它还有别的依赖,一层套一层,像剥洋葱一样没完没了。这个场景,就是RPM包管理器最让人头疼的地方。
RPM(Red Hat Package Manager)本身是一套很扎实的软件包管理机制,它把软件的二进制文件、配置文件、元数据打包在一起,能记录安装了什么、装到了哪里、版本是多少。但它有一个致命短板:它只管单个包,不管包与包之间的关系。你手动装A,A依赖B,B又依赖C,RPM不会帮你自动解决,只会冷冰冰地告诉你缺东西。
Yum(Yellowdog Updater Modified)就是在这个背景下出现的。它本质上是架在RPM之上的一层“智能调度器”,核心能力是自动解决依赖关系。你只需要告诉Yum“我要装nginx”,它会自己去软件仓库里查nginx依赖哪些包,然后一次性把整条依赖链全部下载安装好。而支撑这一切运转的基础设施,就是我们今天要聊的主角——Yum软件仓库。
这篇文章我会从仓库的底层结构讲起,把本地仓库和网络仓库的搭建过程完整走一遍,再分享几个我在实际运维中踩过的坑和排查技巧。不管你是刚学Linux的学生,还是已经工作但一直用现成仓库、没自己搭过的运维,这篇内容都能让你对Yum仓库有一个从“会用”到“懂原理”的跨越。
2. Yum软件仓库的核心原理拆解
2.1 仓库到底是什么:一个带索引的软件货架
你可以把Yum仓库想象成一个超市货架。货架上摆着各种商品(.rpm包),但光有商品还不够,超市还得有一份商品目录,告诉你每个商品放在哪个位置、叫什么名字、有什么用途、和哪些商品配套使用。这份目录,在Yum仓库里就是repodata目录下的元数据文件。
一个标准的Yum仓库目录结构大致是这样的:
myrepo/ ├── Packages/ # 存放所有rpm包 │ ├── nginx-1.20.rpm │ ├── openssl-1.1.rpm │ └── pcre-8.44.rpm └── repodata/ # 存放元数据索引 ├── repomd.xml # 仓库主索引文件 ├── primary.xml.gz # 包的基本信息 ├── filelists.xml.gz # 包内文件列表 └── other.xml.gz # 其他信息repomd.xml是整个仓库的入口,它记录了其他几个元数据文件的位置和校验值。Yum客户端第一次访问仓库时,会先拉取repomd.xml,然后根据里面的指引下载primary.xml.gz等文件,缓存在本地。之后每次执行安装命令,Yum就是靠这份本地缓存来判断“这个包在不在仓库里”“它依赖谁”“谁依赖它”。
注意:
repodata目录不是手动创建的,必须用createrepo工具生成。手动放几个rpm包进去而不生成元数据,Yum是识别不了的。
2.2 仓库的两种形态:本地仓库与网络仓库
从访问方式上分,Yum仓库主要有两种形态,它们的使用场景完全不同。
本地仓库指的是仓库文件就放在本机磁盘上,通过file://协议访问。典型场景是:内网服务器无法访问外网,你把手头的rpm包集中放到一个目录,做成仓库,然后让本机的Yum指向它。这种仓库搭建简单、访问速度快,但缺点是只能自己用,没法共享给其他机器。
网络仓库则是把仓库放在一台服务器上,通过HTTP或FTP协议对外提供服务,其他机器通过http://或ftp://地址访问。企业内网里通常会搭一台专门的仓库服务器,把所有常用软件包集中管理,其他服务器统一指向它。这样既节省了每台机器重复下载的带宽,也方便统一版本管理。
两者的核心结构完全一样,区别只在于客户端baseurl里写的协议不同。理解了这一点,你搭本地仓库和搭网络仓库其实就是同一套流程,只是最后暴露的方式不一样。
2.3 仓库配置文件:Yum怎么知道去哪找包
Yum的所有仓库信息都放在/etc/yum.repos.d/目录下,每个仓库一个.repo文件。这个目录是Yum的“通讯录”,它启动时会扫描这里所有以.repo结尾的文件,把里面的仓库逐个加载进来。
一个典型的.repo文件长这样:
[myrepo] name=My Local Repository baseurl=file:///opt/myrepo enabled=1 gpgcheck=0逐行解释一下这几个关键参数:
[myrepo]是仓库的唯一标识ID,不能和其他仓库重复,Yum命令里用--enablerepo=myrepo就是引用这个ID。name是仓库的描述名称,随便写,只是给人看的。baseurl是最关键的,指向仓库的根目录,也就是包含repodata的那一层。enabled=1表示启用这个仓库,改成0就是禁用。gpgcheck=0表示不校验包的GPG签名。生产环境建议设为1并配置gpgkey,但内网自建仓库为了省事经常关掉。
实操心得:
baseurl指向的路径一定要精确到repodata的上一级。我见过不少人写成file:///opt/myrepo/Packages,结果Yum一直报“Cannot find a valid baseurl”,排查半天才发现是路径多了一层。
3. 本地Yum仓库搭建完整实操
3.1 环境准备与工具安装
先确认你的系统版本和架构,这决定了你后续要下载哪个版本的rpm包。执行:
cat /etc/redhat-release uname -m假设输出是CentOS Linux release 7.9和x86_64,那后面所有包都要选el7.x86_64版本。版本对不上,装上去要么报依赖错误,要么直接不兼容。
搭建仓库需要createrepo这个工具,它负责生成repodata元数据。安装命令:
yum install -y createrepo如果你的机器现在连外网仓库都用不了,那就得先想办法用系统安装镜像里的包把createrepo装上。这也是为什么很多内网环境第一次搭仓库时,会先用光盘镜像做一个临时本地源来装工具,装完再搭正式仓库。
3.2 收集rpm包并生成元数据
假设我把所有需要的rpm包都放在了/opt/myrepo/Packages/目录下。这些包可以来自几个渠道:系统安装镜像的Packages目录、之前用yumdownloader下载的包、或者从其他机器拷贝过来的。
包收集齐之后,执行生成元数据的命令:
createrepo /opt/myrepo/这条命令会在/opt/myrepo/下创建repodata目录,并生成所有索引文件。执行完你可以ls /opt/myrepo/repodata/看一下,应该能看到repomd.xml和几个.xml.gz文件。
如果后续往Packages目录里新增了rpm包,需要更新元数据,执行:
createrepo --update /opt/myrepo/--update只处理新增和变化的包,比全量重建快很多。仓库包多了以后,这个参数能省不少时间。
3.3 编写repo文件并验证
在/etc/yum.repos.d/下新建一个myrepo.repo:
[myrepo] name=My Local Repository baseurl=file:///opt/myrepo enabled=1 gpgcheck=0保存后,先清一下Yum缓存,让它重新读取仓库配置:
yum clean all yum repolistyum repolist会列出当前所有启用的仓库。如果看到myrepo出现在列表里,并且包数量不为0,说明仓库配置成功了。
接下来做个实际安装测试,比如装个nginx:
yum install -y nginx如果仓库里有nginx及其全部依赖,Yum会自动解析依赖并一次性装好。这一步能成功,整个本地仓库就算跑通了。
注意事项:
yum clean all之后第一次执行yum命令会重新下载仓库元数据,如果仓库包特别多,这一步可能要等几秒到几十秒。别以为卡死了,耐心等一下。
3.4 用光盘镜像快速搭建临时仓库
如果你手头有系统安装镜像(ISO文件),可以不用一个个收集rpm包,直接把镜像挂载上来当仓库用。这是内网环境最省事的做法。
挂载镜像:
mkdir -p /mnt/cdrom mount -o loop /path/to/CentOS-7.iso /mnt/cdrom然后写repo文件指向挂载点:
[cdrom] name=CDROM Repository baseurl=file:///mnt/cdrom enabled=1 gpgcheck=0镜像里的Packages目录和repodata目录都是现成的,不需要再跑createrepo。这种方式特别适合新装系统后快速恢复Yum可用状态。
实操心得:用
mount -o loop挂载的镜像,重启后会失效。如果想让它在开机时自动挂载,可以写进/etc/fstab,但更推荐用systemd的mount单元,或者干脆把ISO里的内容拷到磁盘上,一劳永逸。
4. 网络Yum仓库搭建与服务发布
4.1 用HTTP服务发布仓库
本地仓库只能自己用,要让内网其他机器也能访问,就得把它通过HTTP服务发布出去。最常用的方案是装一个轻量Web服务器,把仓库目录作为网站根目录。
安装并启动Web服务:
yum install -y httpd systemctl start httpd systemctl enable httpd然后把仓库目录软链接或拷贝到Web根目录下:
ln -s /opt/myrepo /var/www/html/myrepo确保Web服务能读到这个目录,权限设置一下:
chmod -R 755 /opt/myrepo在浏览器或另一台机器上用curl测试一下:
curl http://仓库服务器IP/myrepo/repodata/repomd.xml如果能返回XML内容,说明HTTP发布成功了。
4.2 客户端配置与访问测试
在其他机器上,把repo文件的baseurl改成HTTP地址:
[myrepo] name=My Network Repository baseurl=http://仓库服务器IP/myrepo enabled=1 gpgcheck=0然后同样执行yum clean all和yum repolist验证。如果客户端能列出仓库里的包,并且能正常安装,网络仓库就搭好了。
这里有个细节值得说一下:客户端第一次访问网络仓库时,Yum会把元数据缓存到本地/var/cache/yum/目录下。之后每次操作,Yum会先检查服务器上的repomd.xml有没有变化,没变化就直接用本地缓存,有变化才重新下载。这个机制让日常安装操作很快,但也意味着如果你在服务器端更新了仓库,客户端可能需要yum clean all才能感知到。
4.3 多仓库优先级管理
实际环境里,一台机器往往同时配置了好几个仓库:系统自带的base仓库、epel扩展仓库、自建的内网仓库。这时候就会出现一个问题——同一个包在多个仓库里都有,Yum到底装哪个?
默认情况下,Yum会选版本号最高的那个。但有时候你希望优先用内网仓库(因为快、可控),这时候就需要配置优先级。安装yum-plugin-priorities插件:
yum install -y yum-plugin-priorities然后在每个repo文件里加一行priority:
[myrepo] name=My Network Repository baseurl=http://仓库服务器IP/myrepo enabled=1 gpgcheck=0 priority=1数字越小优先级越高,范围是1到99。priority=1的仓库会优先被选用。这样即使外网仓库有更高版本的包,Yum也会优先从内网仓库装。
注意事项:优先级插件只影响“选哪个仓库的包”,不改变依赖解析逻辑。如果内网仓库缺某个依赖,Yum还是会去其他仓库找,不会因为优先级高就卡死。
5. 常见问题排查与避坑经验
5.1 仓库配置类问题速查
下面这张表整理了我在实际运维中最常遇到的几类仓库问题,以及对应的排查方向。
| 报错信息 | 常见原因 | 排查方法 |
|---|---|---|
| Cannot find a valid baseurl | baseurl路径写错或服务未启动 | 用curl手动访问baseurl验证 |
| repomd.xml not found | repodata目录缺失或路径不对 | 检查baseurl是否指向repodata上一级 |
| No package xxx available | 仓库里确实没有这个包 | 用`yum list |
| Failed to download metadata | 网络不通或防火墙拦截 | 检查HTTP端口和防火墙规则 |
| GPG key retrieval failed | gpgcheck=1但没配gpgkey | 改gpgcheck=0或导入正确密钥 |
这张表基本覆盖了80%的仓库报错场景。遇到问题先对照着查一遍,能省很多时间。
5.2 依赖地狱的真实排查案例
有一次内网一台服务器要装某个监控agent,仓库里明明有这个包,但yum install一直报缺一个叫libcurl-devel的依赖。我去仓库目录里翻,发现libcurl-devel的rpm包确实在Packages目录里躺着,但yum就是找不到。
排查过程是这样的:先yum list | grep libcurl,发现仓库里只有libcurl没有libcurl-devel。这就奇怪了,包明明在目录里。后来用createrepo --update重新生成元数据,再yum clean all,问题就解决了。
原因很简单:那个libcurl-devel包是后来手动拷进Packages目录的,但拷进去之后没有重新生成元数据,所以Yum的索引里根本没有这个包。往仓库里加包之后一定要跑createrepo --update,这是最容易忘的一步。
5.3 缓存与元数据更新的坑
Yum的缓存机制虽然提升了速度,但也带来了一些“诡异”问题。最常见的就是:服务器端仓库明明更新了,客户端却死活装不到新版本。
这时候第一反应应该是清缓存:
yum clean all yum makecacheyum clean all会清掉所有缓存,包括元数据和已下载的包。yum makecache则强制重新下载元数据并建立缓存。这两条命令组合使用,基本能解决所有“仓库更新了但客户端看不到”的问题。
还有一个坑是/var/cache/yum/目录占满磁盘。如果仓库包特别多,元数据缓存可能占几百MB甚至上GB。定期清理或者把缓存目录挂到独立分区,是个好习惯。
实操心得:我习惯在每次修改仓库配置后,固定执行
yum clean all && yum repolist这一套组合拳。虽然多花几秒钟,但能避免很多“配置改了却不生效”的迷惑现象。
5.4 权限与SELinux的隐形拦截
内网搭HTTP仓库时,还有一个特别隐蔽的坑:SELinux。Web服务器默认只能读取/var/www/html/下的文件,如果你把仓库放在/opt/下然后软链接过去,SELinux可能会拦截,导致客户端访问返回403。
排查方法是看Web服务日志:
tail -f /var/log/httpd/error_log如果看到Permission denied相关的SELinux拒绝记录,有两个解决办法:一是把仓库目录直接放在/var/www/html/下,二是给目录打上正确的SELinux标签:
semanage fcontext -a -t httpd_sys_content_t "/opt/myrepo(/.*)?" restorecon -Rv /opt/myrepo当然,最省事的办法是临时把SELinux设为permissive模式测试,确认是SELinux问题后再针对性配置。但生产环境不建议直接关闭SELinux,还是老老实实打标签比较稳妥。
6. 仓库维护与进阶技巧
6.1 定期同步与增量更新
内网仓库用久了,包会越来越多,手动维护越来越累。一个常见的做法是定期从外网仓库同步需要的包到内网。可以用reposync工具把远程仓库的包全量或按需下载到本地:
reposync -r epel -p /opt/myrepo/Packages/下载完再createrepo --update更新索引。如果只想同步特定包,可以配合--download-metadata和过滤参数使用。
对于包量很大的仓库,每次全量createrepo会很慢。--update参数只处理变化的包,日常增量更新用它就够了。但如果仓库结构变动很大(比如删了很多包),还是建议全量重建一次,避免索引残留。
6.2 仓库分组与模块化
Yum支持“组”的概念,可以把一组相关的包打成一个组,安装时一条命令装一整组。比如“开发工具”组里包含gcc、make、gdb等一堆包。自建仓库也可以定义组信息,在createrepo时通过-g参数指定组文件:
createrepo -g comps.xml /opt/myrepo/comps.xml里定义组名、包含的包、默认安装哪些。这样客户端就能用yum groupinstall "开发工具"一次性装好整套环境。对于需要统一部署开发环境的团队,这个功能很实用。
6.3 仓库签名与安全校验
前面为了省事一直用gpgcheck=0,但生产环境建议开启签名校验。流程是:先生成GPG密钥对,用私钥对每个rpm包签名,然后把公钥导出放到仓库里,客户端配置gpgkey指向公钥地址。
服务端操作:
gpg --gen-key rpm --addsign /opt/myrepo/Packages/*.rpm gpg --export -a "Your Name" > /opt/myrepo/RPM-GPG-KEY-myrep客户端repo文件:
[myrepo] name=My Network Repository baseurl=http://仓库服务器IP/myrepo enabled=1 gpgcheck=1 gpgkey=http://仓库服务器IP/myrepo/RPM-GPG-KEY-myrep这样Yum在安装前会校验包的签名,防止包被篡改。虽然多了一步配置,但对于安全要求高的环境,这一步不能省。
6.4 仓库性能优化建议
当仓库包数量达到几千个时,元数据文件会变得很大,客户端每次yum clean all后重新下载会很慢。有几个优化方向:
一是开启createrepo的--compress-type参数,用压缩率更高的算法生成元数据。二是把repodata目录单独放在SSD上,加快读取速度。三是对于超大仓库,可以考虑拆分成多个小仓库,按软件类别分开,客户端按需启用。
另外,HTTP服务端可以开启gzip压缩,让元数据传输更快。在httpd配置里加一行:
AddOutputFilterByType DEFLATE application/xml这样repomd.xml等文件在传输时会被压缩,客户端下载更快。
7. 我个人的一些实操体会
搭Yum仓库这件事,说起来原理不复杂,但真正动手时细节特别多。我刚开始接触的时候,光是baseurl路径写错就折腾了一下午,后来才养成“改完配置先curl验证”的习惯。
还有一个体会是:仓库的元数据和包本身必须保持一致。我见过太多因为忘了createrepo --update导致的问题,包括我自己也犯过。现在我的做法是,把“拷贝包”和“更新元数据”写成一个脚本,每次加包就执行脚本,从流程上杜绝遗漏。
最后分享一个小技巧:如果你只是临时需要某个包,不想污染正式仓库,可以单独建一个“临时仓库”目录,把包放进去,createrepo一下,然后写一个临时的repo文件,用--enablerepo参数只在需要时启用。这样既不影响主仓库的干净,又能灵活应对临时需求。用完之后把repo文件删掉或者enabled=0,清清爽爽。