news 2026/10/11 14:29:41

内网离线环境用DNF仓库+NFS共享实现多节点软件统一交付

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
内网离线环境用DNF仓库+NFS共享实现多节点软件统一交付

搞运维的人迟早会遇到这样一个场景:内网里的机器不能访问外网,或者只有少数几台机器有外网权限;项目上线前需要在一批服务器上安装同一批软件包,而且版本必须完全一致。手动拷rpm一个个装,装一个报一个依赖缺失,眼泪都能给你装出来。把DNF仓库和NFS共享服务组合起来,就是为了根治这类问题。这篇文章我用自己的实操记录,把从设计到落地的完整过程拆给你看,适合正在做内网源、离线交付或者多节点环境标准化的运维和交付人员参考。

1. 动手之前的方案设计:DNF仓库和NFS为什么会绑定在一起

1.1 先看看现实中到底卡在哪里

在内网环境里,最常见的软件安装麻烦有三个。

第一是没外网。机器装好了,dnf install一执行就超时,因为默认源指向公网,但网络根本不通。于是大家回到石器时代,用U盘拷rpm包,手动rpm -ivh,完全没考虑依赖关系。结果就是装一个vim缺lib,装lib又缺另一个lib,半小时装不上一个编辑器。

第二是版本不一致。就算你把rpm包拷贝到每台机器上,时间一长,有人手动升了级,有人没升,环境差异性越来越大。应用到这些机器上之后,行为不一致,“在我机器上能跑,在你这跑不了”的现象到处冒头。

第三是重复同步。如果统一通过一台机器下载软件包,再用rsync往各个节点推,包是能到,但每台机器都得维护一份完整的本地缓存。10台机器就是10份冗余,占空间不说,更新时还要逐个处理,非常痛苦。

1.2 DNF仓库负责“索引”,NFS负责“分发”

要理解这个组合,先得看清楚各自扮演什么角色。

DNF仓库的本质,是软件包加上一份元数据索引(repodata)。dnf这类的包管理工具之所以强大,关键在于依赖解析,而依赖解析依赖的就是这份索引,索引记录了每个rpm包的名字、版本、依赖关系和文件列表。但仓库本身不解决“多台机器如何同时访问”的问题。

NFS共享服务的本质,是把服务器上的一个目录通过网络挂载到客户端本地。挂载完成以后,客户端看到的就是一个本地目录,可以直接ls、cd、读文件,不需要额外下载整个目录。

所以一个很自然的方案就出来了:**在一台服务器上维护好DNF仓库的数据,通过NFS把这个目录共享出去。客户端挂载NFS之后,再用file:///挂载点作为DNF仓库的baseurl。**这样客户端不用自己下载rpm包,也能获得完整的依赖解析能力。服务器上数据一更新,所有客户端立刻就能看到。

1.3 为什么优先考虑NFS而不是HTTP

很多人会问:仓库共享用HTTP不也一样吗?确实,HTTP也是常规套路,而且跨网段访问更友好。但在内网场景,NFS有它不可替代的优势。

NFS挂载后客户端能直接浏览目录,这对运维排障很有帮助。仓库里到底有哪些包、某个rpm在某目录下是否存在,直接ls就能确认,而HTTP方式只能通过网页或客户端去猜测路径。另外在部署初期,如果发现某个rpm缺了,运维人员可以直接在服务器上往目录里丢包,然后重新生成元数据,客户端刷新缓存即刻生效,不需要等待Web服务的路径映射和缓存刷新。

还有一点,NFS的权限体系比较直观。你通过exports规则控制某个网段是否能访问、是否可写,客户端root默认会被压制为匿名用户,安全模型比裸HTTP更可控。而HTTP一旦配上目录,所有人只要有URL就能读。

也有反过来的场景。如果客户端和服务器跨机房、跨运营商,链路不稳定,NFS对延迟和丢包比HTTP敏感,这时候就该用HTTP仓库。我在下文排查部分会专门展开怎么切换。

1.4 目录结构和容量要做多少预算

我建议按照仓库的层次组织目录。以RPM系发行版为例,你的仓库大概率包含基础包、额外包和更新包,那就按类别建子目录:

/data/repo/dnf/ ├── base ├── extras └── updates

为什么一定分目录?因为createrepo只能对单个目录生成一套repodata,不同类别的包如果混在一起,更新时整个元数据要重新生成,体积大、耗时长。分开之后,基础仓库除非大版本升级,基本不变;extras和updates则按需同步,每次只需要重新生成变化的那部分。

