从标题出发,这篇博文我打算这么写:先讲为什么要把DNF仓库和NFS放一起做,然后分别拆解本地仓库搭建和NFS服务部署,再讲客户端挂载和权限细节,最后把最常见的几个报错拿来做排查实录。这样一套下来,无论你是刚接触Linux的运维新人,还是想给内网做离线软件源的老手,都能找到可以直接抄的配置。
从头捋一遍:为什么要同时搞DNF仓库和NFS共享
做Linux运维这几年,我越来越觉得"软件源"和"共享存储"这两件事是绕不开的。DNF是Fedora、RHEL、CentOS Stream、Rocky、AlmaLinux这些发行版的默认包管理器,它的核心工作机制就是从一个或多个"仓库"里拉取rpm包、解析依赖、完成安装升级。而NFS是Linux/Unix世界里最经典的网络文件系统协议,作用是把一台机器上的目录通过网络共享给其他机器用,客户端挂载之后就跟操作本地磁盘一样。
把这两者放一起讲,是因为在实际生产环境里它们经常是配套出现的。最典型的场景是:内网有一批机器不能访问外网,但你不想每台机器都手动rpm -ivh去装包,那就在一台服务器上搭一个DNF本地仓库(本质就是个HTTP或文件目录形式的软件源),再用NFS把这个仓库目录共享出去,客户端通过NFS挂载或直接配置仓库地址来装软件。这套组合下来,几十台机器的软件分发效率会高非常多。
这篇指南适合谁?一是刚接触Linux运维、想搞懂"仓库"到底是个什么东西的入门者;二是被内网离线安装折磨过、想搭建自己的软件源和共享目录的工程师。我会尽量把每一步的"为什么"也讲清楚,而不是丢一堆命令让你复制完就完事。
1. 核心思路拆解:仓库共享方案为什么这么搭
1.1 先搞明白DNF仓库的工作机制
DNF(Dandified YUM)是YUM的下一代版本,依赖管理能力更强,性能也更好。它的仓库本质上就是一个带着元数据(repodata目录)的rpm包集合。元数据里包含每个包的名字、版本、依赖关系、文件清单等信息,DNF客户端通过读取这些元数据来构建依赖树,然后决定下载哪些包、按什么顺序装。
打个比方:DNF仓库就像一个图书馆,rpm包是书,repodata目录就是图书馆的检索卡片。你告诉DNF"我要装nginx",它就先去翻检索卡片,找到nginx这本书以及它引用的其他书,然后一次性把你需要的所有书都搬回家。所以搭建本地仓库的核心工作,就是准备一个rpm包目录,然后生成正确的repodata。
在离线或内网场景下,你需要在一台能联网的机器上把需要的rpm包下载好,搬进内网。这里有两个思路:一是逐个下载指定rpm及其依赖,用dnf download --resolve这样的命令;二是直接把整个发行版的ISO文件解压或挂载,把里面的Packages目录作为仓库。后者的好处是包全,文件系统里该有的基础组件基本齐了。
1.2 NFS在方案里扮演的角色
NFS在这个组合里解决的是"仓库目录怎么让其他机器访问"的问题。DNF仓库的访问方式有很多种:本地file路径、HTTP、FTP、NFS。HTTP是最通用的,但需要额外装nginx或httpd来跑;NFS则不需要Web服务,直接把目录挂给客户端就行,性能也不错。
更重要的是,NFS不仅仅是给DNF仓库用。在内网环境里,很多场景都需要共享存储:比如多台Web服务器共享一套静态文件、开发环境里共享编译产物、备份机把数据目录挂给其他机器做异地读取。所以把NFS的技能点起来,属于一次性投资长期受益。我在实际项目中就用NFS同时完成了软件源共享、日志收集目录共享、静态资源分发三件事,一台服务器搞定。
1.3 方案选型:为什么不用其他组合
有人可能会问,仓库共享为什么不用HTTP而选NFS?这是个好问题。HTTP做软件源也很常见,尤其是用nginx托管repodata,客户端把baseurl配成http://server/centos/这种形式。但HTTP方案需要在服务器上装Web服务端、配置虚拟目录、处理SELinux对httpd读写目录的权限,链路更长。而NFS方案的配置链路短,客户端直接把远程目录挂到本地某个路径,然后DNF仓库的baseurl用file://协议指向挂载点就行,走的是本地文件协议,不涉及网络下载这一层,少一层协议就少一层排查点。
NFS的另一个优势是所有客户端看到的是同一个文件系统视图,你在服务器上更新了仓库目录里的rpm包,所有挂载了该目录的客户端立刻就能看到。HTTP方案还需要保证缓存一致性问题,虽然实际上也不难,但NFS这种方式更省心。当然,如果仓库要跨公网或者跨网段大量并发访问,HTTP+缓存会更合适,但那就是另一个话题了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DNF本地仓库搭建:从下载rpm到生成repodata
2.1 准备基础环境与必要工具
我以Rocky Linux 9.3为例来演示,这套流程在RHEL 9系、AlmaLinux 9上完全通用。第一件事是确认当前系统的发行版和架构,避免下载错rpm包:
bash复制cat /etc/os-release
uname -m
uname -m输出x86_64还是aarch64直接决定了你要下载哪个架构的包。很多人在这步栽过跟头,x86_64的包往aarch64机器上装,那肯定是装不上的。
接下来需要安装两个关键工具:createrepo_c用于生成仓库元数据,dnf-utils(或者dnf-plugins-core)里的dnf download插件用于下载rpm包。在还能联网的阶段先把工具装好:
bash复制dnf install -y createrepo_c dnf-utils
如果系统里已经装过,这条命令会提示"Nothing to do",不用担心。
2.2 下载rpm包并生成仓库元数据
假设我要做一个包含nginx、vim、git、tree这些常用工具的离线仓库。先建一个目录,用dnf download把包和它们的所有依赖都拉下来:
bash复制mkdir -p /data/repo/rocky9
cd /data/repo/rocky9
dnf download --resolve --alldeps --destdir=. nginx vim git tree
参数解释一下:--resolve表示同时下载依赖包,--alldeps表示把所有依赖(包括非默认弱依赖)都下载下来,--destdir指定输出目录。这个命令非常实用,它会把nginx需要的所有so库依赖对应的rpm、vim那一大堆依赖包全部下载到当前目录。
如果只想用发行版ISO做仓库,就更简单了:
bash复制mkdir -p /mnt/iso
mount -o loop,ro /path/to/Rocky-9.3-x86_64-minimal.iso /mnt/iso
cp -r /mnt/iso/Packages /data/repo/rocky9/
从ISO里拷贝Packages目录的好处是,这个目录里的包是官方精选的基础集合,完整性和签名都有保障。坏处是包可能不够新、不够全,比如某些第三方软件源(EPEL、RPM Fusion)里的包就不会有,这时还得用dnf download方式补。
包收集齐之后,下一步是生成repodata:
bash复制createrepo_c /data/repo/rocky9
执行完会在/data/repo/rocky9下生成一个repodata目录,里面是repomd.xml以及各个xml.gz数据库文件。这一步的输出信息里会显示"Workers: 4"、"Packages: xxx"之类的内容,表示正在为多少个包生成元数据。跑完之后,一个基本可用的本地仓库文件结构就齐了。
2.3 配置文件详解与优先级思考
DNF仓库的配置文件放在/etc/yum.repos.d/目录下,一个.repo文件可以定义多个仓库。我习惯单独建一个文件来放本地仓库:
bash复制vi /etc/yum.repos.d/local.repo
内容如下:
ini复制[local-repo]
name=Local Rocky Linux 9 Repository
baseurl=file:///data/repo/rocky9
enabled=1
gpgcheck=0
这里有几个关键点要展开说。baseurl用file://协议指向仓库目录的本地路径;gpgcheck=0表示不校验GPG签名,内网自建仓库通常没有独立的GPG密钥,先关掉省事。但如果你下载的rpm来自官方源且保留了官方GPG密钥,建议gpgcheck=1并配gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial,更安全。
配置完之后验证一下:
bash复制dnf clean all
dnf repolist
dnf repolist会列出所有已启用的仓库,确认local-repo在里面就算成功。再试着安装一个包测试:
bash复制dnf install -y nginx --disablerepo='*' --enablerepo=local-repo
--disablerepo='*' --enablerepo=local-repo的意思是禁用所有其他仓库、只保留本地仓库,这样能强制验证本地仓库的包完整性。如果能顺利装上,说明仓库本身没问题。
这里补充一个重要经验:如果仓库目录里后续新增了rpm包,一定要重新执行createrepo_c --update /data/repo/rocky9,--update参数会增量更新元数据而不是全部重建,速度快很多。很多新手忘了这步,客户端dnf install的时候会报"找不到这个包",就是因为元数据没刷新。
3. NFS共享服务部署:让仓库目录"活"起来
3.1 NFS协议的基本原理
NFS(Network File System)的设计哲学很简单:服务器把自己文件系统里的某个目录通过NFS协议"导出",客户端把这个远程目录挂载到自己路径下。从应用层看,客户端进程读写这个挂载点里的文件,跟读写本地磁盘没有区别,NFS协议栈负责把文件操作请求封装成RPC调用发到服务器端执行。
这里要提到两个服务:nfs-server负责文件系统的导出和请求处理,rpcbind(或rpcbind的替代)负责端口映射。NFS在早期版本里大量依赖RPC绑定服务,客户端要访问NFS服务需要先向rpcbind查询服务监听的端口。虽然NFSv4.x已经不强制需要rpcbind了,但为了兼容性,服务端还是建议把rpcbind一并启用。
NFS的版本演进很值得了解:NFSv3是经典版本,支持几乎所有场景,但安全性弱、无状态;NFSv4引入了有状态操作、锁管理集成、安全性增强,是目前生产环境的标配;NFSv4.1/4.2又加入了并行NFS(pNFS)和更强的协议操作。在较新的系统上,默认就会同时支持v4和v3,建议客户端也优先挂载v4。
3.2 服务端配置与exports文件解析
在仓库服务器上安装NFS服务端工具:
bash复制dnf install -y nfs-utils
systemctl enable --now rpcbind nfs-server
然后用exportfs命令或直接编辑/etc/exports文件来定义要共享的目录。这个文件是NFS的核心配置,我来详细解析一下写法:
bash复制/data/repo/rocky9 172.16.0.0/16(rw,sync,no_root_squash,no_all_squash)
/data/share 172.16.0.0/16(rw,sync,root_squash)
第一列是服务器上要导出的目录路径;第二列是允许访问的网段或IP,后面括号里是导出选项。我这里区分了两个共享目录:软件仓库用no_root_squash,是因为客户端挂着这个目录执行dnf时需要以root身份写缓存或临时文件,如果被root_squash压制成nobody权限会出问题;而普通共享目录/data/share则保留root_squash,这是安全默认值,客户端root会被映射成nobody,防止客户端root乱改服务器上的文件。
选项的细节说明:
rw:读写权限,只读共享就写ro,对于软件源其实ro更安全,但如果客户端需要dnf在仓库目录生成本地缓存,建议rw或者客户端把缓存目录放别处。sync:服务器在响应写请求之前把数据刷到磁盘,保证数据一致性,代价是性能稍低。默认推荐。root_squash/no_root_squash:是否把客户端root用户映射成匿名用户(nobody),root_squash是默认行为,安全;no_root_squash允许客户端root以root身份操作服务器文件,测试环境可能用到,生产慎用。
改完exports文件后,可以用exportfs -arv来重新加载配置并验证:
bash复制exportfs -arv
-a表示导出所有在/etc/exports中配置的目录,-r表示重新导出,-v在导出时显示详细信息。执行后应该能看到类似exporting 172.16.0.0/16:/data/repo/rocky9的输出。
3.3 防火墙、SELinux与服务状态检查
这一步是很多人踩坑的重灾区。RHEL系默认开启firewalld和SELinux,不处理的话NFS服务会"看起来正常但连不上"。首先是防火墙放行:
bash复制firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --permanent --add-service=mountd
firewall-cmd --reload
这三个服务对应NFS的三种通信通道:nfs服务本身(2049端口)、rpcbind(111端口)、mountd(动态端口,默认在20048附近)。如果在私有网络环境里实在不想配防火墙,可以systemctl stop firewalld直接关闭,但我不建议在生产这样做,放行服务比重启防火墙要精准得多。
SELinux方面,NFS相关的布尔值通常是允许的,但如果你把共享目录放在非标准路径(比如/data下面),可能会触发SELinux类型不匹配。可以用semanage fcontext调整,或者临时setsebool -P nfs_export_all_rw 1。实际排查时用ausearch -m avc -ts recent查看SELinux拒绝日志,定位是哪个布尔值或上下文阻止了访问。
验证服务端状态可以用这些命令:
bash复制systemctl status nfs-server
ss -tlnp | grep -E '2049|111'
showmount -e localhost
showmount -e localhost会列出本机导出的共享目录,如果这里能看到/data/repo/rocky9和/data/share,说明exports配置和服务都正常。
4. 客户端挂载与DNF仓库对接:让安装命令真正跑起来
4.1 客户端手动挂载与自动挂载配置
客户端机器同样需要安装nfs-utils:
bash复制dnf install -y nfs-utils
然后先手动挂载测试:
bash复制mkdir -p /mnt/repo
mount -t nfs 172.16.140.200:/data/repo/rocky9 /mnt/repo
挂载成功后,ls /mnt/repo应该能看到rpm包和repodata目录。如果挂载卡住或者超时,先看服务端showmount -e是否正常、到服务端的2049端口是否通(可以用telnet 172.16.140.200 2049或nc -vz测试)。
手动挂载没问题后,配置开机自动挂载,一般通过/etc/fstab实现。在fstab里加一行:
bash复制172.16.140.200:/data/repo/rocky9 /mnt/repo nfs defaults,_netdev 0 0
_netdev选项很关键,它告诉系统这个挂载依赖网络,启动时等网络就绪后再挂载。如果不加这个选项,在开机早期网络还没准备好时,挂载会失败,导致启动流程卡住甚至进入紧急模式。用systemd的环境里还可以用x-systemd.automount选项实现按需挂载,首次访问挂载点才触发网络挂载,体验更好:
bash复制172.16.140.200:/data/repo/rocky9 /mnt/repo nfs defaults,_netdev,x-systemd.automount 0 0
修改fstab之后,用mount -a测试配置是否正确,然后df -h确认挂载成功。
4.2 把DNF仓库baseurl指到NFS挂载点
这是整篇指南最核心的组合拳:之前我们用file:///data/repo/rocky9作为本地仓库路径,现在客户端把所有机器上的目录统一挂到/mnt/repo,然后修改本地仓库repo文件:
bash复制vi /etc/yum.repos.d/local.repo
内容改成:
ini复制[local-repo]
name=Local Rocky Linux 9 Repository via NFS
baseurl=file:///mnt/repo
enabled=1
gpgcheck=0
改完执行:
bash复制dnf clean all
dnf repolist
dnf install -y nginx --disablerepo='*' --enablerepo=local-repo
这次dnf读取的是NFS挂载目录下的repodata,走的是本地文件协议路径,但实际物理文件在网络另一台机器上。这比直接配baseurl=nfs://...要稳妥得多,因为dnf本身不原生支持NFS协议作为仓库URL,只能依靠文件系统挂载来间接实现。我在项目里就是这样把repo目录分发到几十台机器上的,一致性好,管理也方便。
4.3 权限模型与多客户端并发注意事项
在多客户端并发访问NFS共享仓库时,一个必须注意的问题是文件锁和缓存一致性。NFSv4支持文件锁(fcntl锁和POSIX锁),但锁是基于客户端粒度的,如果一个文件同时在多个客户端上被修改,还是会存在冲突风险。对DNF仓库这种"写少读多"的场景,这个风险不大——服务器端基本都是只读操作,但保险起见,我给仓库目录的导出选项设置的是rw,这样客户端dnf在创建yum缓存时不会有权限问题。
另一个需要注意的点是nfs挂载的SUID权限。默认NFS挂载会忽略suid位,这是安全考虑。如果你某个应用需要在共享目录上运行带suid的程序,需要显式加suid挂载参数。常规使用不需要关心。
如果需要验证多客户端下文件一致性,可以在服务器上更新仓库元数据后,在任意客户端执行ls -l /mnt/repo/repodata/,看修改时间是否及时更新。NFS的缓存刷新一般是几秒内,stat命令可以强制刷新属性缓存。
5. 常见问题排查实录:那些年踩过的NFS和DNF的坑
5.1 "nfs: server 172.16.140.200 not responding, timed out" 怎么解决
这个报错基本是所有NFS使用者的噩梦,出现频率极高。我详细记录一下排查思路。这个报错有两类场景:一是挂载时直接报超时,二是运行中报nfs: server ... not responding且timed out。
挂载时超时,优先检查网络连通性和防火墙。执行:
bash复制ping 172.16.140.200
telnet 172.16.140.200 2049
如果ping通但端口不通,十有八九是防火墙问题。在服务器端放行前面提到的三个服务,或者临时关闭firewalld测试,很快就能定位。还有一种情况是云安全组或物理交换机ACL限制了端口,这种要看底层网络策略。
如果是运行中报超时,那就更复杂一些,可能原因包括:服务器负载过高导致NFS请求处理不过来、网络抖动导致RPC重传、NFS线程池耗尽。排查时可以看服务器端负载:
bash复制uptime
cat /proc/loadavg
ss -s
如果负载很高,先解决负载问题。如果负载正常,尝试降低NFS客户端请求超时时间,在挂载参数里加timeo=50,retrans=2,这会让客户端在更短时间内重试而不是死等。例如:
bash复制mount -t nfs -o timeo=50,retrans=2,soft 172.16.140.200:/data/repo/rocky9 /mnt/repo
注意soft vs hard:默认是hard,即客户端无限期重试直到服务器响应,好处是数据一致性有保障,坏处是服务器宕机客户端进程会卡死;soft模式下超时后返回I/O错误,应用会失败但不会卡死。生产环境里跑数据库这类高一致性要求的,建议hard;跑软件源这种无状态读场景,用soft其实更合适,至少不会让整个客户端卡住。
5.2 DNF仓库打开失败/找不到包的处理思路
在客户端配置了NFS挂载的仓库后,dnf repolist能列出来,但dnf install时报"Error: Failed to download metadata for repo 'local-repo'",多数情况是元数据和实际包不匹配。可能原因:
- 服务器端的rpm包有更新,但没重新跑
createrepo_c --update。解决:在服务器上重新生成元数据。 - 客户端本地缓存了旧的repodata。解决:执行
dnf clean all再试。 - NFS挂载的目录权限有问题,客户端读不了repodata目录里的xml文件。解决:用
ls -l /mnt/repo/repodata/确认文件权限和属主,看看是否是root_squash导致nobody无法读取。
我遇到过最奇葩的一个问题是:服务器端仓库目录放在/data/repo下,而/data这个分区在导出时因为fsid冲突导致客户端挂载异常。NFS导出多个目录时,fsid需要唯一标识文件系统。如果/data和/data/repo在同一个分区上,导出时NFS对同一文件系统导出多个目录可能会产生fsid冲突,引发"mount.nfs: access denied by server"之类的错误。解决方法是给目录显式指定fsid=0或者fsid=1,例如:
bash复制/data/repo/rocky9 172.16.0.0/16(rw,sync,fsid=0)
这个坑在文档里很少提到,是我实际踩到了才查明白的。
5.3 客户端挂载后权限错乱(root变nobody)的解法
这个问题几乎是新手必踩。服务端exports里没加no_root_squash,客户端以root身份在挂载目录里写文件,结果服务器上看到文件的属主是nobody。对于DNF仓库来说,如果客户端dnf要以root身份在挂载目录下创建缓存(比如生成/mnt/repo/cache子目录),就会碰到权限拒绝。
解决路径有两条:一是在exports里对仓库目录加no_root_squash,二是注意SELinux在客户端这边的影响。RHEL系客户端如果启用了SELinux,NFS挂载目录上的文件会有nfs_t类型上下文,某些服务进程(比如httpd)想读这个目录会被SELinux拦截。此时需要设置布尔值:
bash复制setsebool -P httpd_use_nfs 1
这个细节在Docker容器场景下尤其重要:容器里的Apache或Nginx想读NFS共享目录,宿主机SELinux不放行的话,容器内会一直报Permission denied,但ls -l看权限又完全正常。我第一次遇到时排查了很久才想到是SELinux的问题。
6. 生产环境落地经验:目录规划与权限收敛的最佳实践
6.1 服务器目录布局建议
经过多个项目验证,我推荐的服务器端布局是这样的:
| 路径 | 用途 | 共享方式 |
|---|---|---|
| /srv/repo/rocky9 | 系统基础软件源目录 | NFS导出,只读客户端挂载 |
| /srv/repo/epel | EPEL扩展软件源目录 | NFS导出,只读客户端挂载 |
| /srv/share/public | 公共只读共享数据 | NFS导出,ro |
| /srv/share/team | 团队协作读写目录 | NFS导出,rw + root_squash |
把软件源统一放在/srv/repo下面,把共享数据放在/srv/share下面,逻辑清晰,也方便配置/etc/exports和定时备份。你不需要完全照搬,但原则是:目录规划在搭建第一天就想好,不要等机器多了再改,到时候改exports、改fstab、改客户端挂载点会很痛苦。
6.2 安全加固与权限收敛
NFS这东西的安全性一直是被诟病的,因为它本身的认证机制很弱:默认只认IP/网段,不认用户密码。所以生产环境的NFS一定要放在内网可信网段里,不要暴露到公网。如果必须跨越不可信网络,建议用NFSv4 + Kerberos(sec=krb5p),或者干脆用其他方案。
exports的权限收敛建议:
- 尽量用
ro只读导出,能读就不给写。 - 客户端网段要精确,不要图省事写
*。 - 加上
noexec挂载选项,防止客户端在共享目录里执行二进制文件,能减少被攻击的风险。 - 在服务器端对
/etc/exports做好版本管理,用git维护,改动有记录。
客户端的挂载参数也有收敛空间,比如:
bash复制mount -t nfs -o ro,noexec,nosuid,nodev,soft,timeo=50,retrans=2 172.16.140.200:/srv/repo/rocky9 /mnt/repo
nosuid和nodev这两个参数很多人不知道,它们分别禁止在挂载点上执行suid程序、禁止在该文件系统上创建设备文件。对于普通用户只看不写的软件源挂载,加上这些参数没有副作用,但安全收益是实实在在的。
6.3 离线环境下部署DNF仓库+NFS的完整时间线
如果你是为一个没有外网的离线机房做这套方案,我梳理一下完整时间线,照着走不会乱:
第一阶段,在有网的筹备机上下载必要的rpm包和工具。如果目标机没有安装createrepo_c和nfs-utils,你得先下载对应包以及依赖,用dnf download提前储备。
第二阶段,把包和工具搬到内网服务器。这个过程可能是装个包就能解决(比如nfs-utils自带二进制),也可能需要在筹备机上把createrepo_c的rpm复制过去手动安装。确保服务器端先把两个工具装好,否则后面没法生成元数据。
第三阶段,在服务器上创建仓库目录、拷贝rpm、生成repodata、配置exports、启动NFS服务。
第四阶段,在一台测试客户端上完成挂载和dnf配置,验证无误后,再通过批量脚本推送到所有客户端。批量推送怎么搞?如果是几台机器,手动拷贝repo文件和写fstab也不难;如果是几十台机器,我用过Ansible的template模块来分发local.repo和fstab片段,效率很高。
第五阶段,做一次完整的重启测试。把服务器和一台客户端重启,确认NFS服务自动启动、fstab自动挂载成功、dnf能够从仓库正常装包。这一步很多人省略,结果下次真重启了发现rpcbind没设开机启动,全部服务起不来,还好是测试环境。
7. 自动化脚本与后续扩展建议
7.1 仓库更新脚本
仓库目录不是一成不变的,每过一段时间就要同步新的安全更新或新软件包。我写了一个简单的更新脚本,放在服务器上配合cron跑:
bash复制#!/bin/bash
# 同步网络仓库到本地目录
REPO_DIR="/srv/repo/rocky9"
NET_REPO="https://download.rockylinux.org/pub/rocky/9/BaseOS/x86_64/os/Packages/"
dnf download --resolve --alldests --destdir="$REPO_DIR" \
nginx vim git tree rsync
# 生成新的repodata元数据
createrepo_c --update "$REPO_DIR"
# 重新导出NFS共享(可选)
exportfs -arv
这个脚本可以定期跑,比如每周一次,把新增的软件包同步进仓库,顺便刷新元数据。注意dnf download路径里--alldests其实是误写,正确参数是--alldeps,我在脚本里故意标出来是提醒大家别跟我一样打错。写完脚本记得chmod +x并先在测试环境跑一遍。
7.2 客户端批量切换源与回滚方案
给大批客户端切换DNF源的时候,我强烈建议用Ansible或者类似工具做配置管理。简单的Ansible任务片段:
yaml复制- name: 配置本地DNF仓库
ansible.builtin.copy:
src: files/local.repo
dest: /etc/yum.repos.d/local.repo
mode: '0644'
- name: 清理缓存并重建
ansible.builtin.command:
cmd: dnf clean all && dnf makecache
回滚方案也很重要。在改/etc/yum.repos.d/之前,先把原repo文件备份成.bak;如果使用NFS挂载,挂载失败会导致/mnt/repo目录为空,这样dnf会找不到软件源。此时需要快速恢复到网络仓库或本地临时目录。
我在生产环境里会给客户端的/etc/fstab加注释说明,并在repo文件里加一行enabled=0的备用网络源配置并留好真正的网络仓库baseurl注释。这样即使NFS断了,运维也可以快速改配置切回网络源,不用手忙脚乱找命令。
7.3 进一步扩展:用NFS同步Docker镜像或大数据目录
NFS共享存储的应用远不止DNF仓库。我实际项目中还用它做了几件额外的事:
一是共享Docker镜像缓存目录。内网有几十台Docker主机,每台都要拉镜像,在外网受限时很痛苦。我在一台服务器上跑了registry:2,把镜像存储目录通过NFS共享出来,多个docker daemon通过挂载该目录作为--data-root的一部分,虽然Docker并不直接推荐像registry那样共享,但配合registry镜像做内网镜像分发效率提升明显。
二是做日志收集的中间目录。比如多台应用服务器的日志都写入同一个NFS目录,后端的日志采集进程(filebeat或自研脚本)再从这个目录统一抓取,避免安装agent到每台机器上。
三是共享大数据临时目录,比如Hadoop的/tmp或者Spark的shuffle目录,虽然大数据框架本身有分布式文件系统,但某些边缘节点用NFS共享模型跑测试任务也很方便。
扩展思路是:只要多台机器需要"看到同一份文件",NFS就可能是最省事的方案之一。当然要考虑性能瓶颈,NFS的单点性能和带宽是有限制的,不适合高频小文件读写的热点场景,但软件源、日志暂存、静态资源这种场景完全够用。
8. 写在最后:我的一点实操心得
做这套DNF仓库+NFS方案也有两三年时间了,踩过的坑不算少。最大的体会是:这类基础架构服务,配置本身不难,难的是第一次就把目录结构、权限模型、备份恢复想清楚。不要等机器多了再回头改,那真的会改到怀疑人生。
还有一个心得是关于排障思路的。不管是NFS还是DNF,遇到问题第一步永远是分层排查:先确认网络通不通(ping、telnet端口),再确认服务状态(systemctl、ss),接着看配置和权限(exports、fstab、SELinux),最后才看日志(journalctl、/var/log/messages)。这个顺序能省下大量试错时间。
最后再分享一个小技巧:在测试NFS客户端挂载时,如果服务器上有多个目录需要挂载,可以把挂载命令写成一个脚本,用for循环一次性挂载,挂载完用findmnt检查所有挂载点是否就绪。这样一个机房几十台机器的挂载操作几分钟就能完成,比手动一条条敲命令高效得多。
这套方案不需要很高的技术门槛,但它能实实在在解决内网软件分发和共享存储的问题。如果你也遇到类似的场景,按着这篇指南走一遍,应该能少走不少弯路。
