
1. 为什么说镜像源和自建仓库是软件管理的两条腿干运维这行久了你会发现一个规律凡是没有内网软件源的机房迟早会在某个深夜被装不上包这件事狠狠教育一次。我经历过的最典型场景是这样的——新上线一台 CentOS 7 服务器yum 装个 nginx依赖解析完开始下载速度 20KB/s装到一半超时重来又是超时。到最后要么挂着代理慢慢磨要么直接拷离线 rpm 包手动 rpm -ivh 逐个装依赖地狱走一遍人都麻了。Epel 镜像配置解决的就是这个问题的前半段。Epel 是 Extra Packages for Enterprise Linux 的缩写由 Fedora 社区维护给 RHEL 系系统CentOS、Rocky、AlmaLinux 都算提供了大量默认源里没有的软件包比如 nginx、redis、htop、iftop 这些老面孔全在里面。默认官方的 Epel 源在海外国内访问速度极不稳定所以第一件事就是把它指到国内镜像站比如阿里云、清华 TUNA、中科大的镜像。这一步做完yum 的下载速度能直接从几十 KB 拉到十几 MB体验完全不一样。网络自建软件仓库解决的是后半段而且更根本。很多生产环境是内网隔离的压根没有外网你连镜像站都访问不了还有一些场景虽然能上外网但希望所有服务器都从统一的内网源拉包便于审计、锁定版本、保障依赖一致性。这个时候就得自己搭一个 yum 仓库用 reposync 从公网源把 rpm 包和元数据同步到本地用 createrepo 生成 repodata再用 Nginx 或 HTTPD 把目录暴露出去内网机器把这个地址写进 repo 文件就能正常 yum install。整个过程不复杂但细节非常多从 GPG 签名绕过到元数据缓存过期每个坑我都踩过。这篇内容适合所有维护 Linux 服务器的人看不管是刚入门的小白还是已经写过 repo 文件的老手。镜像是基础操作仓库是进阶能力两者配合起来基本能覆盖日常 90% 的软件分发需求。后面我还会把同样的镜像思路延伸到开发语言层面讲一讲 maven 配置多个镜像仓库以及 conda 和 pip 配置镜像地址的写法因为这几个工具在日常开发机上的网络问题同样让人头疼。2. Epel 镜像配置十分钟让 yum 提速2.1 安装 epel-release 的两种姿势配置 Epel 源的第一步是安装 epel-release 这个包它本身就是一个 rpm 包装完后会在 /etc/yum.repos.d/ 下生成 epel.repo 和 epel-testing.repo 两个配置文件。第一种姿势是直接用操作系统自带的源安装。CentOS 7 的 Base 源里就有 epel-release所以直接yum install -y epel-release这种方式最简单但有个前提——你当前的 base 源得能用、得够快。如果基础源都慢得不行那装这个包的过程也够呛。第二种姿势是直接从国内镜像站安装对应版本的 epel-release rpm 包例如 CentOS 7 用rpm -Uvh https://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm用 rpm 安装的好处是绕开了 yum哪怕当前所有源都是坏的也能装上。装完之后你去看 /etc/yum.repos.d/epel.repo里面 baseurl 默认指的还是官方地址 download.fedoraproject.org所以还得改这就到了第二步。2.2 修改 baseurl 指向国内镜像源这一步是整个 Epel 镜像配置的核心操作。编辑 /etc/yum.repos.d/epel.repo把 [epel] 这一段的 baseurl 改成国内镜像地址mirrorlist 那一行直接注释掉。以阿里云镜像站为例修改后的内容长这样[epel] nameExtra Packages for Enterprise Linux 7 - $basearch baseurlhttps://mirrors.aliyun.com/epel/7/$basearch enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7$basearch 变量会自动替换成 x86_64 或者 aarch64这样一份配置在不同架构的机器上都能用。为什么不直接用 mirrors.aliyun.com/epel 这个泛域名而要在后面加 /7/$basearch因为 Epel 仓库是按系统大版本和架构分目录组织的路径指向具体版本才能让 yum 正确找到 repodata 目录。同理如果是 Rocky Linux 9 或者 AlmaLinux 9就把路径改成 epel/9/Everything/$basearch配置内容在 epel 官方文档里都有照着改就行。改完配置文件记得执行yum clean all yum makecache这两条命令的意思是清理本地缓存的元数据然后重新下载仓库的包索引。很多新手改完源不执行导致 yum 还在用旧的缓存数据然后跑来问为什么我改了源还是慢十有八九就是这个原因。2.3 验证效果与注意事项验证配置是否生效最简单的办法是yum repolist这条命令会列出所有已启用的仓库以及每个仓库的包数量。之前没有 Epel 的时候这个列表里只有 base、extras、updates配置之后会多出一个 epel包数量通常在几万个左右。能看到这个数字说明镜像配置已经生效。再进一步验证下载速度可以装个稍微大点的包试试yum install -y htophtop 是 Epel 里的经典软件包下载的时候留意一下 yum 输出的下载速度如果是几 MB/s 而不是几十 KB/s就说明镜像源已经起效了。这里有一个容易忽略的点gpgcheck 要不要关掉。默认配置里 gpgcheck1意味着 yum 会校验包的 GPG 签名Epel 的签名公钥由 epel-release 这个包带到了 /etc/pki/rpm-gpg/ 下。如果你用 rpm 方式安装 epel-release有时候公钥没导入完全yum 会报Public key for xxx is not installed的错误。解决方法是手动导入rpm --import https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-7我个人不建议为了省事把 gpgcheck 改成 0尤其是生产环境。包签名校验是软件供应链安全里非常基础的一环一旦关掉等于告诉攻击者这个服务器可以随便投毒完全没有必要省这一步。3. 网络自建软件仓库从零搭一个内网 yum 源3.1 仓库规划与目录结构自建仓库之前先想清楚三个问题你的仓库要给谁用需要放哪些发行版的包架在哪台机器上我见过太多人上来就同步整个 CentOS 7 的全部仓库——base、extras、updates、epel 全要加起来几百 GB同步了一晚上最后发现内网机器其实只需要其中几个包。所以第一步是明确需求控制范围。仓库目录结构建议跟公网源的布局保持一致。比如你维护的是 CentOS 7 x86_64 的机器就按下面的方式组织mkdir -p /var/www/repos/centos/7/os/x86_64 mkdir -p /var/www/repos/centos/7/extras/x86_64 mkdir -p /var/www/repos/centos/7/updates/x86_64 mkdir -p /var/www/repos/epel/7/x86_64这样的目录结构有个好处客户端的 repo 文件可以直接复用公网 repo 文件的路径格式只是把 baseurl 里的域名换成内网域名改动量最小。至于仓库承载机器建议选择一台磁盘空间充裕、最好是 SSD 的服务器因为后面 reposync 同步大量小文件时IO 性能直接决定同步耗时。网络方面这台机器需要能访问外网镜像站内网其他机器则只需要访问它。3.2 reposync 同步与 createrepo 生成元数据仓库目录建好之后第二步就是把公网源的 rpm 包同步下来。这里用的核心工具是 reposync它由 yum-utils 提供先安装yum install -y yum-utils createrepo同步 base 仓库reposync -r base -p /var/www/repos/centos/7/os/x86_64 --download-metadata这条命令的意思是从启用的源里找到名为 base 的仓库把里面的所有 rpm 包下载到指定目录并同时下载仓库的元数据。但这里有个需要注意的坑reposync 同步的是你本机配置的 base 源对应的内容也就是说本机的 /etc/yum.repos.d/CentOS-Base.repo 得先指向国内镜像源否则它会去连官方源速度感人。同步完成之后再用 createrepo 生成或更新 repodatacreaterepo /var/www/repos/centos/7/os/x86_64createrepo 做的事情是扫描目录下所有 rpm 包分析每个包的名称、版本、依赖关系、提供的能力最终生成 repodata/ 目录里面有 primary.xml.gz、filelists.xml.gz、other.xml.gz 这些元数据文件。yum 客户端安装软件时就是先下载这些元数据解析出依赖树再去仓库里找对应的 rpm 包。所以元数据是仓库的灵魂如果漏掉这一步客户端会报Failed to synchronize cache for repo或者does not appear to be a valid repository之类的错。如果你要维护的是 Epel 仓库同步命令改成reposync -r epel -p /var/www/repos/epel/7/x86_64 --download-metadata createrepo /var/www/repos/epel/7/x86_64注意 Epel 仓库的包数量比 base 大很多首次同步建议先用 --newest-only 参数只同步每个包的最新版本能省不少时间和磁盘reposync -r epel -p /var/www/repos/epel/7/x86_64 --download-metadata --newest-only后面做增量更新的时候再用不带参数的完整同步或者按需同步。3.3 用 Nginx 承载仓库并配置客户端仓库目录和元数据就位后需要用一个 HTTP 服务把目录暴露出去。Nginx 和 HTTPD 都可以我用 Nginx 更多一些原因是轻量、配置简单、并发表现好。配置文件server { listen 80; server_name repo.example.local; root /var/www/repos/; autoindex on; autoindex_exact_size off; autoindex_localtime on; location / { index index.html; } }这里几个配置项分别解释一下root 指定了仓库目录所在的根路径客户端访问 http://repo.example.local/centos/7/os/x86_64/repodata/repomd.xml 时Nginx 会在物理路径 /var/www/repos/centos/7/os/x86_64/repodata/repomd.xml 找到这个文件。autoindex on 很重要开着之后浏览器访问目录能看到文件列表排查问题方便很多尤其当你怀疑某个 rpm 包没同步过来的时候直接浏览器点进去看就完事了。autoindex_exact_size off 是让目录列表显示人类友好的文件大小MB、GBautoindex_localtime on 是让文件时间显示为本地时区这两个都是体验优化项不是必须的。配置完成后启动 Nginxnginx -t systemctl start nginx systemctl enable nginx然后在内网客户端机器上在 /etc/yum.repos.d/ 下新建一个 local.repo[local-base] nameLocal Base Repository baseurlhttp://repo.example.local/centos/7/os/x86_64 enabled1 gpgcheck0 [local-epel] nameLocal EPEL Repository baseurlhttp://repo.example.local/epel/7/x86_64 enabled1 gpgcheck0这里我把 gpgcheck 设置为 0是因为自建仓库里的包都是从公网镜像站同步过来的默认不带内网自己的签名公钥开着 gpgcheck 会直接导致校验失败。这是自建仓库初期最容易踩的坑之一。后续如果要开签名校验需要统一给仓库下的所有包重新用内网自己的 GPG 私钥签名并在客户端导入对应的公钥这属于进阶操作后文会展开讲。配置完成后客户端执行yum clean all yum makecache yum repolist看到 local-base 和 local-epel 两个仓库出现在列表里并且能正常 yum install 软件包自建仓库就正式跑起来了。说到客户端这边还有个小细节如果内网有 DNS 解析服务把 repo.example.local 解析到仓库服务器 IP 就行如果没有 DNS客户端 repo 文件里可以直接写 IPbaseurlhttp://192.168.1.100/centos/7/os/x86_644. 仓库进阶增量同步、GPG 签名与优先级控制4.1 增量同步方案与周期性更新仓库搭好只是第一步持续维护才是正经事。公网源里的软件包会持续更新安全补丁、bugfix 不断发布内网仓库需要跟上节奏。增量同步的本质是每次只拉取公网源新增或变化的 rpm 包而不是把整个仓库重新同步一遍。reposync 本身就支持增量同步——它运行时会扫描本地目录已有的 rpm 包跳过已存在的文件只下载缺失的。所以直接用原命令重新执行一遍即可reposync -r updates -p /var/www/repos/centos/7/updates/x86_64 --download-metadata createrepo --update /var/www/repos/centos/7/updates/x86_64注意增量更新的关键点是 createrepo 要加 --update 参数。这个参数让 createrepo 在已有 repodata 的基础上做增量更新而不是每次重新扫描全部 rpm 包。仓库包数量很大的时候不加 --update 的话每次生成元数据都要扫描数十万个文件耗时会从几分钟飙到几十分钟完全没有必要。周期性更新我推荐用 crontab 定时任务每天跑一次避开业务高峰期0 4 * * * /usr/bin/reposync -r updates -p /var/www/repos/centos/7/updates/x86_64 --download-metadata /var/log/reposync.log 21 /usr/bin/createrepo --update /var/www/repos/centos/7/updates/x86_64 /var/log/reposync.log 21这里踩过的一个坑是reposync 运行过程中如果被中断比如服务器重启可能会导致部分 rpm 包下载一半留在目录里文件名是 .rpm.tmp 或者带部分下载标记。createrepo 扫描到这种文件可能会报错。所以 cron 脚本里最好加一步清理find /var/www/repos/ -name *.tmp -delete4.2 给自建仓库打上 GPG 签名前面提到自建仓库用 gpgcheck0 绕过了签名校验这在初期没问题但如果你所在的公司有安全合规要求或者仓库会被多个团队共用建议把签名补上。思路是这样的用自己的 GPG 私钥给仓库里的每个 rpm 包重新签名然后把对应的公钥分发到所有客户端客户端 repo 文件里配置 gpgkey 指向这个公钥文件并保持 gpgcheck1。先生成 GPG 密钥对gpg --gen-key过程中会让你输入姓名和邮箱作为密钥标识建议写成团队公共标识而不是个人名字比如 IT Operations opsexample.local 。接下来对仓库下的 rpm 文件批量签名find /var/www/repos/ -name *.rpm -exec rpm --addsign {} \;这条命令会比较耗时因为每个 rpm 包都要做一次签名计算。签名完成之后导出公钥gpg --export --armor /var/www/repos/RPM-GPG-KEY-LOCAL然后把公钥文件放到客户端机器上并导入rpm --import /var/www/repos/RPM-GPG-KEY-LOCAL客户端的 local.repo 改成[local-base] nameLocal Base Repository baseurlhttp://repo.example.local/centos/7/os/x86_64 enabled1 gpgcheck1 gpgkeyhttp://repo.example.local/RPM-GPG-KEY-LOCAL改完执行 yum clean all yum makecache正常的话 yum 会提示导入公钥并继续工作。这里有个血泪教训如果客户端之前已经以 gpgcheck0 的方式缓存过该仓库的元数据改成 gpgcheck1 之后必须清缓存重新 makecache否则 yum 可能还会用旧的缓存数据跳过校验。这个问题排查起来很隐蔽症状是 gpgcheck 明明配了 1但安装包时根本不校验签名。4.3 多仓库优先级管理自建仓库之后内网机器上很可能同时存在多个仓库系统自带的 base/updates、自建的 local-base、local-epel可能还有第三方软件商的仓库。这时候就涉及优先级问题——同一个软件在多个仓库里都有yum 默认装哪个如果不同仓库的包版本有差异可能装出意想不到的结果。解决办法是装 yum-plugin-prioritiesyum install -y yum-plugin-priorities然后在每个 repo 文件里加一个 priority 字段数字越小优先级越高。比如[local-base] nameLocal Base Repository baseurlhttp://repo.example.local/centos/7/os/x86_64 enabled1 gpgcheck1 priority1 [local-epel] nameLocal EPEL Repository baseurlhttp://repo.example.local/epel/7/x86_64 enabled1 gpgcheck1 priority2如果你的内网仓库里同步的本来就是和官方源一致的包其实优先级冲突的概率不大。真正需要关注的是那种把多个第三方 repo例如 EPEL、Remi、RPMFusion都装上的机器这些仓库之间包的版本差异很大优先级配不好装 PHP 或 MySQL 的时候经常出现诡异冲突。我的经验是系统自带源优先级最高1自建的业务仓库其次2第三方社区源比如 Remi优先级最低10这样保证官方安全更新不被覆盖同时需要新版本软件时也可以显式指定从某个仓库安装。说到显式指定安装这是面对多仓库冲突时最直接的兜底手段yum install --enablerepolocal-epel nginx或者只从某个仓库安装yum install nginx --disablerepo* --enablerepolocal-epel5. 镜像思路的横向迁移Maven、Conda、Pip 仓库配置Epel 和 yum 的镜像配置只是软件管理的一个侧面。只要做开发你会发现 maven、conda、pip 这几个包管理工具在公共网络环境下同样面临速度慢、连接超时的老问题而解决思路其实一脉相承把默认的官方源指向国内镜像或者搭一个内网统一代理仓库。5.1 Maven 配置多个镜像仓库Java 开发对 Maven 中央仓库的依赖很重而 central 仓库在国外国内访问奇慢无比。Maven 的镜像配置在 settings.xml 的 段里最常用的做法是配置阿里云镜像mirrors mirror idaliyun-central/id mirrorOfcentral/mirrorOf nameAliyun Central Mirror/name urlhttps://maven.aliyun.com/repository/central/url /mirror mirror idaliyun-public/id mirrorOfpublic/mirrorOf nameAliyun Public Mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrorsmaven 配置多个镜像仓库时最核心的是理解 mirrorOf 标签的含义。mirrorOf 的值决定了这个镜像接管哪些仓库的请求常见的取值有mirrorOf 取值含义central只拦截中央仓库的请求*拦截所有仓库的请求external:*拦截所有外部仓库的请求但 localhost 和 file:// 协议的除外repo1,repo2拦截指定仓库 id 的请求这里有个很多新手容易搞错的点mirror 不是仓库它只是流量的转发器。你配置了一个 mirror不代表你的项目就能直接从镜像拉取所有依赖。mirrorOf 的匹配粒度必须和项目实际使用的仓库 id 对上。比如项目 pom 里配置了自定义仓库 my-nexus 指向公司私服而你 settings.xml 里的 mirror 只写了 mirrorOfcentral那么这个自定义仓库的请求根本不会走镜像还是直连公司私服。想要所有依赖都走同一个镜像就得把 mirrorOf 写成 external:*或者用逗号列出所有仓库 id。另一个实际项目里很常见的场景是同时配置多个镜像如果第一个镜像网络超时Maven 会自动尝试第二个镜像。这需要每个镜像的 mirrorOf 使用相同值Maven 解析依赖时会按配置顺序逐个尝试。比如阿里云镜像挂了就自动切换华为云镜像mirror idhuawei-central/id mirrorOfcentral/mirrorOf urlhttps://repo.huaweicloud.com/repository/maven//url /mirror注意配置顺序排在前面的镜像会被优先使用。5.2 Conda 配置镜像地址Conda 的镜像配置放在用户目录下的 .condarc 文件里。用 conda config 命令可以直接生成和修改conda config --add channels https://mirrors.aliyun.com/anaconda/pkgs/main/ conda config --add channels https://mirrors.aliyun.com/anaconda/pkgs/r/ conda config --set show_channel_urls yes生成的 .condarc 内容长这样channels: - https://mirrors.aliyun.com/anaconda/pkgs/r/ - https://mirrors.aliyun.com/anaconda/pkgs/main/ - defaults show_channel_urls: trueConda 的镜像配置和 Maven、Pip 最大的不同在于多了一个 defaults 的处理。默认情况下即使你添加了新的 channeldefaults 还是存在于 list 里的而且 conda 解析包的时候会按 channels 列表顺序从上到下查找。这里最常用的优化是把 defaults 从 channels 列表里删掉只保留镜像源channels: - https://mirrors.aliyun.com/anaconda/pkgs/main/ - https://mirrors.aliyun.com/anaconda/pkgs/r/实际上清华 TUNA 的镜像站点上有一段专门针对 Anaconda 的 .condarc 配置说明它调整的不只是 channels 列表还涉及 default_channels 和 custom_channels 两个字段。TUNA 版本配置的意义在于conda 下载包时不仅会访问 channels 里列出的地址还会访问一些硬编码的默认地址比如 repo.anaconda.com这两个字段是为了把这些硬编码地址也重定向到镜像站。但在阿里云镜像的体系下直接把 channels 指向镜像路径就够了这也是我用阿里云镜像更省心的原因因为配置层面更简单。Conda 配置完记得执行conda clean -i conda infoconda info 的输出里能看到当前配置的 channel 列表确认镜像地址已经生效。踩过的坑是改了 .condarc 之后新开终端失效看起来像是没生效其实是 shell 缓存了旧的 PATH 或者 conda 配置没重新加载重新 source ~/.bashrc 或者干脆重开一个终端就好了。5.3 Pip 配置镜像地址Pip 的镜像地址配置是最简单的。可以在命令行加 -i 参数临时指定pip install requests -i https://mirrors.aliyun.com/pypi/simple/也可以写进配置文件长期生效。Linux 下配置文件路径是 ~/.pip/pip.conf或者 ~/.config/pip/pip.confWindows 下是 %APPDATA%\pip\pip.ini[global] index-url https://mirrors.aliyun.com/pypi/simple/ [install] trusted-host mirrors.aliyun.com这个配置有两部分global 段指定了默认的索引源地址install 段的 trusted-host 用于声明信任的主机名。为什么要写 trusted-host因为早期一些镜像源的 HTTPS 证书链不完整或者在某些 Python 版本下 ssl 校验会报错加上这个字段可以绕过主机名校验。不过现在阿里云、清华、豆瓣的镜像源证书都是好的正常情况下不需要这个字段。如果你的环境是内部自建的 PyPI 仓库又没有配置合法的 SSL 证书trusted-host 才真正派得上用场。Pip 的多个镜像配置不如 Maven 那么灵活它没有原生的镜像切换逻辑只能指定一个 index-url另一个常见方案是用 --extra-index-url 追加备用源[global] index-url https://mirrors.aliyun.com/pypi/simple/ extra-index-url https://pypi.org/simple/这样 pip 会优先从阿里云找包找不到的再去 PyPI 官方源找。这个配置在混合网络环境下部分镜像源包不全非常实用。5.4 什么情况下该上 Nexus 或 Artifactory镜像配置是把外部流量引入国内镜像站解决的是远的问题自建 yum 仓库解决的是无外网的问题。但如果你的团队规模到了几十上百人各种语言生态的包都在用再逐个去配置镜像方案就有点零散了。这时候合理的做法是部署一个统一的二进制仓库管理工具最常见的是 Sonatype Nexus 3 或者 JFrog Artifactory。Nexus 3 支持代理 yum、apt、pypi、maven、npm、docker 等几乎所有主流包格式。运维端只要配好一个 proxy 类型的 yum 仓库内网所有 CentOS 机器的 repo 文件指向 Nexus 地址外网连接只需要 Nexus 一台机器有其他机器全部走内网。开发端同理Maven settings.xml 里把 mirror 指向 Nexus public 组pip.conf 里 index-url 指向 Nexus pypi proxy一套系统解决所有问题。我会在什么情况下建议上 Nexus 而不是继续用手工 reposync createrepo 的方案当出现以下任一信号时需要给多个团队分别开账号做权限隔离的时候需要把构建产物同时提供给开发、测试、生产多个环境使用的时候需要统一审计谁在什么时候拉取了哪个包的时候。在这些场景下手工仓库方案会越维护越累而 Nexus 的开源版基本够用不需要额外成本。6. 常见问题排查与踩坑实录6.1 元数据无法获取yum 报Failed to synchronize cache这个问题排第一因为它真的容易遇到而且排查链条往往比较长。现象是执行 yum makecache 时直接报错后面跟着 Failed to synchronize cache for repo。排查步骤从下往上走。先确认网络通不通curl -I http://repo.example.local/centos/7/os/x86_64/repodata/repomd.xml如果 curl 都不通查 Nginx 进程状态、防火墙、监听端口systemctl status nginx ss -lntp | grep 80如果 curl 通了但 yum 还是报错十有八九是 repodata 目录里的文件本身有问题最常见的是 createrepo 生成元数据不全或者同步源和生成元数据的目录不匹配。这时候直接在仓库服务器上重新生成一次元数据createrepo --update /var/www/repos/centos/7/os/x86_64还有一个隐蔽原因部分 yum 版本要求仓库 URL 必须能直接访问到 repodata 目录下的文件如果 Nginx 配置里加了 location 规则把某些路径拦截了也会导致元数据下载失败。6.2 GPG 签名校验失败的三种情况GPG 校验失败的错误信息通常长这样Public key for xxx.rpm is not installed或者 package xxx.rpm is not signed。我在第 4 节讲过要给仓库统一签名实际操作中遇到过三种情况。第一种是公钥没导入客户端。在客户端执行 rpm --import 公钥地址即可解决。第二种是仓库下有部分旧包没重新签名导致校验失败处理方法是给所有 rpm 重新执行 rpm --addsign或者把仓库清空重新同步。第三种最坑仓库是用 gpgcheck0 配置期间创建了元数据缓存后来改成 gpgcheck1客户端 yum 缓存没有清理。这时候不管你怎么导入公钥yum 都像没看见一样不校验签名。第三种情况的解法在第 4.2 节已经说过了yum clean all然后重新 makecache强制让 yum 重新下载仓库元数据。这里补充一个更根治的方案每次修改 repo 文件的 gpgcheck 配置或导入新公钥之后养成条件反射先执行一次 yum clean all。6.3 仓库更新后客户端装到的还是旧版本包辛辛苦苦用 reposync 同步了最新包createrepo --update 也执行了客户端 yum install 的时候装到的还是旧版本。这个问题出现的频率相当高根本原因在于客户端本地缓存的元数据还是旧的压根不知道仓库里已经有了新版本。解决方法是客户端执行 yum clean all yum makecache。如果服务器数量多逐个跑一遍太累了可以批量处理ansible all -m shell -a yum clean all yum makecache这里想强调一个运维习惯任何仓库端更新操作reposync、createrepo、Nginx 配置变更都不会自动同步到客户端的元数据缓存所以要么主动清缓存要么接受客户端最长缓存周期内的延迟。如果不想让客户端频繁全网更新元数据可以在 repo 文件里用 metadata_expire 参数控制[local-base] nameLocal Base Repository baseurlhttp://repo.example.local/centos/7/os/x86_64 enabled1 gpgcheck1 metadata_expire3600单位是秒这个配置表示元数据缓存一小时过期过期后 yum 会自动重新拉取。但对于追求及时性的安全更新场景建议还是把 metadata_expire 调小甚至不设默认行为是每次 yum 操作都会尝试检查元数据是否过期。6.4 其他高频问题速查症状可能原因解决办法yum 报 404 Not Foundbaseurl 路径写错或者仓库目录结构不匹配用浏览器访问对应 URL 检查目录和文件是否存在reposync 同步速度慢本机源还是官方地址先把 CentOS-Base.repo 和 epel.repo 的镜像配置改好再同步yum 装包时卡住不动仓库服务器 IO 紧张或 Nginx 并发连接不足检查仓库服务器磁盘 IO调大 Nginx worker_processescreaterepo 报 No such file or directory目录结构不完整repodata 目录被误删确认目录存在后重新执行 createrepo客户端显示包数量为 0enabled0 被误设或者 repo 文件格式错误检查 repo 文件的 enabled 字段和 [xxx] 段落标识yum install 报依赖冲突多个仓库存在不同版本的同名包用 yum-plugin-priorities 设置优先级或用 --disablerepo 手动指定仓库6.5 我在实际维护中的一个经验如果你准备把这套方案用在生产环境我强烈建议在动手之前先做一张仓库服务依赖图——不需要多正式就是把仓库服务器、Nginx、reposync 任务、客户端机器之间的关系画出来标注清楚每台机器依赖哪个内网域名或 IP。因为仓库服务一旦挂掉所有内网机器的 yum install 和 yum update 都会失败影响面是全局性的。我维护的其中一个内网仓库曾经因为磁盘空间写满导致 createrepo 生成元数据失败而 cron 任务没有加失败通知整整两天没人发现期间所有新装机器都无法完成 yum makecache。从那之后我养成了两个习惯一是给 /var/www/repos 单独分区避免和其他日志、临时文件争抢磁盘空间二是给 reposync 的 cron 任务加上失败检测任务执行不成功就发送告警到企业微信或者邮件。磁盘规划可以参考base 仓库约 10GBupdates 仓库按年增长约 5GBepel 仓库如果 --newest-only 约 30GB按这个量级预留空间至少 1.5 倍余量。另外还有一个小技巧createrepo 生成元数据时在仓库服务器本地先执行然后把 repodata 目录的属主改成 Nginx 运行用户通常是 nginx 或 www-data否则客户端下载元数据时可能因为文件权限不对而报 403。这个坑很容易被忽视排查起来又很费时间提前注意能省不少事。