ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

内网环境自建Yum仓库实战:从原理到多机共享

内网环境自建Yum仓库实战:从原理到多机共享

1. 项目缘起与整体设计思路

1.1 为什么内网环境必须自建软件仓库

很多刚接触Linux运维的朋友会有一个思维惯性:装软件不就是敲一条yum install的事吗?在能直连外部镜像源的机器上确实如此,但一旦进入企业内网、生产隔离区或者实验室的封闭集群,这条路就直接被堵死了。我最早接手的一批服务器全部位于无外网出口的网段,几十台机器要统一装监控代理、批量升级基础库、部署内部工具链,如果还靠U盘一台台拷rpm包手动rpm -ivh,那基本等于把自己埋进依赖地狱里。

自建yum仓库解决的正是这个核心痛点:把散落的rpm包集中管理,让内网所有机器像访问公网源一样用yum或dnf自动解析依赖、自动下载安装。它带来的价值不只是"能装软件",而是版本可控、依赖可解、批量可复制。你可以把仓库理解成一个"内部应用商店",管理员负责上架,客户端只管下单,中间不需要每台机器都去理解依赖树。

这个项目适合三类人参考:一是刚入行的Linux运维,需要一套能直接落地的内网源方案;二是负责实验室或小集群的技术人员,想摆脱手动装包的重复劳动;三是有一定基础、想搞清楚yum底层工作机制的开发者。整套方案我会从原理讲到实操,从单机仓库讲到多机共享,参数和命令都给全,照着做就能跑通。

1.2 方案选型:httpd、nginx还是file协议

搭建yum仓库的本质,是让客户端能通过某种协议读取到仓库的元数据(repodata)和rpm包。常见的承载方式有三种,我先把选型逻辑讲清楚,避免你上来就选错方向。

承载方式适用场景优点局限
file://本地路径单机自用、临时测试零配置,不需要服务无法被其他机器访问
http协议(httpd/nginx)内网多机共享通用性强,客户端配置简单需要额外起一个Web服务
ftp协议老旧环境兼容部分老系统默认支持配置繁琐,逐渐被淘汰

我的建议很直接:只要涉及两台以上机器,一律上http方式。file协议虽然省事,但它把仓库锁死在本机,失去了"共享"这个最核心的意义。ftp在早期RHEL5/6时代常见,现在新环境基本不用考虑。http服务里,httpd(Apache)和nginx都能胜任,纯静态文件分发场景下两者性能差异可以忽略,选你更熟的那个就行。我个人习惯用httpd,因为它在RHEL/CentOS系里是"亲儿子",配置目录结构清晰,排错时日志也直观。

还有一个容易被忽略的选型点:仓库放本地磁盘还是网络存储。如果rpm包总量不大(几个GB以内),直接放服务器本地盘最稳;如果包量巨大、还要多台仓库服务器同步,那就要考虑NFS或对象存储挂载。我这次演示用的是本地盘方案,简单可靠,适合绝大多数中小规模场景。

1.3 整体架构与数据流向

把架构想清楚,后面每一步操作你都知道自己在干什么。整个yum仓库系统由两部分组成:仓库服务端和客户端。

服务端负责三件事:存放rpm包、生成repodata元数据、通过http把目录暴露出去。客户端要做的只有一件:在/etc/yum.repos.d/下写一个.repo文件,告诉yum"仓库地址在哪、叫什么名字、是否校验签名"。

数据流向是这样的:客户端执行yum install xxx,yum先读取.repo文件拿到baseurl,然后去请求repodata/repomd.xml,这个文件是元数据的索引,里面记录了primary、filelists、other等各个数据库文件的位置和校验值。yum下载这些元数据到本地缓存,在内存里构建出"包名-版本-依赖-文件路径"的关系图,解析出需要安装哪些包,最后按baseurl拼出rpm的实际下载地址,逐个拉取并安装。

理解了这个流程,你就能明白为什么"改了rpm包必须重新生成repodata"——因为元数据是独立于rpm包存在的,你往目录里丢新包,repomd.xml并不会自动更新,客户端看到的还是旧索引。这是新手最容易踩的坑,后面我会专门讲。

2. 核心细节解析与实操要点

2.1 环境准备与基础依赖确认