容量上给个参考值:一个基础仓库的二进制包通常几十GB,updates和extras加起来按基础仓库的1~2倍考虑都正常,再加上repodata的增量空间,准备200GB的磁盘比较稳妥。如果机器是多副本冗余,或者需要保留历史版本,这个值还得再乘倍数。

2. 核心机制与部署前准备:搞清楚原理再敲命令

2.1 DNF仓库的元数据流转过程

理解DNF仓库的元数据,是排查一切诡异问题的前提。

客户端执行dnf install时,会先扫描/etc/yum.repos.d/下的所有repo文件,找到第一个enabled=1的仓库地址,然后去下载repodata/repomd.xml。这个repomd.xml是整个元数据的“入口索引”,里面有primary、filelists、other等元数据文件的文件名、校验和和时间戳。客户端校验这些文件后,把它当作依赖解析的依据,解析出需要下载的rpm包列表,最后按需从baseurl拉包安装。

这里最关键的细节是:**客户端不是每次安装都重新拉取全部元数据,而是优先用本地缓存的元数据,通过repomd.xml里的时间戳判断是否过期。**如果你在服务器端更新了rpm包,却没有重新生成repodata,客户端无论怎么执行dnf install都看不到新包。反过来,如果你重新生成了repodata,但客户端本地缓存没有刷新,情况也一样。

所以仓库维护的动作链路是:服务器更新rpm包 -> 重新生成repodata -> 客户端dnf clean all && dnf makecache。三步缺一不可。

2.2 NFS导出参数与权限模型:root_squash是个大坑

NFS本身不负责用户认证,它信任的是IP网段。真正让新手头疼的是权限参数。

/etc/exports里每行代表一个导出规则,格式是“导出的本地目录 + 允许访问的网段 + 参数”。常碰到的参数有这些。

rw/ro代表可读写或者只读。仓库共享场景下,我建议导出为只读,避免客户端误操作。维护时如果需要客户端写,可以临时改成rw,维护完再改回来。

sync/async代表写入模式。sync保证数据写入磁盘后才返回,安全但慢;async允许先写入内存即返回,性能好但容易丢数据。仓库这种低频写、高频读的场景,sync就够了,没必要冒险。

root_squash和no_root_squash是权限坑。默认情况下,NFS会开启root_squash,意思是即使客户端以root身份访问,NFS服务端也把身份降级成一个匿名用户(通常叫nobody),这样客户端root没法以root权限操作共享目录。这在普通共享场景是安全特性,但仓库维护时你经常需要在服务器本机以root操作目录,服务端没问题;如果是客户端想临时执行createrepo --update之类的操作,就麻烦了,Permission denied没商量。

我建议的场景是:日常共享用ro,root_squash。如果因为流程原因必须在客户端执行写操作,就单独开一个维护入口,用rw,no_root_squash,并且限定具体IP。

no_subtree_check是性能选项,取消子树检查,减少内核开销,大目录场景建议加上。

2.3 环境准备、网络规划与防火墙端口

本文的实操基于RPM体系的企业级Linux发行版做示例,内核版本3.10以上基本没问题。涉及的核心工具主要有:

  • dnf:RPM系发行版的标准包管理工具,自带。
  • createrepo:用于生成repodata元数据的工具,也可以用性能更好的createrepo_c。
  • nfs-utils:包含NFS服务端和客户端所需组件。
  • rpcbind:NFS依赖的RPC端口映射服务,没有它NFS根本起不来。

我用这个网络配置举例:服务器IP是192.168.10.10,客户端分别为192.168.10.20和192.168.10.21。仓库根目录统一放在/data/repo/dnf/。

NFS服务本身依赖多个RPC端口。默认情况下,这些端口不是固定的,会在服务启动时随机分配,这对防火墙很不友好。所以正式部署前,建议先把NFS相关端口固定下来,方便放行。

各服务端口规划参考下面这张表:

服务组件用途默认端口
rpcbindRPC端口映射,必开111
nfs-serverNFS主服务2049
mountd处理挂载请求可固定为4001
statd网络锁管理可固定为4002
lockd文件锁管理可固定为4003

固定端口的方法是在NFS服务的配置文件中指定具体数值,不同的发行版位置略有差异,但配置内容思路一致:把MOUNTD_PORT、STATD_PORT、LOCKD_TCPPORT、LOCKD_UDPPORT等变量修改为你规划的端口号。修改之后必须重启NFS服务,并重新导出。

