装完RHEL 9.3第一件事永远是解决yum源。这件事放在CentOS 7时代基本就是下载个CentOS-Base.repo替换一下可到了RHEL 9.3网上大部分旧教程直接失效你找不到CentOS-Base.repo也找不到 /etc/yum.repos.d/ 下那堆熟悉的文件因为仓库配置玩法已经彻底变了。这篇文章记录我在RHEL 9.3上把阿里云网络源和本地ISO镜像源都跑通的完整过程从redhat.repo的底层机制、repo文件逐字段拆解到本地镜像挂载、双源优先级处理最后再聊换源后最容易翻车的几个坑。如果你手里没有Red Hat订阅或者准备在内网离线环境部署RHEL 9.3这篇应该能帮你少折腾小半天。1. 为什么RHEL 9.3的yum源比老版本棘手得多1.1 redhat.repo接管了一切RHEL 9开始/etc/yum.repos.d/默认只有一个文件redhat.repo。这个文件在装完系统后通常是空的或者根本不生效因为它是subscription-manager用来写入订阅仓库信息的——只有你注册并订阅了Red Hat账号redhat.repo里才会出现rhel-9-for-x86_64-appstream-rpms这类仓库ID并且baseurl指向cdn.redhat.com。换句话说没订阅的你世界就是空的dnf repolist看不到任何可用仓库。很多老教程上来就让你“备份CentOS-Base.repo再替换”这招在RHEL 9.3上根本不存在起点。你真正要做的是自己在/etc/yum.repos.d/下新建repo文件而新建的repo文件里仓库ID不能和redhat.repo中的ID重复否则dnf会在makecache阶段直接报“Repository xxx is missing name”或者干脆匹配到多个仓库导致安装软件时行为异常。1.2 仓库ID的命名规范变了RHEL 9.3的redhat.repo里仓库ID非常长例如rhel-9-for-x86_64-appstream-rpms rhel-9-for-x86_64-baseos-rpms这里有个隐含细节RHEL 9把原来的base、appserver等拆成了BaseOS和AppStream两个核心仓库基础运行环境kernel、glibc在BaseOS其他运行时和用户态软件在AppStream。因此配本地源时你同样要分别指BaseOS和AppStream两个目录。如果只配其中一个很快你就会发现有些包无论如何都装不上报“No package xxx available”。我把这两个仓库的角色理解为系统的一楼和二楼BaseOS是地基AppStream是房间里的家具。修地基只走BaseOS没错但真正住进去还得把二楼打开。1.3 dnf命令只是长了yum的皮RHEL 9.3虽然保留了yum命令但底层跑的是dnf所以你会发现很多细节和老yum不一样比如老yum支持在repo文件里反复写多个baseurl新dnf里baseurl写多行也认但更推荐一个仓库一个baseurl比如老yum对噪音输出很含蓄dnf一开verbose模式就铺天盖地。这个变化和我们换源的关系在于换源后很多排错命令得用dnf的体系去查比如dnf repolist -v、dnf repoquery。老运维常犯的一个错误是拿到RHEL 9.3之后还坚持用yum这个马甲去思考问题遇到报错第一时间去翻老掉牙的CentOS 6/7资料。我的建议很简单把脑子切成dnf模式所有以前用yum的运维思路都换成对应的dnf命令很多理解障碍会瞬间消失。所以在RHEL 9.3上配置国内阿里源和本地镜像源本质上是在和一个全新的仓库管理机制打交道而不是像以前那样“改一个baseurl就完事”。搞清楚这层背景之后往下看配置过程就不会觉得绕了。2. 阿里云网络源的repo文件手写还是套模板2.1 阿里云镜像里到底哪个路径配RHEL 9.3首先要明白一个事实阿里云镜像站没有RHEL官方软件仓库——Red Hat不允许第三方镜像站分发它的二进制仓库。所以给RHEL 9.3用阿里云网络源实际上是借道CentOS Stream 9或Rocky Linux 9的仓库。我个人的倾向是借Rocky Linux 9的仓库原因很实际Rocky Linux源码级重编译自RHEL包版本和依赖关系最接近RHEL 9.xCentOS Stream是滚动发布的上游预览版包版本往往比RHEL 9.3新半拍在RHEL 9.3上装某些包时容易遇到依赖版本对不齐的尴尬。当然如果你只是拿来做编译环境、开发容器CentOS Stream也无所谓。阿里云上对应路径是固定的几个https://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/ https://mirrors.aliyun.com/rockylinux/9/AppStream/x86_64/os/ https://mirrors.aliyun.com/rockylinux/9/extras/x86_64/os/写baseurl时我建议把x86_64这种架构写死不要用$basearch变量。原因是为了排查方便变量一旦拼错错误提示非常隐晦你还得去分析是不是变量解析出了问题。写死虽然不够“优雅”但胜在直白好排错。2.2 实测可用的repo文件内容下面这份是我在RHEL 9.3上跑通的阿里云网络源配置放在/etc/yum.repos.d/aliyun.repo下[aliyun-baseos] nameAliyun RockyLinux 9 BaseOS baseurlhttps://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/ enabled1 gpgcheck0 module_hotfixes1 [aliyun-appstream] nameAliyun RockyLinux 9 AppStream baseurlhttps://mirrors.aliyun.com/rockylinux/9/AppStream/x86_64/os/ enabled1 gpgcheck0 module_hotfixes1 [aliyun-extras] nameAliyun RockyLinux 9 extras baseurlhttps://mirrors.aliyun.com/rockylinux/9/extras/x86_64/os/ enabled1 gpgcheck0 module_hotfixes1注意gpgcheck我建议先设为0后面我会专门讲如何把gpgkey配起来而不是让你一直裸奔。module_hotfixes1这个参数在第4章会详细解释它对RHEL 9配第三方源的价值非常大建议直接保留。可能有朋友会问直接用阿里云的rockylinux路径来给RHEL用真的没问题吗我实测下来日常安装工具链、nginx、php、数据库客户端这些都没问题。原因还是Rocky和RHEL的源码同源二进制包层面的ABI兼容性非常稳。但你要有一个预期这毕竟不是Red Hat官方的支持路径生产环境里如果追求绝对合规还是得走订阅。2.3 配置完立刻执行的三个验证命令写完repo文件后必须先做一次缓存清理和重建dnf clean all dnf makecache dnf repolist这三个命令里dnf makecache是真正检查repo路径是否有效的试金石。如果baseurl写错它会非常诚实地说Errors during downloading metadata for repository aliyun-baseos并且把404状态码打在你脸上。这时候最有效的排查手段是拿浏览器直接打开baseurl看有没有repodata目录。服务器能正常联网的前提下dnf makecache成功后dnf install -y vim装一个最小软件包确认整套链路是通的。这一步别省略因为有些环境里代理配置、DNS解析、证书链都会等到真实下载时才会暴露。3. 挂载本地ISO镜像源的完整链路3.1 拿到RHEL 9.3 ISO之后先做loop挂载内网环境里网络源往往是奢望本地镜像源才是保命手段。RHEL 9.3的ISO文件本身就是一个可直接读取的文件系统Linux支持通过loop设备直接挂载不需要刻录光盘也不需要写入U盘。先把ISO放到服务器某个路径下比如/root/isos/rhel-9.3-x86_64-dvd.iso然后执行mkdir -p /mnt/cdrom mount -o loop /root/isos/rhel-9.3-x86_64-dvd.iso /mnt/cdrom挂载完用ls看目录结构你会发现顶层有BaseOS和AppStream两个目录这两个目录下各有一个Packages目录和repodata目录。记住这个结构因为repodata就是你repo文件要指向的元数据所在地dnf靠它做依赖解析。查看挂载是否成功df -h /mnt/cdrom如果看到iso9660文件系统挂在/mnt/cdrom下说明loop挂载OK。3.2 把挂载做成开机自动生效内网服务器动不动就重启每次重启手动mount不现实。有两种做法。第一种直接写/etc/fstab用ISO文件作为挂载源/root/isos/rhel-9.3-x86_64-dvd.iso /mnt/cdrom iso9660 loop,ro,noauto 0 0加了noauto之后开机不会自动挂载需要手动mount -a。我建议保留noauto尤其是当ISO文件所在分区在启动阶段尚未挂载时硬要自动挂载会让启动流程卡住或报错。等系统启动完成执行mount -a就挂上了。第二种物理光驱/虚拟光驱场景通常用设备名/dev/cdrom或/dev/sr0/dev/cdrom /mnt/cdrom iso9660 ro,noauto 0 0如果虚拟机里已经挂载了虚拟光驱用这种更直接。3.3 写本地源的repo文件注意大小写敏感本地源repo文件我通常独立放在/etc/yum.repos.d/local.repo[local-baseos] nameLocal RHEL 9.3 BaseOS baseurlfile:///mnt/cdrom/BaseOS enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release [local-appstream] nameLocal RHEL 9.3 AppStream baseurlfile:///mnt/cdrom/AppStream enabled1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release这里有个很多人踩过的大坑baseurl里的目录名是大小写敏感的必须写BaseOS和AppStream不能写成baseos或appstream否则dnf makecache会报目录不存在。RHEL 9.3 ISO里目录叫这个名路径就得一字不差。为什么本地源我敢把gpgcheck设为1因为ISO里所有rpm包都是用Red Hat官方GPG key签名的而RHEL 9.3系统里天然带着这个公钥文件路径就是file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release。本地源开启gpg校验没有任何风险还能防止有人往ISO目录里塞进未经签名的包。写完后同样执行dnf clean all dnf makecache然后dnf repolist确认local-baseos和local-appstream都处于enabled状态。4. 本地源和网络源并存时谁先谁后4.1 priority优先级数字越小越优先当本地源和阿里云网络源同时启用的场景里最怕的是dnf一会儿从本地装、一会儿从网上下载导致同样的rpm在不同机器上装出不同hash。要控制这个行为就得给仓库配优先级。DNF的priority机制和YUM老版本一致数字越小优先级越高。我希望系统默认优先用本地源所以给local仓库设priority1给aliyun仓库设priority2。# local.repo [local-baseos] priority1 ... # aliyun.repo [aliyun-baseos] priority2 ...有个前提DNF默认只认priority字段但要让它真正生效并影响依赖解析最好安装dnf-plugins-corednf install -y dnf-plugins-core验证优先级是否生效可以用dnf repolist -v输出里每一行会带priority1或priority2。只要数值对得上dnf在安装软件时会优先从本地源解析依赖这条链路就符合我的预期了。优先级带来的另外一个好处是当同一个软件在所有仓库里都有时dnf不会因为版本不同而纠结而是直接锁定高优先级仓库的版本。对于内网环境里需要统一软件版本的场景很有用。4.2 module_hotfixes1RHEL 9上配第三方源的救命参数这个是RHEL 9时代才凸显出来的问题。RHEL 9的AppStream仓库里带了不少模块化软件流模块流会规定某些软件只能安装特定版本、不能被普通rpm覆盖。当你把Rocky Linux或者CentOS Stream的仓库配进来时你会发现dnf install某些包经常报错错误信息类似Error: Unable to find a match: xxx或者The operation would result in switching of module stream这是因为模块流的过滤规则把第三方仓库里的包挡在了外面。这个问题的解决办法就是在第三方repo文件里加一行module_hotfixes1module_hotfixes1的意思是告诉dnf这个仓库里的rpm可以绕过模块流保护做hotfix级别的替换。它的本质只是放宽过滤不会降低gpg安全校验所以不用担心安全层面被掏空。如果你不写这行第5章里我列的那几个模块流冲突场景会反复折磨你。4.3 验证仓库命中的方法配置完优先级和module_hotfixes之后怎么确认系统实际用了哪个仓库的包有几个实用命令。比如我要确认vim-enhanced到底从哪个仓库装dnf repoquery --location vim-enhanceddnf repoquery --location会把解析到的rpm下载地址打出来。如果出来的是file:///mnt/cdrom/...说明本地源优先逻辑生效如果出来的是https://mirrors.aliyun.com/rockylinux/...说明走到了网络源。还有更暴力的方式dnf reinstall vim-enhanced在下载阶段看它到底从哪里取包。实际生产里我一般用repoquery就够了reinstall容易引发时间成本尤其是包很多的时候。同样如果你想验证Java运行时来自哪个源dnf repoquery --location java-17-openjdk这条命令会在RHEL 9.3上返回本地还是阿里云的具体rpm路径。别小看这步曾经我遇到过一台机器本地源和网络源同时开着结果本地源的BaseOS目录不完整dnf把老依赖解析到网络源上去了导致装出来的软件集合非常混乱。用repoquery --location一查就暴露了。5. 换源之后最容易翻车的五个典型场景5.1 gpgcheck1导致metadata下载失败关掉还是配key这个坑我几乎每配一台RHEL 9.3都会遇到。当我们借道Rocky仓库配阿里云源时如果你把gpgcheck设为1gpgkey还指到RHEL自带的redhat-release公钥dnf makecache会直接报错因为Rocky仓库的repomd.xml签名用的是Rocky自己的keyRHEL的key验不过。解决有两个方向。一是临时把gpgcheck改成0先把软件装起来适合内网测试环境二是把Rocky的公钥导入系统然后配置gpgkeycurl -o /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 https://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/RPM-GPG-KEY-rockyofficial rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9然后在aliyun.repo的每个仓库里加上gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9其实mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/目录下有一个RPM-GPG-KEY-rockyofficial文件你也可以直接浏览器下载再传到服务器不一定非要curl。做法上我的建议是一律导入公钥再开gpgcheck1不要图省事长期gpgcheck0。尤其是这台机器以后要接入正式业务时没有包签名校验的yum源就是供应链上的一个洞万一镜像站被污染所有包都会被连带污染。5.2 模块流冲突Error Unable to find a match这个问题在RHEL 9.3上装nginx、php这类模块化软件时尤其常见。你敲dnf install -y nginx结果告诉你Unable to find a match但你明明确定nginx就在AppStream仓库里。根因有两个可能。一个是AppStream仓库没启用用dnf repolist检查一下aliyun-appstream或local-appstream是否enabled另一个就是模块流过滤问题第三方源的nginx rpm因为模块流规则匹配不上dnf干脆连候选列表都过滤掉了。此时module_hotfixes1就是解法。如果你已经在repo里配了module_hotfixes1还是报错那就要看dnf module list nginx的输出确认当前模块流的默认流是什么。你还可以用dnf module reset nginx -y dnf module enable nginx:1.22 -y手动把模块流切到期望的版本。这种方式在老yum时代是没有的RHEL 9的模块流机制宁可先reset再enable也不要硬拆包。我把常见问题和解决思路整理成了表格典型场景报错特征根因快速解法gpg校验失败Errors during downloading metadata第三方源包使用了同发行版以外的GPG key导入对应key或临时gpgcheck0模块流冲突Unable to find a match模块过滤规则挡掉第三方仓库包module_hotfixes1 dnf module reset/enable缓存残留版本始终不变metadata未清理dnf clean all rm -rf /var/cache/dnf路径404404 Not Found镜像目录变更或同步滞后浏览器验证repodata路径仓库过多makecache超时repodata体积过大只开必要的仓库5.3 缓存不刷新明明配置改了装的还是旧包dnf对metadata的缓存非常执着你在repo里改了baseurl或新增了仓库后如果没做dnf clean all老元数据会继续被使用装出来自然还是旧包。曾经有一次我排查一台机器软件版本死活不对最后发现是上次makecache的缓存还挂在系统里dnf一直在用旧仓库的repodata。所以任何repo变更后的标准动作是dnf clean all rm -rf /var/cache/dnf dnf makecache两个命令里rm -rf /var/cache/dnf是双保险确保缓存被彻底清掉。虽然dnf clean all理论上够用但我见过不少dnf clean all没完全清理干净的怪异案例所以重过程中我习惯直接清目录。5.4 镜像站同步滞后或路径404怎么定位阿里云镜像站不是实时与上游同步的Rocky/EPEL等上游仓库发布新包后镜像站一般有几小时到一天的延迟。如果你发现某个包在上游已经有了但dnf install时找不到可以先强制刷新缓存再等等。路径404的情况也时有发生尤其是仓库版本目录更新后旧的baseurl整个目录会404。排查方法很简单浏览器打开baseurl看看目录是否存在、repodata/repomd.xml是否可以下载。如果目录活着但repomd.xml下载超时那就是镜像站正在同步或网络到阿里云的路径有波动。5.5 enabled仓库过多拖慢makecache这个问题很隐蔽。有人配置网络源时喜欢把BaseOS、AppStream、extras、plus、CRB、devel全部enabled1结果每次dnf makecache要下载几百MB的repodata内网或者带宽吃紧时直接卡死。我的习惯是能用两个仓库解决的需求绝不开第三个。默认只开baseos和appstreamextras通常也不需要开。本地源同理。启用太多仓库还会增加依赖解析时版本冲突的概率百害而少利。6. 一份可以抄作业的最终配置清单6.1 网络源本地源完整repo文件把上面的经验整合成一份可以直接用的配置。假设你已经把ISO挂载到/mnt/cdrom下面两个文件分别放在/etc/yum.repos.d/local.repo和/etc/yum.repos.d/aliyun.repo。local.repo[local-baseos] nameLocal RHEL 9.3 BaseOS baseurlfile:///mnt/cdrom/BaseOS enabled1 priority1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release [local-appstream] nameLocal RHEL 9.3 AppStream baseurlfile:///mnt/cdrom/AppStream enabled1 priority1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-releasealiyun.repo借道Rocky含公钥校验[aliyun-baseos] nameAliyun RockyLinux 9 BaseOS baseurlhttps://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/ enabled1 priority2 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 module_hotfixes1 [aliyun-appstream] nameAliyun RockyLinux 9 AppStream baseurlhttps://mirrors.aliyun.com/rockylinux/9/AppStream/x86_64/os/ enabled1 priority2 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 module_hotfixes1注意如果你还没导入Rocky公钥请先执行第5.1小节的导入命令再dnf makecache否则会验证失败。6.2 从空机到能装软件的分步命令整理一份可以直接复制的命令序列适配一台刚装完RHEL 9.3、没有订阅的空机器# 1. 下载并导入Rocky公钥假设服务器能访问外网 curl -o /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 https://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/RPM-GPG-KEY-rockyofficial rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 # 2. 写入阿里云repo cat /etc/yum.repos.d/aliyun.repo EOF [aliyun-baseos] nameAliyun RockyLinux 9 BaseOS baseurlhttps://mirrors.aliyun.com/rockylinux/9/BaseOS/x86_64/os/ enabled1 priority2 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 module_hotfixes1 [aliyun-appstream] nameAliyun RockyLinux 9 AppStream baseurlhttps://mirrors.aliyun.com/rockylinux/9/AppStream/x86_64/os/ enabled1 priority2 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 module_hotfixes1 EOF # 3. 挂载本地镜像源 mkdir -p /mnt/cdrom mount -o loop /root/isos/rhel-9.3-x86_64-dvd.iso /mnt/cdrom # 4. 写入本地repo cat /etc/yum.repos.d/local.repo EOF [local-baseos] nameLocal RHEL 9.3 BaseOS baseurlfile:///mnt/cdrom/BaseOS enabled1 priority1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release [local-appstream] nameLocal RHEL 9.3 AppStream baseurlfile:///mnt/cdrom/AppStream enabled1 priority1 gpgcheck1 gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release EOF # 5. 清理并重建缓存 dnf clean all rm -rf /var/cache/dnf dnf makecache # 6. 验证仓库列表和优先级 dnf repolist -v # 7. 装个包测试 dnf install -y vim dnf repoquery --location vim-enhanced这里的从1到7每一步都有明确的检查点第5步确认baseurl有效第6步确认仓库数量和优先级第7步确认本地源优先命中。全部跑通后这台RHEL 9.3的软件安装就稳了。如果你想顺手把Java环境也配好直接dnf install -y java-17-openjdk装完用dnf repoquery --location java-17-openjdk确认来源逻辑和验证vim完全一样。6.3 最后的运维习惯建议我现在给RHEL 9.3配yum源默认动作已经固化成这套本地ISO只保留baseos和appstream两个仓库网络源只开baseos和appstream并且一定给网络源写module_hotfixes1每次大版本升级前先把ISO重新挂载并makecache避免系统把旧ISO里的包和线上仓库混用update时我习惯加--excludekernel*防止内核包被意外升级后在重启时引入麻烦。这些习惯不是一次形成的是踩了上面的坑攒出来的。你照着配一遍大概率能避开大多数常见问题如果还有意外查dnf的报错时记住一条总原则所有装不上的问题都可以通过dnf clean all、dnf repolist -v、dnf repoquery这三个命令的组合来缩小排查范围。配置国内源这事本质上就是让dnf拿到一份有效的元数据剩下的大多数报错都能从“元数据到底从哪来”这个角度找到答案。