动手之前先把环境摸清楚,能省掉后面一大堆莫名其妙的报错。我这次用的是一台内网服务器,系统是RHEL/CentOS 7系的衍生版本,IP假设为192.168.10.20,主机名repo-server。你换成自己的实际地址即可,命令里的路径和IP都要对应调整。

第一步确认系统版本和架构,因为仓库目录通常要按"系统版本+架构"分层存放,混放会导致客户端拉到不兼容的包:

cat /etc/redhat-release uname -m

输出会告诉你类似CentOS Linux release 7.9和x86_64。记住这两个信息,后面建目录要用。如果你的环境是RHEL 8/9系,命令基本一致,只是包管理器默认从yum变成了dnf,但dnf完全兼容yum的仓库配置,.repo文件写法一模一样,不用担心。

第二步确认httpd是否已安装。很多最小化安装的系统是不带Web服务的:

rpm -q httpd

如果提示package httpd is not installed,那就装。这里有个前提:你的仓库服务器本身得能访问到软件源,否则连httpd都装不上。如果服务器也是纯内网,那就需要先用系统安装镜像(ISO)挂载出一个临时本地源,把httpd和createrepo装进去。挂载ISO的方法:

mkdir -p /mnt/cdrom mount -o loop /path/to/system.iso /mnt/cdrom

然后临时写一个指向file:///mnt/cdrom的repo文件,就能用yum装包了。这一步是"先有鸡还是先有蛋"的经典问题,很多内网新手卡在这里,记住用ISO做跳板就行。

2.2 关键工具createrepo的作用与安装

整个仓库搭建里,createrepo是绝对的核心工具,没有它,你放的rpm包就只是一堆文件,yum根本不认识。它的作用就是扫描指定目录下的所有rpm包,读取每个包的头部信息(包名、版本、依赖关系、提供的能力、包含的文件列表),然后生成一套结构化的XML元数据,统一放在repodata/目录下。

安装它:

yum install -y createrepo

装完后可以用createrepo --version确认。这里要提醒一点:不同系统版本对应的createrepo版本不同,生成的元数据格式也有细微差异。RHEL 8/9系推荐用createrepo_c(C语言重写版,速度更快),命令名可能是createrepo_c,但用法和createrepo完全一致。如果你在8系上执行createrepo提示找不到命令,试试createrepo_c。

注意:createrepo只负责生成元数据,它不会去校验rpm包本身是否完整、依赖是否齐全。也就是说,你放进去一个缺依赖的包,createrepo照样能生成元数据,但客户端安装时会报依赖错误。所以入库前的包质量把关,得靠人工或额外脚本。

2.3 仓库目录结构的设计原则

目录结构设计得好,后期维护省一半力气。我的习惯是按"用途+系统版本+架构"三层来分,比如:

/var/www/html/repo/ ├── base/ │ └── 7/ │ └── x86_64/ ├── extras/ │ └── 7/ │ └── x86_64/ └── custom/ └── 7/ └── x86_64/

base放系统基础包,extras放额外工具,custom放自己打的内部包。这样分的好处是:客户端可以按需只启用某几个仓库,不会因为一个仓库出问题影响全部;后期扩容或迁移时,按目录整体搬走就行。

为什么不把所有包堆在一个目录?因为createrepo每次生成元数据都要扫描全目录,包越多越慢。而且一旦某个包损坏导致元数据生成失败,整个仓库都受影响。分目录能把故障域隔离。当然,如果你包量很小(几十个),图省事放一个目录也无可厚非,但养成分类习惯没坏处。

目录建好后,把rpm包拷进去。拷包的方式看你的包来源:从ISO提取、从其他机器scp、从内部制品库下载都行。拷完记得ls | wc -l数一下数量,心里有个底。

3. 实操过程与核心环节实现

3.1 服务端:从零生成仓库元数据

假设rpm包已经放进了/var/www/html/repo/custom/7/x86_64/,现在生成元数据:

createrepo /var/www/html/repo/custom/7/x86_64/

执行完你会看到目录下多了一个repodata/文件夹,里面有几个.xml.gz和.xml文件。这就是元数据。第一次生成会稍慢,包越多越明显。

如果后续往目录里加了新包,必须重新生成,而且要用--update参数,它只处理新增和变化的包,比全量重建快得多:

createrepo --update /var/www/html/repo/custom/7/x86_64/