防火墙放行时,除了111和2049,还要把上面固定好的端口放行。放行范围不要用0.0.0.0/0,只放客户端所在的内网网段。

3. 完整实操记录:从一台空服务器到客户端能正常装包

3.1 在服务器端创建仓库目录并填充rpm包

登录服务器,先检查工具是否就绪。如果没有createrepo和nfs-utils,直接安装:

dnf install -y createrepo nfs-utils

创建目录结构:

mkdir -p /data/repo/dnf/{base,extras,updates}

如果你有一台能访问外网的机器,可以使用dnf reposync一次性把某个远端仓库的数据镜像到本地:

dnf reposync --repoid=base --download-metadata --destdir=/data/repo/dnf/base

这里的--repoid指定仓库ID,--download-metadata会顺带把远端元数据拉下来,--destdir是本地存放目录。执行前先用dnf repolist确认远端仓库ID对应的名字。

如果没有外网访问条件,那就从安装介质或者另一台机器拷贝rpm包过来。注意要拷贝完整的rpm二进制包,不要拷半截文件,否则后续生成元数据时会出现校验失败或解析出空包列表的问题。

3.2 用createrepo生成与更新元数据

包放好之后,生成元数据是这个方案能不能落地的最关键一步:

createrepo -v /data/repo/dnf/base

-v参数会输出详细过程,方便你观察是否每个rpm包都成功解析。执行完成后,/data/repo/dnf/base/repodata/目录会出现,里面包含repomd.xml和几个以.xml.gz结尾的元数据文件。

之后每次加入新rpm包,不需要重新全量生成,用--update参数增量更新即可:

createrepo --update /data/repo/dnf/base

--update只检查目录中新增和变化的rpm包,生成速度快很多。但要注意,如果你的仓库是从dnf reposync命令直接带元数据镜像回来的,那本身就已经包含了repodata目录,可以直接使用,不必重新生成。只有当你手动添加或修改了rpm包之后,才需要跑一次createrepo --update。

3.3 配置并启动NFS服务

编辑/etc/exports文件,把仓库目录导出给整个内网网段:

/data/repo/dnf 192.168.10.0/24(ro,sync,no_subtree_check,root_squash)

日常只读,加root_squash保护。如果你确定某台维护机需要写权限,可以临时追加一行具体IP加rw的规则,不要直接对全网段开写。

启动服务并让导出生效:

systemctl enable --now rpcbind systemctl enable --now nfs-server exportfs -arv showmount -e 192.168.10.10

exportfs -a让所有导出条目生效,-r重新导出,-v显示详细状态。showmount -e用来验证服务器端导出的目录是否已经可见。执行后如果列出了/data/repo/dnf,说明NFS服务端已经准备好了。

3.4 客户端挂载NFS并配置DNF源

到客户端机器上,同样先装nfs-utils:

dnf install -y nfs-utils

创建挂载点并测试挂载:

mkdir -p /mnt/repo mount -t nfs 192.168.10.10:/data/repo/dnf /mnt/repo

挂载成功后,可以实际操作感受一下,ls /mnt/repo/base/能直接列出服务器上的rpm包,说明NFS链路已经通了。

接下来写入/etc/fstab,实现开机自动挂载:

192.168.10.10:/data/repo/dnf /mnt/repo nfs defaults,ro,_netdev 0 0

_netdev这个参数很关键,它告诉系统在网络上这一层就绪之后再挂载这个NFS目录,否则开机时网络配置还没起来,挂载就会失败。如果你用了网络管理服务来管理网卡,建议配合remote-fs.target确保网络真正可用后再挂载。

然后创建DNF源配置文件/etc/yum.repos.d/local.repo:

[local-base] name=Internal Base Repository baseurl=file:///mnt/repo/base enabled=1 gpgcheck=0

这里要说明一下:NFS挂载后,/mnt/repo就是一个普通本地目录路径,所以baseurl直接用file://接本机路径即可,不需要专门的“NFS协议地址”。多套仓库之间不要混用,一个[仓库ID]对应一个独立目录。

3.5 验证安装与缓存刷新机制

在客户端执行:

dnf clean all dnf makecache dnf install -y tree rpm -q tree

dnf clean all清掉本地旧缓存,dnf makecache重新拉取服务器端元数据建立缓存。如果这两步都能成功执行,并且rpm -q tree能看到已安装版本,说明客户端已经从NFS共享的仓库中完成了安装,整套链路跑通了。

换一台新客户端重复同样的操作,如果也能正常安装,并看到完全一致的软件包版本,说明多节点统一交付的目标已经达成。建议此时用另一台客户端对比一下rpm -q tree的输出结果,版本号一字不差才算成功。

