在 RHEL、Rocky Linux 和 AlmaLinux 上使用 PostgreSQL Yum 仓库的管理员,需要尽快更新仓库配置 RPM。此前,同名的主版本与小版本仓库 RPM 会参与版本比较,可能导致 dnf update 把通用仓库包替换成当时最新的小版本包,并让系统在后续升级后仍停留在旧的小版本仓库。这个行为现已修复,但已经安装的仓库 RPM 不会自动摆脱旧状态,因此仍需要主动更新。
问题不是 PostgreSQL 包,而是仓库入口被固定了
为了分别支持各个 RHEL 小版本,构建系统会为 9.6、9.7、9.8、10.0 等版本生成独立的仓库 RPM,同时保留面向 9、10 等主版本的仓库 RPM。
问题来自三个条件的组合:
- 主版本和小版本仓库 RPM 使用相同的包名;
- 小版本专用 RPM 也会出现在当时最新的小版本仓库中;
- DNF 根据 RPM 的版本与发行号选择更新包,而不会理解管理员原本想跟随“主版本”还是“当前操作系统小版本”。
因此,某台最初使用主版本仓库 RPM 的 RHEL 9 系统,可能在执行更新时被替换为面向 9.6 的仓库 RPM。之后,即使操作系统升级到 9.7,该仓库配置仍可能继续指向 9.6,而不是自动跟随新的系统版本。
这种问题很容易被忽略:操作系统升级成功,dnf repolist 也不一定报错,但 PostgreSQL 仓库提供的软件包集合已经和当前 OS 小版本错位。结果可能表现为更新不可见、依赖解析异常,或节点之间拿到不同的软件包版本。
修复后的预期行为
调整后的仓库 RPM 会跟随操作系统小版本,避免仓库入口永久停留在某个历史小版本。这里需要区分两件事:
- 上游已经修复仓库 RPM 的发布与升级行为;
- 已经安装旧仓库 RPM 的机器仍应更新该包,才能获得修正后的配置。
也就是说,仅升级操作系统或普通 PostgreSQL 软件包并不足够。仓库 RPM 本身属于供应链入口,它决定 DNF 去哪里读取元数据,应该被单独纳入升级和审计范围。
在节点上更新并验证
以下脚本假设仓库 RPM 的包名为 pgdg-redhat-repo。执行前可以移除 -y,先查看 DNF 的事务计划;生产环境则建议先在一台非关键节点上验证。
#!/usr/bin/env bash
set -euo pipefail
repo_pkg="pgdg-redhat-repo"
source /etc/os-release
printf 'Operating system: %s %s\n' "${NAME}" "${VERSION_ID}"
if ! rpm -q "${repo_pkg}" >/dev/null 2>&1; then
echo "${repo_pkg} is not installed; nothing to update."
exit 0
fi
echo "Before update:"
rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE}.%{ARCH}\n' "${repo_pkg}"
sudo dnf --refresh upgrade -y "${repo_pkg}"
echo "After update:"
rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE}.%{ARCH}\n' "${repo_pkg}"
echo "Enabled PGDG repositories:"
sudo dnf repolist --enabled | grep -i pgdg || true
echo "Repository configuration files:"
find /etc/yum.repos.d -maxdepth 1 \
\( -iname '*pgdg*.repo' -o -iname '*pgdg*.rpmnew' \) \
-print
如果旧仓库配置无法发现修复后的 RPM,应从组织批准的来源下载最新仓库 RPM,再从本地文件安装。把下面的路径替换成实际文件:
REPO_RPM=/path/to/approved/pgdg-redhat-repo-latest.noarch.rpm
rpm -K "${REPO_RPM}"
sudo dnf install -y "${REPO_RPM}"
sudo dnf clean metadata
sudo dnf makecache
rpm -K 用于检查 RPM 签名和摘要。不要为了绕过验证而使用 --nogpgcheck;如果签名无法验证,应先检查密钥来源、下载过程以及内部镜像同步状态。
更新后还可以直接查看仓库定义,确认没有意外遗留的小版本路径或旧配置:
sudo grep -RniE '^(baseurl|metalink|mirrorlist)=' \
/etc/yum.repos.d/*pgdg*.repo 2>/dev/null || true
sudo dnf --refresh repolist --enabled
sudo dnf check-update || rc=$?
# dnf check-update 返回 100 表示存在可更新软件包,不代表命令失败。
if [[ ${rc:-0} -ne 0 && ${rc:-0} -ne 100 ]]; then
exit "${rc}"
fi
如果 RPM 认为现有 .repo 文件被本地修改过,升级时可能生成 .rpmnew,而不是直接覆盖原文件。此时需要比较差异:
sudo find /etc/yum.repos.d -name '*.rpmnew' -print
# 将文件名替换为实际结果后再比较
sudo diff -u \
/etc/yum.repos.d/pgdg-redhat-all.repo \
/etc/yum.repos.d/pgdg-redhat-all.repo.rpmnew || true
不要直接删除自定义代理、内部镜像地址、GPG 检查或模块设置。应把修正后的版本选择逻辑与本地配置合并,再删除已处理的 .rpmnew 文件。
批量环境不能只看“更新成功”
在 Ansible、Salt 或其他配置管理系统中,建议同时采集以下信息:
/etc/os-release中的VERSION_ID;pgdg-redhat-repo的完整版本与发行号;- 已启用的 PGDG 仓库 ID;
- 实际仓库配置中的
baseurl、metalink或mirrorlist; - 是否存在尚未处理的
.rpmnew文件。
对于使用内部镜像的组织,还要确认镜像同步的不只是 PostgreSQL 软件包,也包括更新后的仓库 RPM。否则公网源已经修复,内网客户端仍可能继续安装旧版本。
需要特别谨慎的是 EUS、锁定 release 的订阅或刻意保持在某个小版本的系统。这些环境不应机械地追随最新小版本;管理员应确认新的仓库 RPM 与组织的生命周期策略一致,并在更新前检查 subscription-manager release、DNF versionlock 以及内部镜像保留策略。
建议的落地顺序
可以按以下顺序推进:
- 在测试节点记录 OS 小版本、仓库 RPM 版本和启用的仓库;
- 只更新仓库 RPM,检查事务中是否包含非预期的软件包;
- 刷新元数据并验证 PGDG 仓库可用;
- 检查
.rpmnew和本地修改过的.repo文件; - 执行一次 PostgreSQL 包更新预检,但不要在同一变更窗口中盲目升级数据库主版本;
- 验证通过后,再通过配置管理工具分批推广。
这次问题说明,仓库 RPM 并不是一次安装后就可以遗忘的引导文件。它控制后续所有软件包的来源和版本边界,应当像 GPG 密钥、镜像配置与系统生命周期策略一样持续维护和审计。