实操心得:我见过太多人加了包直接yum install,然后纳闷为什么"找不到包"。原因就是忘了--update。养成习惯——只要动了rpm目录,就立刻createrepo --update,可以写成一个别名或小脚本,减少遗忘。

生成完检查一下repodata/repomd.xml是否存在且非空:

ls -lh /var/www/html/repo/custom/7/x86_64/repodata/

如果这个文件是0字节或者不存在,说明生成失败,通常是目录权限问题或磁盘满了,看命令输出里的报错信息定位。

3.2 服务端:配置httpd暴露仓库目录

httpd默认的网站根目录就是/var/www/html,所以只要把仓库放在这个目录下,理论上启动httpd就能访问。但为了规范,我建议单独写一个配置文件,而不是去改主配置。

在/etc/httpd/conf.d/下新建repo.conf:

Alias /repo /var/www/html/repo <Directory /var/www/html/repo> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory>

Options Indexes的作用是允许目录浏览,这样你在浏览器里访问仓库地址时能看到文件列表,方便排查。Require all granted是放行所有访问,内网环境够用;如果对安全有更高要求,可以限制来源网段。

配置写好后启动并设置开机自启:

systemctl start httpd systemctl enable httpd

然后验证。先在服务器本机用curl测:

curl -I http://192.168.10.20/repo/custom/7/x86_64/repodata/repomd.xml

返回HTTP/1.1 200 OK就说明Web服务正常。如果返回403,多半是SELinux在拦,临时用setenforce 0测试,确认是SELinux问题后再去配布尔值或上下文,别直接永久关闭SELinux,那是运维大忌。

3.3 客户端:编写repo文件并验证

客户端这边,进入/etc/yum.repos.d/,新建custom.repo:

[custom] name=Internal Custom Repository baseurl=http://192.168.10.20/repo/custom/7/x86_64/ enabled=1 gpgcheck=0

逐行解释:[custom]是仓库ID,必须唯一;name是描述,随便写但要有意义;baseurl指向repodata的上一级目录,注意结尾的斜杠不能少,少了有时会解析异常;enabled=1表示启用;gpgcheck=0表示不校验包签名。

关于gpgcheck,这里展开说一下。生产环境建议开启签名校验,防止包被篡改,但需要你导入对应的GPG公钥(gpgkey=指向公钥地址)。内部自制的包如果没签名,就只能设gpgcheck=0。这是个安全与便利的权衡,内网可信环境下关掉可以接受,但心里要清楚风险。

配好后清理缓存并验证:

yum clean all yum repolist

repolist会列出所有启用的仓库及其包数量。如果custom仓库显示包数量为0,说明元数据没读到,回去检查baseurl路径和httpd访问。如果显示正常数量,就可以测试安装了:

yum install -y 你的包名

3.4 多仓库与优先级管理

实际环境里往往不止一个仓库,比如系统基础源、内部工具源、自研包源同时存在。这时候包名冲突就可能出现——同一个包在多个仓库里都有,yum该选哪个?

yum的默认策略是"选版本最高的",但有时候你希望优先用内部源。这时可以用priority插件(需要装yum-plugin-priorities),在repo文件里加一行:

priority=1

数字越小优先级越高,范围1到99。给内部源设1,给外部源设99,就能保证优先用内部包。这个细节在混合源环境里非常关键,我踩过一次坑:内部源里有个定制版的工具包,结果被外部源里版本号更高的同名包覆盖了,排查半天才发现是优先级问题。

另一个技巧是用includepkgs和excludepkgs做包级别的过滤,比如某个仓库只想提供特定几个包:

includepkgs=nginx,redis

这样即使仓库里有几百个包,客户端也只认这两个,能有效避免意外引入不需要的包。

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

4.1 元数据相关故障速查

元数据问题是yum仓库最高频的故障来源,我把典型现象和排查路径整理成表,遇到问题直接对号入座。

现象可能原因排查与解决
repolist显示包数为0元数据未生成或路径错检查repodata目录是否存在,baseurl是否指向正确层级
报错Cannot retrieve repomd.xmlhttp服务不通或路径404curl测试URL,检查httpd状态和SELinux
报错repomd.xml signature could not be verifiedgpgcheck开启但无有效签名设gpgcheck=0或正确导入gpgkey
新加的包找不到忘记createrepo --update重新生成元数据后yum clean all
报错Metadata file does not match checksum元数据与包不一致或缓存脏yum clean all清缓存重试

