
在内网环境里给 Linux 机器装软件是几乎所有运维都绕不过去的一道坎。系统默认的 yum 或 dnf 会跑去公网找源网络一断yum install立马甩出一句Cannot find a valid baseurl for repo人直接卡在原地。能救命的只有本地 yum 源——说白了就是把这台机器的包管理请求从远端拽回本地磁盘让它从挂载的安装镜像、从我们自己整理的 RPM 目录里取包。这件事看着简单真正上手会碰到一堆细节ISO 挂哪儿、repo 文件怎么写、CentOS 7 和 Rocky 9 的目录结构为什么不一样、createrepo 生成的元数据什么时候需要刷新、内网几十台机器怎么共用一个源。这篇内容就是把我自己在离线机房、生产内网、涉密项目里反复折腾过的整套做法摊开讲一遍从最小可用的镜像源一路做到内网 HTTP 共享源适合刚接手内网运维的新人也适合想把手头流程规范化的老手。1. 先把内网离线 yum 仓库这件事想明白1.1 一个真实场景没有源的机器有多难受我印象最深的一次是给某单位的三层内网做环境交付。机器上架、系统装好、SSH 能连一切正常直到要装一个gcc编译工具链才发现内网完全不出公网连 DNS 都没有配。当时的做法是最原始的rpm -ivh手工装结果 A 依赖 BB 又依赖 CC 回头还依赖 A 的某个高版本一圈下来装了两个小时中间还因为版本错位把系统的glibc搞出了告警。那台机器后来是重装的。这件事之后我就养成习惯只要拿到一台没法出网、或者只能走代理出去、或者说网络极其不稳定的机器第一件事不是装软件而是先把本地 yum 源搭起来。有了源yum install会自动帮你算依赖、按顺序装包、记录事务历史yum history undo还能回滚手工 rpm 装出来的烂摊子它一个都不会有。离线 yum 仓库的价值其实就三条第一不依赖外网网络断了照样能装第二依赖关系由包管理器负责人不用去猜第三统一来源意味着全内网的软件版本一致不会出现这台机器是 httpd 2.4.6、那台是 2.4.37 的混乱局面。1.2 yum 和 dnf 到底靠什么找到包很多人搭源是照着别人的文档抄一遍命令跑通了就不管了。一旦报错就抓瞎。所以这里花点篇幅讲清楚原理理解了它后面所有的报错你都能自己推。一个 yum 仓库的本质是一堆 RPM 包 一份索引。索引就放在仓库根目录下的repodata文件夹里核心文件有四个repomd.xml仓库的总索引记录其他元数据文件的位置、大小和校验值。客户端的第一个请求就是拉它。primary.xml.gz包含每个包的名称、版本、架构、依赖关系、文件列表摘要。yum 算依赖主要靠它。filelists.xml.gz记录每个包安装了哪些文件yum provides和yum whatprovides依赖它。other.xml.gz包含变更日志等附加信息。客户端那边/etc/yum.repos.d/下的.repo文件告诉 yum 去哪儿找。一个典型的 repo 段落有这么几个关键字段[repo-id] name可读的名字 baseurl仓库根目录的地址可以是 file:// 也可以是 http:// enabled1 是否启用 gpgcheck1 是否校验包的签名 gpgkey签名公钥的位置yum 的工作流程是读所有 enabled 的 repo → 拉各自repodata/repomd.xml→ 对比本地缓存默认在/var/cache/yum或/var/cache/dnf判断元数据有没有过期 → 过期就重新下载 → 在内存里建一张包依赖图 → 你敲install的时候在这张图里做求解。所以后面所有报错基本可以归到三类找不到 repomd.xml路径或地址错了、找到了但内容不对元数据和实际包不匹配或者架构不匹配、找到了但签名校验失败GPG 问题。抓住这个分类排查效率会高很多。1.3 三条路线怎么选别一上来就上最复杂的内网离线源不是只有一种做法按复杂度和适用面排大致是这么三条路方案数据来源搭建难度能覆盖的包适用场景安装镜像挂载系统 ISO 里的 Packages最低10 分钟只有随系统发行的包应急、单机、基础环境createrepo 自建自己收集的 RPM 目录中等任意取决于你收集了多少需要装镜像里没有的包HTTP 共享上面两种 Web 服务中等同上且全内网共用内网多台机器统一源我的建议是分两步走先用镜像挂载把基础环境跑起来保证yum能正常工作然后在同一个目录下用createrepo补一个自建仓库专门放镜像里缺的包比如 EPEL 的、内部自研的、特定版本的。这两步做完单机就够用了。等机器数量上到五台以上再把目录用 HTTP 暴露出去省得每台机器都拷一份 ISO。注意不要一开始就追求同步全量镜像。RPM 全量仓库动辄几十 GB而且在纯离线环境里同步本身就是个难题。按需收集才是务实做法。2. 动手之前三件必须先对齐的事2.1 版本、架构、发行版一个都不能错这是最容易被忽略、后果也最严重的一步。RPM 包和系统之间是强绑定的跨大版本、跨发行版、跨架构的包混用轻则装不上重则把系统依赖搞崩。需要确认的信息cat /etc/os-release # 发行版和版本号 uname -m # 架构x86_64 还是 aarch64 cat /etc/redhat-release # RHEL 系专用更直观 rpm -q --qf %{VERSION}\n centos-release # CentOS 专用要特别说一句CentOS 7、CentOS 8、Rocky 9、AlmaLinux 9 这几个虽然都是 RHEL 系但它们的 RPM 不能互相替代。CentOS 7 的包在 Rocky 9 上装不上反过来也一样。国内不少单位现在还在跑 CentOS 7同时又新采购了 Rocky 9 的机器这两种环境的源要分开建目录不能合并。架构方面现在国产化场景里 ARM 服务器越来越多。x86_64 的 ISO 和 aarch64 的 ISO 是两个完全不同的文件一个源里混装两种架构的包repodata是能生成但客户端拉下来会发现包的架构对不上报package xxx is not installable。稳妥做法是按架构建两套目录。2.2 物料和工具清单开工之前把东西备齐比中途到处找要省时间得多系统安装 ISODVD 版不是 Minimal 版。CentOS 7 的 DVD 大概 4.4 GBRocky 9 的 DVD 大概 9 GB 左右。Minimal 版 ISO 里没有 Packages 目录做不了源这点很多人踩过。如果要用 createrepo需要在能出网的机器上先准备好createrepo或createrepo_c的 RPM 及其依赖。收集 RPM 的辅助工具yum-utils里面含yumdownloader、repotrack、dnf-plugins-core含dnf download。存储空间镜像源按 ISO 大小预留自建源按包数量 × 平均 0.5 MB估算一般留 20 GB 比较从容。关于工具真心推荐repotrackyum install -y yum-utils repotrack httpd -p /tmp/rpms它和yumdownloader --resolve最大的区别是repotrack会把整个依赖树全部下载下来不管本机是不是已经装了。这一点在收集离线包时至关重要——你在 A 机器上用yumdownloader --resolve下载它会把本机已有的依赖跳过结果拷到内网 B 机器上就缺包了。这个坑我踩过不止一次。2.3 三个容易忽略的前置条件第一SELinux。如果你后面要用 HTTP 暴露仓库目录SELinux 开着的话Web 服务默认没有权限读/data下的自定义目录客户端会拿到 403。要么用chcon打标签要么用semanage fcontext做持久化规则。临时排查可以setenforce 0但生产环境别这么干。第二防火墙。本机file://方式不需要开端口但 HTTP 共享必须放行 80 或你自定义的端口。firewall-cmd --permanent --add-servicehttp firewall-cmd --reload这条命令看起来简单忘了执行的时候表现是服务端 curl 通、客户端 curl 卡住很容易误判成网络问题。第三随机启动。挂载 ISO 如果只写mount命令重启之后就没了。HTTP 服务如果只systemctl start没enable重启也不会自启。内网机器重启往往没人盯着等你发现源不可用的时候可能已经过了很久。3. 方案一用安装镜像搭一个最小可用的本地源3.1 挂载 ISO 到固定目录先把 ISO 传到服务器上比如放到/opt/iso/下。然后建挂载点、挂载mkdir -p /mnt/iso mount -o loop,ro /opt/iso/CentOS-7-x86_64-DVD-2009.iso /mnt/iso-o loop是让内核把这个文件当成块设备来用ro是只读挂载。ISO 挂载一定要加只读不然某些情况下内核会尝试回写报read-only file system之类的怪错。挂载完先看一眼目录结构ls /mnt/isoCentOS 7 的镜像根目录下会直接看到Packages、repodata、isolinux、images这些目录repodata就在根上所以 baseurl 直接指向/mnt/iso就行。但 CentOS 8、Rocky 9、AlmaLinux 9 的结构不一样根目录下是BaseOS和AppStream两个子目录每个子目录里各有一套独立的Packages和repodata。这就是为什么很多人照着 CentOS 7 的教程配 CentOS 8 的源会失败——路径少了一层。ls /mnt/iso # 会看到 BaseOS AppStream EFI images isolinux ls /mnt/iso/BaseOS # 这里有 Packages 和 repodata提示挂载前先确认 ISO 完整sha256sum对一下官方校验值或者至少确认能mount成功且ls正常。下到一半的 ISO 挂上后目录能出来但repodata可能损坏后面报错会很难查。3.2 写 repo 文件逐行解释每个参数CentOS 7 的写法[local-dvd] nameCentOS 7 Local DVD baseurlfile:///mnt/iso enabled1 gpgcheck1 gpgkeyfile:///mnt/iso/RPM-GPG-KEY-CentOS-7Rocky 9 / CentOS 8 需要拆成两个段落[local-baseos] nameRocky 9 BaseOS baseurlfile:///mnt/iso/BaseOS enabled1 gpgcheck1 gpgkeyfile:///mnt/iso/RPM-GPG-KEY-Rocky-9 [local-appstream] nameRocky 9 AppStream baseurlfile:///mnt/iso/AppStream enabled1 gpgcheck1 gpgkeyfile:///mnt/iso/RPM-GPG-KEY-Rocky-9文件名必须是以.repo结尾放在/etc/yum.repos.d/下这是 yum 扫描的唯一目录。file://后面是三个斜杠不是两个——两个斜杠表示主机名三个才表示本地路径写错的表现是Cannot find a valid baseurl。gpgkey指向的 key 文件一般就在镜像根目录ls /mnt/iso | grep GPG能找到。如果一时找不到可以临时把gpgcheck设成 0 先把流程跑通但不要把 gpgcheck0 当成最终配置。离线环境同样存在包被替换的风险能开签名校验就开着多花两分钟的事。改完之后把系统自带的那些公网 repo 文件挪走或者禁用避免 yum 每次都要去公网超时mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/backup/判断哪些是系统自带的很简单ls一眼就能看出来一般名字里带CentOS-、Rocky-、almalinux前缀的都是。3.3 刷新缓存并做真实验证配置改完必须刷缓存yum clean all yum makecacheclean all会把旧的元数据、包缓存、插件缓存全删掉makecache再按新配置重新下载。这两步在 CentOS 7 上不到十秒但在 CentOS 8 上如果源配错makecache会卡很久然后报错反而成了最快的验证手段。看清楚yum repolist的输出状态是enabled的才是生效的yum repolist all最后做个真实安装测试别用yum list糊弄过去yum install -y lrzsz tree yum provides */ifconfig装个实际的小包再测一下provides能否命中。provides依赖filelists.xml能正常返回结果说明元数据是完整的不是残缺的。3.4 让它重启之后还在手动mount的挂载重启就没了。写进/etc/fstab让它开机自动挂/opt/iso/CentOS-7-x86_64-DVD-2009.iso /mnt/iso iso9660 loop,ro 0 0写完先mount -a验证一遍没有报错才说明语法正确。这一步千万别跳过——fstab写错会导致系统启动进紧急模式内网机器又多一台台去救援很痛苦。稳妥点可以加个nofail选项即使挂载失败也不阻塞启动/opt/iso/xxx.iso /mnt/iso iso9660 loop,ro,nofail 0 0另外可以把mount -a放进一个开机执行的脚本里替代 fstab或者用 systemd 的 mount unit看个人习惯。fstab 最省事但容错性最差。4. 方案二createrepo 自建离线仓库把缺的包补齐4.1 光盘源最大的短板镜像源跑通之后你会发现它只包含随系统发行的包。编译工具、EPEL 里的工具、内部自研的 RPM、特定版本的中间件全都没有。我遇到过一个典型情况内网要装htopCentOS 7 的 DVD 里根本没有这个包它在 EPEL 里。另一个场景是版本漂移。生产环境要求 httpd 必须锁在某个特定小版本而镜像里只有初始发行版这时候就得自己把对应的 RPM 收进来建一个内部仓库。所以自建仓库的定位很清楚它是一个补充仓库不是替代品。基础包从镜像源拿缺失包从自建源拿两个源同时 enabledyum 自己会去找。4.2 目录结构怎么规划这个建议一开始就定好后面改起来很麻烦/data/repo/ ├── base/ # 镜像挂载点或从镜像同步过来的包 ├── extras/ # 从其他渠道收集的 RPM │ └── Packages/ └── internal/ # 内部自研 RPM └── Packages/我自己更习惯按来源而不是按用途分目录因为一个 repo 段落对应一个目录最清晰。比如mkdir -p /data/repo/extras/Packages mkdir -p /data/repo/internal/Packages cp /tmp/rpms/*.rpm /data/repo/extras/Packages/把要归入同一个仓库的所有 RPM 平铺在一个Packages目录里不要嵌套多层子目录。虽然createrepo -p能保留路径信息但嵌套目录会让元数据变大、扫描变慢而且客户端下载时路径也会变复杂。平铺最省心。4.3 收集 RPM 的几种途径按可靠性从高到低排第一种repotrack全量拉依赖。前面提过它是离线收集最靠谱的工具因为它不看本机装了什么repotrack -a x86_64 -p /tmp/rpms httpd mod_ssl-a指定架构-p指定输出目录。它会把 httpd 和 mod_ssl 的完整依赖树都拉下来通常十几个到几十个包。第二种dnf download --resolve --alldeps。CentOS 8 上的现代做法dnf download --resolve --alldeps --destdir/tmp/rpms httpd--alldeps是关键不加它同样是跳过已安装的依赖。第三种yum install --downloadonly。适合已经确定了依赖组合、只想把这一次事务的包拿下来的场景yum install --downloadonly --downloaddir/tmp/rpms httpd它的局限是只下载当前事务需要的包依赖判断基于本机环境。第四种直接找 RPM。比如内部自研的包、别人给的一批 RPM。这种要格外小心版本和架构最好用rpm -qp --qf批量检查一遍rpm -qp --qf %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n /tmp/rpms/*.rpm发现有i686混在x86_64里、发现有el7混在el9里都要清出去。第五种去 EPEL 等官方镜像站下载。在有网络的机器上下好再拷进内网。注意记录来源和版本方便以后追溯。4.4 生成元数据以及什么时候需要重新生成安装 createrepo# CentOS 7 / RHEL 7 yum install -y createrepo # CentOS 8 / Rocky / Alma dnf install -y createrepo_ccreaterepo_c是 C 语言重写版速度比老版 Python 版快很多大仓库上差距很明显。命令名还是createrepo_c。生成元数据createrepo_c /data/repo/extras执行完目录下会多出一个repodata文件夹。这时候这个目录就已经是一个合法的 yum 仓库了。关键点来了每次往Packages目录里新增或删除 RPM都必须重新生成元数据否则 yum 看不到新包。全量重新生成在大仓库上很慢用增量更新createrepo_c --update /data/repo/extras--update只处理变化的包秒级完成。但它有个前提repodata必须已经存在。第一次建仓库不能用--update会报错。另外两个我常用的参数--general-compress-typegz指定压缩格式。默认是gz某些环境下xz压缩率更高但客户端兼容性略差内网追求稳的话保持gz。--changelog-limit5限制other.xml里记录的 changelog 条数能显著减小元数据体积。默认是 10仓库大了以后这个参数很值钱。注意--update之后最好再跑一次createrepo_c /data/repo/extras不带参数做一次全量校验尤其是批量导入过包之后。我遇到过一次增量更新后某个包的依赖信息没刷进去客户端报缺依赖全量重建就正常了。4.5 多个来源合并成一个仓库实际项目里经常是镜像里的包一部分、EPEL 拿一部分、内部自研一部分客户要求只配一个源。这时候可以在同一个仓库的Packages目录里放所有包然后一次性建元数据。但要注意同名包的冲突。同一个包名可能有多个版本、多个来源yum 默认会选最新的。如果想让它忽略某些包可以用createrepo_c生成之前就把不想暴露的包删掉最干净。在客户端用exclude在 repo 段落里排除比如excludehttpd*。用includepkgs反过来做白名单只开放指定包这在严格控制软件来源的场景里很实用。还有一种做法是分成多个 repo 段落每个对应一个目录用priority控制优先级。CentOS 7 需要装yum-plugin-prioritiesCentOS 8 的 dnf 原生支持[internal-extras] nameInternal Extras baseurlhttp://10.10.20.30/extras enabled1 gpgcheck0 priority10数字越小优先级越高。默认是 99。把内部源的优先级设低数字小可以让内部版本优先于系统版本被选中这在必须用我们审过的那个版本的场景里非常有用。客户端配置好之后同样要yum clean all yum makecache再看yum repolist。5. 方案三让整个内网一起用把源做成 HTTP 服务5.1 服务选型httpd 还是 nginx单机用file://就够了但内网有十台、几十台机器每台都挂一份 ISO 太浪费。这时候把仓库目录用 HTTP 暴露出去客户端通过http://内网IP/路径访问。服务选型上httpd 和 nginx 都能干纯静态文件服务没什么性能差异。我一般看机器上已经装了什么如果系统里已经有 httpd很多内网机器为了别的用途装了就顺水推舟用它干净的机器我会用 nginx配置更简洁。以 httpd 为例yum install -y httpd systemctl enable --now httpd # 把仓库目录挂到 Web 根目录下 ln -s /data/repo /var/www/html/repo # SELinux 标签开着 SELinux 必须做 chcon -R -t httpd_sys_content_t /data/repo # 持久化规则推荐 semanage fcontext -a -t httpd_sys_content_t /data/repo(/.*)? restorecon -Rv /data/repo # 防火墙 firewall-cmd --permanent --add-servicehttp firewall-cmd --reloadln -s做软链接的时候要注意httpd 默认配置里FollowSymLinks是开的但 SELinux 对链接目标的标签校验依然会做所以chcon这一步不能省。nginx 的配置更直接改一下root就行server { listen 80; server_name _; root /data/repo; autoindex on; location / { try_files $uri $uri/ 404; } }autoindex on打开目录列表调试的时候能直接在浏览器里看目录结构非常方便。生产环境如果不想暴露文件列表把它关掉即可。另外还要注意HTTP 服务跑起来之后验证不能只在服务端curl localhost一定要从另一台内网机器上curl目标地址。我遇到过一次服务端一切正常、客户端死活连不上最后发现是中间的网络策略只放行了特定网段服务端自己的回环请求完全测不出来。5.2 客户端的 repo 配置服务端搞定后客户端只需要把baseurl从file://换成http://[corp-base] nameCorp Base Repository baseurlhttp://10.10.20.30/repo/base enabled1 gpgcheck1 gpgkeyhttp://10.10.20.30/repo/RPM-GPG-KEY metadata_expire3600 [corp-extras] nameCorp Extras Repository baseurlhttp://10.10.20.30/repo/extras enabled1 gpgcheck0 metadata_expire3600metadata_expire控制元数据多久算过期。默认值通常是 6 小时21600 秒内网环境下可以调小一点比如 3600 秒这样服务端加了新包客户端一小时内就能看到。如果是file://这个值设多少都行元数据读取基本是瞬时的。还有一个内网专属的优化点CentOS 7 上默认装了fastestmirror插件它的作用是测速选最快的镜像站。内网环境下它只会白白浪费时间甚至卡住。关掉它vim /etc/yum/pluginconf.d/fastestmirror.conf # enabled0或者直接yum remove yum-plugin-fastestmirror。CentOS 8 的 dnf 没有这个插件不用管。5.3 批量下发和版本锁定机器一多一台台改/etc/yum.repos.d/不现实。做法是把 repo 文件做成一个模板用 scp 循环或者直接用 Ansible# 简单粗暴的循环 for ip in $(cat /tmp/hosts.txt); do scp /tmp/corp.repo root$ip:/etc/yum.repos.d/ ssh root$ip yum clean all yum makecache doneAnsible 版本更清爽- hosts: intranet_servers tasks: - name: 下发 repo 配置 copy: src: files/corp.repo dest: /etc/yum.repos.d/corp.repo mode: 0644 - name: 清理缓存 command: yum clean all - name: 重建缓存 command: yum makecache版本锁定是另一个内网运维必须掌握的能力。生产环境最怕的就是yum update一跑某个关键包的版本无预警升级把应用搞挂。yum-plugin-versionlock就是干这个的yum install -y yum-plugin-versionlock yum versionlock add httpd-2.4.6-97.el7.centos yum versionlock list yum versionlock delete httpd-2.4.6-97.el7.centos锁定之后yum update会跳过这个包yum remove也会提示。把锁定列表存进版本库换机器的时候照着恢复版本一致性就有了保障。如果不想让客户端意外执行更新还可以在 repo 段落里直接把gpgcheck之外的更新路径断掉——更彻底的办法是只提供一个不含新版本的仓库让 yum 根本看不到新包。这是最原始也最可靠的手段。6. 踩过的坑报错速查与排查思路6.1 报错速查表下面这张表是我这些年反复遇到过的报错基本覆盖了 90% 的场景报错信息常见原因排查方向Cannot find a valid baseurl for repo路径写错、ISO 没挂载、网络不通先ls挂载点再curl http://地址/repodata/repomd.xmlrepomd.xml: [Errno 256]baseurl 指向的目录里没有 repodataCentOS 8 记得加/BaseOS和/AppStreamGPG check FAILEDkey 文件路径错、key 与包不匹配、系统时间不对先查date再核对 gpgkeyFailed to download metadata for repo元数据损坏或服务不可达服务端手动curl一次看 repomd.xml 能否返回There are no enabled repos文件后缀不是.repo或enabled0yum repolist all看状态Package X requires Y依赖包没收集全用repotrack重新拉完整依赖树No more mirrors to try所有 baseurl 都失败逐个curl测试多半是防火墙装包速度极慢fastestmirror 在测速禁用该插件6.2 缓存、元数据和系统时间前三类问题的排查里有两个隐藏的坑值得单独说。第一个是缓存。内网源的配置改过之后如果不执行yum clean allyum 会继续用旧缓存里的元数据表现就是我明明加了新包yum list就是看不到。完整的清理流程是yum clean all rm -rf /var/cache/yum/* yum makecacheCentOS 8 换成/var/cache/dnf/。这一步在排查任何元数据不一致的问题时都应该无条件先做一遍成本极低收益极高。第二个是系统时间。GPG 签名校验会检查证书的有效期如果内网机器的系统时间比实际时间晚了很多虚拟化环境里很常见会直接报签名校验失败而且报错信息完全不提时间的事非常误导。排查 GPG 问题时先敲一个date比什么都快。date timedatectl status内网没有 NTP 的话时间漂移是很常见的问题。可以在搭源的时候顺手把时间也校一遍。6.3 依赖地狱到底怎么破自建仓库最头疼的就是依赖不全。表现是装 A 的时候报A requires B装上 B 又报B requires C而且这些 B、C 在镜像里都没有。正确的破法不是一个个去补而是回到源头重新收集# 在能出网的机器上 repotrack -a x86_64 -p /tmp/full-deps 目标包名把依赖树一次性拉全。如果是已经装了一部分的情况可以先看看到底缺什么yum deplist 包名 rpm -qpR /path/to/package.rpmrpm -qpR是查看单个 RPM 文件的依赖声明不依赖任何仓库非常快。批量检查一批 RPM 的依赖可以写个循环for f in /tmp/rpms/*.rpm; do echo $f rpm -qpR $f 2/dev/null | grep -v rpmlib donegrep -v rpmlib是为了过滤掉rpmlib(...)这类由 RPM 自身提供的能力不是真正的依赖看多了干扰判断。如果实在收不齐可以用--skip-broken让 yum 跳过装不上的包但这只是掩盖问题能不这么干就别这么干。6.4 几条个人经验文档里通常不会写别把 ISO 解压出来当仓库用。有人觉得挂载麻烦用unsquashfs或者直接把 ISO 的内容拷到硬盘上再建源理论上可行但解压过程会丢掉权限位和符号链接某些包会出问题。老老实实mount -o loop。检查 ISO 里的 Packages 数量。挂载完ls /mnt/iso/Packages | wc -lCentOS 7 DVD 正常在 4000 以上。如果只有几百个说明你拿到的是 Minimal 版做不了源。这个检查只需五秒能省下后面半小时的困惑。同一台机器上不要同时挂两个不同版本的 ISO 当源。有同事为了图方便把 CentOS 7 和 CentOS 8 的 ISO 都挂上两个源同时 enabled。结果是 yum 求解器在一个混合的包里挑版本装出来的组合根本不兼容。多个版本一定要分目录、分 repo id、并且用enabled0把不用的关掉。给仓库目录留足 inode。大量小 RPM 文件对 inode 消耗比想象中大df -i提前看一眼别等仓库建到一半报No space left on device结果空间还有富余。把构建仓库的整个过程脚本化。从挂载、建目录、拷包、createrepo 到配置 repo 文件写成一个脚本下次遇到新机器直接跑。我自己的脚本大概 80 行覆盖了 CentOS 7、Rocky 9 两种分支实际用起来比翻文档快得多。而且脚本本身就是最好的文档参数和路径一目了然。内部源一定要做版本记录。每次往仓库里加包在Packages同级写一个CHANGELOG记录时间、包名版本、来源、添加人。半年后出问题需要追溯某个包的来源时这个文件能救命。最后分享一个我在实际项目里用顺手的小技巧把仓库的构建和校验拆成两个脚本构建脚本只负责生成校验脚本每次跑一遍createrepo_c --update之后再执行一次yum clean all yum makecache yum list available | wc -l把包数量和上一次的记录对比。数量突然从 3200 掉到 800基本就是有人的操作把 Packages 目录清空了一部分能第一时间发现。内网环境下的很多问题都不是技术难度问题而是没人盯着就慢慢坏掉的问题加个自动对比就能解决。