我在实际维护某内网Linux服务器集群时遇到过最头疼的问题就是软件包分发。几十台机器分散在不同网段每次更新都要一台台跑yum update不仅慢而且在没有外网的环境下根本没法做。后来下决心搭建了本地的DNF仓库再用NFS把仓库目录共享出去整个更新流程从逐台折腾变成了一行命令搞定。这篇文章就把这次部署过程完整记录下来包含方案选型、具体配置、参数计算和踩坑实录给同样被软件分发问题困扰的朋友一个可复用的参考。1. 整体设计与方案选型思路1.1 为什么需要自建DNF仓库很多团队早期管理服务器软件包的方式很原始要么靠运维人员手动登录每台机器跑命令要么用脚本批量scp软件包。前者的痛点显而易见——几十台机器逐台操作耗时长还容易漏后者的隐患在于依赖关系很难处理尤其像内核升级、应用依赖库这种需要联动更新的场景手动拷包基本不可靠。DNF仓库的核心价值在于它把软件包和元数据集中存放在一个标准化的目录结构中客户端通过DNF工具自动解析依赖、完成校验和下载安装。自建仓库之后内网所有机器都可以像访问外网源一样使用dnf install、dnf update只不过流量全部走内网速度和安全性完全可控。对于没有外网权限的生产内网自建仓库几乎是唯一合理的软件分发方案。就算有外网在内网做一个镜像仓库也能大幅降低带宽占用同时保证所有机器使用同一版本的软件包避免机器A和机器B环境不一致这类经典问题。1.2 仓库与共享服务的技术选型方案设计时对比过多种组合核心决策点集中在仓库目录如何让多台机器访问。第一层是仓库本身的对外服务方式。常见选项是HTTP和FTP但实际生产环境几乎都用HTTP。原因有三一是HTTP客户端支持最完善DNF原生支持baseurlhttp://...二是HTTP的断点续传、缓存策略让大包传输更稳定三是后续如果要对外开放HTTP配合反向代理、访问控制都非常成熟。第二层是共享方式。我最终选用NFS来共享仓库目录而不是让每台机器直接走HTTP拉取考虑到的是内网多台机器同时拉包时HTTP服务端压力较大而NFS本质上让客户端直接读仓库文件省掉了一层HTTP封装。还有一个很实际的原因仓库同步、日常巡检需要直接在仓库机器上操作文件NFS挂载后团队成员可以在任何一台机器上直接维护仓库内容而不用SSH跳来跳去。当然NFS也有自身的注意事项——网络抖动会导致挂载点卡顿权限配置不当会有安全隐患。这些都是配置时需要重点处理的后面会详细展开。1.3 最终架构与工作流程整套系统的最终架构如下一台仓库服务器承担两类角色DNF仓库数据存储与HTTP服务对外提供仓库访问、NFS服务端共享仓库目录。多台客户端机器通过NFS挂载仓库目录或者直接通过HTTP方式访问DNF仓库。仓库数据来源外网镜像站同步或者手工导入软件包。部署完成后的工作流程是先从外网或软件包源同步数据到仓库服务器仓库目录通过HTTP服务对外提供DNF访问同时通过NFS共享给需要直接操作仓库文件的机器。客户端统一配置本地仓库地址所有软件安装和更新都走内网完成。实际使用中建议HTTP和NFS同时启用。NFS适合运维人员直接管理仓库文件、同步数据HTTP才是客户端DNF访问仓库的主要通道。两者并不冲突反而互为补充。2. 环境准备与仓库基础部署2.1 服务器与系统环境要求仓库服务器不需要太高配置软件包仓库主要是存储和带宽消耗CPU和内存压力不大。我用的是一台2核4G的虚拟机系统盘和数据盘分离仓库数据放在独立的数据盘上避免系统盘被撑满。操作系统选择方面DNF是新一代软件包管理器默认使用DNF的发行版是Fedora、RHEL 8、Rocky Linux、AlmaLinux等。如果你还在用CentOS 7这种默认YUM的系统建议先升级到8以上版本否则后面的DNF仓库配置会有差异。需要注意一个关键点DNF仓库的元数据格式与旧版YUM不兼容。如果你管理的机器还是老系统需要单独为它们保留YUM仓库两套仓库并行维护。2.2 安装DNF相关工具集仓库服务器上需要准备以下工具# 安装DNF及其插件 dnf install -y dnf dnf-plugins-core createrepo_c # 安装HTTP服务 dnf install -y httpd # 安装NFS服务端 dnf install -y nfs-utils # 用于生成仓库元数据 dnf install -y createrepo_ccreaterepo_c是元数据生成工具它扫描指定目录下的所有RPM包生成仓库索引信息。DNF客户端在访问仓库时先拉取这些索引文件然后在本地做依赖解析。有个小细节createrepo_c是C语言实现的加速版本比老旧的Python版createrepo快很多尤其当仓库里RPM包数量过千时性能差别非常明显。强烈建议直接用createrepo_c。2.3 仓库目录规划与RPM包导入仓库目录结构建议按实际用途来规划而不是把所有RPM包塞到一个目录里。我采用的目录结构如下/data/dnf_repo/ ├── base/ # 基础软件包系统自带源同步 ├── updates/ # 更新包同步自外网更新源 ├── extras/ # 扩展包第三方源同步 └── custom/ # 自制RPM包内部开发的软件手工向仓库添加RPM包的流程是这样的# 1. 创建目录 mkdir -p /data/dnf_repo/custom # 2. 拷贝RPM包到目录 cp ./myapp-1.0.0-1.el8.x86_64.rpm /data/dnf_repo/custom/ # 3. 生成/更新仓库元数据 createrepo_c --update /data/dnf_repo/custom/这里--update参数很关键它表示增量更新——只扫描新增或变化的文件而不是全量重建索引。仓库里包数量多了以后全量重建会非常慢增量更新几乎是秒级完成。如果是从外网镜像同步整个仓库推荐用dnf reposync命令dnf reposync --repoidbase --download-path/data/dnf_repo/base/ --download-metadata--download-metadata会连仓库索引一起下载省去本地再跑一次createrepo_c的步骤。不过建议同步完成后还是手动跑一次createrepo_c --update确保索引和实际文件完全一致。3. HTTP服务配置与DNF仓库对外发布3.1 配置HTTP服务对外提供仓库访问DNF的baseurl支持HTTP协议这也是客户端最推荐的访问方式。HTTP服务配置比较简单核心是把仓库目录映射成Web可访问路径。使用Apache httpd时编辑配置文件/etc/httpd/conf.d/dnf_repo.confAlias /dnf_repo /data/dnf_repo Directory /data/dnf_repo Options Indexes FollowSymLinks Require all granted IndexOptions FancyIndexing /Directory然后启动服务并设置为开机自启systemctl enable --now httpd systemctl status httpdOptions Indexes这个配置非常有用它允许客户端浏览器直接查看仓库目录下的文件列表方便人工浏览和快速下载单个RPM包。FancyIndexing让文件列表更美观带图标和排序功能。还有一个在配置文件中容易漏掉的事情SELinux。默认情况下SELinux会阻止httpd访问非标准目录需要单独放行# 给仓库目录设置正确的SELinux标签 semanage fcontext -a -t httpd_sys_content_t /data/dnf_repo(/.*)? restorecon -Rv /data/dnf_repo这一条不执行你会在客户端遇到403权限错误排查半天还找不到原因。这是新手最容易踩的坑之一。3.2 同仓库HTTP访问连通性验证配置完成后先在服务器本机验证HTTP服务是否正常curl -I http://localhost/dnf_repo/base/repodata/repomd.xml如果返回200 OK说明HTTP服务正常。然后再验证从客户端访问curl -I http://192.168.1.100/dnf_repo/base/repodata/repomd.xml注意检查防火墙将80端口放行firewall-cmd --permanent --add-servicehttp firewall-cmd --reload这个验证步骤非常关键。很多人部署完直接去客户端执行dnf install报错之后才慢慢排查其实用curl先验证HTTP层的连通性能快速区分问题是出在网络层、HTTP层还是DNF配置层。3.3 外网仓库同步的优化策略如果你的服务器有外网访问权限可以定期从官方镜像站同步仓库数据。这里推荐一个组合方案用dnf reposync拉包搭配rsync做增量同步。# 同步base仓库 dnf reposync --repoidbase --download-path/data/dnf_repo/base --download-metadata # 同步updates仓库 dnf reposync --repoidupdates --download-path/data/dnf_repo/updates --download-metadata同步频率建议根据实际需求来。如果对安全更新非常敏感每天同步一次如果只是常规软件包管理每周同步一次足够。可以把同步命令写到crontab里0 2 * * * /usr/bin/dnf reposync --repoidbase --download-path/data/dnf_repo/base --download-metadata /usr/bin/createrepo_c --update /data/dnf_repo/base/选择凌晨2点是因为这个时段网络相对空闲同步速度更快也不会影响白天的业务流量。一个同步细节务必在同步完成后检查磁盘空间。仓库数据增长很快尤其base和updates仓库动辄几十GB。建议同步脚本里加上磁盘空间检查低于阈值时发送告警。4. NFS共享服务部署与配置4.1 NFS服务端配置NFS共享仓库目录目的是让运维人员能够在多台机器上直接管理仓库文件。在服务器上配置NFS服务端很简单但有一些细节会影响稳定性和安全性。编辑NFS导出配置/etc/exports/data/dnf_repo 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check,crossmnt)各参数含义如下192.168.1.0/24允许访问的网段建议按实际内网网段严格控制不要用*开放所有来源。rw读写权限允许客户端修改仓库文件。sync同步写入模式数据落盘后才返回成功避免掉电时数据不一致。no_root_squash允许root用户以root权限写文件。这个参数有安全隐患如果客户端机器被攻破等于可以随意篡改仓库文件。但不开的话客户端root写文件会变成nobody用户管理起来很别扭。no_subtree_check关闭子目录检查提升性能。crossmnt允许跨挂载点访问。完整的配置还需要考虑安全加固。我的做法是增加一个只读导出供普通客户端挂载查看仓库内容/data/dnf_repo 192.168.1.0/24(ro,sync,no_subtree_check,crossmnt) /data/dnf_repo_admin 192.168.2.0/24(rw,sync,no_root_squash,no_subtree_check,crossmnt)用exportfs -rv重新加载配置exportfs -rv systemctl enable --now nfs-server systemctl status nfs-server4.2 NFS客户端挂载配置客户端挂载NFS共享的配置比较讲究。直接编辑/etc/fstab加入一行192.168.1.100:/data/dnf_repo /mnt/dnf_repo nfs rw,vers4.2,hard,timeo30,retrans3 0 0挂载参数的解释vers4.2指定NFS版本4.2支持更完善的锁和并发机制。hard硬挂载模式如果服务器短暂故障客户端会持续等待而不是立刻报错。生产环境建议用hard配合timeo超时参数防止永久卡死。timeo30超时时间30秒单位是1/10秒30即3秒。retrans3重传3次仍失败则停止。挂载测试mkdir -p /mnt/dnf_repo mount -a df -h /mnt/dnf_repo这里有一个NFS常见坑fstab挂载如果遇到网络初始化顺序问题开机时会挂载失败。解决方法是在fstab挂载参数中加上_netdev192.168.1.100:/data/dnf_repo /mnt/dnf_repo nfs rw,vers4.2,hard,timeo30,retrans3,_netdev 0 0_netdev告诉系统在网络就绪后再尝试挂载NFS避免开机时网络未启动就挂载导致失败。4.3 通过NFS直接维护仓库数据的场景NFS共享仓库目录最大的优势是运维效率。举个例子某团队在服务器B上开发了一个内部软件打包成RPM后放在服务器B的本地目录需要同步到仓库服务器。如果没有NFS需要scp到仓库服务器再拷贝有NFS挂载后直接cp到挂载目录就行cp myapp-1.0.2-1.el8.x86_64.rpm /mnt/dnf_repo/custom/然后再在仓库服务器上更新元数据。这里有个习惯问题很多人图省事直接在客户端机器上跑了createrepo_c结果客户端没有安装这个工具或者元数据生成在NFS挂载点上出现了奇怪的权限问题。建议在仓库服务器本机操作元数据更新客户端只负责复制RPM过去。# 在仓库服务器执行 ssh repo-server createrepo_c --update /data/dnf_repo/custom/顺便把这条命令做成一个脚本放在客户端机器的PATH里团队所有人都可以直接用#!/bin/bash repo_server192.168.1.100 mount_point/mnt/dnf_repo ssh $repo_server createrepo_c --update $mount_point/custom/5. 客户端DNF仓库配置与联动校验5.1 客户端仓库源配置文件编写客户端使用DNF仓库核心是配置仓库源文件。在/etc/yum.repos.d/下创建文件local.repo[local-base] nameLocal Base Repository baseurlhttp://192.168.1.100/dnf_repo/base/ enabled1 gpgcheck0 [local-updates] nameLocal Updates Repository baseurlhttp://192.168.1.100/dnf_repo/updates/ enabled1 gpgcheck0 [local-extras] nameLocal Extras Repository baseurlhttp://192.168.1.100/dnf_repo/extras/ enabled1 gpgcheck0 [local-custom] nameLocal Custom Repository baseurlhttp://192.168.1.100/dnf_repo/custom/ enabled1 gpgcheck0注意gpgcheck0因为本地仓库是自己维护的通常没有GPG签名。如果你有内部GPG签名机制可以保持gpgcheck1并且配置gpgkey安全性会好一些但需要额外维护签名流程。配置完成后执行dnf clean all清理缓存然后测试仓库是否可用dnf clean all dnf repolist5.2 常见安装与更新操作验证仓库配置完成后在客户端机器上做一轮完整验证确认安装、更新、卸载三类操作都正常。先测试安装一个之前没装过的包dnf install -y htop如果之前在base仓库里有这个包安装应该非常快因为流量走内网。然后测试更新操作dnf update -ydnf update会检查所有仓库的可用更新。如果仓库是刚同步的可能会列出很多待更新包这是正常的。注意dnf update不带包名时会更新所有可更新的软件包生产环境建议先dnf check-update查看有多少包待更新再决定是否执行。测试卸载dnf remove -y htop5.3 版本锁定与依赖冲突处理在仓库环境下依赖冲突问题比外网源更突出。因为内网仓库版本更新之后部分其他软件包可能还依赖旧版本的库。例如某内部软件依赖libssl.so.1.1但仓库更新后系统装的是libssl.so.3这时候安装内部软件就会报依赖失败。解决方案是在仓库中保留多个版本的RPM包。DNF仓库不像某些私有仓库那样自带版本保留功能所以需要在同步脚本中做额外处理默认dnf reposync会同步仓库中的最新版本旧版本可能被丢弃。如果内部软件对版本敏感建议单独建立一个custom-pinned目录手工放入指定版本的RPM包并且在repo配置中优先启用这个目录。[local-pinned] nameLocal Pinned Repo baseurlhttp://192.168.1.100/dnf_repo/pinned/ enabled1 gpgcheck0 priority1priority1需要dnf-plugin-priorities插件支持它保证优先从pinned仓库安装软件包版本。另一种依赖问题的处理方式是用dnf versionlock锁定已安装版本dnf install -y dnf-plugin-versionlock dnf versionlock add myapp这样在系统层面的更新中被锁定的包不会被升级从源头上避免了依赖连锁反应。6. 常见问题、性能优化与安全加固6.1 客户端连接仓库失败的排查思路客户端报错Failed to download metadata或者Cannot retrieve repository metadata是最常见的故障之一。排查顺序建议按照下面这个流程来第一步验证网络连通性ping 192.168.1.100第二步验证HTTP服务是否可用curl -I http://192.168.1.100/dnf_repo/base/repodata/repomd.xml如果第二步成功说明网络和HTTP层都没问题问题一定出在DNF配置或元数据损坏上。第三步检查元数据文件是否完整ls -l /data/dnf_repo/base/repodata/repomd.xml file /data/dnf_repo/base/repodata/repomd.xml一个容易忽视的问题同步或手工导入RPM时如果仓库目录里出现了损坏或不完整的RPM包比如下载了一半的文件createrepo_c生成的索引可能不包含这个文件但客户端拉取时会尝试读取并校验导致报错。解决办法是定期清理掉目录中的不完整文件find /data/dnf_repo -name *.rpm -size -1M -delete仓库中正常的RPM包最小也有几KB小于1M的基本是损坏的半截文件删掉后重新跑createrepo_c --update。6.2 NFS挂载卡顿与性能调优NFS在生产环境中表现不稳定多数时候与参数配置有关。我遇到过挂载后ls卡住的情况排查后发现是vers4.2与客户端内核版本不兼容改回vers4.0后恢复正常。NFS调优的几个建议网络基础要好NFS严重依赖网络质量建议在千兆内网使用网络延迟过高时性能下降很厉害。修改挂载参数增加rsize1048576,wsize1048576设置更大的读写块大小。NFS默认的32KB传输块大小在千兆网络下偏小调大到1MB能显著提升大文件复制速度。避免在NFS挂载点上跑大规模createrepo_c。元数据生成是IO密集型操作在NFS上跑会拖慢整个共享服务的响应。正确做法是本地生成后再同步到仓库目录。如果NFS服务端性能不足可以考虑使用其他同步方案做补充。例如使用rsync定时同步共享目录到其他节点多节点并行提供仓库服务。这样即使NFS挂载点出现问题客户端还可以通过HTTP访问仓库。6.3 仓库数据安全性加固仓库目录是内网所有软件包的源头一旦被破坏所有机器都会受影响。安全加固做得越全面越好我的实践经验分为三层第一层是访问控制。HTTP服务层面可以通过nginx或httpd的访问控制模块限制只有内网IP才能访问仓库Directory /data/dnf_repo Options Indexes FollowSymLinks Require ip 192.168.0.0/16 /Directory第二层是数据完整性。建议对仓库目录做定期快照或镜像确保异常情况能快速恢复。最简单的做法是rsync -av --delete /data/dnf_repo/ /backup/dnf_repo/放在每天的crontab里保留最近7天的备份。更专业的方案是用reflink快照但需要文件系统支持Btrfs或XFS。第三层是权限控制。确保仓库目录的属主和权限严格设置chown -R root:wheel /data/dnf_repo chmod -R 755 /data/dnf_repo同时建议对仓库内的RPM包做一次哈希校验确保没有被篡改find /data/dnf_repo -name *.rpm -exec sha256sum {} \; /var/log/repo_checksum.log这条命令首次运行会比较耗时建议放在空闲时段执行。6.4 多网段场景下的仓库分发策略如果你的内网有多个网段NFS共享在跨网段场景下不会太稳定。实际部署中我更倾向于在多网段各放一个仓库节点通过rsync保持数据一致性。架构如下主仓库节点负责从外网同步数据生成元数据提供HTTP服务。从仓库节点各网段一台定时从主节点rsync数据提供本网段的HTTP服务。从节点的rsync同步命令rsync -av --delete --exclude*.tmp root192.168.1.100:/data/dnf_repo/ /data/dnf_repo/设置crontab每小时同步一次。客户端配置时根据所在网段选择最近的仓库地址[local-base] nameLocal Base Repository baseurlhttp://192.168.2.200/dnf_repo/base/ enabled1 gpgcheck0这种方式兼具集中管理和就近分发的优点数据只有主节点从外网拉取从节点之间、从节点与主节点之间都是内网同步。某个节点故障时只要修改客户端repo文件的IP就能快速切换到备用节点。脚本化的分发配置可以借助配置管理工具比如Ansible批量下发repo文件。实际操作中我在Ansible中定义了一个变量文件记录各网段的仓库地址然后用template模块生成repo文件分发到所有客户端。这样改一个地址全量客户端同步生效。6.5 仓库元数据规模管理与日常维护随着使用时间增加仓库中的RPM包数量会逐渐膨胀。base和updates仓库的包数可能上万每次createrepo_c全量重建需要很长时间因此日常维护必须养成增量更新的习惯。经验数据可以参考大约5000个RPM包时全量重建元数据需要5-10分钟增量更新只处理变动的包只需要几秒钟。所以维护脚本必须固定使用--update参数。可以封装一个简洁的维护命令#!/bin/bash repo_root/data/dnf_repo for repo in base updates extras custom; do /usr/bin/createrepo_c --update $repo_root/$repo/ /dev/null 21 echo repo $repo updated done另一个维护经验是定期检查仓库中孤立或废弃的RPM包。有些包被新版本替换后旧包一直躺在仓库里虽然不会直接影响使用但会增加元数据大小并在客户端dnf list时列出大量旧版本。维护工具可以使用dnf repoquery --latest-limit2 --repolocal-base找出每个包的最新两个版本确认无误后手动清理较旧的RPM文件再重新生成元数据。6.6 客户端批量切换与验证整套系统部署完成后最后一个环节是客户端批量切换仓库源。手动在每台机器上操作是非常低效的推荐使用Ansible批量执行。简单的批量推送repo文件并刷新缓存- name: 推送本地仓库配置 ansible.builtin.copy: src: local.repo dest: /etc/yum.repos.d/local.repo mode: 0644 - name: 清理DNF缓存 ansible.builtin.command: dnf clean all - name: 验证仓库列表 ansible.builtin.command: dnf repolist批量执行完后抽查几台机器确认dnf repolist输出正确。这一步不要省略有些机器可能残留旧的repo文件导致仓库源冲突。检查命令dnf repolist alldnf repolist all会列出所有已安装和未启用的仓库一眼就能看出是否有多余配置。清理掉不用的仓库文件mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak批量切换后建议再进行一轮实际安装测试确认所有客户端都能正常工作。我通常会在切换完成后挑一个非业务时间在所有客户端批量执行dnf install -y tree这个包非常轻量几分钟内全部执行完能够有效验证仓库在实际应用中的可用性。安装成功后再批量卸载减少不必要的残留。我个人在实际操作中体会最深的一点整套系统部署本身不算复杂真正的复杂度在于日常维护的规范。如果同步脚本、元数据更新、备份策略都能形成固定的自动化流程这套仓库体系用起来非常省心。前端时间我维护的集群因为一次误操作导致仓库目录权限错乱要不是有前一天的rsync备份恢复起来要找RPM包重新生成元数据至少要花大半天。所以备份和自动化两项一定要从一开始就落实到位不要等到出了问题再临时抱佛脚。
阅读完成 · 觉得有帮助?