在企业内网、隔离环境或者一些不允许直接访问外部软件源的场景里,自己动手搭一个本地 yum 源几乎是每个运维和系统管理员都会遇到的需求。尤其是当你手里只有一堆从网络上下载下来的 rpm 软件包,而目标机器又完全无法连接外网时,怎么把这些零散的包变成一个可用的、能被 yum 正常识别的仓库,就成了一个必须解决的问题。我最近刚在一个纯离线环境里完整走了一遍这个流程,从收集 rpm 包到生成元数据、配置客户端、最终成功安装,中间踩了几个不大不小的坑,这里把整个过程和踩坑经验完整记录下来,给有同样需求的朋友一个可以直接抄作业的参考。
这篇文章适合有一定 Linux 基础、需要在内网或离线环境搭建 yum 源的运维人员,也适合刚接触 rpm 包管理、想搞清楚 yum 仓库底层原理的开发者。我会从最基础的概念讲起,把每一步为什么这么做、参数怎么来的都说明白,最后给出完整的操作命令和验证方法。
1. 先搞清楚 yum 源到底是个什么东西
很多人一上来就急着敲命令,结果遇到报错完全不知道从哪查起。我觉得在动手之前,花十分钟把 yum 仓库的组成结构搞清楚,后面会省掉大量排查时间。
1.1 yum 仓库的本质就是目录加元数据
yum 本身并不神秘,它做的事情可以简单理解为:去一个指定的目录(或者网络地址)里,读取一份描述"这个目录里有哪些包、每个包依赖什么、包与包之间什么关系"的清单文件,然后根据这份清单帮你解决依赖、下载安装。
所以一个能用的 yum 仓库,必须包含两样东西:
- 一堆 rpm 软件包文件本身
- 一份描述这些包的元数据(metadata)
元数据不是随便写的,它由专门的工具扫描 rpm 包后自动生成,放在一个叫repodata的目录里。yum 客户端访问仓库时,第一步就是去读repodata/repomd.xml,这个文件相当于仓库的入口索引,里面记录了其他元数据文件的位置和校验值。
理解了这一点,你就能明白为什么"把 rpm 包丢到一个目录里,然后配置 yum 指向这个目录"是不行的——因为缺少repodata,yum 根本不知道目录里有什么。
1.2 为什么不能直接一个个 rpm -ivh 安装
有人会问,既然包都在本地,我直接rpm -ivh *.rpm一个个装不就行了,为什么还要费劲搭 yum 源?
这个问题我在实际工作中被问过很多次。直接 rpm 安装最大的问题是依赖关系需要手动解决。比如你装 A 包,它依赖 B 和 C,B 又依赖 D,你得自己一个个去找、去按顺序装,一旦依赖链很长,手动处理会非常痛苦,而且容易出错。
yum 的核心价值就在于自动依赖解析。它会读取元数据里的依赖信息,自动计算出需要安装哪些包、按什么顺序装,你只需要一条yum install命令。所以在离线环境里,把 rpm 包做成 yum 源,本质上是为了重新获得这种自动依赖解析能力。
1.3 本地源和网络源的区别在哪
从 yum 客户端的角度看,本地源(file://)和网络源(http://、ftp://)在使用方式上几乎没区别,区别只在于仓库地址的协议不同。
file://方式:仓库就在本机文件系统上,配置最简单,适合单机使用http://方式:仓库放在一台服务器上,通过 HTTP 服务共享给多台机器,适合内网批量部署
我这次的需求是给一批内网机器提供统一的软件源,所以最终采用的是 HTTP 方式。但制作仓库元数据的过程两者完全一样,区别只在最后配置客户端时的 baseurl 写法。下面我会把两种方式都讲到。
2. 收集和整理 rpm 包时最容易忽略的细节
仓库能不能用,很大程度上取决于你收集的 rpm 包是否完整、是否匹配目标系统。这一步看起来简单,实际上坑最多。
2.1 包的来源和版本必须和目标系统匹配
我这次拿到的 rpm 包是从一台和目的一样的系统上通过yumdownloader下载下来的。这里有个关键点:下载包的机器和目标机器的系统版本、架构必须一致。
举个例子,如果目标机器是 CentOS 7 x86_64,那你下载的包也必须是针对 el7 x86_64 的。如果你不小心混入了 el8 或者 i686 的包,生成仓库时可能不报错,但客户端安装时会出现依赖找不到或者包不兼容的问题。
怎么确认一个 rpm 包的目标平台?可以用这条命令看:
rpm -qp --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' xxx.rpm输出里如果看到el7、x86_64这样的字样,就说明平台信息。批量检查的话可以写个循环:
for pkg in *.rpm; do echo -n "$pkg: " rpm -qp --queryformat '%{ARCH}\n' "$pkg" done把架构不一致的包挑出来剔除掉,能避免后面很多莫名其妙的报错。
2.2 依赖包要下全,否则仓库等于白搭
这是离线搭源最核心的痛点。你从网络上下载的包,往往只是你直接想装的那个包,但它依赖的一大堆包你并没有一起下下来。结果就是仓库搭好了,yum install的时候还是提示缺依赖。
正确的做法是:在一台能联网、且系统和目标一致的机器上,用yumdownloader把目标包及其所有依赖一起下载下来。命令大概是这样:
yumdownloader --resolve --destdir=/tmp/rpms httpd--resolve这个参数是关键,它会自动把依赖树上的所有包都下载下来。--destdir指定下载目录。这样下载下来的包集合才是完整的。
提示:如果目标机器和下载机器的系统版本有细微差异(比如小版本号不同),依赖包可能会有出入。最稳妥的办法是尽量让两台机器的系统版本完全一致。
2.3 目录结构建议按需规划
rpm 包放哪个目录其实没有硬性规定,但建议规划得清晰一点,方便后续维护。我一般会这样组织:
/data/yum-repo/ ├── base/ # 基础系统包 ├── extras/ # 额外软件包 └── custom/ # 自己收集的定制包每个子目录都是一个独立的仓库,各自生成自己的 repodata。这样以后要更新某一类包,不会影响到其他仓库。如果包不多,全部放一个目录也完全可以,不用过度设计。
3. 用 createrepo 生成仓库元数据
包整理好了,接下来就是最关键的一步:生成元数据。这一步用到的工具是createrepo。
3.1 安装 createrepo 工具
createrepo这个工具本身可能不在最小化安装的系统里,需要先装上。如果目标机器能连外网,直接:
yum install -y createrepo如果是完全离线的环境,那就得先想办法把 createrepo 的 rpm 包搞进来手动安装。它本身依赖 python 相关的一些库,通常系统里已经有了,直接rpm -ivh createrepo-*.rpm一般能装上。装完后用createrepo --version验证一下。
3.2 执行 createrepo 生成元数据
假设你的 rpm 包都放在/data/yum-repo/custom/目录下,生成元数据的命令非常简单:
createrepo /data/yum-repo/custom/执行完之后,你会看到该目录下多了一个repodata目录,里面就是生成的各种元数据文件。这时候一个最基本的仓库就已经成型了。
这里解释一下 createrepo 背后做了什么:它会遍历目录下所有 rpm 包,读取每个包的头部信息(名称、版本、依赖、提供的能力等),然后把这些信息汇总、压缩,生成primary.xml.gz、filelists.xml.gz、other.xml.gz等文件,最后生成入口文件repomd.xml。yum 客户端就是通过读这些文件来了解仓库内容的。
3.3 包有更新时怎么重新生成
如果你往目录里新增了 rpm 包,需要重新生成元数据,否则 yum 看不到新包。有两种做法:
- 直接重新跑一遍
createrepo /data/yum-repo/custom/,它会重新扫描全部包 - 用
--update参数增量更新:createrepo --update /data/yum-repo/custom/
包数量少的时候两者差别不大,包多的时候--update会快很多,因为它只处理新增和变化的包。不过实测下来,--update偶尔会有缓存导致新包识别不到的情况,如果发现更新后 yum 还是看不到新包,直接全量重新生成一次最保险。
注意:每次重新生成元数据后,客户端的 yum 缓存需要清理才能看到变化,命令是
yum clean all,这个后面会详细说。
4. 把仓库共享出去:HTTP 方式的配置
单机使用的话,仓库放在本地用file://引用就够了。但如果要给多台机器用,就需要通过 HTTP 服务把仓库目录共享出去。
4.1 用 HTTP 服务暴露仓库目录
最常用的做法是装一个 HTTP 服务,把仓库目录作为网站根目录或者子目录暴露出来。假设仓库在/data/yum-repo/,可以这样配置:
# 安装 http 服务(以常见的为例) yum install -y httpd # 创建一个软链接,把仓库目录挂到网站目录下 ln -s /data/yum-repo /var/www/html/repo # 启动服务 systemctl start httpd systemctl enable httpd这样其他机器就可以通过http://服务器IP/repo/custom/来访问这个仓库了。
4.2 权限和防火墙的常见问题
这一步最容易卡住的地方有两个:
权限问题:HTTP 服务默认以特定用户身份运行,如果仓库目录的权限设置得太严,服务进程读不到文件,客户端访问就会返回 403。解决办法是确保仓库目录及其父目录对服务进程可读,通常给个755权限就够了:
chmod -R 755 /data/yum-repo防火墙问题:如果服务器开了防火墙,需要放行 HTTP 端口,否则客户端根本连不上。这个根据实际环境配置即可,测试阶段可以先临时关闭防火墙验证,确认没问题后再按需放行。
验证服务是否正常,可以在服务器本机执行:
curl -I http://localhost/repo/custom/repodata/repomd.xml如果返回 200,说明 HTTP 这一层通了。如果返回 403 或 404,就回头查权限和路径。
5. 客户端配置与完整验证流程
仓库服务端搭好了,最后一步是在客户端配置并验证。这一步我会把配置文件的写法、缓存清理、以及完整的验证命令都列出来。
5.1 编写 repo 配置文件
在客户端的/etc/yum.repos.d/目录下新建一个.repo文件,比如custom.repo,内容如下:
[custom] name=Custom Local Repository baseurl=http://服务器IP/repo/custom/ enabled=1 gpgcheck=0如果是单机本地使用,把 baseurl 改成file:///data/yum-repo/custom/即可。
这里几个字段解释一下:
[custom]:仓库的 ID,必须唯一,客户端用它来标识这个仓库name:仓库的描述,随便写,只是给人看的baseurl:仓库地址,指向包含 repodata 的那个目录enabled=1:启用这个仓库gpgcheck=0:关闭 GPG 签名校验。自己做的仓库一般没有签名,所以关掉,否则会报签名验证失败
提示:如果出于安全考虑想开启 gpgcheck,需要自己生成 GPG 密钥对包进行签名,流程会复杂不少。内网自用场景下,关闭 gpgcheck 是常见做法。
5.2 清理缓存并验证仓库可用
配置文件写好后,先清理旧缓存,再让 yum 重新读取仓库:
yum clean all yum repolistyum repolist会列出当前所有可用的仓库。如果你看到custom这个仓库出现在列表里,并且包数量不为 0,说明仓库配置成功了。
如果custom没出现,或者包数量是 0,常见原因有:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 仓库不出现 | repo 文件语法错误 | 检查文件名后缀是否为 .repo,字段拼写 |
| 包数量为 0 | repodata 未生成或路径不对 | 确认 baseurl 指向的目录下有 repodata |
| 报 403/404 | HTTP 权限或路径问题 | 用 curl 直接访问 repomd.xml 验证 |
| 报签名错误 | gpgcheck 开着但无签名 | 设置 gpgcheck=0 |
5.3 实际安装测试
仓库通了之后,做一次真实安装测试才算真正验证完成。挑一个你下载的包,比如 httpd,执行:
yum install -y httpd如果 yum 能自动解析依赖并从你的仓库里把包都装上,那就说明整个流程完全跑通了。这一步我建议一定要做,因为yum repolist只能证明仓库被识别,不能证明依赖关系是完整的。只有真正装一次,才能确认你收集的依赖包是全的。
安装过程中如果提示某个依赖找不到,那就说明你下载包的时候漏了依赖,需要回到第 2 步,把缺的包补进来,重新生成元数据,再清理客户端缓存重试。
6. 几个我实际踩过的坑和应对经验
前面流程讲完了,这里单独把几个实际踩过的坑拎出来说,这些是文档里一般不会写、但实际很容易遇到的问题。
6.1 元数据生成后客户端看不到新包
这个坑我遇到过一次。当时往仓库里加了几个新包,跑了createrepo --update,结果客户端yum repolist显示的包数量没变,新包死活看不到。
排查过程是这样的:先在服务端确认 repodata 目录的修改时间是不是最新的,发现确实是刚更新的。然后直接在客户端用 curl 拉取 repomd.xml,对比里面的校验值和服务器上的文件,发现客户端拿到的还是旧的缓存。
原因就是客户端 yum 有本地缓存,yum clean all之后重新yum makecache就好了。所以记住一个原则:服务端元数据一变,客户端必须清缓存。这个顺序不能乱。
6.2 依赖包版本冲突导致安装失败
还有一次,仓库里同时存在同一个包的两个版本,结果 yum 解析依赖时选错了版本,导致安装失败。这种情况通常是因为多次下载包时没注意,把新旧版本都放进去了。
处理办法是保持仓库里同一个包只保留一个版本,把多余的删掉再重新生成元数据。如果确实需要保留多版本,那就要在配置依赖时格外小心,或者用yum install 包名-版本号的方式明确指定版本。
6.3 大仓库生成元数据很慢怎么办
如果仓库里的包非常多(比如上千个),createrepo全量生成会非常慢,可能要好几分钟甚至更久。这时候有几个优化方向:
- 用
--update做增量更新,避免每次全量扫描 - 用
--workers参数开启多线程,加快生成速度,比如createrepo --workers 4 /data/yum-repo/custom/ - 把仓库按类别拆分成多个小仓库,各自独立生成,减少单次生成的数据量
我实测下来,--workers参数对多核机器提升比较明显,包多的话建议加上。
6.4 关于仓库的日常维护建议
仓库搭好只是开始,后续维护才是长期工作。我的经验是:
- 每次增删包之后,养成"重新生成元数据 + 客户端清缓存"的固定动作
- 定期检查仓库里有没有重复版本、架构不匹配的包
- 如果仓库要长期给多台机器用,建议写个简单的脚本把"生成元数据"这一步自动化,避免手动操作遗漏
这套流程我从第一次摸索到现在,已经在内网环境里稳定用了很久,中间除了上面提到的几个坑,整体还是很顺的。核心其实就是理解"rpm 包 + repodata 元数据 = yum 仓库"这个本质,剩下的都是围绕这个本质做配置和验证。把依赖包收集全、把元数据生成对、把客户端缓存清干净,这三件事做到位,离线 yum 源基本就不会出问题。