这张表基本覆盖了八成以上的元数据故障。我的经验是,遇到问题先yum clean all再yum repolist,很多缓存导致的假故障一清就好。如果还不行,再逐层往上查:客户端网络→http服务→目录权限→元数据文件。

4.2 依赖地狱的预防与处理

自建仓库最头疼的不是搭仓库本身,而是依赖完整性。你从某台机器上拷了几个rpm包放进仓库,客户端一装就报"缺少依赖xxx"。这是因为rpm包之间有复杂的依赖关系,单独拷几个包往往凑不齐依赖链。

预防办法有两个。第一,尽量从完整的系统ISO或官方源里提取包,它们的依赖关系是经过验证的。第二,用yumdownloader工具连同依赖一起下载:

yumdownloader --resolve --destdir=/tmp/pkgs 包名

--resolve会自动把该包的所有依赖一并下载到指定目录,然后你把整个目录的包一起入库,依赖就齐了。这个命令在准备离线包时简直是神器,强烈建议掌握。

如果已经出现依赖缺失,用yum deplist 包名可以列出该包的所有依赖,再逐个确认仓库里有没有。缺哪个补哪个,补完记得createrepo --update。

避坑技巧:入库前用rpm -qpR 包名.rpm快速查看单个包的依赖列表,批量检查可以写个循环脚本遍历目录。提前发现缺依赖,比客户端报错后再回头补要高效得多。

4.3 权限、SELinux与防火墙三座大山

内网服务搭不起来,十有八九栽在这三样上。我按排查顺序说。

权限:httpd进程默认以apache用户运行,它需要对仓库目录有读和执行权限。如果目录是root建的且权限是700,httpd就读不到。解决:

chmod -R 755 /var/www/html/repo chown -R root:root /var/www/html/repo

755对仓库目录足够,不需要给写权限,因为写操作是你手动做的,不是httpd做的。

SELinux:这是最隐蔽的坑。即使文件权限对了,SELinux也可能因为安全上下文不对而拒绝httpd访问。检查:

ls -Z /var/www/html/repo

如果上下文不是httpd_sys_content_t,用chcon或semanage修正:

semanage fcontext -a -t httpd_sys_content_t "/var/www/html/repo(/.*)?" restorecon -Rv /var/www/html/repo

semanage方式更规范,因为它写入策略,重启后依然生效;chcon是临时的,重启可能失效。

防火墙:确认80端口放行:

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

三样都确认后,客户端访问基本就通了。我的排查口诀是:先curl本机,再curl远程,先关SELinux测试,再查防火墙,逐层缩小范围,比盲目重启服务高效得多。

4.4 仓库扩容与日常维护建议

仓库不是搭完就一劳永逸的,日常维护有几个点要注意。

第一,定期清理无用包。时间久了仓库里会堆积大量旧版本包,既占空间又拖慢createrepo。可以按修改时间清理,或者用repomanage工具(来自yum-utils)自动保留最新N个版本:

repomanage --keep=3 --old /var/www/html/repo/custom/7/x86_64/

这条命令会列出除最新3个版本外的旧包,确认无误后加| xargs rm -f删除,再createrepo --update。

第二,元数据生成脚本化。手动敲命令容易忘,写个脚本把"拷包→createrepo→清缓存"串起来,每次入库跑一遍,规范又省心。

第三,多仓库服务器同步。如果有多台仓库服务器做冗余,可以用rsync同步rpm目录和repodata,注意同步后客户端要yum clean all刷新缓存。同步时建议先同步rpm包,再同步repodata,避免客户端读到"元数据说有包但包还没到"的中间状态。

第四,监控仓库可用性。写个简单的健康检查,定时curl一下repomd.xml,返回非200就告警。仓库挂了会导致所有客户端装不了包,属于基础设施级别的重要服务,值得监控。

我个人在实际维护中最大的体会是:仓库的规范性比技术难度更重要。目录分层、命名统一、入库流程固定,这些看似琐碎的规矩,在包数量涨到几百上千之后,会决定你是从容维护还是焦头烂额。刚开始多花十分钟设计结构,后面能省几十个小时的排查时间。另外,每次改动仓库后,务必在一台测试客户端上验证一遍再全量推广,这个习惯帮我挡掉过好几次"元数据没更新就上线"的低级事故。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进