装完一台 Rocky Linux 9 的机器,第一次敲dnf install却卡在 “Cannot download repomd.xml” 或者转上几十秒才超时,这个体验想必不少人都遇到过。Rocky Linux 9 作为与 RHEL 9 二进制兼容的下游发行版,默认仓库指向的是官方 mirrorlist 服务,在境内网络环境下经常会出现握手慢、元数据拉取失败、GPG key 下载不下来等一系列问题。同时很多人手上还有 RHEL 9 的机器,想配个在线源,结果发现照着 Rocky 的教程抄一通,dnf repolist出来一片红。这篇文章就是把这两件事放在一起讲透:Rocky Linux 怎么把在线镜像源换得干净利落,RHEL 9 的仓库体系为什么和 Rocky 不是一回事,以及在不能直接照抄的情况下有哪些稳妥的替代路线。内容面向刚接触 RHEL 系发行版的运维新手,也面向需要给一批机器做统一源配置的老手,命令、参数、报错、坑点都会给到位,你可以直接拿去改改就用。
1. 先把关系理清楚:Rocky Linux 9 和 RHEL 9 到底谁是谁
1.1 一次“装完机器没源可用”的真实场景
我之前接手过一批测试机,系统是同事用 Rocky Linux 9 的 DVD ISO 装的,装的时候没勾选网络,装完之后/etc/yum.repos.d/底下躺着一堆 repo 文件,看着挺完整,但dnf install vim-enhanced直接报错。原因很简单:ISO 安装默认生成的是指向官方 mirrorlist 的配置,而当时那台机器只配了内网 DNS,压根解析不到外网域名。后来我干脆把源换成了内网自建的仓库,一条命令下去秒装,这个问题才彻底解决。
这个场景其实很典型。很多人以为“换源”就是改个网址,实际上它牵扯到三层东西:配置文件里的地址写在哪、这个地址背后的服务能不能连上、连上之后包里带的签名能不能验过。任何一层没打通,表现都是“装不上”,但排查方向完全不同。所以真正开始改文件之前,先把 Rocky Linux 9 和 RHEL 9 的关系搞明白,会让你后面少走很多弯路。
Rocky Linux 是从 RHEL 的公开源码重新构建出来的发行版,目标是做到与对应版本的 RHEL 在二进制层面兼容,也就是同一个 RPM 包理论上在两者上都能装、都能跑。这个特性带来的直接好处是:为 RHEL 9 编译的第三方软件,在 Rocky Linux 9 上基本不用改就能用。但兼容的是包本身,不是仓库服务。这两者之间的差别,就是很多人踩坑的起点。
1.2 二进制兼容 ≠ 仓库可以互换
一个很常见的误解是:既然 Rocky Linux 9 和 RHEL 9 这么像,那我直接把 RHEL 9 的机器指向 Rocky 的源不就行了?从技术上说,短期内确实可能有部分包能装上,但这属于典型的“能跑起来不代表没问题”。真正的隐患有三处。
第一处是签名体系不同。两个发行版各自有自己的 GPG 密钥,Rocky Linux 9 的密钥放在/etc/pki/rpm-gpg/目录下,文件名里带 Rocky 字样;RHEL 9 的包签名用的是另一套。你把 RHEL 9 的gpgcheck开着指向 Rocky 的源,校验必然失败;把gpgcheck关掉,等于把所有包的完整性校验都扔了,这在任何有安全要求的环境里都不能接受。
第二处是包版本策略不同。虽然源码同源,但两个发行版的更新节奏、补丁回溯策略、包的重构建时间点都不完全一致。混合使用的时候,很容易出现依赖解析出一个“半新半旧”的组合,系统看起来正常,跑到某个特定功能时才炸,而且极难定位。
第三处是元数据格式和仓库划分的细微差异。Rocky Linux 9 的仓库拆分方式(BaseOS、AppStream、CRB、extras 等)和 RHEL 9 大体对应,但具体到某个包落在哪个仓库里,偶尔会有出入。依赖链一旦跨仓库断裂,dnf给出的报错往往指向一个看起来毫无关系的包名,新手很容易被带偏。
所以我的基本立场很明确:Rocky Linux 9 就用 Rocky 的源,RHEL 9 就走 RHEL 自己的分发渠道,两边不要互相串。下面所有关于“换源”的操作,都是在各自体系内部做优化,而不是跨体系搬运。
1.3 认准版本号和仓库代号,别在第一步就错
Rocky Linux 9 的仓库配置文件里大量用到了两个变量:$releasever和$basearch。前者是发行版主版本号,后者是 CPU 架构(x86_64、aarch64 等)。这里有个细节值得单独拎出来说:在 Rocky Linux 9 上,$releasever通常解析成9,而不是 9.4 这种带小版本号的形式。
为什么这么设计?因为小版本升级(比如 9.4 升到 9.5)不应该导致仓库地址失效。如果地址里写死了小版本号,每次系统更新之后你还得手动改一遍源,非常容易忘。用主版本号的好处是,仓库地址在小版本之间保持稳定,你换个镜像站也只需要改域名部分。
想确认自己的$releasever到底是多少,可以这样查:
# 查看 dnf 解析出来的变量值 dnf -q --eval '%{releasever}' # 或者间接看:列出仓库时注意 repo id 后面跟随的版本 dnf repolist -v | grep -i "Repo-baseurl" | head -5另外要注意仓库的代号。Rocky Linux 9 的核心仓库是BaseOS和AppStream这两个,前者放的是系统运行必需的基础包(内核、glibc、systemd 这类),后者放的是应用层软件和模块流(各种语言运行时、Web 服务、数据库客户端之类)。再往上是CRB(CodeReady Builder,很多开发库和头文件在这里)、extras、以及highavailability、resilientstorage这类特殊用途仓库。
新手最容易犯的错是只配了 BaseOS 就以为万事大吉,结果装个开发包死活找不到。所以第一步不是急着改地址,而是先把当前系统上到底有哪些 repo 文件、每个文件里有哪些 section 摸清楚:
# 列出所有 repo 文件 ls -l /etc/yum.repos.d/ # 列出所有 section 名字(方括号里的内容) grep -h '^\[' /etc/yum.repos.d/*.repo # 看某个仓库当前是否启用、指向哪里 dnf repolist --all跑完这三条,你对自己的仓库版图就有底了。dnf repolist --all会输出所有仓库,enabled 那一列是enabled还是disabled一目了然。这一步花两分钟,后面能省两小时。
2. 换源的整体思路:为什么我不建议上来就改 baseurl
2.1 官方源、公共镜像站、内网源,三条路怎么选
“换源”这个词听起来简单,实际上有三种完全不同性质的选择,各自适用的场景差别很大。
官方源指的是 Rocky Linux 官方维护的镜像分发体系。它的优势是元数据权威、更新最及时、仓库划分最标准;劣势是对境内网络不够友好,元数据文件和包体下载经常慢到让人怀疑人生。默认安装出来的配置基本都是走这条路,也就是为什么很多人第一次装包就卡住。
公共镜像站是各类组织提供的 Rocky Linux 全量镜像,路径结构和官方保持一致,只是服务节点离你更近。它的优势是速度快、覆盖全,劣势是同步可能有延迟(通常是几小时级别),而且不同镜像站的完整度、稳定性参差不齐,你得挑一个靠谱的长期用。选择的时候建议优先看是否提供完整的 BaseOS、AppStream、CRB、extras 四件套,只镜像了 BaseOS 的站点基本没法用。
内网源是自己搭的仓库服务,从公共镜像站或者官方同步下来,用 HTTP 服务在内网分发。它的优势是完全可控、速度快到飞起、不受外网波动影响,适合机器数量多或者网络环境受限的场景;劣势是需要额外维护一台机器和定期同步任务。我个人的经验是:机器超过十台,就值得搭内网源,省下的时间和排查成本远超搭建成本。
这三条路的选择逻辑可以用一张表概括:
| 方案 | 适合场景 | 前期成本 | 长期维护成本 | 速度表现 |
|---|---|---|---|---|
| 官方源 | 临时测试、能直连外网 | 极低 | 低 | 波动大 |
| 公共镜像站 | 单机或少量机器 | 低 | 低 | 较好 |
| 内网自建源 | 十台以上、内网隔离环境 | 中 | 中 | 最好 |
对绝大多数个人和小团队场景,公共镜像站是性价比最高的选择,后面的实操也主要围绕它展开。
2.2 repo 文件里那几个字段到底在干嘛
在动手之前,把 repo 文件的字段含义吃透,比抄十份模板都管用。一份典型的 Rocky Linux 9 仓库配置长这样:
[baseos] name=Rocky Linux $releasever - BaseOS mirrorlist=https://mirrors.example.org/rocky/$releasever/BaseOS/$basearch/os/ # baseurl=https://mirrors.example.org/rocky/$releasever/BaseOS/$basearch/os/ enabled=1 countme=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9逐个说:
[baseos]是 repo id,全局唯一,dnf --enablerepo=baseos这类命令用的就是它。改名会影响脚本引用,非必要别动。name=是给人看的描述,随便写,但建议保留版本信息,方便排查时一眼认出。mirrorlist=和baseurl=是互斥的两种寻址方式,同时存在时mirrorlist优先。这个细节很多人不知道,改动的时候只改了baseurl却忘了注释mirrorlist,结果配置完全没生效,还以为是缓存问题。enabled=1表示默认启用,0表示默认不启用但可以临时用--enablerepo打开。CRB 仓库我一般设成enabled=0,需要时再开,避免默认把大量开发包暴露出来。gpgcheck=1是包签名校验开关。这个绝对不能关,关掉之后你装的每一个包都可能是被篡改过的。gpgkey=指向公钥文件,可以是本地路径(file://)也可以是远程 URL。本地路径的好处是不依赖网络,稳定性更好,建议优先用本地。
还有一个容易被忽略的字段是countme=1,它用于统计访问量,对功能没有影响,但在某些镜像站上会额外产生一个计数请求。内网环境下建议删掉,少一次无效请求。
提示:修改 repo 文件之前一定先备份。
cp -a /etc/yum.repos.d /etc/yum.repos.d.bak.$(date +%F)这一条命令能救你无数次,回滚的时候直接rm -rf再mv回来就行。
2.3 metalink 和 baseurl 的取舍
这是换源环节最值得展开讲的一个技术决策,因为它直接决定了你后续维护的省心程度。
mirrorlist和metalink都是“让客户端自己去问一组候选地址”的机制。区别在于:mirrorlist返回的是一个纯文本列表,客户端按顺序尝试;metalink返回的是带元信息的 XML,里面包含每个镜像的协议、优先级、地理位置、更新时间等字段,客户端可以更智能地选择。
而baseurl是写死一个具体地址,简单直接,没有协商过程。
那为什么不干脆全用baseurl?因为写死地址意味着你放弃了自动容灾。假设你写死了某个镜像站的地址,某天这个站点维护或者限流,你所有的dnf操作都会失败,而且报错信息不会告诉你“换个源就行”,只会告诉你“连不上”。机器一多,挨个改配置的痛苦指数直线上升。
反过来,全用metalink也有问题:境外网络环境下,获取 metalink 文件本身可能就是那个最慢的环节,有时候要等十几秒才超时失败。所以我实际的做法是折中:
- 对主仓库(BaseOS、AppStream),用固定的
baseurl指向一个我验证过的、长期稳定的镜像站,追求速度。 - 对次要仓库(CRB、extras、addons),保留
metalink,反正用得少,容错优先。 - 在内网源场景下,全部用
baseurl指向内网地址,因为内网服务的稳定性是可控的。
这个思路的核心是:稳定性由你掌控的部分用 baseurl,不由你掌控的部分用 metalink。想清楚这一点,你的源配置策略就成型了。
3. 实操:Rocky Linux 9 换国内公共镜像源
3.1 动手前必须做的三件确认
我在给任何机器换源之前,固定会做三件事,缺一件都可能白忙一场。
第一件:确认系统版本和架构。这不是多余的谨慎,而是因为 Rocky Linux 8 和 9 的仓库路径结构不完全一样,x86_64 和 aarch64 的路径里也带架构段。路径拼错了,报错是 404,看着像镜像站挂了,实际是你自己写错了。
cat /etc/rocky-release uname -m输出类似Rocky Linux release 9.5 (Blue Onyx)和x86_64,这两个信息后面拼 URL 要用。
第二件:确认网络能通到目标镜像站。直接curl -I打一下你要用的镜像站根路径,看返回码。这一步能提前排除掉 DNS 解析失败、出口被限、镜像站临时故障这几种情况。如果这里就不通,后面配什么都是白配。
curl -sI -m 8 https://mirrors.example.org/rocky/ | head -3-m 8是超时时间,8 秒足够判断连通性了,避免卡太久。
第三件:备份现有配置。前面已经提过,这里再强调一次。很多人觉得“我就改一行,不用备份”,然后一个 sed 命令写错把整个文件覆盖了,最后只能重装rocky-repos包来恢复默认配置。备份的成本是三秒钟,不备份的成本是半小时。
cp -a /etc/yum.repos.d /etc/yum.repos.d.bak.$(date +%F_%H%M)做完这三件事,才进入真正的修改环节。
3.2 两条命令完成批量替换
Rocky Linux 9 默认的 repo 文件里,地址部分通常写成mirrorlist=的形式,指向官方或某个默认镜像。批量替换的思路是:把mirrorlist行注释掉,同时在每个 section 里插入一个指向新镜像站的baseurl。
手工一个个改太慢,用sed更实际。但sed用不好很容易把文件改坏,所以我建议分两步走,每步都能验证。
第一步:先统一处理 mirrorlist 和 metalink 行。
sed -i.bak \ -e 's|^metalink=|#metalink=|g' \ -e 's|^mirrorlist=|#mirrorlist=|g' \ /etc/yum.repos.d/rocky*.repo这里用了-i.bak,sed会顺手生成一份.bak后缀的备份,比事后手动恢复方便。|作为分隔符是因为 URL 里有/,用/当分隔符得转义一大堆,容易出错。
第二步:为每个 section 补上 baseurl。这一步没法用一个通用sed搞定,因为不同 section 对应的路径不一样(BaseOS 走 BaseOS 目录,AppStream 走 AppStream 目录)。我的做法是先看清楚每个 section 名字和它应该对应的路径,再用带 section 匹配的脚本处理。
# 先看清楚有哪些 section grep -h '^\[' /etc/yum.repos.d/rocky*.repo | sort -u假设输出里有[baseos]、[appstream]、[crb]这几个,就可以针对性地插入。下面这段用awk处理,逻辑是:遇到目标 section 的头部,就在紧跟的name=行后面插入baseurl:
awk ' /^\[baseos\]/ { ins=1 } /^\[appstream\]/ { ins=1 } /^\[crb\]/ { ins=1 } /^\[/ && !/^\[(baseos|appstream|crb)\]/ { ins=0 } { print if (ins && /^name=/ && !done) { if (prev ~ /baseos/) print "baseurl=https://mirrors.example.org/rocky/$releasever/BaseOS/$basearch/os/" if (prev ~ /appstream/) print "baseurl=https://mirrors.example.org/rocky/$releasever/AppStream/$basearch/os/" if (prev ~ /crb/) print "baseurl=https://mirrors.example.org/rocky/$releasever/CRB/$basearch/os/" done=1 } if (/^\[/) { prev=$0; done=0 } } ' /etc/yum.repos.d/rocky.repo > /tmp/rocky.repo.new说实话,这段脚本对不同小版本的 repo 文件结构不一定完全兼容。如果你的目标是长期稳定,我强烈建议直接手写一份干净的 repo 文件,而不是靠脚本去改原始文件。下面就是模板。
3.3 手搓一份干净的 repo 文件模板
这是我实际在用的做法:不去改官方的rocky*.repo,而是把它们全部改名为.repo.disabled(或者挪到一个备份目录),然后新建一份rocky-custom.repo,把需要的仓库显式写清楚。好处是配置完全可控,出问题一眼能看出哪一行错了,而且系统更新时不会因为包升级把你的自定义配置覆盖掉。
[baseos] name=Rocky Linux $releasever - BaseOS baseurl=https://mirrors.example.org/rocky/$releasever/BaseOS/$basearch/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 metadata_expire=3600 [appstream] name=Rocky Linux $releasever - AppStream baseurl=https://mirrors.example.org/rocky/$releasever/AppStream/$basearch/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 metadata_expire=3600 [crb] name=Rocky Linux $releasever - CRB baseurl=https://mirrors.example.org/rocky/$releasever/CRB/$basearch/os/ enabled=0 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 [extras] name=Rocky Linux $releasever - extras baseurl=https://mirrors.example.org/rocky/$releasever/extras/$basearch/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9几个设计上的讲究:
metadata_expire=3600把元数据缓存有效期设成 1 小时。默认值通常是 6 小时或者更短,设成 3600 是在“新鲜度”和“网络请求次数”之间取的一个平衡点。内网源场景可以设成 86400,因为内网同步本身就是定时的,没必要频繁回源。
CRB 设为enabled=0是我一直坚持的习惯。CRB 仓库里有大量开发库和头文件,默认开启会让dnf search的结果变得非常嘈杂,而且某些情况下会引入非预期的包。需要编译软件的时候再dnf install --enablerepo=crb xxx,用完关掉,干净。
gpgkey用本地file://而不是远程 URL。这一点在批量部署时尤其重要:一百台机器同时去拉一个远程 key 文件,不仅慢,还可能因为限流失败导致整个安装流程中断。本地路径的路径一定要确认存在,可以先ls一下/etc/pki/rpm-gpg/目录。
路径中的$releasever和$basearch不要手动替换成具体值,让dnf自己去解析。手动写死 9.5 和 x86_64 看起来更“明确”,实际上等于把配置锁死了,小版本一升级就得改。
注意:镜像站的路径结构不是完全统一的。有的站点在域名后面直接跟
rocky/,有的会多一层rockylinux/或者其他前缀。配之前一定先用curl把拼好的 URL 打一遍,确认返回 200 再写进配置,别凭感觉拼。
3.4 刷新缓存并逐项验证
配置写完不等于生效,dnf有本地缓存机制,不清理的话它会继续用旧数据,你会以为配置没生效,然后又去改配置,越改越乱。
# 清掉所有缓存 dnf clean all # 重建缓存,这一步会实际访问每个仓库 dnf makecache # 列出启用的仓库,确认地址正确 dnf repolist -vdnf makecache是验证配置最直接的手段。它会把每个 enabled 的仓库都拉一遍元数据,哪个仓库配错了,这里立刻报错,而且报错信息里带的 URL 能直接告诉你是哪个字段写错了。
dnf repolist -v的输出比较长,但值得完整看一遍。重点看每个仓库的Repo-baseurl是不是你期望的地址,Repo-expire是不是你设的值,Repo-pkgs是不是一个合理的数字(BaseOS 通常几千个包,AppStream 也差不多,如果显示 0 那肯定有问题)。
缓存重建完之后,再做一次实际安装验证,这一步别省:
# 装一个体积小、依赖少的包,验证整条链路 dnf install -y tree # 验证能查到多版本(说明元数据完整) dnf --showduplicates list nginx | head -20--showduplicates这个参数在排查“为什么装不到某个版本”的时候特别好用。如果它列出来的版本比你预期的少,说明你的源不完整,可能是只配了 BaseOS 没配 AppStream,也可能是镜像站本身同步不全。
3.5 顺手把 EPEL 和 CRB 也安排上
Rocky Linux 9 的官方仓库覆盖了大部分基础需求,但有些常用工具(比如htop、jq的某些版本、各类 Python 第三方库的 RPM 封装)在 EPEL 里。EPEL 是面向企业级 Linux 的附加包仓库,Rocky Linux 直接兼容。
配置 EPEL 的关键是版本对应。Rocky Linux 9 对应的是 EPEL 9,装错版本会出现依赖冲突,症状是dnf报一堆nothing provides或者版本不匹配。
# 安装 EPEL 仓库配置(包名带版本号,别装错) dnf install -y epel-release # 装完之后检查一下,EPEL 的 repo 文件会出现在同一目录 grep -A5 '^\[epel\]' /etc/yum.repos.d/epel*.repo装完epel-release之后,同样建议把它的metalink改成指向你常用的镜像站,理由和主仓库一样。EPEL 的镜像路径通常是在镜像站的epel/9/Everything/$basearch/下面,配置方式跟主仓库基本一致。
CRB 仓库前面说了默认关闭,但有些场景必须开:编译 PHP 扩展、装某些开发库、用dnf groupinstall "Development Tools"的时候。开启方式是临时加参数,或者改配置文件把它设成enabled=1。我的建议是临时用参数,用完就关,别让它常驻。
# 临时启用 CRB 装开发工具组 dnf groupinstall -y --enablerepo=crb "Development Tools"4. RHEL 9 这边:在线源为什么不能照着 Rocky 抄
4.1 订阅制和 CDN 的分发逻辑差异
这是很多人概念上最容易混淆的地方。Rocky Linux 的仓库是公开可匿名访问的,任何能连上镜像站的人都可以直接拉包。RHEL 9 不是这样,它的官方仓库走的是订阅授权 + CDN 分发的模式:系统注册之后,订阅服务会给这台机器下发一套客户端证书,dnf拿着这套证书去 CDN 节点请求包。
这套机制体现在文件系统上,就是/etc/pki/entitlement/目录下那几个.pem文件,以及/etc/yum.repos.d/redhat.repo这个由订阅服务动态生成的配置文件。这个文件的特点是:它是自动生成的,手工改了会在下次刷新时被覆盖。所以你在 RHEL 9 上直接编辑redhat.repo加个baseurl,短期看着有效,过一段时间就失效了,而且失效得毫无征兆。
未注册的 RHEL 9 系统,dnf repolist通常是空的,或者只有从安装介质生成的本地源。这时候想装包,dnf会明确告诉你没有可用的仓库。这不是配置写错了,而是设计如此。
理解了这一点,就能明白为什么“把 RHEL 9 指向 Rocky 的源”这种做法不可取:不只是签名校验过不去的问题,而是你绕开了整个授权和分发体系,后续的补丁追溯、安全更新、支持服务全都断了。短期能装上包,长期是个技术债。
4.2 四条相对稳妥的路线对比
那 RHEL 9 想做在线装包,有哪些正经路子?我梳理了四条,各有适用场景:
| 路线 | 前置条件 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 激活订阅走官方 CDN | 有可用订阅 | 最规范、更新最全、有支持 | 需要订阅 | 生产环境首选 |
| 用开发者订阅 | 个人开发者身份 | 免费、合法、功能完整 | 有数量限制、仅限非生产 | 学习测试首选 |
| 从安装 ISO 建本地源 | 有完整 DVD ISO | 完全离线可用 | 无安全更新、需定期换 ISO | 隔离环境可用 |
| 内网自建仓库 | 一台可同步的机器 | 速度最快、集中管控 | 需要维护同步任务 | 机器多时首选 |
第一条是标准答案,有订阅就走这条,配好subscription-manager之后redhat.repo会自动生成,什么都不用手写。
第二条适合个人学习和测试。开发者订阅拿到的权限足够日常开发用,注册流程也简单,注意别用在生产环境上就行。
第三条适合完全隔离的环境。用 DVD ISO 做本地源,思路是把 ISO 挂载到某个目录,然后写一个指向file://的 repo。缺点很明显:ISO 是发布时的快照,之后所有的安全更新都拿不到,长期用等于把系统暴露在已知漏洞下。我的做法是只把它当作“应急装包”的手段,不作为长期方案。
第四条是机器数量多时的正解,下面单独展开。
4.3 内网自建仓库的完整流程
内网自建仓库这件事,听起来工程量大,实际做下来一个下午能搞定,而且一次投入长期受益。核心思路是:找一台能访问外网的机器(或者用离线同步的方式),把 Rocky Linux 9 的仓库完整同步下来,然后用一个 HTTP 服务在内网分发。
第一步:准备同步工具和存储空间。
# 装同步和建索引的工具 dnf install -y dnf-plugins-core createrepo_c nginx # 看一眼需要的空间(Rocky 9 全量四个仓库大概几十 GB 量级) df -h /data空间估算这块要提前做,别同步到一半磁盘满了。BaseOS + AppStream + CRB + extras 的全量数据加上历史版本,体积不小。如果只是想满足日常装包,可以只同步--newest-only,体积能压下来一大截。
第二步:配置同步源并执行同步。
# 建目录结构,按仓库分开放 mkdir -p /data/repo/rocky/9/{BaseOS,AppStream,CRB,extras}/x86_64/os # 同步 BaseOS(只同步最新版本,节省空间) reposync \ --repoid=baseos \ --download-metadata \ --newest-only \ --delete \ -p /data/repo/rocky/9/BaseOS/x86_64/osreposync的几个参数值得说明:--download-metadata会把仓库元数据一起拉下来,这样客户端不用自己生成;--newest-only只下载每个包的最新版本,能省大量空间;--delete会删掉源端已经不存在的包,保持同步一致性,这个参数配上定时任务很关键,否则仓库会无限膨胀。
第三步:生成仓库元数据。
createrepo_c --update /data/repo/rocky/9/BaseOS/x86_64/os--update是增量更新,比全量重建快得多。如果是首次建库,可以不加这个参数。每次reposync之后都要跑一遍,否则客户端看到的是旧索引。
第四步:起 HTTP 服务对外提供。
# nginx 配置片段 cat > /etc/nginx/conf.d/repo.conf <<'EOF' server { listen 80; server_name _; root /data/repo; autoindex on; location / { try_files $uri $uri/ =404; } } EOF systemctl enable --now nginx第五步:客户端指向内网地址。
[baseos] name=Internal Rocky 9 - BaseOS baseurl=http://repo.internal.example/rocky/9/BaseOS/x86_64/os/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9注意这里的$releasever被我替换成了字面的9。原因是在内网环境下,路径完全由我自己控制,用字面值反而更直观,也避免了某些特殊场景下变量解析出问题。这是我个人的偏好,两种写法都可以。
第六步:把同步做成定时任务。
# 每天凌晨同步一次,日志留在本地方便排查 cat > /etc/cron.d/repo-sync <<'EOF' 30 2 * * * root /usr/local/bin/sync-rocky-repo.sh >> /var/log/repo-sync.log 2>&1 EOF同步脚本里把reposync和createrepo_c --update串起来,中间加上错误判断。这里有个经验:同步任务一定要加日志和告警,否则某天同步失败了你还以为仓库是新的,等到装包报错才发现元数据已经半个月没更新,排查起来非常费劲。
5. 报错现场:我踩过的坑和速查表
5.1 常见报错对照速查
换源这件事,报错信息五花八门,但归归类其实就那么几种。下面这张表是我这些年攒下来的,遇到问题先对号入座,能省掉大量瞎试的时间。
| 报错关键词 | 大概率原因 | 优先排查动作 |
|---|---|---|
Cannot download repomd.xml | 地址错误或站点不可达 | curl -I打目标 URL 看返回码 |
Failed to synchronize cache for repo | 元数据拉取失败,多为网络或 DNS | 检查resolv.conf,试nslookup域名 |
Curl error 60: SSL certificate problem | 系统 CA 证书过期或缺失 | dnf install ca-certificates或检查时钟 |
repodata/repomd.xml ... 404 | 路径拼错,多一层或少一层目录 | 逐段对照镜像站的实际目录结构 |
GPG key retrieval failed | key 地址不可达或路径写错 | 改用本地file://路径 |
Package X is not signed | 签名校验不通过,key 不匹配 | 确认gpgkey指向对应版本的 key |
No matches found for X | 仓库不全,或包在未启用的仓库里 | dnf search all X,检查 CRB 是否需开启 |
nothing provides Y needed by X | 依赖链断裂,源不完整 | dnf repoquery --whatprovides Y反查 |
Metadata file does not match checksum | 缓存损坏或镜像站同步中 | dnf clean all后重试 |
这张表用起来的关键是不要跳步。比如看到Cannot download repomd.xml,很多人的第一反应是“镜像站挂了,换一个”,但实际上可能是自己 URL 里少写了一层目录。先用curl打一遍,返回 200 就说明是路径问题不是网络问题,方向立刻明确。
5.2 GPG 校验失败的三层排查
GPG 相关的报错是换源过程中最容易让人放弃的一类,因为报错信息通常很抽象,不会告诉你是哪一步出了问题。我总结了一个三层排查法,从上往下走,基本能覆盖所有情况。
第一层:key 文件存不存在。这是最基础也最常被忽略的。配置文件里写着gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9,但如果这个文件压根不存在(比如系统是从别的发行版转过来的,或者清理时误删了),dnf会尝试去下载然后又失败,报错绕来绕去。
ls -l /etc/pki/rpm-gpg/ | grep -i rocky目录里应该有对应版本的 key。如果没有,最稳妥的办法是从官方仓库包里提取,或者从可信来源重新导入。
第二层:key 有没有被 dnf 正确导入。repo 文件里的gpgkey只是告诉dnf去哪找,实际生效需要导入到 RPM 数据库。
# 查看已导入的 key rpm -qa gpg-pubkey* # 手动导入(如果 repo 配置里的自动导入失效) rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9导入之后再试一次安装,很多“签名校验失败”的问题在这一步就解决了。
第三层:包和 key 是否属于同一个体系。如果前两层都没问题,校验还是失败,那基本可以确定是跨体系混用了:拿 RHEL 的 key 去验 Rocky 的包,或者反过来。这种情况没有优雅的解决办法,唯一正确的做法是把源改回对应体系,别在混用的路上继续折腾。
注意:临时用
--nogpgcheck绕过校验来“验证问题”是可以的,但绝对不能把这个参数写进生产脚本。绕过签名校验等于放弃了包完整性保护,一旦仓库被投毒或者传输被劫持,你装进去的东西是什么完全不可控。
5.3 网络层排查的正确顺序
很多“源配好了但装不上”的问题,最后追下去都是网络层。排查顺序错了,会在应用层反复折腾,浪费时间。
我固定的顺序是:DNS → TCP 连通 → TLS 握手 → HTTP 响应 → 内容正确性,一层层往上走。
# 1. DNS 能不能解析 getent hosts mirrors.example.org # 2. TCP 端口通不通(80 和 443 分别试) timeout 5 bash -c '</dev/tcp/mirrors.example.org/443' && echo "443 ok" # 3. TLS 握手和证书 curl -sI -m 8 https://mirrors.example.org/rocky/ | head -5 # 4. 具体路径的响应码 curl -sI -m 8 https://mirrors.example.org/rocky/9/BaseOS/x86_64/os/repodata/repomd.xml # 5. 内容是不是真的元数据(而不是一个错误页) curl -s -m 8 https://mirrors.example.org/rocky/9/BaseOS/x86_64/os/repodata/repomd.xml | head -3第 5 步特别值得说。有些网络环境会返回一个 HTTP 200,但内容其实是个提示页或者被重定向过的页面。curl -I只看状态码看不出问题,必须实际拉一下内容确认。我曾经遇到过一台机器,所有curl -I全是 200,但dnf一直报元数据解析失败,最后发现是中间设备对响应做了替换,返回了一个格式完全不同的页面。
还有一个高频原因是系统时钟不准。TLS 握手对时间敏感,时钟偏差超过一定范围会导致证书验证失败,报错看起来跟证书本身有关,很容易误判。
timedatectl status # 如果发现时间偏差大,开启时间同步 timedatectl set-ntp true5.4 几条不写在文档里的经验
别在同一个系统上堆太多 repo 文件。我见过有人为了图方便,从五六个不同来源抄了七八个 repo 文件扔进/etc/yum.repos.d/,结果同一个包在多个仓库里都有,dnf的依赖解析变得极其混乱。合理的做法是一个体系一个文件,Rocky 的归 Rocky,EPEL 归 EPEL,第三方软件源单独一个文件,命名上带上前缀方便识别。
换源之后一定要验证一次完整的事务。光dnf makecache成功不代表能装包。我习惯装一个依赖链稍微复杂点的包当作冒烟测试,比如dnf install -y git,它会牵出几个依赖,能顺带验证依赖解析是否正常。
保留一份可回滚的配置。我的习惯是在/etc/yum.repos.d/同级建一个repos.d.orig目录,放一份刚装完系统时的原始配置。任何时候配置乱了,把当前目录整体挪走、把原始配置复制回来,三十秒恢复出厂状态。
镜像站不要只用一个。主用 + 备用是基本配置。虽然 repo 文件里只能写一个baseurl,但你可以另存一份带了备用地址的配置文件,出问题的时候mv一下切换,比临时找地址快得多。
记录你用的镜像站路径。我维护了一个小文本文件,记录每个环境的镜像站地址和最后验证日期。听起来很土,但排查问题时这个文件能省掉大量“这个地址是哪来的、什么时候配的”的困惑。
6. 收尾小抄:版本锁定、模块流与日常维护
6.1 别让换源打乱你的版本锁定
换源之后有个副作用容易被忽视:可用版本集合变了。原来可能只有两三个版本可选,换了源之后变成十几个,dnf update有时候会一口气升到很新的版本,把原本稳定的环境打乱。
如果你的环境对版本敏感,配合versionlock插件把关键包锁住是比较稳妥的做法:
dnf install -y python3-dnf-plugin-versionlock # 锁定某个包到当前版本 dnf versionlock add nginx # 查看当前锁定列表 dnf versionlock list # 需要升级时先解锁,升完再锁 dnf versionlock delete nginx我用这个插件的场景主要是数据库客户端和一些 ABI 敏感的运行库。Web 服务器这类反而不太需要锁,跟着仓库走反而能及时拿到安全修复。这个取舍要看你自己的环境特点。
6.2 模块流的坑
AppStream 里有一类东西叫模块流(module stream),它允许同一个软件在仓库里存在多个版本系列。比如某个运行时可能有 8 和 9 两个流,装了哪个流就决定你后续能装到哪个版本系列。
# 看某个模块有哪些流 dnf module list nodejs # 切换到一个特定的流 dnf module reset nodejs dnf module enable nodejs:20 dnf module install nodejs这里的关键动作是reset再enable。很多人只做enable,结果发现切换不生效,因为旧的流状态还残留着。reset会把模块状态清回初始,再enable才能干净切换。
模块流和换源叠加的时候还有个坑:不同镜像站的模块流内容可能不完全同步,你在 A 站能切到某个流,换到 B 站就找不到了。遇到这种情况先dnf module list确认流是否存在,别怀疑自己的命令写错了。
6.3 日常维护几条命令
最后整理一组我日常会用的命令,频率不高但每次都用得上:
# 查看哪些仓库被启用了,以及包数量 dnf repolist --enabled # 找出某个文件属于哪个包(排查依赖问题神器) dnf provides /usr/bin/vim # 查看某个包的所有可用版本和来源仓库 dnf --showduplicates list nginx # 只从指定仓库装包,避免多源混淆 dnf install --enablerepo=crb --disablerepo=* -y some-devel # 查看事务历史,确认某次操作装了什么 dnf history dnf history info last # 清理无用依赖(慎用,先加 --assumeno 看清单) dnf autoremove --assumenodnf history这条我要特别提一句。换源之后如果出现奇怪的问题,第一件事应该是看历史记录,确认最近一次事务到底动了哪些包。dnf history info last会把那次操作涉及的包全列出来,比你在日志里翻半天高效得多。而且dnf history undo <id>能直接回滚一次事务,这个功能在紧急情况下非常救命,前提是你得知道事务 id。
还有个小习惯值得养成:每次大规模换源或者改 repo 配置之后,先跑一遍dnf check-update,看看有哪些包会被升级。这个命令只检查不执行,是观察影响面的最佳手段。如果输出里出现了你不想动的大件(比如内核、glibc),那就先想清楚再决定要不要继续。我个人的经验是,换源之后不要马上dnf update,先观察一两天,让缓存稳定下来,确认新源的元数据没有异常,再安排升级窗口。