☰
离线yum源搭建全攻略:从createrepo到内网HTTP仓库
2026/10/19 4:10:52 网站建设 项目流程

在外面跑运维的人应该都遇到过这种场景:机器在隔离网段,物理上就跟公网隔开了,系统装完第一件事想执行yum install,结果一串Could not resolve host把心态直接搞崩。上周我帮客户处理一台 CentOS 7 的离线服务器,花了大半天把整个离线 yum 源从零搭完,整个过程踩了不少坑,也总结出一套可以复现的流程。

这篇内容适合谁?两类人。一类是刚接手内网环境的新手运维,不熟悉 yum 仓库结构,遇到断网就只会拿 U 盘来回拷 rpm 还老缺依赖。另一类是做等保、内网穿透部署的老手,想把自己手里那套“半自动化”离线源方案整理得更规范。离线 yum 源能解决的核心问题,就是让无外网机器依然可以用 yum 去装软件、自动解析依赖,而不是依赖rpm -ivh人肉排错。

我先把结论放在最前面:离线源的完整链路只有四步——在联网机器上把需要的 rpm 包连依赖一起下载;用createrepo给包目录生成 repodata 元数据;通过 U 盘或内网传输把仓库搬到目标机器;写.repo文件让 yum 指向本地目录或局域网 HTTP 地址。下面我按实操顺序把每一步的细节和坑都拆开讲。

1. 离线源这件事,难的不是命令,是思路

很多新手第一次接触离线源,下意识的想法是“我直接把 rpm 包拷过去装上不就行了”。单机装一两个包确实可以,但一旦遇到几十个依赖的软件,比如装个图形库、数据库客户端,rpm 手动依赖解析能做到怀疑人生。yum 之所以好用,核心在于它读取仓库根目录下的 repodata 元数据,里面记录了每个包的文件清单、依赖关系、校验信息,装包时自动在仓库里找依赖。

所以要实现离线 yum,本质上不是“拷包”,而是要把一个完整的、带元数据的软件仓库搬运到目标环境。这是一个思路转换问题。

1.1 为什么内网环境是刚需

我见过不少客户的真实情况:机房建在独立网段,出口有严格的访问控制,公网 DNS 都不通。部署的机器可能是等保二级、三级环境,也可能是生产区、测试区、办公区之间的隔离区。在这些环境里,运维装软件只有三条路:找厂商要安装包、自己人工拷包、搭一个内部软件源。前两条路短期应急可以,长期维护一定会出问题,因为补丁、安全更新完全跟不上。

自己搭一个离线源,一次准备,后面所有机器都能用。这也是这块工作最大的价值:它不是一锤子买卖,而是一套可以持续使用的内部基础设施。

1.2 离线源的两种形态:目录源和局域网源

根据目标环境的大小,离线源有两种典型形态。

第一种是单机目录源,适合一两台机器。把仓库文件夹放到服务器本地磁盘,配置baseurl=file:///路径,yum 直接读取本地文件。优点是零额外组件,缺点是如果机器多了,每台都要拷一份。

第二种是局域网 HTTP 源,适合几十台甚至上百台机器。找一台能通内网的机器做仓库服务器,用 Nginx 或 Apache 把仓库目录通过 HTTP 暴露出来,其他机器配置baseurl=http://仓库服务器IP/路径。优点是一份仓库全网共享,管理方便,缺点是要维护一台仓库服务器。

具体选哪种,取决于机器数量和你的维护周期。我个人的经验是:机器超过三台,就直接上 HTTP 源,不要各拷各的,否则补丁更新时你会疯掉。

1.3 动手前先想清楚的三个问题

第一个问题,目标系统的版本和架构。CentOS 7 的 x86_64、CentOS 7 的 aarch64,它们对应的仓库内容是不同的,下载包时必须用相同版本和架构的机器去下载,否则装的时候一堆冲突。

第二个问题,需要哪些软件集合。是只要基础工具,还是要覆盖某个业务依赖的完整堆栈?建议按业务需求列一个软件清单,不要贪多。仓库里包越多,createrepo 生成的元数据越大,后期同步时间越长。

第三个问题,多久更新一次。离线环境不是永远不更新,安全补丁还是要打的。想清楚更新频率,决定了你要不要提前规划增量同步的策略。这三个问题想清楚,后面所有操作都是水到渠成的事。

2. 准备物料:在联网机器上把 RPM 包连依赖一起拉下来

离线源的第一步,是在一台可以联网、系统和目标机器版本一致的机器上,把需要的 rpm 包全部下载到本地。这一步的关键不是下载软件包本身,而是把它的依赖树完整地拉下来。

