搞内网运维这几年但凡服务器数量超过二十台、业务又对公网访问有强限制你早晚会遇到这样一个问题明明在联网机器上一条yum install就能装好的包到了内网机器上就成了“Could not retrieve mirrorlist”要么超时、要么干脆连不通。折腾几次之后你会非常自觉地去搭一套本地YUM源。这篇文章就是把我实际操作中搭建内网本地YUM源的完整过程、踩过的坑、以及最终稳定运行的方案一起写出来从场景评估到同步策略、从元数据生成到客户端配置都有适合刚入门的新手也适合想优化现有方案的老手。1. 场景拆解与方案选择先想清楚再动手1.1 我这个需求是怎么来的先说背景。某次接手一个内部项目生产环境在物理隔离网络里服务器数量大概有三四十台系统版本是某Linux发行版的7和8两代部分机器还要承担基础的开发、编译和日常运维工具安装。最初大家是怎么干的手动下载一个rpm然后rpm -ivh一层一层装依赖装到怀疑人生。后来有人提出来用U盘拷贝整个离线包目录但是离线包目录也有问题依赖不全、版本匹配混乱、不同架构的包混在一起装到一半报错找不到.so.1的情况比比皆是。其实需求很简单让内网机器能用yum并且能像连了公网一样装上常规工具和系统组件。这就需要一个内网YUM源——一个放置了完整RPM包、能生成元数据、能通过HTTP供全网段访问的仓库服务。有了这个东西批量软件安装、系统补丁更新、新机器初始化就都变得统一和可控了。1.2 三种常见方案哪种适合你方案A直接挂载安装镜像ISO当本地源。最省事适合临时用、只装基础系统包的场景。缺点是软件包数量有限很多常用工具在ISO里没有。方案B用缓存类工具把公网YUM源缓存到本地。对联网不限速、只求加速的场景好使但不适合物理隔离的内网因为第一遍拉取仍然需要公网。方案C在能联网的跳板机上用同步工具抓取软件仓库的RPM包传到内网后自建仓库目录再用Nginx等Web服务发布。这是生产环境里最成熟稳妥的方案。我最终选了方案C因为只有它的可控性最强包是完整的、目录结构清晰、元数据自己生成、客户端配置简单。下面所有内容都按这个方案展开。1.3 工具选型每样东西解决什么问题reposync或dnf reposync批量同步远程YUM仓库里的RPM包支持按仓库ID筛选、按架构筛选、只保留最新版。createrepo_c生成/更新仓库元数据也就是客户端读取的repodata目录。没有它YUM源只是个普通的文件目录。rsync解决联网机器和内网源服务器之间的文件传输支持增量同步和断点续传。Nginx用HTTP协议把仓库目录暴露给内网机器简单、可靠、性能好。这里有一个经验不要在一开始就想着把所有仓库全部同步下来。不同的发行版会有不同仓库集合先评估你要什么再决定同步什么否则硬盘空间和首次同步时间都会很被动。2. 环境准备与同步规划磁盘、目录、架构2.1 磁盘空间怎么评估评估磁盘空间是最容易出错的一步。曾经见过有人把整个仓库同步到一半磁盘写满最后同步机连系统日志都写不进去。一般做法是先看目标发行版对应的多个仓库在公共软件源里的实际体积再按你需要同步的架构数量留出1.3到1.5倍的余量。我的做法是分三类估算BaseOS类基础仓库体积中等但升级频繁。AppStream类应用仓库体积大且有很多模块化包。自定义第三方包可能不大但也要占空间。实际操作时我会先挂载一个临时目录执行一次reposync的试同步然后立刻看真实占用再决定是否有必要调整策略。比如在只装x86_64的集群里就不需要同步aarch64和src的包可以省下不少空间。2.2 目录结构设计我用的目录结构如下/data/yum/ ├── baseos/ │ └── repodata/ ├── appstream/ │ └── repodata/ └── extras/ └── repodata/一个仓库一个顶级目录互不嵌套。为什么不要嵌套因为YUM源会在每个仓库目录下找repodata/repomd.xml如果目录层级太深客户端配置baseurl时很容易写错路径而且同步的时候也不好维护。另外仓库目录不要放在系统盘根分区要单独挂数据盘。这个很关键毕竟仓库会越来越大万一系统盘满了影响的就不只是YUM源了。2.3 在联网机器上部署同步工具同步机上需要先装好dnf-utils或createrepo_c等工具包。不同发行版的包名不同但一般通过下面一组命令都能拿到yum install -y yum-utils createrepo_c装完之后验证一下reposync --help能正常输出帮助信息就说明环境没问题了。3. 核心实操同步、裁剪与元数据生成3.1 首次全量同步首次同步是最耗时的也最能考验方案的合理性。我会先做一次全量同步确保内网源是完整的。这里直接贴出我常用的同步命令mkdir -p /data/yum/baseos /data/yum/appstream /data/yum/extras dnf reposync \ --repoidbaseos \ --repoidappstream \ --repoidextras \ -p /data/yum \ --download-metadata \ --newest-only \ --delete \ -a x86_64逐项解释一下这些参数--repoid指定要同步的仓库ID。仓库ID一般用dnf repolist查看。-p /data/yum指定包文件的存放根目录。它会在根目录下自动生成与仓库ID同名的子目录。--download-metadata下载仓库元数据。这个参数有一个坑后面会详细说。--newest-only只保留每个软件包的最新版本。对于内网通用源这个选项非常实用能节约大量磁盘空间。--delete删除本地存在但远程仓库已经移除的包。这个参数在增量维护时很有用但首次同步时无所谓。-a x86_64只同步x86_64架构。同步完成后检查是否有大量0字节文件或半截文件。一般来说只要网络没断reposync会自动重试但逻辑卷满了、磁盘IO异常的时候也会静默产生一些残缺文件我习惯同步完用find /data/yum -name *.rpm | wc -l统计一下再对比远程仓库的包数量做到心里有数。3.2 处理Group和模块元数据这里必须多说一句如果你希望内网客户端还能使用yum groupinstall这类按包组安装的功能只靠普通同步是不够的。reposync --download-metadata下载下来的元数据虽然能保证基本安装但包组信息不一定完整。我踩过这个坑同步完成、客户端也显示仓库正常可是执行yum groupinstall Development Tools却提示找不到group。解决办法是在同步机上手动下载对应仓库的comps文件一般叫*-comps-*.xml然后在生成元数据时通过--groupfile参数把它合并进去。cd /data/yum/baseos wget URL_PATH/repodata/xxx-comps-xxx.xml mv xxx-comps-xxx.xml comps.xml createrepo_c --update --groupfile comps.xml /data/yum/baseos生成完元数据后可以这样验证yum --disablerepo* --enablerepolocal-baseos group list能看到包组列表就说明group信息已经生效。3.3 生成元数据的正确姿势元数据生成是整个本地YUM源建设里最需要耐心的一步。首次生成时会扫描所有RPM包耗时取决于包数量和磁盘性能。我用的命令是createrepo_c --update /data/yum/baseos createrepo_c --update /data/yum/appstream createrepo_c --update /data/yum/extras--update参数是在已有元数据的基础上做增量更新首次运行时不加也能跑但加了也不会出错所以我习惯一直带着。生成结束后每个仓库目录下会出现repodata/目录里面有repomd.xml和一堆*-primary.xml.gz之类的文件。注意看repomd.xml的时间戳是不是刚刚生成的这个时间戳后面会作为判断源是否新鲜的重要依据。补充一个优化包数量特别多的时候可以用createrepo_c而不是老的createrepo速度快很多生成的元数据格式也更现代。3.4 裁剪和磁盘回收同步完成的仓库如果太臃肿可以通过定期清理旧版包来回收空间。但如果首次全量同步已经使用了--newest-only包数量通常处于可控状态不需要额外的清理策略。另外很多运维朋友会把src.rpm也同步下来我建议除非有源码编译需求否则不要同步源码包。在配置文件中排除掉*.src是很常见的做法。4. 传输到内网与发布服务从跳板机到源服务器4.1 数据搬运rsync是首选同步机往往是临时的真正的YUM源服务器在内网。这时候需要把整个仓库打包或增量搬运过去。我的首选工具是rsync因为它能增量传输、断点续传而且同步时可以通过SSH加密传输避免明文裸传。普通跨网段场景rsync -avP --delete /data/yum/ 用户名内网源服务器IP:/data/yum/如果网络隔离比较严格需要经过中间机转存就用两步走# 从同步机同步到中间存储比如移动硬盘或U盘 rsync -avP --delete /data/yum/ /mnt/usb/yum/ # 在源服务器上从中间存储复制进仓库目录 rsync -avP --delete /mnt/usb/yum/ /data/yum/强烈建议保留--delete这样源端删除的软件包也会同步删除避免内网源越滚越大。4.2 安装并配置Nginx文件到位后内网源服务器提供HTTP服务。我选Nginx因为轻量、配置简单、并发能力也不错。安装省略直接看关键配置server { listen 80; server_name repo.internal; root /data/yum; autoindex on; autoindex_exact_size off; autoindex_localtime on; location / { charset utf-8; } }核心就三点root指向仓库根目录autoindex on打开目录列表这样浏览和排错都很直观。配置完成后重载Nginxnginx -t nginx -s reload然后在源服务器本机先自测curl http://127.0.0.1/baseos/repodata/repomd.xml能返回XML内容说明服务已经起来了。4.3 配置客户端YUM源内网客户端这一步很简单在/etc/yum.repos.d/下新建一个local.repo[local-baseos] nameLocal BaseOS Repository baseurlhttp://repo.internal/baseos enabled1 gpgcheck0 [local-appstream] nameLocal AppStream Repository baseurlhttp://repo.internal/appstream enabled1 gpgcheck0 [local-extras] nameLocal Extras Repository baseurlhttp://repo.internal/extras enabled1 gpgcheck0关于gpgcheck0这是内网环境里非常常见的取舍。因为GPG公钥需要额外导入而内网源本身已经有边界防火墙保护如果不想引入密钥管理的复杂度可以直接关掉。如果团队安全要求高就改为gpgcheck1提前把公钥文件分发到各服务器并执行rpm --import。两种方式我都用过大多数场景下内网关掉校验更省事。配置完成后客户端执行下面三条命令yum clean all yum repolist yum makecacherepolist能看到刚才配置的仓库且状态正常就可以正常安装了。安装验证用一个小工具比如yum install -y tree能装成功整个链路就通了。4.4 双机备份思路源服务器不是永远不会坏的。我会把同样的仓库目录用rsync再同步到另一台内网备用机器上并用同样的Nginx配置提供服务。客户端侧不切换地址也行只要做好DNS解析指向主源服务器故障时直接把域名指向备用服务器就能恢复。这个方案实现成本很低但关键时刻能救命。5. 使用效果验证与依赖处理细节5.1 新机器裸机安装第一个包很多人在内网新机器上刚配好源立马执行yum install gcc然后看到一堆依赖错误就开始怀疑源不完整。其实不是源不完整而是客户端还没有生成完整的缓存索引。第一次使用前务必执行yum makecache有些版本还需要yum update先刷新一次。我在生产上见过一个新机器因为没makecache直接安装失败后来把两个仓库都 disable 再 enable问题立刻好了。5.2 依赖缺失时怎么办依赖缺失是内网源场景下最典型的问题。一旦客户端报类似libfoo.so.1()(64bit) is needed by xxx而当前源里确实没有这个包就需要回到联网机器上把它找回来。这里我推荐repotrack它可以下载某个包及其全部依赖比反复yumdownloader试错效率高得多。repotrack -p /data/need-to-inner gcc下载完后把这些rpm直接放到内网源的某个自定义仓库目录下比如/data/yum/custom/然后createrepo_c --update /data/yum/custom rsync -avP --delete /data/yum/ 内网源服务器IP:/data/yum/客户端刷新缓存后就能正常解析依赖了。用repotrack时要注意它的输出是扁平目录文件名不区分仓库来源所以如果有同名不同版本的情况宁可单独装到独立目录也不要和正式仓库混在一起否则createrepo会直接扫描到重复包容易引发版本混乱。5.3 验证YUM源整体健康状态的技巧给内网源做体检我一般看三处repomd.xml时间戳是否过期。仓库目录下的rpm数量是否和预期一致。随机挑几个包用curl下载确认HTTP服务正常响应。顺手写一个小脚本放在源服务器上定期跑发现问题直接发告警。脚本的核心逻辑很简单for repo in baseos appstream extras; do repomd_time$(stat -c %Y /data/yum/$repo/repodata/repomd.xml) current_time$(date %s) diff$((current_time - repomd_time)) if [ $diff -gt 86400 ]; then echo $repo repodata is stale fi done超时阈值可以按自己的更新频率设置我这边是每天更新所以阈值设为24小时。6. 常见问题排查与增量维护脚本6.1 问题排查速查表现象原因解决办法客户端yum repolist看不到本地仓库repo文件写在错误目录或后缀名不是.repo检查/etc/yum.repos.d/*.repo文件命名报错Cannot find a valid baseurl for repobaseurl路径写错或者HTTP服务没起用curl手动访问baseurl下的repodata/repomd.xml验证报错Failed to synchronize cache for repo元数据过期或文件权限问题重新createrepo确认目录可读报错GPG key retrieval failed开启了gpgcheck但没导入公钥改为gpgcheck0或导入公钥安装时报依赖缺失依赖包不在当前源里用repotrack补充下载放入自定义仓库安装速度慢但公网正常客户端走了其他仓库配置禁用非必要repo只保留本地源元数据时间戳不准同步中断或createrepo没执行完重新执行createrepo观察退出码6.2 定时增量更新脚本本地YUM源不是搭完就一劳永逸的它需要跟着上游仓库定期更新。否则新买的服务器想装一个刚发布的安全更新包源里却没有那就失去了意义。我写了一个简单的更新脚本放在同步机上用cron每天凌晨执行#!/bin/bash exec /var/log/yum-sync.log 21 set -e REPO_LISTbaseos appstream extras LOCK_FILE/tmp/yum-sync.lock if [ -f $LOCK_FILE ]; then echo Previous sync still running or crashed. Check lock file. exit 1 fi touch $LOCK_FILE trap rm -f $LOCK_FILE EXIT for repo in $REPO_LIST; do dnf reposync \ --repoid$repo \ -p /data/yum \ --download-metadata \ --newest-only \ --delete \ -a x86_64 createrepo_c --update /data/yum/$repo done rsync -avP --delete /data/yum/ 用户名内网源服务器IP:/data/yum/有几个细节值得注意同步和生成元数据必须按顺序执行不能并行。因为createrepo扫描的是rpm文件如果一边写入一边扫描很容易生成不完整的元数据。--delete在维护场景下要谨慎。如果上游仓库临时少了一个repo比如网络抖动导致同步失败--delete会把本地对应包全部删除。因此我在脚本里加了set -e只要reposync返回非0后面的流程直接停止不会误删。用flock或简单的锁文件防止上一次同步未结束时再次触发。cron虽然可以精确控制时间但同步耗时长同一时段叠加执行会出乱子。6.3 我的个人避坑心得最后分享几个只有实际跑过才会注意到的细节。第一个同步机不要和内网源服务器共用同一台机器除非你所有环境都是隔离的。因为同步机需要公网访问而源服务器长期开在内网。一旦两台机器放在同一个安全域里还好说如果同步机被扫描攻击或系统被入侵内网源服务器也会被牵连。我个人是把同步机当“中转站”看待用完就关里面不存业务数据。第二个仓库目录的属主和权限要统一。Nginx默认以nginx或www用户运行如果仓库文件是root用户传上去的目录权限为700Nginx读取时会报Permission denied而客户端只会看到404或403。处理办法是执行chown -R root:root /data/yum chmod -R 755 /data/yum或者把Nginx的运行用户改成root的组用户并确保目录有读权限。第三个传输完成之前不要让客户端刷新缓存。尤其在使用rsync同步到源服务器时如果客户端刚好读到一半的repomd.xml会报错。我的办法是先rsync到一个临时目录比如/data/yum_tmp/确认完整后再整体mv到正式目录。虽然mv不能跨文件系统但仓库目录和临时目录在一个挂载点下就没问题。第四个定期备份repodata的生成时间记录。有时候你怀疑元数据过期但时间戳显示没问题其实是createrepo在更新时没有完全扫描到新包。把每次生成的时间点和rpm总量记录到一个日志文件里下次对不上号的时候一查就知道。第五个也是我觉得最实用的一点初始搭建时不要把Nginx的日志级别调太低。排错阶段看access.log和error.log是最快定位问题的方式如果默认级别把请求都吞了客户端出问题你根本找不到方向。这套方案在我这边跑了大半年几批新服务器初始化时的软件安装时间从小时级降到了分钟级补丁更新也在内网直接完成不再依赖外部网络。如果你也在内网环境里被YUM的问题反复折磨按这个流程走一遍应该能得到一套好用又不太需要操心的本地源。以后等仓库体积大了、服务器数量翻倍还能在这个基础上扩展包校验、自动化发布和更细粒度的仓库划分那就又是另一个话题了。