3.6 首轮makecache容易忽略的一个小细节

第一次在客户端执行dnf makecache时会发现一个现象:明明只配置了local-base一个源,但输出里可能还出现系统自带的仓库,或者提示某些仓库元数据失效。这是因为官方默认配置文件里的repo文件仍然存在,只是状态是enabled=1的引擎会去公网拉取元数据,然后卡住或超时。

遇到这种情况不用慌,也不要在local.repo里纠结。直接把其他仓库文件在配置里加一行enabled=0,或者把那些后缀为.repo的文件移出目录。简单粗暴但管用。这个动作要趁早做,否则内网环境下每次执行dnf命令都卡在那十来秒超时重试上,体验非常差。

4. 生产环境排查手册:这些坑我基本都踩过

4.1 元数据过期导致的“明明有包却装不上”

现象是最迷惑人的:服务器/data/repo/dnf/base/目录里明明有某个rpm包,但客户端执行dnf install时却提示没有这个包。

按这个顺序排查:

  1. 服务器上检查rpm包是否完整放入目录。
  2. 服务器上执行ls /data/repo/dnf/base/repodata/repomd.xml,确认repodata存在。
  3. 检查repomd.xml的时间戳。如果你放弃一个包很久才想到要同步,很可能一直没跑createrepo --update。
  4. 客户端执行dnf clean all && dnf makecache,强制刷新缓存。

绝大多数情况是第3步没做。记住一句话:目录里放了包只是第一步,repodata里不索引等于白放。

4.2 NFS权限和文件属主显示异常

客户端挂载后,ls -l看到的文件属主全是nobody,或者以root执行操作时报Permission denied,这就是root_squash在生效。

默认root_squash模式下,任何客户端root操作都会被降级为匿名用户,普通读取没影响,写操作直接就拒了。如果你的维护流程确实需要在客户端对仓库目录做写操作,只能临时在exports里对这个客户端IP开rw,no_root_squash,然后执行:

exportfs -arv

客户端上再重新挂载一次,注意要umount后再mount,让新的导出参数生效。

还有一类情况:客户端和服务器的账号体系不同,uid不一致。NFS权限判断完全看数字uid,不看用户名。服务器上uid=1000的用户导出的文件,客户端同一uid可能是另一个用户,ls -l显示的用户名自然就不对。这通常只影响显示和访问控制,不影响读取。如果要在NFS上做写入控制,统一账号uid才是正解。

4.3 挂载超时和端口不通

症状是客户端执行mount -t nfs时没有任何反应,最后超时;或showmount -e是好的,但挂载一直失败。这大概率是防火墙拦了mountd服务。

NFS的常规端口111和2049被大多数人记住了,但mountd、statd这些辅助服务的端口默认是随机分配的,防火墙根本不知道要放行哪一个。解决方式仍然是固定端口再放行,具体做法在前面环境准备部分已经说了。

固定端口并重启NFS服务后,用ss -lntp检查端口是否已经监听,确认端口都在规划范围内,然后再去客户端重试挂载。

4.4 NFS vs HTTP:什么情况下必须切换仓库形态

NFS好用,但并非万能。如果你遇到以下任一情况,建议果断切HTTP仓库:

  • 客户端与服务器跨网段跳数多,NFS挂载后操作卡顿明显。
  • 有大量客户端同时刷新缓存,NFS性能撑不住。
  • 客户端环境不可控,有人挂着共享目录长时间不释放文件句柄,NFS服务端可能需要强制重启才能恢复。

切换成本其实很低。服务器端只需要额外装一个Web服务,把/data/repo/dnf目录作为Web根目录或虚拟目录发布出去。客户端repo文件把baseurl=file:///mnt/repo/base改成baseurl=http://192.168.10.10/dnf/base,然后dnf clean all && dnf makecache,链路就切换过去了。

我实际维护的几个项目里,NFS仓库和HTTP仓库同时存在:小规模内网机器直接用NFS,省心;跨地域的交付环境用HTTP,稳定。

4.5 多客户端并发场景下的性能策略

几十台客户端同时执行dnf makecache的瞬间,服务器磁盘I/O很容易扛不住。元数据文件不大,但同一时间几十个请求同时读,机械盘和低配云盘都会掉链子。

