简介在内网环境中服务器无法访问互联网时如何高效安装和更新软件是一大难题。这份基于Rocky Linux 9.2的实战文档专门面向Linux运维工程师及内网服务器管理人员演示了通过HTTP服务构建局域网YUM源的完整流程。资源为单个docx文档大小仅120KB内容涵盖服务器端ISO镜像挂载、本地YUM源配置、httpd服务安装与启动以及客户端baseurl修改、yum clean all和makecache缓存更新等核心操作并附有BaseOS与AppStream仓库的配置示例。已有1691人学习下载。按照文档逐步操作即可为多台同版本Linux服务器快速搭建统一的软件源实现软件包集中管理和版本一致性大幅提升内网运维效率文档还特别说明了关闭firewalld、setenforce 0等安全策略调整方法可帮助读者顺利部署。该方法亦可迁移至其他基于RPM的Linux发行版对DevOps、系统管理员及运维新手均具有较高的实用价值。1. 为什么三十台 Rocky 9.2 一起 dnf install 时最先崩的是公网源三十台 Rocky 9.2 同时 dnf install 时公网 yum 源开始报错全组一起抓瞎——这就是我决定在局域网里用 http 方式搭一套自有 yum 源的那个下午。做法是用一台能上外网的 Rocky 9.2 做同步机reposync 把官方仓库拉到本地经 httpd 用 http 协议发布到内网其余机器把 yum 源 baseurl 指向它装包速度直接变成局域网水平。它适合离线机房、内网测试环境和培训教室新手照着做一天能跑通熟手能拿走增量更新与排错的关键参数。2. 先搞懂 dnf 在要什么repomd.xml、http 协议与连接复用的关系2.1 dnf 的读取链从 repomd.xml 到 primary.xml.gz再到具体 rpmdnf 的仓库机制和“把目录里的 rpm 列出来”完全不是一回事。客户端执行 dnf makecache 或 dnf install 时第一件事是请求仓库根目录下 repodata/repomd.xml 这个清单文件。repomd.xml 本身只有几十 KB里面记录着 primary.xml.gz、filelists.xml.gz、modules.yaml 等元数据文件的路径和 SHA-256 校验值dnf 把这些元数据下载下来并逐个校验全部通过后才会在本地建立包索引。真正装包的时候它再按 primary 里记录的相对路径去拉取对应的 rpm 包。# 模拟 dnf 的第一跳直接看 repomd.xml 内容 curl -s http://192.168.10.20/rocky9/baseos/repodata/repomd.xml | head -n 20这条 curl 把 dnf 的黑匣子打开了一层。如果看到的是 XML 里的 type 和 location 标签说明元数据链路是通的如果返回 403 或者 HTML 错误页那客户端改再多配置也白搭。另外要注意 repodata 里几类文件的职责不同primary 管包名和依赖filelists 管文件级 providesmodules.yaml 管 AppStream 模块流comps 管分组。局域网源只要漏了其中一类就会有“某些依赖永远解析不出来”这类诡异问题所以同步时必须把整份 repodata 完整带下来不能只挑 rpm。2.2 为什么选 http和 file://、https 对比之后的选择很多实验教材讲 linux 配置本地 yum 源都是挂载 ISO 后 baseurl 写 file:///mnt/iso那是单机教学场景局域网源要服务几十台机器这套就不成立了。我实际对比过三种方案的差别方案客户端 baseurl 写法跨机器共享并发与续传额外成本file://file:///mnt/iso要配 NFS挂载一断全部报错无NFS 服务端httphttp://ip/rocky9/baseos天然支持dnf 下载支持断点续传一个 httpdhttpshttps://ip/rocky9/baseos天然支持同上证书签发与 ca 信任维护内网我直接选 http三个理由。第一防火墙成本最低firewalld 里一条 add-servicehttp 就完事https 则要额外放行 443 和维护证书信任第二http 连接复用是实打实的收益dnf 在同步元数据阶段要连续请求几十个小文件httpd 默认开 Keep-Alive同一个 TCP 连接反复使用省掉了大量的握手开销局域网里这点差别在几十台机器同时 makecache 时会被明显放大第三包的安全已经有 gpgcheck 的 GPG 签名兜底完整性和来源都有保障内网再套 https 属于重复投入。http 和 https 的区别在公网场景才是刚需局域网里 http 就是性价比最高的默认项。2.3 单机同步还是双机分离局域网搭建源的两种拓扑仓库从哪里拉、由谁发布决定了整套东西的可靠边界。常见做法是两种拓扑。第一种是单机同一台机器既当同步机又当发布机。它能上外网配好官方源后 reposync 把仓库拉到本地磁盘再由本机 httpd 发布出去。结构最简单故障点最少一台机器扛几十个客户端毫无压力我的环境一直这么跑。要注意的是这台机器的流量第一次全量同步会吃满不少带宽尽量安排在业务低峰期。第二种是双机同步机和发布机物理分离。同步机拉完仓库后用 rsync 推到完全离线的发布机 /var/www/html 下发布机不配外网也不启用官方源。好处是发布机暴露面小同步过程不抢占发布机的磁盘 IO客户端上百台时更稳代价是要维护 rsync 的密钥和推送策略多一个环节就多一类故障。我的建议是客户端规模在百台以内都别上双机先把磁盘预算给足这比架构折腾更管用运维成本低一个量级。3. 服务端搭建reposync 拉全量仓库、createrepo 重建元数据、httpd 发布3.1 装机准备dnf-utils 与 createrepo_c 缺一不可服务端要做三件事拉仓库、生成元数据、发布对应三个包。# Rocky 9.2 服务端一次性安装 dnf install -y dnf-utils createrepo_c httpd systemctl enable --now httpd hostnamectl set-hostname repo.landnf-utils 是 Rocky 9 上提供 reposync 命令的包一些老教程让你装 yum-utils在 Rocky 9 里它只是个指向 dnf-utils 的兼容包直接装 dnf-utils 即可。createrepo_c 是 C 语言实现的元数据生成器比老一代 Python 版的 createrepo 快一个量级处理 AppStream 的模块元数据也更可靠安装后系统会同时提供 createrepo_c 和 createrepo 两个命令前者是本体后者是兼容软链后面脚本我统一用 createrepo_c避免歧义。httpd 就是 Apache默认监听 80文档根目录 /var/www/html发布静态仓库足够。装完顺手看一眼磁盘df -h。仓库我放在 /var/www/html/rocky9 下这样 SELinux 的文件上下文直接就是 httpd_sys_content_t后面不会撞上 403。容量按 BaseOS 十五到二十 GB、AppStream 四五十 GB 起步预算再加三成冗余独立分区给到 100GB 左右比较从容。磁盘写满是搭源最容易翻的车预算给足后面少很多事。3.2 禁用官方源后 reposync 同步 BaseOS 和 AppStream同步之前先做一个看起来有点反直觉的操作把服务端自带的官方源临时关掉。不是因为它没用而是防止 reposync 读到混乱的源配置也为了让这台机器后续统一由自建源管理。# 备份官方源配置之后可随时恢复 cp -a /etc/yum.repos.d/rocky.repo /etc/yum.repos.d/rocky.repo.bak sed -i s/enabled1/enabled0/ /etc/yum.repos.d/rocky.repo # 创建仓库根目录并同步 mkdir -p /var/www/html/rocky9 reposync --repoidbaseos -p /var/www/html/rocky9 --download-metadata -a x86_64 reposync --repoidappstream -p /var/www/html/rocky9 --download-metadata -a x86_64reposync 的参数逐个说清楚。--repoid 后面跟仓库 id必须和 rocky.repo 里的 [baseos]、[appstream] 段名严格一致区分大小写写错会直接报 “no repository to sync”。-p 指定仓库根目录同步出来的结构是 /var/www/html/rocky9/baseos 这样的“根目录/仓库id”两层。--download-metadata 会把上游 repodata 完整拉下来其中包括 AppStream 的 modules.yaml这个参数不能省第 5 章讲模块包会再提。-a x86_64 只同步当前架构的包不写的话会把 aarch64、s390x 的包也拖回来磁盘占用直接翻倍。如果团队还需要编译类工具可以把 crbCodeReady Builder也同步一份命令完全一样。同步耗时看带宽BaseOS 加 AppStream 全量跑一两个小时是常态中途断了就重新跑同一条命令reposync 会跳过已下载完成的文件直接续上不会有副作用。3.3 createrepo_c 重建元数据为什么不能只信上游的 repomd.xml同步完成后目录里其实已经带了一份从上游抄来的 repodata为什么我坚持再生成一遍这是不少教程没讲透的地方。# 首次全量生成元数据 createrepo_c /var/www/html/rocky9/baseos createrepo_c /var/www/html/rocky9/appstream原因有两条。第一上游元数据里的路径记录是基于它自己的仓库布局计算的本地目录必须和它完全相同才能命中自己生成一遍等于给“这个目录里实际有什么文件”拍了一张快照彻底消除这种隐式依赖。第二也是最关键的这套源后面一定会被增量更新、被塞进本地自研 rpm每一次操作都在改变目录内容上游抄来的 repomd.xml 会越跑越失真而 createrepo_c 的 --update 参数只重扫有变化的文件几分钟的增量几秒就能刷完这是第 6 章自动化脚本能跑起来的性能基础。顺带一提首次全量生成元数据可能要等几分钟屏幕上滚动的 worker 进度条是正常的不是卡死。生成的 repodata 目录里会看到 repomd.xml 和一堆 .xml.gz 文件这才是一份自洽的元数据。生成完可以顺手统计一下包数量find /var/www/html/rocky9/baseos -name *.rpm | wc -l这个数字后面用来和客户端 repolist 对账。3.4 发布与自检启动 httpd 后先 curl 一通 repomd.xmlhttpd 装完就监听 80仓库又放在文档根目录里理论上已经可以访问了。但发布动作必须配一次验证否则客户端那边排错成本更高。firewall-cmd --add-servicehttp --permanent firewall-cmd --reload curl -I http://127.0.0.1/rocky9/baseos/repodata/repomd.xml curl -I http://本机内网IP/rocky9/baseos/repodata/repomd.xml第一条 curl 验证本机 httpd 进程和文件权限第二条验证走内网 IP 能通判断标准只有一个返回 HTTP/1.1 200 OK。403 大概率是 SELinux 或目录权限500 基本是 httpd 配置错误这些留到第 5 章细说。如果希望浏览器里能直接浏览目录树方便人肉排查可以在 /etc/httpd/conf.d/repo.conf 里针对仓库目录加一句 Options Indexes不加也不影响 dnf它对目录列表没有需求。注意本机 curl 通了不代表局域网里通一定要用内网 IP 再测一次这一条最容易在刚架好的时候被漏掉。4. 客户端配置 yum 源baseurl、gpgcheck、module_hotfixes 一次配对4.1 先备份并禁用 rocky.repo别让官方源和局域网源打架客户端默认的 /etc/yum.repos.d/rocky.repo 指向公网 mirrorlist如果不处理dnf 会把官方源和局域网源同时启用。后果是元数据更新时间翻倍某些包从公网拉到一半断掉报错还在两个源之间互相甩锅。我的习惯是先整体离线官方源再放新的仓库文件。# 每台客户端执行 cp -a /etc/yum.repos.d/rocky.repo /etc/yum.repos.d/rocky.repo.bak sed -i s/^enabled1/enabled0/ /etc/yum.repos.d/rocky.repo备份保留在原目录里将来要切回官方源恢复备份并把局域网源 enabled0 就行。有个细节sed 匹配的是行首的 enabled1防止误伤其他配置项。如果不做这一步后面 dnf repolist 的输出会非常嘈杂排错时你很难分清某个包到底命中哪个源。文件以 .bak 结尾也不会被 dnf 读取放心放着。4.2 手写 rocky-lan.repo三段完整配置与参数解释新仓库文件放在 /etc/yum.repos.d/rocky-lan.repoBaseOS、AppStream、Extras 各一段每段只有几个关键参数[baseos] nameRocky9-LAN-BaseOS baseurlhttp://192.168.10.20/rocky9/baseos enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 [appstream] nameRocky9-LAN-AppStream baseurlhttp://192.168.10.20/rocky9/appstream enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 module_hotfixes1 [extras] nameRocky9-LAN-Extras baseurlhttp://192.168.10.20/rocky9/extras enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9参数逐个讲。baseurl 用 http 协议加服务器内网 IP这里绝不能沿用官方源里的 metalink 字段那是公网源用来做镜像调度的在局域网里既慢又不可靠路径要和服务端目录严格一致后面目录规划一旦变化这里要同步改。gpgcheck1 保持默认开启内网源也要验签名这是包可信度的底线不值得为了省事改成 0。gpgkey 指向 Rocky 9 官方公钥路径固定是 /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9每台客户端这个文件系统自带。module_hotfixes1 只写在 appstream 段它告诉 dnf本仓库的包允许覆盖模块流里同名同版本号的包否则同步来的 AppStream 在装 php、nginx 这类模块包时会莫名失败这是模块仓库特有的坑。4.3 dnf repolist 与 makecache装包之前必须做的两次验证配置文件就位后先验证再装包顺序不能乱。dnf clean all dnf makecache dnf repolist dnf install -y treednf clean all 清掉旧缓存这是改动仓库配置后的固定动作否则 dnf 可能继续用内存里的旧元数据。makecache 从局域网源拉元数据建索引这一步如果报 Failed to download metadata报错信息里会带具体 URL拿那个 URL 去 curl 就能快速定位是路径还是网络问题。repolist 输出里三行 repo id 前面带星号表示启用后面的包数量对应服务端 createrepo 的统计数量对得上说明元数据解析成功。最后装个 tree 这种小包试水确认依赖解析、下载、签名校验、安装全链路都通再放量大装。这一步做完客户端的“配置 yum 源”才算真正闭合。4.4 批量下发一个循环脚本把源推到三十台客户端几十台机器一台台手敲不现实。我把 rocky-lan.repo 放在服务器上用循环批量推前提是运维已经给服务器配了免密 ssh。SRV_IP192.168.10.20 for host in 192.168.10.21 192.168.10.22 192.168.10.23; do scp rocky-lan.repo root$host:/etc/yum.repos.d/ ssh root$host cp -a /etc/yum.repos.d/rocky.repo /etc/yum.repos.d/rocky.repo.bak \ sed -i s/^enabled1/enabled0/ /etc/yum.repos.d/rocky.repo \ dnf clean all dnf makecache done脚本逻辑很简单scp 推文件ssh 过去做禁用官方源、清缓存、建缓存三连。第一次 makecache 是串行的客户端越多越慢三十台机器同一秒打过来 httpd 的瞬时压力不小更稳的做法是先推文件和禁用配置makecache 让各机器错峰跑或者用 cron 把时间分散到五分钟窗口内。如果团队用 Ansible整个循环对应 copy 加 command 两个模块思路完全一样无非是把循环交给它去并行。5. 避坑与排查搭 Rocky 9 局域网源最容易翻车的 5 个地方5.1 客户端报 unexpected status 502 bad gateway先查反代再查并发现象客户端 dnf makecache 或 dnf install 时偶发 “unexpected status 502 bad gateway: unknown error”有时伴随连接被重置重试几次又恢复正常。原因这个报错最常见的是仓库前面套了 nginx 反代——有人贪图统一入口用 nginx 把公网源反代到内网上游源一旦慢速响应或带宽打满nginx 就直接吐 502。另一种情况是并发太高几十台客户端同一秒触发 makecache默认配置的 httpd worker 被打满后开始拒绝新连接。解决首选方案是不套反代局域网源用 httpd 直接发布静态目录少一层代理少一类故障。如果必须用 nginx在 location 里加 proxy_read_timeout 300s 和 proxy_next_upstream error timeout容忍上游慢速响应。httpd 侧想调 MaxRequestWorkers 之前先去看 /var/log/httpd/error_log 里有没有对应警告别凭感觉调参玄学调参会把问题掩盖得更深。5.2 同步到一半磁盘写满repomd.xml 校验失败现象reposync 跑着跑着报 “no space left on device”更隐蔽的是同步虽然结束但客户端 dnf 报 “Failed to download metadata”错误指向 repomd.xml 校验失败。原因磁盘写满导致同步中断目录里留下半截文件元数据不完整reposync 重跑会跳过已存在文件半截的损坏文件不会被自动清理于是每次都在同一个地方翻车。解决同步前 df -h 确认磁盘余量至少是仓库体积的一倍一旦报磁盘错误把对应仓库目录整个删了重跑别指望增量修复。想省磁盘第一轮同步就加 -n--newest-only只保留每个包的最新版本局域网源几乎不需要历史版本这一条能把 AppStream 的体积砍掉一半左右。源端重新生成过元数据后客户端一定要 dnf clean all旧缓存里的校验和会让客户端来回报错。5.3 装包时报 Public key 未安装gpgkey 路径写错或密钥混乱现象dnf install 走到中途提示 “Public key for xxx.rpm is not installed”事务直接失败下载好的包缓存起来不进入安装。原因配置文件 gpgcheck1 但 gpgkey 行缺失或路径写错另一类是这台机器之前导入过别的密钥本地缓存里的密钥指纹和 Rocky 9 官方公钥对不上dnf 拒绝信任新包。解决先确认 repo 文件每段都有 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9且客户端这个文件真实存在。如果密钥确实混乱先 rpm -qa gpg-pubkey 列出已导入的公钥记下可疑的完整名字用 rpm -e gpg-pubkey-完整名字 删掉再 rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 重新导入最后 dnf clean all 重试。就算在封闭内网我也建议保持 gpgcheck1局域网源一旦被误放进来源不明的 rpm这把签名钥匙是最后一道防线。5.4 目录返回 403 ForbiddenSELinux 文件上下文没配现象服务端自己 curl http://127.0.0.1/rocky9/baseos/repodata/repomd.xml 返回 403但 ls 看文件权限完全正常属主也对。原因Rocky 9 默认 SELinux 强制模式httpd 只能读 scontext 为 httpd_sys_content_t 的文件。仓库放在 /var/www/html 下时默认就是这种类型一旦把仓库放到独立挂载点或自定义目录新目录会被标记成 default_thttpd 没权限读就统一表现为 403。解决最省事是把仓库规划在 /var/www/html 下目录已经定死的情况下用 semanage 声明类型再 restorecon 刷新dnf install -y policycoreutils-python-utils semanage fcontext -a -t httpd_sys_content_t /data/rocky9(/.*)? restorecon -Rv /data/rocky9执行完再 curl 一次返回 200 就说明类型生效了。这条坑我踩了两次才记住排错顺序应该是先确认 SELinux再怀疑权限顺序反了会白折腾半天。5.5 AppStream 模块包永远装不上module_hotfixes1 是后悔药现象客户端 dnf install php 时提示包不可用或版本冲突但服务端目录里明明摆着 php 的 rpm 文件手动指定 rpm 路径又能装上。原因AppStream 仓库带着模块元数据 modules.yaml同步下来的包按模块流组织。客户端 repo 文件里没有 module_hotfixes1 时dnf 的模块解析器认为这些包和已有模块存在冲突直接把仓库里的包忽略了表现就是“目录里有文件但 dnf 说没有”。解决在 appstream 段补上 module_hotfixes1dnf clean all 后重试同时确认服务端同步时带了 --download-metadata否则本地根本没有 modules.yaml这个参数也无力回天。这条是我回给别人最多的一条经验写 repo 模板时直接默认带上比踩了再查省时间。6. 增量更新与自动化把局域网源养成长期服务6.1 cron 里跑增量同步reposync -n 配合 createrepo_c --update源架好只是开始跟上上游版本节奏才是常态。我把同步写成脚本 /usr/local/bin/rocky-repo-sync.sh执行 chmod x 后放入 /etc/cron.d/rocky-repo 按周跑#!/bin/bash reposync --repoidbaseos -p /var/www/html/rocky9 --download-metadata -a x86_64 -n reposync --repoidappstream -p /var/www/html/rocky9 --download-metadata -a x86_64 -n createrepo_c --update /var/www/html/rocky9/baseos createrepo_c --update /var/www/html/rocky9/appstream-n 只拉新增版本--update 只重扫变化文件整套增量跑下来通常几分钟。定时任务用 flock 锁防止手动触发和定时任务重合日志重定向到固定文件出问题时有据可查。6.2 每月的体检三连我每个月在源上做三件事本机和客户端分别 curl -I repomd.xml 确认 200df -h 看磁盘余量AppStream 增长很快余量低于 20% 就该清理或扩容找台客户端 dnf repolist 对照包数量数量没涨说明定时同步大概率挂了去翻日志里 cron 的执行记录。6.3 一个源服务多个发行版同一台 httpd 可以按目录分发行版/var/www/html/rocky9 放 Rocky旁边再放 CentOS 7 的 BaseOS 和 Updates各自客户端配各自的 baseurl 就行。CentOS 7 配置本地 yum 源的老经验在这里大半还能复用只是命令体系还是 yumreposync 用它对应的仓库 id 同步。这套东西带给我最大的习惯是源服务器上每次改动防火墙、SELinux 或目录结构顺手留下一条 curl -I 的自检命令记录。后来公司在公网源 502 的那个晚上全靠这条命令让全组十分钟内切到内网源恢复部署那一刻觉得搭源花的时间都值了。希望帮到你。本文还有配套的精品资源点击获取