模块化与 module_hotfixes
一句话总结
模块化 RPM 在普通 RPM 依赖关系之上又增加了一层。如果缺少这一层的信息,软件包即使以文件形式存在,也无法被查询到。
为什么需要理解模块元数据
隔离网络中经常收到下面这样的报障。
ls /srv/repo/appstream/ | grep nodejs
# nodejs-18.20.4-1.module+el9.4.0+21212+d9e3c1f2.x86_64.rpm
dnf list available nodejs
# No matching Packages to list
文件确实在那里,dnf 却说不存在,而且不会报错。
它是如何工作的
为什么会发生这种情况
整个因果链有五个阶段。
- 使用
dnf reposync下载 AppStream 时,没有添加--download-metadata - 软件包文件都下载了下来,模块元数据却没有下载
createrepo_c只能从 rpm 文件生成普通元数据。模块数据不在 rpm 内,因此无法由它生成- 内网 dnf 即使看到模块化 RPM,也不知道它属于哪个流
- 过滤之后,这些软件包便无法被查询到
RHEL 9 文档将模块依赖描述为“普通 RPM 依赖关系之上的额外一层”,并说明它“像仓库之间的虚拟依赖关系一样运作”。
不同版本之间的差异
| 项目 | RHEL 8 | RHEL 9 | RHEL 10 |
|---|---|---|---|
| 模块的定位 | AppStream 的核心机制 | 从 9.1 起用于提供生命周期较短的附加版本 | 官方文档中已没有模块章节 |
| 默认流 | 有 | 没有预先定义的默认流 | 不适用 |
| 不指定流进行安装 | 自动启用默认流 | 必须指定流 | 不适用 |
| 切换流 | distro-sync → module reset → module enable → distro-sync |
一条 dnf module switch-to 命令 |
不适用 |
从 RHEL 8 发展到 RHEL 9 后,模块的重要性大幅降低,到 RHEL 10 时实际上已经消失。但是,RHEL 8/9 系统还会长期存在。
命令
dnf module list
dnf module list nodejs
dnf module info nodejs:20
dnf module enable nodejs:20
dnf module install nodejs:20/common
dnf module reset nodejs
dnf module switch-to nodejs:20 # RHEL 9
dnf module list --installed > state-modules.txt
reset 用于重置启用状态。切换流之前通常要先执行 reset。
三种解决办法
方法 1——下载时一并获取(标准做法)
dnf reposync --repoid=...appstream-rpms --download-path=/srv/sync \
--download-metadata --gpgcheck --arch=x86_64 --arch=noarch
这种情况下,导入后不能再次运行 createrepo_c。重新运行反而会丢失模块数据。
方法 2——dnf modulesync
它会在下载模块所属软件包的同时,创建包含模块数据的仓库。
dnf --destdir=/srv/bundle/nodejs modulesync nodejs:20/minimal --resolve
方法 3——module_hotfixes=1(最后手段)
[airgap-appstream]
name=RHEL 9 AppStream (airgap, no modular metadata)
baseurl=file:///srv/repo/appstream
enabled=1
gpgcheck=1
module_hotfixes=1
metadata_expire=-1
文档定义是:“禁用模块 RPM 过滤,让仓库中的所有 RPM 都可用。默认值为 False。”
这样做需要付出代价。 不同流的软件包可能混合安装,而这种组合并未经过 Red Hat 测试,因此它只能作为“最后手段”。
使用 installroot 时
以空根目录为基准进行计算时,还必须指定模块平台 ID。
dnf download --installroot=/var/tmp/airgap-root --releasever=9.4 \
--setopt=module_platform_id=platform:el9 \
--resolve --alldeps --destdir=/srv/bundle nodejs
如果没有显式指定,该值会从空根目录中的 /etc/os-release 读取;但创建时这个文件并不存在,所以值为空,所有模块内容都会在没有任何报错的情况下被漏掉。 正因为它会悄无声息地消失,这也是最难发现的一类事故。
grep PLATFORM_ID /etc/os-release
# PLATFORM_ID="platform:el9"
实际工作中会遇到的情况
记录四类状态。 在导入前后保留下面四份记录,后续调查会容易得多。
dnf module list --installed > state-modules.txt
rpm -qa --qf '%{NAME}\t%|EPOCH?{%{EPOCH}}:{0}|\t%{VERSION}\t%{RELEASE}\t%{ARCH}\n' | sort > state-packages.tsv
dnf history userinstalled > state-userinstalled.txt
dnf history list > state-history.txt
第三份记录尤其有用。它只列出用户显式安装的软件包,所以只要在另一台服务器上安装这份列表中的内容,依赖项就会自动跟随安装。这比原样强行安装完整软件包列表安全得多。
下一步检查什么
本模块以判断概念的测验结束。先区分模块元数据缺失时出现的
症状与 module_hotfixes 的风险范围,再到下一个模块的 rh-createrepo 和
rh-airgap 实验中选择仓库复制方式。