几个缓解手段按优先级排序:

  • 客户端把metadata_expire设大一点,比如86400秒,减少反复拉元数据的频率。
  • 客户端开启keepcache=1,rpm包缓存保留在本地,重复安装不用反复读NFS。
  • 仓库服务器使用SSD或NVMe盘,这是最直接的性能提升。
  • 如果客户端实在太多,不要全部挤一个NFS出口,在部分机器上做本地同步,NFS只做更新源的“母版”。

5. 稳定运行后的维护与扩展

5.1 增量同步与自动化:避免仓库越跑越乱

仓库建好只是开始,日常维护才是重头。从外网同步过来的仓库需要定期刷新,我维护的某项目用的是reposync加createrepo --update组合。

先同步:

dnf reposync --repoid=base --download-metadata --destdir=/data/repo/dnf/base

再更新元数据:

createrepo --update /data/repo/dnf/base

这两条命令很适合放进定时任务。比如每天凌晨2点执行一次,避免业务高峰期间同步影响使用。脚本里还要注意日志记录,方便回溯。

5.2 多副本冗余:防止单点故障

NFS仓库最怕的就是服务器磁盘坏了或者系统崩了,一旦这块出问题,所有依赖该源的客户端全部瘫痪。

最省事的做法是用rsync把仓库根目录定时同步到另一台备用服务器,备用机上同样启动NFS服务。客户端如果配了多个baseurl,可以把备用服务器地址加在后面。dnf在第一个源不可用时,会自动尝试下一个源,这个特性用来做高可用非常顺手。

同步时要注意,repodata和rpm包必须一起同步,不能只同步rpm包。否则备用服务器上目录有包,但元数据不匹配,照样没法用。

5.3 仓库安全加固与签名验证

内网环境虽然相对封闭,但该有的安全意识不能丢。gpgcheck=1不是摆设,它可以防止rpm包被篡改后混入安装流程。

服务器端生成GPG密钥对,把公钥分发到所有客户端的RPM数据库里。客户端执行:

rpm --import /path/to/RPM-GPG-KEY

然后repo文件设置gpgcheck=1。这样每次安装包时都会校验签名,任何未签名的包都会拒绝安装。配置签名后,新增rpm包时不要忘了用签名工具对包签名,否则客户端安装时会报校验失败。

仓库目录本身也建议只对运维账户开放写权限,不要为了图省事把所有用户都丢进同一组里。

这套方案我前后维护过好几套环境,踩过的坑基本都是权限、缓存和端口这三类问题。尤其是那些看起来不起眼的小选择,比如本地缓存没刷新、root_squash权限没放开、mountd随机端口没固定,往往就是故障主因。

仓库更新后的习惯动作,我个人强烈建议固定为:服务器跑createrepo --update、客户端跑dnf clean all && dnf makecache,两步缺一不可。把这个动作固化成脚本,能省掉后续大把排查时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 14:29:35

为什么Stack-chan的MOD更新这么快?Host/MOD分离式固件架构深度解析

嵌入式物联网智能硬件机器人硬件开发前端AI 应用 【免费下载链接】stack-chan A JavaScript-driven M5Stack-embedded super-kawaii robot. 项目地址: https://gitcode.com/gh_mirrors/sta/stack-chan 点击查看 免费下载 Stack-chan 是一款基于 M5Stack 硬件、用 J…

作者头像 李华
网站建设 2026/10/11 14:25:18

SpringBoot+Vue实战:校园活动管理系统从需求梳理到部署上线

每年开学季,社团招新、讲座报名、比赛登记这种事情总会把学生会的同学折腾得够呛。海报贴一墙、Excel传一圈、现场签到全靠纸质名单,最后统计人数还得人工数。做一套基于SpringBoot Vue的校园活动管理系统,就是把这些琐碎流程线上化&#xf…

作者头像 李华
网站建设 2026/10/11 14:24:16

OpenGL 4.5+C++复刻我的世界:图形管线与体素渲染实战

简介:这是一份基于OpenGL与C实现的《我的世界》风格方块化3D沙盒游戏源码工程,面向具备C基础和图形编程入门经验的开发者,用于学习现代OpenGL渲染管线、Voxel引擎架构与实时交互逻辑设计。资源共429个文件,包含15个可执行程序&…

作者头像 李华
网站建设 2026/10/11 14:22:54

面对信息缺失的项目:从rea案例拆解命名规范与逆向工程

1. 当标题只剩三个字母:一次“信息真空”下的项目复盘“rea”这个标题,第一次看到的人大概率会愣一下。三个小写字母,没有上下文,没有正文,没有关键词,连摘要都是空的。放在任何项目列表里,它都…

作者头像 李华