背景
EDA / CAD / HPC 集群的特点是:
- 服务器数量从几十到上千;
- 大量主机长期处于离线或半离线状态,只能访问内网;
- 需要长期固定在某个 Y 流(如 RHEL 8.10、9.4),避免
dnf update把基线偷偷推到 8.11; - 既跑 Rocky Linux 也跑 AlmaLinux,甚至同一机房两种发行版并存;
- 业务上还要部署一批内部 RPM(license 监控、CAD 启动器、运维 agent 等)。
公网镜像(哪怕是国内高校镜像)都不能直接当作生产仓库使用——网络抖动一次 dnf makecache,就可能把上百台 LSF/SGE 计算节点同时打挂。本文给出一套在内网长期可维护的方案,同时容纳多个版本的 AlmaLinux 与 Rocky Linux,并且可以平滑扩展到 EPEL、内部 RPM 与未来的 EL10。
一、需求拆解与策略
1.1 仓库矩阵
以一个比较真实的企业基线为例,需要同时维护:
| 发行版 | 版本 | 架构 | 必要仓库 |
|---|---|---|---|
| Rocky Linux | 8.10 | x86_64 | BaseOS / AppStream / extras / PowerTools |
| Rocky Linux | 9.4 | x86_64 | BaseOS / AppStream / extras / CRB |
| Rocky Linux | 9.5 | x86_64 | BaseOS / AppStream / extras / CRB |
| AlmaLinux | 8.10 | x86_64 | BaseOS / AppStream / extras / PowerTools |
| AlmaLinux | 9.4 | x86_64 | BaseOS / AppStream / extras / CRB |
| EPEL | 8 / 9 | x86_64 | Everything |
| 内部 RPM | el8/el9 | x86_64 | internal |
注意几个差异点:
- Rocky / AlmaLinux 的 EL8 系叫 PowerTools,EL9 系改名为 CRB(CodeReady Builder)。
- EL8 / EL9 都没有独立
updates仓库,更新会直接合入 BaseOS / AppStream。 - AppStream 是模块化仓库,禁止用
createrepo_c覆盖官方 repodata,否则会丢掉modules.yaml。
1.2 $releasever 的坑
Rocky 与 AlmaLinux 的默认 $releasever 是大版本号(8、9),公网镜像里 /8/ 通常是软链接,指向当前最新的 8.Y。如果要把仓库固定在 8.10,客户端 repo 一定要写绝对路径 /8.10/,不要写 $releasever,否则未来上游切到 8.11 时,你的”基线冻结”就被绕过了。
1.3 同步方式选择
| 方式 | 适用 | 备注 |
|---|---|---|
rsync |
上游提供 rsync 模块(Rocky / AlmaLinux 官方都有) | 首选,能完整保留 repodata、modules.yaml、updateinfo |
dnf reposync |
上游只提供 HTTP / HTTPS | 需要 --download-metadata,否则丢模块化信息 |
dnf download |
一次性补少量包 | 不适合做仓库镜像 |
本文以 rsync 为主,reposync 作为补充。
二、目录规划
把”同步落盘目录”和”Web 发布目录”统一在一个根目录下,按 发行版 / 版本 组织:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/tools/yumrepo/
├── rocky/
│ ├── 8.10/
│ │ ├── BaseOS/
│ │ ├── AppStream/
│ │ ├── extras/
│ │ └── PowerTools/
│ ├── 9.4/
│ └── 9.5/
├── almalinux/
│ ├── 8.10/
│ └── 9.4/
├── epel/
│ ├── 8/
│ └── 9/
├── internal/
│ ├── el8/x86_64/Packages/
│ └── el9/x86_64/Packages/
├── .snapshots/ # 可选,快照原子切换使用
└── logs/
这种布局在客户端 repo 里就能简单地拼出 URL:
1
2
3
4
http://yumrepo.icinfra.local/rocky/8.10/BaseOS/x86_64/os/
http://yumrepo.icinfra.local/almalinux/9.4/AppStream/x86_64/os/
http://yumrepo.icinfra.local/epel/9/Everything/x86_64/
http://yumrepo.icinfra.local/internal/el8/x86_64/
后续无论新增 EL10、ARM 架构还是其它发行版,只要在第一层加目录即可,不会扰动现有客户端。
准备工作:
1
2
3
4
5
useradd -r -m -d /var/lib/repomirror -s /sbin/nologin repomirror
mkdir -p /tools/yumrepo/{rocky,almalinux,epel,internal,logs,.snapshots}
mkdir -p /var/lock/repomirror
mkdir -p /tools/common
chown -R repomirror:repomirror /tools/yumrepo /var/lock/repomirror
三、上游源选择
近源优先,公网链路抖动时也建议至少配置 1~2 个备份源:
| 发行版 | 推荐 rsync 源(示例) |
|---|---|
| Rocky | rsync://mirror.nyist.edu.cn/rocky/ |
| Rocky 备份 | rsync://msync.rockylinux.org/rocky-linux/(官方 master) |
| AlmaLinux | rsync://mirrors.tuna.tsinghua.edu.cn/almalinux/ |
| AlmaLinux 备 | rsync://rsync.repo.almalinux.org/almalinux/ |
| EPEL | rsync://mirrors.tuna.tsinghua.edu.cn/fedora/epel/ |
官方 master mirror 不建议直接打。Rocky / AlmaLinux 镜像管理文档都建议生产环境优先使用就近的二级镜像,并把同步频率控制在每天 4~6 次、避开整点,分散上游压力。
四、统一的 exclude 列表
EL8 / EL9 / EL10 共用同一份 exclude 文件,按”按需打开”思路写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cat > /tools/common/el-exclude.list <<'EOF'
# === 镜像中常见的非仓库目录 ===
isos/
Live/
images/
isolinux/
EFI/
kickstart/
# === source / debug ===
*/source/
*/debug/
*/debuginfo/
*/debugsource/
*/Source/
*/Devel/
# === 不需要的架构 ===
*/aarch64/
*/ppc64le/
*/s390x/
# === 不需要的仓库(按需开关) ===
HighAvailability/
ResilientStorage/
NFV/
RT/
Plus/
SAP/
SAPHANA/
# === 临时文件 ===
*.tmp
*.~tmp~
.~tmp~/
EOF
如果用到 Pacemaker / Corosync / GFS2,把 HighAvailability/、ResilientStorage/ 注释掉即可。
五、统一同步脚本:参数化驱动
不要给每个版本写一份脚本,让发行版和版本号变成参数,这样新增 9.5、9.6 时只改 cron / timer,不动脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
cat > /usr/local/sbin/sync-elrepo.sh <<'EOF'
#!/usr/bin/env bash
# Usage: sync-elrepo.sh <distro> <version> [extra rsync args...]
# distro: rocky | almalinux
# version: 8.10 | 9.4 | 9.5 | ...
set -euo pipefail
DISTRO="${1:?distro required: rocky|almalinux}"
VERSION="${2:?version required: e.g. 8.10}"
shift 2 || true
case "$DISTRO" in
rocky)
SRC="rsync://mirror.nyist.edu.cn/rocky/${VERSION}/"
;;
almalinux)
SRC="rsync://mirrors.tuna.tsinghua.edu.cn/almalinux/${VERSION}/"
;;
*)
echo "unsupported distro: $DISTRO" >&2
exit 2
;;
esac
DST="/tools/yumrepo/${DISTRO}/${VERSION}/"
EXCLUDE="/tools/common/el-exclude.list"
LOCK="/var/lock/repomirror/${DISTRO}-${VERSION}.lock"
LOG_DIR="/tools/yumrepo/logs"
LOG_FILE="${LOG_DIR}/${DISTRO}-${VERSION}-$(date +%F).log"
mkdir -p "$DST" "$LOG_DIR"
exec 9>"$LOCK"
if ! flock -n 9; then
echo "$(date '+%F %T') [$DISTRO $VERSION] another sync running, skip." | tee -a "$LOG_FILE"
exit 0
fi
echo "========== $(date '+%F %T') sync $DISTRO $VERSION start ==========" | tee -a "$LOG_FILE"
rsync -aHvi4 \
--numeric-ids \
--safe-links \
--delete \
--delete-delay \
--delay-updates \
--partial-dir=.rsync-partial \
--exclude-from="$EXCLUDE" \
"$@" \
"$SRC" "$DST" 2>&1 | tee -a "$LOG_FILE"
# 清理半成品目录
find "$DST" -type d -name ".rsync-partial" -prune -exec rm -rf {} + || true
# 校验关键 repodata
fail=0
for repo in BaseOS AppStream extras PowerTools CRB; do
d="$DST/$repo/x86_64/os"
[[ -d "$d" ]] || continue
if [[ ! -s "$d/repodata/repomd.xml" ]]; then
echo "WARN: missing repomd.xml in $d" | tee -a "$LOG_FILE"
fail=1
fi
done
echo "========== $(date '+%F %T') sync $DISTRO $VERSION done (fail=$fail) ==========" | tee -a "$LOG_FILE"
exit "$fail"
EOF
chmod 0755 /usr/local/sbin/sync-elrepo.sh
调用示例:
1
2
3
4
sudo -u repomirror /usr/local/sbin/sync-elrepo.sh rocky 8.10
sudo -u repomirror /usr/local/sbin/sync-elrepo.sh rocky 9.4
sudo -u repomirror /usr/local/sbin/sync-elrepo.sh almalinux 8.10
sudo -u repomirror /usr/local/sbin/sync-elrepo.sh almalinux 9.4
几个细节:
- 没有
-C参数。-C是 CVS 风格隐式排除,做仓库镜像时容易误伤合法文件,必须显式用--exclude-from。 -H必须保留。EL 仓库内大量包之间存在硬链接,去掉-H会让磁盘膨胀一倍以上。--delete-delay+--delay-updates:先把所有文件下完再原子地切换,避免客户端正好访问到只下了一半的repodata。- 每个
<distro>-<version>用独立 lock 文件,可以多个版本并行同步,不会互相阻塞。
六、生产推荐:快照 + 软链原子切换
如果集群里有上千台机器同时 dnf makecache,rsync 期间哪怕一秒钟的 repodata 不一致都可能让客户端报 Status code: 404。这种规模建议用快照模式:
1
2
3
4
/tools/yumrepo/rocky/8.10 -> ../.snapshots/rocky-8.10-20260506-220000
/tools/yumrepo/.snapshots/
├── rocky-8.10-20260505-220000/
└── rocky-8.10-20260506-220000/
脚本核心逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
cat > /usr/local/sbin/sync-elrepo-snapshot.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
DISTRO="${1:?}"
VERSION="${2:?}"
BASE="/tools/yumrepo"
SNAP_DIR="${BASE}/.snapshots"
CURRENT="${BASE}/${DISTRO}/${VERSION}"
EXCLUDE="/tools/common/el-exclude.list"
LOCK="/var/lock/repomirror/${DISTRO}-${VERSION}.lock"
STAMP="$(date +%Y%m%d-%H%M%S)"
NEW_SNAP="${SNAP_DIR}/${DISTRO}-${VERSION}-${STAMP}"
case "$DISTRO" in
rocky) SRC="rsync://mirror.nyist.edu.cn/rocky/${VERSION}/" ;;
almalinux) SRC="rsync://mirrors.tuna.tsinghua.edu.cn/almalinux/${VERSION}/" ;;
*) echo "bad distro"; exit 2 ;;
esac
mkdir -p "$SNAP_DIR" "$(dirname "$CURRENT")" "$NEW_SNAP"
exec 9>"$LOCK"
flock -n 9 || { echo "skip: locked"; exit 0; }
PREV=""
[[ -L "$CURRENT" ]] && PREV="$(readlink -f "$CURRENT" || true)"
LD=()
[[ -n "$PREV" && -d "$PREV" ]] && LD=(--link-dest="$PREV")
rsync -aHvi4 --numeric-ids --safe-links \
--delete --delete-delay --delay-updates \
--partial-dir=.rsync-partial \
--exclude-from="$EXCLUDE" \
"${LD[@]}" \
"$SRC" "$NEW_SNAP/"
# 关键 repodata 校验
for r in BaseOS AppStream extras PowerTools CRB; do
d="$NEW_SNAP/$r/x86_64/os"
[[ -d "$d" ]] && test -s "$d/repodata/repomd.xml"
done
ln -sfn "$NEW_SNAP" "$CURRENT"
# 只保留最近 7 个快照
find "$SNAP_DIR" -maxdepth 1 -type d -name "${DISTRO}-${VERSION}-*" \
| sort | head -n -7 | xargs -r rm -rf
EOF
chmod 0755 /usr/local/sbin/sync-elrepo-snapshot.sh
--link-dest 让相邻两次快照之间未变化的 RPM 通过硬链接共享 inode,磁盘占用几乎不增长——这是把”快照”变成生产可行方案的关键。
七、systemd timer 调度
每个 <distro>-<version> 用一个 timer,错峰执行,避免同时打满上游:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cat > /etc/systemd/system/sync-elrepo@.service <<'EOF'
[Unit]
Description=Sync EL repository %i
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=repomirror
Group=repomirror
# %i 形如 rocky-8.10
ExecStart=/bin/bash -c '/usr/local/sbin/sync-elrepo.sh $(echo %i | cut -d- -f1) $(echo %i | cut -d- -f2-)'
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
TimeoutStartSec=12h
EOF
cat > /etc/systemd/system/sync-elrepo@.timer <<'EOF'
[Unit]
Description=Periodic sync for EL repository %i
[Timer]
OnCalendar=*-*-* 02,06,10,14,18,22:25:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now sync-elrepo@rocky-8.10.timer
systemctl enable --now sync-elrepo@rocky-9.4.timer
systemctl enable --now sync-elrepo@almalinux-8.10.timer
systemctl enable --now sync-elrepo@almalinux-9.4.timer
systemctl list-timers --all | grep elrepo
RandomizedDelaySec=30m 能让多个 timer 自动错开,避免同一秒拉同一个上游。
手动触发:
1
2
systemctl start sync-elrepo@rocky-9.4.service
journalctl -u sync-elrepo@rocky-9.4.service -f
八、HTTP 发布:Nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
dnf install -y nginx policycoreutils-python-utils
cat > /etc/nginx/conf.d/yumrepo.conf <<'EOF'
server {
listen 80;
server_name yumrepo.icinfra.local;
root /tools/yumrepo;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
# 仓库元数据不应被中间代理长时间缓存
location ~* /repodata/ {
add_header Cache-Control "no-cache, max-age=0";
}
# RPM 包可以放心缓存
location ~* \.(rpm)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
access_log /var/log/nginx/yumrepo_access.log;
error_log /var/log/nginx/yumrepo_error.log;
}
EOF
semanage fcontext -a -t httpd_sys_content_t "/tools/yumrepo(/.*)?"
restorecon -Rv /tools/yumrepo
firewall-cmd --add-service=http --permanent && firewall-cmd --reload
systemctl enable --now nginx
烟雾测试:
1
2
3
curl -I http://yumrepo.icinfra.local/rocky/8.10/BaseOS/x86_64/os/repodata/repomd.xml
curl -I http://yumrepo.icinfra.local/rocky/9.4/AppStream/x86_64/os/repodata/repomd.xml
curl -I http://yumrepo.icinfra.local/almalinux/8.10/BaseOS/x86_64/os/repodata/repomd.xml
九、客户端 repo 文件
9.1 Rocky 8.10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /etc/yum.repos.d/Rocky-Local-8.10.repo
[local-baseos]
name=Rocky Linux 8.10 - BaseOS - Local
baseurl=http://yumrepo.icinfra.local/rocky/8.10/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-8
[local-appstream]
name=Rocky Linux 8.10 - AppStream - Local
baseurl=http://yumrepo.icinfra.local/rocky/8.10/AppStream/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-8
[local-extras]
name=Rocky Linux 8.10 - extras - Local
baseurl=http://yumrepo.icinfra.local/rocky/8.10/extras/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-8
[local-powertools]
name=Rocky Linux 8.10 - PowerTools - Local
baseurl=http://yumrepo.icinfra.local/rocky/8.10/PowerTools/$basearch/os/
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-8
9.2 Rocky 9.4(注意 PowerTools 改名为 CRB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# /etc/yum.repos.d/Rocky-Local-9.4.repo
[local-baseos]
name=Rocky Linux 9.4 - BaseOS - Local
baseurl=http://yumrepo.icinfra.local/rocky/9.4/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
[local-appstream]
name=Rocky Linux 9.4 - AppStream - Local
baseurl=http://yumrepo.icinfra.local/rocky/9.4/AppStream/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
[local-crb]
name=Rocky Linux 9.4 - CRB - Local
baseurl=http://yumrepo.icinfra.local/rocky/9.4/CRB/$basearch/os/
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
9.3 AlmaLinux 9.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /etc/yum.repos.d/Alma-Local-9.4.repo
[local-baseos]
name=AlmaLinux 9.4 - BaseOS - Local
baseurl=http://yumrepo.icinfra.local/almalinux/9.4/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[local-appstream]
name=AlmaLinux 9.4 - AppStream - Local
baseurl=http://yumrepo.icinfra.local/almalinux/9.4/AppStream/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
9.4 禁用默认公网源
1
2
3
mkdir -p /etc/yum.repos.d/backup
mv /etc/yum.repos.d/Rocky-*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true
mv /etc/yum.repos.d/almalinux*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true
或者更稳妥的方式:保留但禁用。
1
dnf config-manager --set-disabled baseos appstream extras powertools crb 2>/dev/null || true
9.5 客户端验证
1
2
3
4
5
6
dnf clean all
dnf makecache
dnf repolist -v
dnf module list # 必须能列出 AppStream 模块流
dnf updateinfo summary # 必须能看到 RHSA / RHBA / RHEA 计数
dnf update --assumeno
如果 dnf module list 为空、updateinfo summary 是 0,几乎可以肯定上游 repodata 没完整同步,或者你手动跑过 createrepo_c 把官方 metadata 覆盖了。这是后续排障的两个最大坑点。
十、reposync 备用方案
少数上游(例如某些第三方仓库、商业产品自带的 yum 源)只提供 HTTPS,没有 rsync 模块,这时改用 dnf reposync:
1
2
3
4
5
6
7
8
9
10
11
dnf install -y dnf-plugins-core createrepo_c
# 先在镜像服务器本地配置好上游 repo(仅本机使用)
# 然后:
dnf reposync \
--repoid=baseos --repoid=appstream --repoid=extras --repoid=crb \
--download-path=/tools/yumrepo/reposync/rocky9.4 \
--download-metadata \
--delete \
--remote-time \
--arch=x86_64 --arch=noarch
要点:
- 必须加
--download-metadata,否则只下载 RPM、丢掉modules.yaml与updateinfo.xml,AppStream 模块化机制会全部失效。 --delete让本地与上游保持一致,移除已经被上游下架的包。- 同步出来的目录可以直接当 yum repo 用,不需要再跑
createrepo_c。
十一、内部 RPM 仓库
公司自研工具(CAD 启动器、license agent、内部 Python 工具链等)应该走独立目录,与上游镜像隔离开:
1
2
3
4
5
6
7
8
mkdir -p /tools/yumrepo/internal/el8/x86_64/Packages
mkdir -p /tools/yumrepo/internal/el9/x86_64/Packages
cp build-output/*.el8.*.rpm /tools/yumrepo/internal/el8/x86_64/Packages/
cp build-output/*.el9.*.rpm /tools/yumrepo/internal/el9/x86_64/Packages/
createrepo_c --update --retain-old-md=2 /tools/yumrepo/internal/el8/x86_64
createrepo_c --update --retain-old-md=2 /tools/yumrepo/internal/el9/x86_64
--update 会复用未变包的旧 metadata,对几千个 RPM 的内部仓库可以把元数据生成时间从分钟级降到秒级。
客户端 repo:
1
2
3
4
5
6
7
# /etc/yum.repos.d/Internal-EL.repo
[internal-el8]
name=Internal RPM Repository - EL8
baseurl=http://yumrepo.icinfra.local/internal/el8/$basearch/
enabled=1
gpgcheck=1
gpgkey=http://yumrepo.icinfra.local/internal/RPM-GPG-KEY-icinfra
不要把
gpgcheck=0当作”图省事”的默认值。即使是内部 RPM,签名也是阻止”运维同事手滑把恶意/损坏的包丢进仓库导致全网中招”的最后一道闸。
十二、ACL:让某些 repo 只对特定网段开放
EDA license agent、HPC 调度器 agent 这类内部包,往往不希望对全公司开放。Nginx 上做一层 IP ACL 就够:
1
2
3
4
5
location /internal/ {
allow 10.20.0.0/16; # EDA 集群
allow 10.30.0.0/16; # HPC 集群
deny all;
}
如果有更细粒度的需求(例如按用户、按 client cert),再考虑接入企业 SSO,但这超出本文范围。
十三、磁盘容量与保留策略
经验值(仅供参考):
| 仓库 | 单版本 x86_64 占用 |
|---|---|
| Rocky / Alma 8.x BaseOS+AppStream+PowerTools | 约 70~90 GB |
| Rocky / Alma 9.x BaseOS+AppStream+CRB | 约 60~80 GB |
| EPEL 8 / 9 Everything | 约 80~120 GB |
| 7 份快照(依靠 hardlink) | ~ 1.1×–1.3× 单份大小 |
实际部署时建议:
- 生产仓库放在独立 LV 或独立挂载点上,至少预留 1 TB;
- 每次同步后跑一遍
du -sh写入 metric 系统,触发容量告警; - 快照保留 7 份,足以回滚一周内的任何 baseline 漂移。
十四、常见问题排查
14.1 Error: Failed to download metadata for repo 'xxx'
99% 是这两个原因之一:
- rsync 还没跑完,客户端正好访问到半成品
repodata:把--delay-updates加上,或者改用快照原子切换; - 客户端 repo 里写了
$releasever,而上游/8/软链接此时指向了一个尚未完整同步的 8.Y。
14.2 dnf module list 为空
上游 repodata/*.modules.yaml.gz(或新格式 *.modulemd.yaml.gz)没有同步过来。检查:
- exclude 列表是不是把
*/repodata/误排除了; - 是不是手工跑过
createrepo_c /path/to/AppStream/x86_64/os,把官方带模块化信息的 metadata 覆盖了——这个动作对 AppStream 是破坏性的,恢复办法是重新 rsync 一次。
14.3 客户端 GPG 校验失败
1
The GPG keys listed for the "Rocky Linux 8.10 - BaseOS - Local" repository are already installed but they are not correct for this package.
通常是 gpgkey 指向了错误的版本。Rocky 8 / Rocky 9 / AlmaLinux 8 / AlmaLinux 9 各有独立的 key 文件,不要混用:
1
2
3
4
/etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-8
/etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
/etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-8
/etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
14.4 同步后磁盘占用比预期大很多
最常见的原因是少了 -H,导致硬链接被展开成独立文件。检查:
1
2
rsync -aHvi4 ... # 必须有 H
du -sh /tools/yumrepo/rocky/8.10
十五、最终落地清单
按本文方案,最终能拿到的能力:
- 一台镜像服务器同时承载 Rocky 8.10 / 9.4 / 9.5 + AlmaLinux 8.10 / 9.4 + EPEL 8 / 9 + 内部 RPM;
- 客户端 repo 通过
/<distro>/<version>/...URL 就能定位到任意基线,不依赖$releasever,可固定 Y 流; - AppStream 模块化、
updateinfo、GPG 校验全部可用; - 同步靠
systemd timer,每天 6 次错峰,不打爆上游; - 生产可选快照原子切换 +
--link-dest,磁盘不爆,客户端零中断; - 内部 RPM 与上游隔离,
createrepo_c --update增量更新,敏感仓库可按网段做 ACL。
后续要扩展时只需要:
- 新增上游版本 →
systemctl enable --now sync-elrepo@<distro>-<version>.timer; - 新增发行版(比如 EL10 出来)→ 在
sync-elrepo.sh的case里加一条上游 URL; - 新增内部包 → 丢到
internal/el8/x86_64/Packages/,跑一次createrepo_c --update。
整套体系的”演进成本”被压到了配置层面,而不是脚本层面,这是企业内网仓库长期可维护的关键。