2.1 三个下载工具怎么选

下载 rpm 包最常用的工具有三个:yumdownloader、repotrack和dnf download。CentOS 7 上主要用前两个,CentOS 8 及以上的机器用dnf download更多。

先说yumdownloader,它属于yum-utils包,基本用法是:

yumdownloader --resolve --destdir=/opt/localrepo nginx

--resolve参数的意思是同时下载依赖,--destdir指定保存目录。这个工具适合下载单个或少量软件包,但它有局限性——它只会下载指定软件包的依赖,如果依赖本身还有依赖,某些版本可能不会全量递归。

再说repotrack,同样属于yum-utils,用法更简单:

repotrack -p /opt/localrepo nginx

-p指定保存目录。repotrack 会完整记录依赖链,把该软件包以及所有依赖全部下载,包括依赖的依赖。实测下来,处理复杂依赖时 repotrack 比 yumdownloader 更稳,我习惯把它作为主力工具。

CentOS 8 上,优先用 dnf 自带的能力:

dnf download --resolve --alldeps --destdir=/opt/localrepo nginx

--alldeps会拉取所有平台的依赖,某些场景下包会多一点,但对离线环境来说宁多勿缺。

提示:下载时建议在干净系统上操作,也就是只挂官方基础源,不要混入其他第三方源。混用第三方源会导致依赖包版本混乱,拷到目标机器上装的时候容易出现无法解决的冲突。

2.2 官方源和 EPEL 源怎么处理

CentOS 只有 base、extras、updates 这几个默认源,它们里面的包相对克制。但是很多常用软件,比如htop、python3-pip之类,在默认源里没有,需要 EPEL 源。如果你确定目标离线环境需要 EPEL 的包,必须在下载机上提前装好 EPEL 源,然后把epel-release这个包本身也放进离线仓库。

epel-release这个包比较特殊,它本身来自 EPEL 源,但它又是开启 EPEL 的钥匙。我的习惯是,在建离线源的仓库根目录下单独放一个epel-release的 rpm 包,当目标机器需要装 EPEL 软件时,先手动rpm -ivh epel-release-latest-7.noarch.rpm导入 EPEL 源配置,再用离线 yum 源里的 EPEL 目录装软件。

如果目标机器上已经配置的源指向公网,记得在离线配置完成后禁用或注释掉原来的公网源,否则 yum 会先去尝试连外网,超时后才会走本地源,每次操作会卡很久。

2.3 直接把缓存目录搬过去可行吗

有人会问:联网机器上执行yum install后,rpm 包缓存都在/var/cache/yum,把这个目录整个拷到离线机器不就行了吗?理论上可以,但实际上不推荐,原因有三。

第一,缓存目录的目录结构是按仓库、架构、包名组织的,直接搬过去 yum 不会自动识别为可用仓库,要手动拼 repodata,非常麻烦。

第二,缓存里的包是“装过的”,不代表“完整的”,很多装系统时已经存在的依赖不会出现在缓存里,别的机器装同一个软件时会缺依赖。

第三,缓存目录里往往同时混着多个源、多个版本的包,完整性无法保证。

如果你一定要用缓存目录,正确做法是从缓存目录把 rpm 包文件全部提取出来,放到同一个平铺目录,然后用 createrepo 重新生成元数据,不要直接把缓存目录当成仓库。平铺方式虽然简单粗暴,但配合 createrepo 之后是可靠的。

2.4 打包搬运的体积优化

下载完的 rpm 包积累多了之后,仓库体积会很大,一套基础工具加 EPEL 常用软件,动辄几个 GB。拷贝的时候可以考虑先打成 tar 包再搬运,注意 CentOS 7 自带的 tar 版本比较老,大文件用tar -czf可能效率不高,我用的是:

tar -czf localrepo.tar.gz /opt/localrepo

如果仓库超过 4GB,建议保持文件系统格式支持大文件,U 盘用 exFAT 或 NTFS,拷贝前先检查目标机器挂载的分区剩余空间。还有一个细节:FAT32 的 U 盘单文件不能超过 4GB,包文件超过这个限制会拷不进去,这是我在现场被坑过一次的问题。

3. 生成仓库元数据:createrepo 是真正的核心

所有 rpm 包放到一个目录后,这个目录还不能直接被 yum 使用。yum 真正读取的是目录下repodata子目录里的元数据文件。生成这个元数据的工具就是createrepo,这一步是整个离线源搭建的灵魂。

3.1 repodata 里到底装了什么

如果你把一个在线源的 repodata 目录列出来,会看到类似这样的文件:

repodata/repomd.xml repodata/xxxxx-primary.xml.gz repodata/xxxxx-filelists.xml.gz repodata/xxxxx-other.xml.gz

repomd.xml是入口文件,记录了其他元数据文件的位置和校验值。primary.xml.gz里是每个 rpm 包的名字、版本、依赖关系、提供能力等核心信息,yum 解决依赖时主要靠它。filelists.xml.gz记录每个包包含哪些文件,安装时检查文件冲突会用到。other.xml.gz存储一些 changelog 之类信息,相对没那么关键。

所以当你手动createrepo一个本地目录时,本质上就是在生成一份和在线源结构一致的元数据。对 yum 客户端来说,它不关心仓库是来自公网还是本机目录,只关心repomd.xml是否存在、内容是否合法。

3.2 生成元数据的标准操作

先在下载机上安装 createrepo:

yum install -y createrepo

接着对包目录执行:

createrepo /opt/localrepo

执行完成后,/opt/localrepo/repodata目录就出现了。这个过程会扫描目录下所有 rpm 包,逐个解析包头信息,生成 XML 元数据并压缩。包的数量越多,耗时越长,几百个包一般几十秒到几分钟,上万个包可能要等一阵。

执行完以后检查一下:

ls -lh /opt/localrepo/repodata/ cat /opt/localrepo/repodata/repomd.xml | head -20

repomd.xml里能看到<revision>标签,记录的是元数据生成时间戳。不要小看这个时间戳,后面做增量更新时,它就是你判断仓库是否更新的依据之一。

注意:createrepo 生成的元数据只覆盖“执行时目录里已有的包”。如果你后面往目录里新增了 rpm 包但没有重新执行 createrepo,目标机器上yum install就找不到新包。这不是 yum 出故障,而是元数据没刷新。

3.3 增量更新与多架构并存

仓库首次创建后,后续添加新包不需要全量重新生成。用--update参数可以增量更新元数据:

createrepo --update /opt/localrepo

--update的原理是读取已有的 repodata,只对新增或变更的包做分析,然后重写元数据文件。对大型仓库来说,这个参数能把刷新时间从几分钟缩短到几秒。

如果仓库里同时要放 x86_64 和 aarch64 两种架构的包,建议不要混在一个目录里。最稳妥的做法是分目录管理:

/opt/localrepo/x86_64/ /opt/localrepo/aarch64/

每个子目录独立执行 createrepo,客户端配置时按架构指向对应子目录。混在一个目录里,createrepo 也能处理,但 yum 安装时可能出现架构不匹配的重复包,自找麻烦。

3.4 校验与完整性检查

元数据生成完,搬运到目标机器之前,强烈建议做一次完整性校验。一个常见问题是:rpm 包在拷贝过程中损坏,导致目标机器报Metadata file does not match checksum。

我自己常用的校验方法是,在下载机上先算好整个目录的 MD5 校验文件:

cd /opt && md5sum localrepo.tar.gz > localrepo.md5

拷到目标机器后执行:

md5sum -c localrepo.md5

如果校验不通过,不要解压直接装,老老实实重新拷贝。现场赶时间硬着头皮解压安装,后面报错排查的耗时一定远超重新拷贝的时间,这个亏我吃过。

4. 客户端配置:让 yum 认你的本地仓库

仓库到了目标机器上,剩下的工作就是让 yum 知道从哪里读取。这一步看起来简单,其实坑都在细节里,我把目录源和 HTTP 源分开讲。

4.1 .repo 文件的正确写法

yum 的仓库配置放在/etc/yum.repos.d/目录,每个仓库一个.repo文件,文件名任意但建议语义清晰。仓库文件的核心结构是:

[localrepo] name=Local Repository baseurl=file:///opt/localrepo enabled=1 gpgcheck=0

[localrepo]是仓库 ID,不能和已有仓库 ID 重复,否则后加载的会覆盖前面的。baseurl是仓库根目录的地址,这里注意:本地路径必须写成file:///加绝对路径,不能省略协议头。很多新手写baseurl=/opt/localrepo然后执行yum clean all,发现仓库没生效,就是因为少了file://。

如果仓库目录下还有架构子目录,可以把 baseurl 写成:

baseurl=file:///opt/localrepo/$basearch

$basearch是 yum 自带的变量,会自动替换为x86_64或aarch64。同理还有$releasever,会自动替换成大版本号。这些变量在配置多版本仓库时特别有用。

4.2 配置完成后必须执行的两个命令

写完.repo文件,不要急着直接yum install。先清缓存再建缓存:

yum clean all yum makecache

yum clean all是清掉旧缓存,避免 yum 读取到以前在线源留下的过期数据。yum makecache是让 yum 重新读取所有可用仓库并生成缓存。执行完makecache后,可以用:

yum repolist

查看当前启用的仓库列表,确认本地源的名字已经出现。这一步能看到仓库是否被识别,比直接装软件更快速定位问题。

4.3 本地缓存模式与离线源的关系

CentOS 7 的 yum 有--cacheonly选项,意思是仅使用本地缓存,不尝试连接远程仓库。离线源配置好之后,如果要确认 yum 完全没有走网络,可以执行:

yum --cacheonly install nginx

如果本地仓库里有这个包,它能正常安装。这个选项不能替代离线仓库,但可以作为验证手段,确认你的仓库是真离线还是假离线。

4.4 GPG 签名问题怎么处理

rpm 包默认都带 GPG 签名,仓库源也带了公钥。离线源最常见的一类报错就是这样:

warning: /var/cache/yum/.../package.rpm: Header V4 RSA/SHA256 Signature, key ID xxxx: NOKEY

出现这个报错,说明目标机器上没有导入对应仓库的 GPG 公钥。注意,在线源可以自动从公网拉取公钥,离线环境没网,公钥必须提前手动导入。解决方式有两种。

第一种,把官方 GPG 公钥手动导入。CentOS 7 的公钥文件路径通常是/etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7,如果你已经把整个系统装好了,这个文件一般已经在机器上,直接执行:

rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

如果是 EPEL 的包,导入对应公钥:

rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

第二种,图省事的话,在.repo文件里把gpgcheck=0,关掉验签。内网环境如果安全要求不是特别高,这种方式能少很多麻烦。但正式生产环境,尤其是做等保测评的机器,建议保持验签开启,不要关。我个人的习惯是验签开着,提前把公钥导入,一劳永逸。

注意:即使仓库配置了gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7,如果公钥文件不存在,导入一样会失败。离线环境里,公钥文件本身也是需要保障的前置条件。

5. 从单机到局域网:离线源进阶玩法

单机离线源能用,但管理效率低。机器一多,最佳实践是搭一个小型局域网仓库,一台仓库服务器,全网共享一份离线源。

5.1 用 HTTP 服务把仓库目录共享出去

仓库服务器上,我习惯用 Nginx 来做静态文件服务,配置极简。CentOS 7 上先安装:

yum install -y nginx

然后在 Nginx 配置里加上一个 server 块:

server { listen 80; server_name repo.local; root /opt/localrepo; autoindex on; }

autoindex on这句很关键。yum 客户端访问仓库时,需要能列出目录内容,否则某些情况下会报 403 或找不到 repodata。启动服务后,先在浏览器里访问http://仓库服务器IP/repodata/repomd.xml,如果能正常显示 XML 内容,说明 HTTP 服务没问题。

客户端配置改为:

[localrepo] name=Local Repository baseurl=http://192.168.1.10/localrepo enabled=1 gpgcheck=0

我的实践中,不建议在 baseurl 里写域名而写 IP,离线环境 DNS 解析不一定可靠,写 IP 最稳妥。如果网络环境里有严格的 ACL 限制,记得在防火墙上放行仓库服务器的 80 端口,客户端到服务器方向。

5.2 局域网源的防火墙与权限细节

Nginx 默认监听 80 端口,CentOS 7 自带的 firewalld 默认会挡掉外部访问。仓库服务器上要执行:

firewall-cmd --permanent --add-service=http firewall-cmd --reload

如果用 Nginx 做访问控制,比如只允许内网某网段访问,可以加:

allow 192.168.1.0/24; deny all;

这个在安全要求高的内网里很实用。另外一个细节:如果客户端通过 IP 访问,Nginx 的server_name匹配不上时,会走默认 server,建议把默认 server 直接指向仓库目录,避免客户端访问时被转发到其他站点。

5.3 离线源的定期更新机制

离线源不是搭完就完事,补丁还是要定期打。我总结了一套简单可行的更新流程,周期建议一个月一次。

下载机上,先把需要更新的包重新拉取:

repotrack -p /opt/localrepo/update nginx

然后把新包合并进仓库目录:

