干运维这些年,最烦的事情之一就是给新服务器装软件。外网源速度不稳定,有时候一个 dnf install 卡上半小时,还经常遇到连接超时。后来我彻底忍不了了,在内网搭了一套自用的 DNF 软件仓库,把常用的 RPM 包集中同步到一台服务器上,再用 NFS 把仓库目录共享给整个内网。这套 DNF 仓库 + NFS 共享的方案,我从前几年就开始用,从 CentOS 7 一路用到 Rocky Linux 9,还帮同事在麒麟V10的离线环境里复刻过整套逻辑,稳定性和便利性都经过验证。这篇文章把这套方案的架构设计、部署步骤、参数细节和排查经验全部梳理出来,给同样被外网源折腾的运维伙伴一个可以照抄的作业。
1. 项目背景与整体思路拆解
1.1 为什么要自建 DNF 仓库
实际工作中,自建软件仓库的核心驱动力永远是这几个:慢、断、不可控。
慢很好理解。外网 dnf 源连接速度不稳定,特别在业务高峰期,一个几百 MB 的包可能要下很久。而同一机房内网 NFS 读文件的速度可以达到几百 MB 每秒,这个差距是数量级的。
断指的是连接中断。外网源偶尔抽风,或者机房网络策略调整,都会导致正在进行的安装任务直接失败。如果你在批量初始化几十台服务器,遇到一次中断,整个任务就得从头再来。
不可控更关键。外网仓库的内容随时在变,今天装的和下周装的可能是不同版本。对于生产环境来说,版本一致性是审计和排障的基础。自建仓库可以把某个时间点的软件版本固定下来,将来无论什么时候装,都是同一批包,排障难度会低很多。
做 Java 开发的朋友对这个场景应该特别有共鸣——就像团队内部搭了 maven 私服,开发同学不再每次去中央仓库碰运气,而是优先从内网拉依赖,速度和稳定性都稳了。Linux 的 DNF 仓库也是同一个逻辑。
1.2 为什么共享层选 NFS 而不是 HTTP
这是我在设计初期纠结最久的一个点。分发软件仓库有很多种方式,最常见的就是 HTTP 和 NFS,还有少数场景用 FTP 或 SMB。我最终选了 NFS,主要原因有三个。
第一个原因是操作路径自然。仓库维护需要定期往目录里写包、更新元数据,这些操作在仓库服务器本地做就行;客户端挂载 NFS 之后,看到的就是和本地目录一样的路径,不需要通过 Web 服务器做任何映射。repo 文件里 baseurl 直接写 file:///mnt/repos/... 就能用,逻辑上非常顺。
第二个原因是内网性能好。NFS 在局域网内的读写速度很可观,而且默认支持内核级缓存。客户端反复执行 dnf makecache、dnf search 的时候,体验明显比 HTTP 好。
第三个原因是分发方式可以平滑迁移。仓库目录本身是死的,NFS 只是挂载方式之一。将来如果客户端从几十台涨到几百台,我可以把目录再暴露成 HTTP,客户端 repo 文件的 baseurl 改一个前缀就能切换,仓库结构和元数据完全不用动。这个灵活性非常重要。
当然 NFS 也有它的边界。跨网段、跨机房或者客户端数量规模很大的场景,NFS 就不那么合适了,这时候老老实实上 Nginx。所以选择不是绝对的,看你的网络环境。
1.3 整体架构与组件选型
整套方案的架构可以用一句话概括:一台仓库服务器负责同步 RPM 包、生成元数据,通过 NFS 把整个仓库目录共享出去;客户端挂载共享目录后,把 baseurl 指向本地挂载点,dnf 就从内网仓库装包。
用表格把组件列清楚:
| 角色 | 核心软件 | 职责 |
|---|---|---|
| 仓库同步 | dnf-plugins-core(reposync) | 从上游源拉取 RPM 包和元数据 |
| 元数据生成 | createrepo_c | 扫描 RPM 目录,生成 repodata |
| 共享服务 | nfs-server(nfs-utils) | 导出仓库目录 |
| 客户端挂载 | nfs-utils | 挂载 NFS 共享到本地 |
| 客户端包管理 | dnf/yum | 读取仓库配置并安装软件 |
其中仓库同步和元数据生成是最核心的两个环节,后面我会分成两节专门讲细节。NFS 主要负责“把仓库送到客户端面前”,配置看起来简单,但坑不少,也单独开一节。
2. 环境准备与基础配置
2.1 服务器与客户端规划
我先说一下我实际用的规划,大家照抄也好、微调也好都方便。
仓库服务器我建议独立一台,系统用 Rocky Linux 9 minimal 就够了。关键点在于磁盘:仓库目录一定要放在独立数据盘上,而不是系统盘。为什么?因为 RPM 包体量很大,BaseOS 加 AppStream 全量同步下来轻松上百 G,而且还要持续增长。系统盘如果被仓库数据塞满,系统本身就会出各种奇怪问题。我的服务器上挂了一块 200 G 的数据盘,挂载到 /data。
客户端就是公司内网同一网段的其他机器。为了演示方便,我用的是 172.16.140.0/24 这个网段,仓库服务器 IP 是 172.16.140.200,客户端 IP 比如 172.16.140.11。
目录规划如下:
/data/repos/ ├── rocky9/ │ ├── BaseOS/ │ └── AppStream/ ├── centos7/ │ ├── os/ │ └── updates/ └── kylinv10/ ├── os/ └── updates/按系统版本分一级目录,按仓库类型分二级目录,后期如果要加架构维度,就在版本目录下再加 x86_64、aarch64 层。这个规范越早定越好,往后包多了再调整目录结构会非常痛苦。
2.2 服务端基础软件安装
仓库服务器需要装的包有三个:nfs-utils、createrepo_c、dnf-plugins-core。
dnf install -y nfs-utils createrepo_c dnf-plugins-core为什么是 createrepo_c 而不是老的 createrepo?因为 createrepo_c 是 C 语言实现,扫描大量 RPM 文件时性能领先很多,尤其是仓库从几百个包涨到几千个包之后,差距非常明显。
如果你的环境是完全离线的,比如银河麒麟 V10 这类受限网络场景,需要提前在一台能联网的机器上把包下载下来:
dnf install --downloadonly --downloaddir=/tmp/pkgs nfs-utils createrepo_c dnf-plugins-core然后把 /tmp/pkgs 整个目录拷到 U 盘,到目标机器上执行:
rpm -Uvh /tmp/pkgs/*.rpm离线安装要注意依赖顺序,rpm 会自动检查,如果报缺失依赖,就回到联网机器上把提示的依赖包也下下来。这个步骤看起来笨,但在没有外网的环境里是唯一办法。
2.3 SELinux、防火墙等基础项
SELinux 是 NFS 场景经常踩坑的地方。我的建议是:能理解 SELinux 就按规范配,不想深究就先确认它不会拦截 NFS 服务。具体来说,两个布尔值比较关键:
setsebool -P nfs_export_all_rw 1 setsebool -P nfs_export_all_ro