内网里装机器这件事,只要规模上了两位数,早晚会撞上同一个问题:几十台服务器同时去公网拉软件包,出口带宽被占满,装一台机器要等十几分钟,碰上上游源抽风的时候整批装机任务全卡在那里。我第一次遇到这个场景是在一个离线的机房,环境根本不允许访问公网,那时候才真正意识到,把软件源搬到内网来,不是优化项,是必需项。这篇内容就把 Linux 搭建私有 yum 源仓库这件事从头到尾拆一遍,包括为什么要做、目录怎么规划、同步怎么做、元数据怎么生成、客户端怎么接、出问题怎么查。无论你是刚接手运维的新手,还是已经管着一批机器想把手动装包改成内部统一分发的老手,下面的内容都能直接拿去用。
1. 内网自建 yum 源到底解决什么问题
先把话说清楚:私有 yum 源不是"把光盘挂上去当源"这么简单的事,它是一套软件包分发体系。核心逻辑是——把上游发行版仓库里的 RPM 包和元数据,按原有目录结构搬到内网服务器上,再用一个 Web 服务把它暴露出去,内网所有机器把 repo 文件的 baseurl 指向这台服务器,从此装包不再走公网。
1.1 三类真实场景,对号入座
第一类是完全隔离的内网环境。生产网、测试网、某些工业场景的内网,物理上就不通外网,机器装完系统之后啥也没有,连 vim 都装不上。这种场景下私有源不是可选项,是唯一的软件来源。做法通常是用一台能上公网的机器做同步,同步完再搬进去,或者定期用移动介质倒一次数据。
第二类是批量部署时的带宽与效率问题。一百台机器同时yum install同一个包,出口带宽会被瞬间吃满,而且每台机器都要独立去解析元数据、下载、校验,重复劳动。内网源把这份流量收敛成一次,客户端从局域网拉包,速度从几百 KB/s 变成几十 MB/s,装机时间从小时级压到分钟级。
第三类是版本可控性。上游源是滚动的,今天装和三个月后装,拿到的可能是不同的小版本,出问题时很难复现。私有源可以固定某一次同步的快照,整个环境的软件版本保持一致,这在需要长期维护的项目里价值极高。
注意:如果你的机器数量在十台以内,而且能正常访问上游源,其实不必急着自建。自建源会带来额外的磁盘成本、同步成本和维护成本,规模不到阈值的时候,性价比是负的。
1.2 三种技术方案怎么选
搭建私有 yum 源有几种主流路径,选错了后面全是返工。我把它们的差异整理成一张表,你按自己的条件挑:
| 方案 | 数据来源 | 空间占用 | 适合场景 | 主要限制 |
|---|---|---|---|---|
| 全量同步上游仓库 | reposync从上游源拉取 | 大,单个发行版常见 15–30GB | 内网需要完整软件生态 | 首次同步慢,需定期增量 |
| 本地 ISO 挂载 | 安装镜像 | 小,单张 ISO 约 10GB | 只需要基础包,环境极端受限 | 包不全,无更新 |
| 自建 RPM 包仓库 | 自己编译或收集的包 | 按需,通常几百 MB | 分发内部自研软件 | 需要自己处理依赖与签名 |
实际生产中常见的组合是:用全量同步做基础仓库,再挂一个自建包仓库放内部软件。两个仓库在目录上并列,客户端 repo 文件里写两条配置,互不干扰。这种方式既解决了系统包的问题,也解决了内部软件分发的问题,后面扩容也方便。
还有一种取巧做法是把 ISO 挂载和全量同步结合:先用 ISO 快速把基础仓库搭起来应急,再后台慢慢同步完整仓库,同步完了切换 baseurl。这在赶工期的时候很实用。
1.3 目录结构必须提前规划
这是最容易被忽略、后面最难改的一环。目录结构一旦铺开,客户端 repo 文件就绑死了,改一次要动几十台机器的配置。我的建议是三层结构:
/data/yum/ ├── rocky/ │ ├── 9/ │ │ ├── BaseOS/x86_64/os/ │ │ ├── AppStream/x86_64/os/ │ │ └── CRB/x86_64/os/ │ └── 8/ ├── epel/ │ └── 9/x86_64/ └── internal/ └── noarch/第一层是发行版或仓库来源,第二层是大版本号,第三层保持上游的仓库名/架构/os原始路径。保持原始路径的好处是:客户端配置的 baseurl 能和上游文档一一对应,出问题时对照官方文档直接就能看懂,不用在脑子里做路径映射。
os这一层是上游的习惯命名,代表这是一个发行版发布仓库;对应地还有updates之类的目录,不同发行版叫法不同。如果你同步的源本身就是这种结构,reposync会自动帮你保持,别手动去改。
2. 环境准备与核心组件认知
动手之前,先把需要的东西和它们的职责搞清楚,比照着教程敲命令靠谱得多。很多人搭完之后不知道repodata目录是干嘛的,一出问题就只能瞎试。
2.1 服务器规格与磁盘容量估算
私有源服务器的压力主要在磁盘和网络出口,CPU 和内存要求很低。一台 2 核 4G 的虚拟机跑几十个客户端完全够用,真正要算的是盘。
容量估算可以按这个思路来:以某个主流企业级发行版 9 为例,BaseOS 大约 2GB,AppStream 大约 15GB,CRB 大约 2GB,加上 EPEL 的十来 GB,一套完整的基础源在 25–30GB 量级。如果同时保留两个大版本,就是 60GB 左右。再留出 50% 的余量用于同步过程中的临时文件和后续增量,实际规划 100GB 是比较舒服的。
同步过程中的临时占用经常被低估:reposync下载时会在目标目录里写.tmp文件,createrepo重新生成元数据时会先写临时目录再替换,这两个动作叠加时,瞬时占用可能比最终体积多出 10–20%。盘写满了导致元数据生成中断,是新手最常见的翻车点之一。
磁盘类型上,机械盘完全够用,因为这是顺序读为主的场景。但如果客户端数量上百,同时发起的 HTTP 请求会很多,用 SSD 能让响应更干脆。文件系统用 ext4 或 xfs 都行,xfs 在大量小文件场景下表现更好一些。
2.2 四个核心工具各自干什么
reposync:负责把上游仓库的 RPM 包拉到本地。它读的是本地已经配好的 repo 文件,所以你得先在服务器上把上游源的配置写好,再用它去同步。记住这个顺序,很多人上来就敲reposync,然后报"找不到仓库"。
createrepo_c:负责扫描目录里的 RPM 包,生成repodata目录。这个目录里是元数据文件,客户端yum或dnf装包时第一步就是拉这些文件,从中知道有哪些包、什么版本、依赖谁。没有 repodata,这个目录对客户端来说就是一堆无意义的二进制文件。
httpd或nginx:负责把目录通过 HTTP 暴露出去。选哪个都行,httpd 配置简单、和系统集成好;nginx 并发性能更好、静态文件服务效率高。我一般用 nginx,配置文件短,排查起来直观。
createrepo_c里的_c是 C 语言重写版本,比老的 Python 版createrepo快一个数量级,处理上万包时差距非常明显。新系统上直接装createrepo_c,别装老的。
# 一次性把工具装齐 dnf install -y createrepo_c nginx dnf-plugins-corednf-plugins-core提供dnf reposync和dnf config-manager,前者是同步主力,后者用来管理 repo 配置开关,非常顺手。
2.3 防火墙与 SELinux:别急着关
我见过太多人第一反应是systemctl stop firewalld加setenforce 0。这在临时测试环境无所谓,生产环境这么干是给自己埋雷。正确的做法是精确放行。
防火墙放行 80 端口:
firewall-cmd --permanent --add-service=http firewall-cmd --reload firewall-cmd --list-servicesSELinux 是更隐蔽的坑。你把仓库放在/data/yum这种非标准路径下,nginx 或 httpd 默认没有权限读,浏览器访问直接 403,日志里写的是Permission denied。解决办法是给这个目录打上正确的安全上下文标签:
semanage fcontext -a -t httpd_sys_content_t "/data/yum(/.*)?" restorecon -Rv /data/yumsemanage来自policycoreutils-python-utils包。这两条命令的意思是:把/data/yum及其所有子目录的默认标签设为 Web 服务可读,然后立即应用。做完之后ls -Z能看到httpd_sys_content_t字样就对了。
提示:如果你发现访问 403 但文件权限明明是 755,先别改权限,去看
/var/log/audit/audit.log里有没有avc: denied记录。有的话基本就是 SELinux 标签问题,用ausearch -m avc -ts recent能快速定位。
3. 实操第一步:把上游软件包同步下来
这一步是整个流程里最耗时的环节,也是最需要耐心调参的。同步策略定得好,后面维护轻松;定得不好,每天都要盯着磁盘余量。
3.1 先配好上游源,再谈同步
同步服务器本身要能访问上游源。配好 repo 文件后,用dnf repolist确认能列出来:
dnf repolist输出里能看到每个仓库的 ID 和包数量。记住这些 ID,reposync的--repoid参数用的就是它们。如果某个仓库不想要,用dnf config-manager --set-disabled <repoid>关掉,避免同步时误拉。
接下来是同步命令,这是核心,参数逐个说:
dnf reposync \ --repoid=baseos \ --repoid=appstream \ --download-metadata \ --newest-only \ --delete \ -p /data/yum/rocky/9逐个拆解:
--repoid:可重复指定,一次同步多个仓库。比一个个跑效率高,因为连接复用。
--download-metadata:把上游的repodata目录一起下下来,包括comps.xml组信息和模块元数据。这个参数非常关键,用了它就不用再手动createrepo,而且能保住模块化包的信息。老版本的yum reposync没有这个参数,只能后续手动生成,这是个重要的版本差异。
--newest-only:只下每个包的最新版本。不加的话会把仓库里所有历史版本全拉下来,体积可能翻三到五倍。除非你有明确的版本回退需求,否则一定要加。
--delete:本地有但上游没有的包,同步时删掉。保持本地和上游一致,避免陈年垃圾堆积。
-p:目标路径。reposync会在该路径下按仓库 ID 建子目录,所以最终结构是/data/yum/rocky/9/baseos/这样。注意这里生成的目录名是 repoid,不是上游原始路径,跟你前面规划的BaseOS/x86_64/os/结构不一样。这是很多人第一次搭的时候会懵的地方。
3.2 路径对齐的两种处理方式
上面那个路径偏差必须处理掉,否则客户端配置会很别扭。两个办法:
第一种是同步时就对上。reposync的-p指定到父级,配合后续mv调整:
mv /data/yum/rocky/9/baseos /data/yum/rocky/9/BaseOS mkdir -p /data/yum/rocky/9/BaseOS/x86_64/os mv /data/yum/rocky/9/BaseOS/* /data/yum/rocky/9/BaseOS/x86_64/os/ 2>/dev/null坦白说这个操作有点笨,容易出错。我更推荐第二种:直接接受 repoid 目录名,别强求和上游一致。因为路径是你自己定的,客户端配置里写清楚就行,没必要为了"看起来像上游"折腾自己。统一用/data/yum/<发行版>/<版本>/<repoid>/这种结构,简单直接,脚本也好写。
3.3 只同步需要的包,别一把梭
全量同步在带宽紧张的环境里可能跑一整夜。如果你的需求只是"内网能装常用工具",完全可以按需下载,体积能从几十 GB 压到几百 MB。
做法是先配一个指向内网的源(或者直接用上游源),用--downloadonly把指定的包连同依赖一次性下到目录里:
dnf install --downloadonly \ --downloaddir=/data/yum/internal/noarch \ --resolve \ vim-enhanced tar gzip net-tools lsof tcpdump--resolve会自动把依赖链上的所有包一起解决掉,这是关键。不加的话你只下到一个主包,装的时候还是缺依赖。下完之后再跑createrepo_c生成元数据,一个精简版的内部源就有了。
这个方式的局限是不会自动更新。上游发新版本了,你不主动去下,源里永远是旧包。适合"只求能装上、不追求最新"的场景。
3.4 增量同步与磁盘清理的节奏
首次全量同步之后,日常维护就靠增量。增量同步的命令和首次完全一样,reposync会自动比对本地已有文件,跳过已下载的包,只拉新增和变更的。实际耗时通常在几分钟量级。
建议用 systemd timer 或者 cron 定时跑,时间选在业务低峰。下面是一个 cron 的例子:
# /etc/cron.d/yum-sync 30 3 * * * root /usr/local/bin/sync-repo.sh >> /var/log/yum-sync.log 2>&1脚本里做的事:同步、清理、生成元数据、写日志。日志一定要留,出问题时这是唯一的线索。日志按天切割,保留两周,避免把根分区写满。
磁盘清理方面,--delete已经处理了大部分。补充一个容易被漏掉的点:reposync中断时会留下.tmp后缀的残file,这些文件不会被--delete清掉,日积月累能占好几个 G。定期跑一次清理:
find /data/yum -name "*.tmp" -mtime +7 -delete-mtime +7是只删七天前的,避免误删正在下载中的临时文件。这个细节不注意,清理脚本跑在同步任务中间,会把正在下的包删掉,然后同步报错,非常难查。
4. 实操第二步:生成元数据并对外发布
包下完了只是原料,元数据才让这些原料变成"可用的仓库"。这一步的坑比同步环节更隐蔽,因为出错时往往不报错,只是客户端装不上或者装到旧版本。
4.1 什么时候需要手动 createrepo
如果你用了--download-metadata,理论上不需要再手动生成元数据,上游的repodata已经跟着下来了。但以下几种情况必须手动跑:
- 用
--downloadonly按需下载的包,没有上游元数据 - 自己编译或收集的内部 RPM 包
- 你需要重建元数据来修正某些异常
- 同步过程中元数据下载失败,包全了但
repodata缺失
手动生成的命令:
createrepo_c /data/yum/internal/noarch/如果是更新已有仓库(只处理新增或变更的包,速度快很多):
createrepo_c --update /data/yum/internal/noarch/--update的原理是读旧的元数据,只对新增包做处理,然后合并。但有个坑:如果删了包,--update不会把旧元数据里的记录清掉,客户端会看到一个已经不存在的包,下载时 404。所以删包之后要跑一次不带--update的完整重建,或者干脆每次都完整重建(包量不大时差异可以忽略)。
4.2 模块化元数据的坑,必须单独说
这是我认为整个搭建过程中最容易踩的坑。某些发行版 8 之后的版本引入了模块化(modularity),AppStream仓库里除了普通的 RPM 包,还有一套模块元数据(modules.yaml),描述哪些包属于哪个模块流。createrepo_c不会生成这个文件,它只处理 RPM 包。
结果就是:你用createrepo_c手动重建了AppStream的元数据,客户端dnf install某个模块里的包时报错,提示找不到模块,或者装上了但版本不对。排查起来非常痛苦,因为repolist显示仓库正常,包也在目录里躺着,就是装不上。
正确做法只有两条:一是用--download-metadata,让上游的modules.yaml原样下来,别手动重建;二是如果必须重建,重建完之后手动把上游的modules.yaml拷贝到新生成的repodata/目录里。记住:modules.yaml文件名里带哈希值,拷贝的时候要保持一致,或者用modules.yaml这个通用名加个modules.yaml.gz。
判断方法很简单,看目录里有没有这个文件:
ls /data/yum/rocky/9/AppStream/x86_64/os/repodata/ | grep -i modules4.3 Web 服务的配置要点
nginx 配置静态文件服务,短小精悍:
server { listen 80; server_name _; root /data/yum; autoindex on; autoindex_exact_size off; autoindex_localtime on; location / { try_files $uri $uri/ =404; } access_log /var/log/nginx/yum-access.log; error_log /var/log/nginx/yum-error.log; }autoindex on打开目录浏览,好处是用浏览器直接能验证目录结构和文件是否可访问,排查时省很多事。生产环境不想暴露目录结构可以关掉,但搭建阶段建议开着。
autoindex_exact_size off会把文件大小显示成 KB/MB,比字节数好读。autoindex_localtime on让时间显示为本地时区,否则默认 UTC,看日志时容易算错时间。
配置完重载:
nginx -t && systemctl reload nginx systemctl enable --now nginxnginx -t先测语法,通过了再 reload,别直接 reload 一个语法错误的配置进去。
验证方式就是浏览器或者 curl 访问:
curl -I http://127.0.0.1/rocky/9/BaseOS/x86_64/os/repodata/repomd.xml返回 200 就通了。返回 403 基本是 SELinux 标签或文件权限;返回 404 是路径不对;返回 500 去看 error_log。这三种情况覆盖了九成问题。
4.4 权限配置的最小化原则
仓库目录的权限不用给太宽。推荐做法:
chown -R root:nginx /data/yum chmod -R 755 /data/yum find /data/yum -type d -exec chmod 755 {} \; find /data/yum -type f -exec chmod 644 {} \;目录 755、文件 644,属组给 Web 服务用户,这样 nginx 能读,其他用户只能读不能写。千万别图省事chmod -R 777,一个对内网开放的软件源目录,权限全开意味着任何能访问的机器都能往里塞文件,被人替换一个 RPM 包就是供应链问题。
5. 实操第三步:客户端接入与验证
服务器端搭好只是做了一半,客户端配置写不对,前面全白干。这一节讲怎么配、怎么验、怎么防止"看起来通了其实没通"。
5.1 repo 文件的写法与关键字段
客户端配置放在/etc/yum.repos.d/下,一个仓库一个.repo文件,或者多个仓库放一个文件里,用段落区分:
[local-baseos] name=Local BaseOS baseurl=http://10.0.0.10/rocky/9/BaseOS/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky metadata_expire=3600 [local-appstream] name=Local AppStream baseurl=http://10.0.0.10/rocky/9/AppStream/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky metadata_expire=3600几个字段值得细说:
baseurl必须指向包含repodata目录的那一层。这是新手第一大错误来源——把 baseurl 写成.../os/repodata/或者.../os/BaseOS/,客户端就找不到元数据。判断标准:baseurl 后面拼上repodata/repomd.xml,这个 URL 能用浏览器打开,就对了。
gpgcheck=1和gpgkey一起用,对包做签名校验。内网源同样要开校验,因为内网不等于可信,包在传输或存储过程中被替换、损坏都有可能。gpgkey 可以直接指向本地文件,也可以指向你服务器上的公钥地址。首次配置时要把公钥导入:
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-rockymetadata_expire控制元数据缓存过期时间。默认值比较保守,私有源更新频率不高的话设成 3600 秒(一小时)能减少重复请求。如果你的源一天才同步一次,设成86400也行。
enabled=1之外,建议把系统自带的公网源全部禁用,避免客户端在私有源出问题时悄悄回退到公网源,导致"明明内网有源却装了个不同版本"的诡异情况。禁用命令:
dnf config-manager --set-disabled '*' # 先全关 dnf config-manager --set-enabled local-baseos local-appstream # 再开私有源'*'这个写法会匹配所有仓库,操作前确认一下repolist的输出,别把不该关的关了。
5.2 一套完整的验证清单
配置完别急着下结论,按这个清单走一遍,每一项都有明确的预期结果:
# 1. 清缓存,避免用旧数据骗自己 dnf clean all # 2. 重建缓存,这一步会拉所有仓库的元数据 dnf makecache # 3. 列出所有可用仓库,确认私有源在里面且状态正常 dnf repolist -v # 4. 查一个具体的包,确认能看见 dnf list available vim-enhanced # 5. 看包的信息,确认来源是私有源而不是别的 dnf info vim-enhanced # 6. 实际装一个,做端到端验证 dnf install -y lsof第 5 步的dnf info输出里有Repo字段,显示包来自哪个仓库。这一步很多人跳过,结果装完发现是从公网源下的,白折腾。
第 2 步的dnf makecache如果报错,错误信息非常关键,直接看关键字就能定位:Cannot download repomd.xml是路径或 Web 服务问题;repomd.xml signature could not be verified是 GPG 问题;Metadata file does not match checksum是元数据和实际文件不一致,通常是同步中途被打断。
5.3 用脚本批量下发客户端配置
机器一多,手动配一百遍不现实。写个脚本,通过自动化工具推下去:
#!/bin/bash set -e REPO_HOST="10.0.0.10" REPO_BASE="http://${REPO_HOST}/rocky/9" mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true cat > /etc/yum.repos.d/local.repo <<EOF [local-baseos] name=Local BaseOS baseurl=${REPO_BASE}/BaseOS/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky metadata_expire=3600 [local-appstream] name=Local AppStream baseurl=${REPO_BASE}/AppStream/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rocky metadata_expire=3600 EOF dnf clean all dnf makecacheset -e保证任何一步失败就退出,避免半成品状态。把原有 repo 文件挪到 backup 而不是直接删,出问题时能快速回滚。
然后执行bash client-setup.sh。
提示:批量下发前,先在一台机器上跑通全流程,确认无误再推。我见过有人脚本里 baseurl 少写了一个斜杠,一百台机器全部配错,第二天早上才发现,回滚又是一轮。
6. 常见问题排查实录
搭建过程中会遇到各种报错,这里把高频的整理成速查表,再补几个查起来特别费劲的隐性问题。
6.1 报错速查表
| 现象 | 可能原因 | 排查方向与解法 |
|---|---|---|
Cannot download repomd.xml | baseurl 路径错 / Web 服务未起 / 防火墙拦截 | 浏览器直接访问 baseurl 看是否返回 200 |
| 访问目录返回 403 | SELinux 标签不对 / 文件权限不足 | ls -Z检查标签,ausearch -m avc看审计日志 |
repomd.xml signature could not be verified | GPG 公钥未导入 / 公钥不匹配 | rpm -q gpg-pubkey查看已导入公钥,重新 import |
Metadata file does not match checksum | 元数据与包不一致,同步中断 | 重新同步并完整重建元数据 |
| 客户端能看到包但下载 404 | 元数据里有记录,实际文件被删 | 完整重建元数据,别用--update |
| 模块包装不上、提示找不到模块 | modules.yaml缺失 | 检查 repodata 目录,补回模块元数据 |
| 同步跑了一夜还没结束 | 未加--newest-only,拉全量历史版本 | 加参数重跑,或先清理本地已有文件 |
| 磁盘写满导致同步中断 | 容量估算不足 | 清理.tmp残file,扩容或改按需同步 |
这张表覆盖了我实际遇到过的绝大部分情况。遇到问题时先按现象对号,比盲目搜报错信息效率高得多。
6.2 三个特别隐蔽的问题
第一个:元数据没更新,客户端装到旧版本。
现象是上游已经发了新版本,你在服务器上看目录里也有新包,但客户端dnf list看不到。原因是同步只拉了包,没有重建元数据。--download-metadata模式下拉包和元数据是同步进行的,但如果同步中途失败,可能出现包下了一部分、元数据没更新的中间状态。
排查方法:比对repodata/repomd.xml的时间戳和最新包的时间戳。如果包比元数据新,说明元数据过期了,手动重建一次即可。这个问题最恶心的地方在于它不报错,客户端一切正常,只是版本不对,靠肉眼看很难发现。
第二个:--update模式下的幽灵包。
前面提过,createrepo_c --update不会清理已删除包的元数据记录。场景是这样的:你发现某个包有问题,从目录里删掉了,然后跑createrepo_c --update,客户端依然能dnf info到它,装的时候报 404。
解决办法是删除包之后跑完整重建:
createrepo_c /data/yum/internal/noarch/包量大的时候完整重建要几十秒到几分钟,可以接受。只要做过任何删除或替换操作,就别用--update。
第三个:客户端 cache 导致的假故障。
有时候你明明在服务器上确认了包存在、元数据也对,客户端就是装不上。八成是客户端缓存问题。dnf的缓存在/var/cache/dnf/,元数据过期时间内不会重新拉。
快速验证:
dnf clean all && dnf makecache如果清完缓存就好了,说明是缓存问题,把客户端的metadata_expire调小一点,比如同步完之后主动推一次dnf clean all到所有机器。
6.3 几个实操心得
心得一:搭建阶段先在测试机上把整个流程跑三遍,包括"故意制造错误"再修复。比如故意写错 baseurl、故意删掉 repodata、故意不给 SELinux 打标签。这些错误你在测试环境见过一次,生产环境遇到时就不会慌。
心得二:同步脚本一定要加日志和失败告警。
reposync是静默的,失败了不会通知你,第二天早上才发现源是空的。脚本里加个判断:同步后检查目标目录的大小或文件数量,低于阈值就发告警。
心得三:源服务器的 Web 服务一定要设成开机自启,
systemctl enable nginx。这个太基础了,但我确实遇到过重启之后忘了 enable,第二天全公司的机器都装不上包的情况。同理,同步的定时任务也要确认生效。
7. 长期维护:版本、空间与监控
搭起来只是开始,能长期跑住不出问题,才算真的搭好了。这部分讲讲日常维护里需要持续关注的东西。
7.1 多版本并存的目录策略
一个常见的需求是同时维护两个大版本的源,比如老系统还在跑版本 8,新系统已经上版本 9。前面规划的目录结构已经支持这个:/data/yum/<发行版>/8/和/data/yum/<发行版>/9/并列,客户端按需指向。
同步脚本要按版本分开跑,别混在一个reposync命令里。原因是不同版本的 repoid 可能重名,混在一起容易搞乱。分开跑还有个好处是能分别控制同步频率——新版本同步勤一点,老版本一周一次就够。
老版本的生命周期到了之后,不要立刻删目录。保守做法是先在客户端配置里禁用,观察两周确认没有机器在用,再删。删之前先mv到一个archive目录,再观察一周。这个流程看着繁琐,但能避免"某个没人记得的老系统突然装不上包"的事故。
7.2 空间与同步耗时的日常监控
两个核心指标:磁盘使用率和同步耗时。
磁盘使用率低于 70% 比较安全,超过 85% 就该处理了。最简单的监控就是一条 cron,配合简单的阈值判断:
#!/bin/bash THRESHOLD=85 USAGE=$(df /data | awk 'NR==2 {gsub(/%/,"",$5); print $5}') if [ "$USAGE" -ge "$THRESHOLD" ]; then echo "yum repo disk usage ${USAGE}% on $(hostname) at $(date)" | mail -s "yum源磁盘告警" ops@example.internal fi同步耗时也需要记录。把每次同步的开始结束时间写进日志,定期看一眼趋势。如果某天耗时突然翻倍,可能是上游仓库结构调整了,也可能是网络变差了。耗时异常往往是容量问题的前兆,早点发现能省很多事。
7.3 元数据完整性的定期校验
这是个很少人做但很有价值的动作:定期校验元数据里记录的包是否真实存在。repomd.xml里有校验和,可以用createrepo_c的验证能力或者简单脚本核对。
一个轻量的做法是每次同步完成后,随机抽几个包做rpm -qp校验,确认文件没损坏:
find /data/yum -name "*.rpm" | shuf -n 20 | while read f; do rpm -qp --nosignature "$f" > /dev/null 2>&1 || echo "CORRUPT: $f" doneshuf -n 20随机抽 20 个,成本低但能发现大面积的损坏问题。文件损坏的原因可能是磁盘故障、传输中断、或者存储层的静默错误。抽样发现的概率不高,但一旦发现就说明存储有问题,值得立刻深查。
7.4 内部自研包怎么纳入进来
最后说说内部软件的发布。自己打的 RPM 包,扔进/data/yum/internal/noarch/或者对应架构的目录,然后跑一次createrepo_c就完事了。有几个细节值得注意:
包的Name、Version、Release要规范,因为客户端判断"有没有新版本"就是靠这三个字段比较。Release里带上构建号,比如1.el9、2.el9,每次发包递增,客户端才能正确识别为升级。
依赖声明要写全。自己打的包如果依赖了某个系统库但没在 spec 里声明,客户端装的时候不会自动拉,运行时报找不到库。打包时在干净的容器里用rpmrebuild或者ldd检查一遍动态依赖,能避免大部分这类问题。
签名这件事,内网源也可以做。如果嫌 GPG 签名的流程麻烦,至少要在客户端把gpgcheck打开针对系统包,内部包可以单独建一个仓库把gpgcheck=0,但要清楚这是在降低安全等级,得配合其他手段(比如发布前的包哈希校验)补回来。
把一个完整的内部发布流程串起来大概是:开发提交构建产出的 RPM 到制品库,发布脚本把包同步到源服务器的对应目录,跑createrepo_c重建元数据,然后触发客户端dnf clean all。这套流程跑顺了,内网软件分发基本不用再人工干预。
我自己在这套东西上栽得最狠的一次,是早期用reposync没加--newest-only,一个 AppStream 目录拉出了一个多 T 的体量,磁盘直接爆掉,还把上游源的请求频率搞得太高被限了速。后来改成先估算、再小范围试跑、确认体积合理之后再全量跑,再没出过类似问题。慢一点比返工快。