cp /opt/localrepo/update/*.rpm /opt/localrepo/ rm -rf /opt/localrepo/update

增量刷新元数据:

createrepo --update /opt/localrepo

打包传输到仓库服务器,替换目录,最后在仓库服务器上重新执行一次 createrepo(如果打包传输的是整个仓库的话)。整个过程半小时内能完成,比每台机器手动拷包省太多时间。

5.4 同一套源同时支持不同版本系统

实际环境里,很可能同时有 CentOS 7 和 CentOS 8 的机器。这种情况下,我的建议是仓库服务器上按大版本分目录:

/opt/repo/7/x86_64/ /opt/repo/7/aarch64/ /opt/repo/8/x86_64/ /opt/repo/8/aarch64/

每个目录独立执行 createrepo。客户端配置时,用$releasever和$basearch变量,就能一套配置兼容多个版本:

[localrepo] name=Local Repository baseurl=http://192.168.1.10/repo/$releasever/$basearch enabled=1 gpgcheck=0

第一次配置时注意验证目标机器的$releasever实际值,CentOS 7 一般是7。调试时可以在命令行执行:

yum --showduplicates list 2>/dev/null | head

如果 baseurl 路径不对,yum 会提示找不到 repodata,看yum repolist -v的Repo-baseurl字段能最快定位。

6. 常见问题与排查技巧实录

离线源搭建过程中,几乎所有问题都集中在元数据、路径和签名这三类。我把自己和一个同行朋友实际上踩过的坑整理成速查表,方便你遇到问题时对照排查。

6.1 报错速查表

报错/现象直接原因解决办法
Could not resolve host系统尝试解析公网源域名禁用公网 repo,只保留本地 repo
Cannot find a valid baseurl for repo.repo中路径写错或目录不存在检查baseurl路径与repodata是否存在
repomd.xml does not match metadata元数据与仓库内容不一致重新执行createrepo或createrepo --update
Public key for xxx is not installed缺少 GPG 公钥导入公钥或关闭gpgcheck
Metadata file does not match checksum包文件损坏重新校验并拷贝完整文件
Nothing to do / No package available元数据中无该包确认包是否下载成功、是否执行过 createrepo
delta rpm 相关报错客户端尝试应用增量包失败清理缓存后重试,必要时关闭 deltarpm

6.2 一个典型的排查过程

举个真实例子。某次我在目标机器上执行yum install htop,报错内容是:

http://192.168.1.10/repo/7/x86_64/repodata/repomd.xml: [Errno 14] curl#7 - "Failed to connect to 192.168.1.10 port 80: Connection refused"

这个报错一眼看是连不上 HTTP 服务,但仓库服务器上 Nginx 明明在运行。后来排查发现是 firewalld 没放行 80 端口,curl 127.0.0.1通、curl 服务器IP不通,典型的本地服务正常但外部访问被防火墙拦截。执行firewall-cmd --add-service=http后解决。

另一个例子是客户端报repomd.xml does not match metadata。当时我反复执行 createrepo 都没用,最后发现是仓库服务器上有两个不同的元数据缓存,Nginx 开着 autoindex,但客户端访问时走了一个旧的软链路径。删除软链、用真实路径后解决。这类问题提醒我:元数据刷新以后,一定要检查客户端实际访问的 URL 路径是否指向了同一个目录。

6.3 我强烈建议养成的四个习惯

第一,所有.repo文件先备份再改动。改之前cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak,出问题能立刻回滚。

第二,仓库目录用只读方式挂载或设置权限。离线源内容原则上不应该被客户端改,仓库服务器上把这个目录设为chmod -R a-w或挂载为只读,能避免误操作污染仓库。

第三,每次增补包后必须重新 createrepo。哪怕只多了一个包,不刷新元数据这个包在客户端就是不可见的。这条我已经被坑过多次,现在写进了自己的操作清单。

第四,离线环境一定要记录仓库的构建时间戳和包清单。我习惯每次更新后在仓库根目录生成一个README或timestamp.txt,写上“本次更新日期、新增包、构建人”,后面排查问题时能少走很多弯路。

最后再分享两个小技巧

离线源搭完以后,我个人还有一个使用习惯:把仓库构建机上常用的 yum 操作提前在离线环境下验证一遍,比如安装gcc、python3、openssl-devel这类高依赖组件。这样真正业务部署时不会临时发现缺包,又得回头补拉依赖。

另一个技巧是,如果目标机器型号比较杂,建议在下载机上直接用yumdownloader --resolve多下载几个常用开发库的包,主动把“可能用到”的依赖提前囤进仓库。虽然会多占一些空间,但在现场缺包时的紧张感,远大于那点空间成本。

离线 yum 源这件事,工具链并不复杂,难点全在细节上。路径写错、公钥没导入、元数据忘了刷新,任何一个环节出错都够你排查一阵子。但只要把这套流程跑顺,后续所有内网机器的软件安装都会变得像有网一样自然。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询