跳转至

Harbor 内网镜像仓库 — 实施结果

对应需求规格书:HARBOR-HANDOFF.md 实施时间:2026-10-01 实施状态:部分完成 —— Harbor 与节点接入已落地并验证;存量镜像预热与 A1~A7 验收因上游链路带宽受限未完成,详见第 6、9 节。 凭据一律脱敏,只写存放路径。


1. 部署形态与主机

采用方案 A(独立 VM)。

项 值
Harbor 主机 harbor-01 / 192.168.31.31
配置 Rocky Linux 9.8 / 4 vCPU / 7 GB 内存 / 内核 5.14.0-687
系统盘 /dev/vda 50 G → /,实测余 43 G
数据盘 /dev/vdb 200 G → /data,实测余 198 G
数据落盘 Harbor data_volume=/data/harbor;Docker data-root=/data/docker
部署方式 Docker CE 29.8.1 + Harbor 官方离线包 v2.15.2(compose 部署在 /opt/harbor)
访问方式 https://192.168.31.31(用 IP,规避 K1 的 Clash fake-ip 劫持)
运行组件 9 个容器全部 healthy:core / portal / jobservice / registry / registryctl / postgresql / redis / nginx / log
上游加速 经香港跳板 rainchen.top 的 SOCKS5 隧道(systemd 常驻),详见第 7 节

k8s 节点:k8s-master-01(.10)、k8s-node-01~03(.13/.14/.15),containerd v2.3.5。


2. 实际改动清单

2.1 Harbor 主机 192.168.31.31

# 改动 说明
1 /etc/yum.repos.d/rocky*.repo 换国内源(阿里云)。原文件带时间戳备份。原因:官方源实测 3~4 KB/s,dnf 装包会挂住
2 /etc/yum.repos.d/docker-ce.repo 指向 mirrors.aliyun.com/docker-ce
3 安装 Docker CE docker-ce / cli / containerd.io / docker-compose-plugin,systemd 常驻
4 /etc/docker/daemon.json data-root=/data/docker;dns=[223.5.5.5,119.29.29.29](原因见第 7 节);log 轮转 50m×3
5 SELinux 上下文 semanage fcontext -a -t container_file_t '/data/harbor(/.*)?' + restorecon(Enforcing 下容器读宿主目录)
6 firewalld 放行 80/tcp、443/tcp;放行 1080/tcp 且来源限 172.16.0.0/12 与 127.0.0.1
7 /data/harbor/certs/ 自签 CA 与服务端证书(第 3 节)
8 /data/harbor/pkg/ Harbor v2.15.2 离线包,sha256 校验通过
9 /opt/harbor/ Harbor 安装目录;harbor.yml 由工程渲染(600)
10 /root/.secrets/(700) harbor-admin.pass、harbor-db.pass、harbor.netrc(均 600),Harbor admin 口令在此
11 /usr/local/sbin/harbor-ssh /usr/bin/ssh 的副本(bin_t 上下文),供 systemd 隧道使用(第 7 节)
12 /etc/systemd/system/harbor-upstream-tunnel.service SOCKS5 隧道常驻单元(enabled + Restart=always)
13 Harbor registry endpoint × 5 + proxy cache project × 5 见第 4 节

2.2 k8s 节点 × 4(.10 / .13 / .14 / .15)

每台只新增以下内容,未改 config.toml、未重启 containerd、未影响运行中 Pod:

/etc/containerd/certs.d/<registry>/hosts.toml    # 5 个 registry 各一份
/etc/containerd/certs.d/<registry>/ca.crt        # 自签 CA 公钥

hosts.toml 实际内容(以 ghcr.io 为例):

# 由 harbor 接入工程生成;删除本文件即回到直连(回滚点)
[host."https://192.168.31.31/v2/ghcr.io"]
  capabilities = ["pull", "resolve"]
  ca = "/etc/containerd/certs.d/ghcr.io/ca.crt"
  override_path = true

改动前的备份:每台 /root/harbor-nodeaccess-backup/<ts>/certs.d.tar.gz。

写法要点【已确认,2026-10-01 修正】:containerd 默认把请求发到 https://<mirror>/v2/<repo>/...,路径里不带 registry 名;要让请求落在同名 proxy cache project 上(R1 镜像引用名零改动),须把前缀 /v2/<registry> 写进 host 的 URL,并配 override_path = true 阻止再补一层 /v2。

⚠ 原写法作废——它曾导致 4 台节点接入全部失效【已确认】

首版生成的是 server = "..." + [host."..."] + override_path = "/v2/ghcr.io"。 containerd 2.x 的 override_path 是 bool,不接受字符串,整份 hosts.toml 因此解析失败并被静默忽略,节点悄悄回退直连上游(经 Clash)。

表象极具欺骗性:文件在、containerd active、节点侧校验项全过 —— 但接入实际未生效。 证据:journalctl -u containerd 报 failed to decode ... OverridePath of type bool; Harbor 入口日志 docker logs nginx 中查不到任何 /v2/ 请求。

教训:文件写入正确 ≠ 配置被接受并生效。 判定接入生效的唯一有效证据,是 Harbor 入口日志里出现来自节点的 containerd/v2.3.5 的 /v2/<registry>/... 请求。

2.3 K1(Clash fake-ip 劫持内网域名)的处理

项 处理
节点访问 Harbor 全部用 IP 192.168.31.31,hosts.toml 的 server 即 IP,不经过 DNS
Harbor 证书 SAN 含 IP:192.168.31.31
意外耦合 发现并处理了反向影响:容器 DNS 继承 Clash 会把上游域名解析成 fake-ip,见第 7 节

2.4 K3(docker.io 路径不一致)的处理

docker.io 的 project 与 endpoint 按下表配置后,library/ 前缀差异不需要额外处理:

  • 引用 redis:7.4-alpine → containerd 规范化为 docker.io/library/redis:7.4-alpine,project 取第一段 docker.io,repo 为 library/redis;
  • Harbor 以 docker.io project 对应的 endpoint https://registry-1.docker.io 请求 library/redis,与 Docker Hub 实际命名空间一致。

已写入 config/warmup-images.txt 的 docker.io 条目采用 docker.io/library/<name> 显式写法。


3. 证书方案与轮换方式

方案:内网自签 CA + 服务端证书,分发 CA 公钥到 4 台节点(R5 二选一中的前者)。

文件 位置 权限 说明
CA 私钥 /data/harbor/certs/ca.key 600 不出 Harbor 主机
CA 证书 /data/harbor/certs/ca.crt 644 分发到 4 节点 certs.d/<registry>/ca.crt
服务端私钥 /data/harbor/certs/server.key 600
服务端证书 /data/harbor/certs/server.crt 644 SAN = IP:192.168.31.31, DNS:harbor-01
  • CA 有效期 3650 天,服务端证书 825 天(超出会被多数客户端拒绝,配置校验已拦)。
  • 轮换方式:deploy-harbor 的 cert 步骤探测「服务端证书 30 天内到期 或 SAN 不含当前 IP」即重建一对新证书(CA 一并重建),随后必须重跑 deploy-nodes 把新 ca.crt 分发到 4 节点——节点侧以内容 sha256 判定,CA 变化会自动触发更新。
  • 换 IP / 加域名:改 config/default.yaml 的 harbor.hostname 与 cert.server_san_dns 后重跑上述两步即可,无需手工 openssl。
  • 轮换影响面:轮换瞬间新老证书并存期为零,节点上正在拉取的镜像可能失败一次,重试即可。

4. registry → Harbor project 映射表

上游源 Harbor project 名 endpoint Harbor 适配器 状态
ghcr.io ghcr.io https://ghcr.io github-ghcr ✅ 已建
registry.k8s.io registry.k8s.io https://registry.k8s.io docker-registry ✅ 已建
quay.io quay.io https://quay.io docker-registry ✅ 已建
registry.gitlab.com registry.gitlab.com https://registry.gitlab.com docker-registry ✅ 已建
docker.io docker.io https://registry-1.docker.io docker-hub ✅ 已建

5 个 project 均为 public + proxy cache,project 名与 registry 域名完全一致(R3)。

适配器选择说明:Harbor 2.15 已不再接受 quay 适配器(建 project 时报 unsupported registry type quay);gitlab 适配器在本环境创建时返回 500。二者均改用通用 docker-registry——它们都是标准 OCI v2 接口,代理功能等价(registry.gitlab.com 的 Bearer token 流程亦正常)。


5. 存量镜像入库清单与容量占用

清单:config/warmup-images.txt,共 58 条:

来源 条数 说明
集群运行中容器镜像去重 54 采集自 kubectl get pods -A(64 个 Pod)
helm hook 类镜像 4 采集自 helm get hooks,含 ghcr.io/jkroepke/kube-webhook-certgen:1.8.8、registry.gitlab.com/gitlab-org/build/cng/{cfssl-self-sign,kubectl}:v19.3.2、docker.io/envoyproxy/ratelimit:17b1956c

容量占用(2026-10-01 下午更新):/data/harbor/registry = 4.9 GB。

当前已缓存:58 条清单中 57 条入库(R10 达成 57/58);唯一缺口 quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z —— 集群实际在用该 tag,但上游已不可得 (直连 quay.io 亦取不到),属源侧问题。续做与依据见 ../../services/harbor/upstream-direct-20261001/SOP.md。


6. 验收结果

2026-10-01 全项通过;报告 acceptance-20261001-152309.md(在工程 reports/ 下)。

置顶提醒:本节此前两版结论均不成立。第一版写"接入完成、验证项全过",实际只验证了 文件写入(配置被 containerd 静默忽略,见 §2.2);第二版写"部分通过",而其探针 根本没走 Harbor(用了 ctr)。修复上述两点后重测,才是下列结果。

# 验收项 结论 证据
A1 缓存命中后拉取 ≥1 MiB/s、≤60 s 通过 ghcr.io/headlamp-k8s/headlamp:v0.45.0(104.0 MiB)14.7 s / 7.09 MiB/s
A2 确实走 Harbor 通过 再拉一次:Harbor 入口请求计数 31 → 38(UA containerd/v2.3.5)
A3 现有负载无扰动 通过 Pod 64→64,无消失 / 新增 / 重启数增加
A4 hook 镜像从 Harbor 取用 ≤5 s 通过 kube-webhook-certgen:1.8.8 1.84 s
A5 幂等(二次执行零变更、全跳过) 通过 deploy-harbor + deploy-nodes 二次复跑全跳过、零变更
A6 回滚可用(删 hosts.toml 回到直连) 通过 移除后 Harbor 计数 40 → 40 未增长,hosts.toml 已恢复
A7 5 个源全覆盖 通过 五源各拉 1 个镜像成功,且 Harbor 中制品存在

判定与测量方法学(本轮踩到的坑,均会产生假结论):

  1. 接入是否生效,只能看 Harbor 入口日志:docker logs nginx 中出现来自节点的 /v2/<registry>/... 请求才算生效。节点侧"文件在 + containerd active"证明不了任何事。
  2. 探针必须走 CRI:ctr images pull 不读 CRI 的 config_path(除非显式传 --hosts-dir), 完全不经过 certs.d 接入,用它测出来的是直连上游的速度。已改用 crictl pull。
  3. 清理必须用 ctr images rm --sync:crictl rmi 只删镜像记录、层仍在 content store; 而 containerd 2.x 的 ctr content prune 不清理内容(执行时只打印 help)。清理不彻底时 pull 命中本地,测不出真实下载。
  4. A1 的阈值必须配较大镜像:Harbor proxy cache 每次拉取都要回源校验 manifest (1.7~2.2 s/次,属设计行为、无关闭开关),这是固定开销。9 MB 的 hook 镜像被它主导 (0.95 MiB/s),109 MB 镜像摊薄后为 7.09 MiB/s。故 A1 使用专用探针 a1_probe_image。

其它已实测结论:Harbor 9 容器 healthy、/api/v2.0/ping=200、5 个 proxy project 齐备; deploy-harbor --dry-run 实测零改动。


7. 遇到的问题与绕过方式

# 现象 根因 处理
1 dnf 装 Docker 卡住 9 分钟无进展 Rocky 官方源 dl.rockylinux.org 实测 3~4 KB/s,AppStream repodata 数十 MB 下不完 换阿里云镜像(实测 11 MB/s),并清 dnf 锁与陈旧 metadata;原 repo 文件带时间戳备份
2 Harbor prepare 报 AttributeError: 'list' object has no attribute 'split' harbor.yml 的 proxy.no_proxy 被写成 YAML 列表,Harbor 期望逗号分隔字符串 改为字符串;已在模板中加注释说明
3 离线包从 GitHub 直连约 30 KB/s(696 MB 需数小时) 出口链路对 GitHub release 限速 加可配置的传输加速前缀(release_mirror_prefix),实测 9.5 MB/s;来源锁定与 sha256 校验仍以原始 release_url 为准,加速仅作传输通道
4 harbor 步骤每次都重复执行 未认证的 /api/v2.0/systeminfo 不返回 harbor_version,版本判定恒失败 改用本机 goharbor/harbor-core:<tag> 镜像 tag 判定
5 registry 创建成功但 project 建不了(registry id 未知) Harbor 的 POST /registries 返回 201 但响应体为空(id 只在 Location 头) 创建后以列表回查拿 id
6 quay.io / registry.gitlab.com 适配器不可用 Harbor 2.15 不再支持 quay;gitlab 适配器本环境返回 500 均改 docker-registry(标准 OCI v2,功能等价)
7 已存在 registry 的类型改不动 Harbor 不支持修改 registry 的 type(PUT 返回 200 但不生效) 改为「删除后重建」(该 registry 尚未被 project 引用时可安全删除)
8 系统 systemd 服务执行 ssh 失败:status=203/EXEC SELinux Enforcing 下 /usr/bin/ssh 类型是 ssh_exec_t,systemd(init_t 域)无权执行(手动执行正常;SELinuxContext= 覆盖亦无效) 复制 ssh 到 /usr/local/sbin/harbor-ssh(经 restorecon 得 bin_t 上下文)供 systemd 使用;按 cmp 幂等刷新
9 隧道通了但 Harbor 仍报 registry unhealthy 最关键的一处:容器内的 127.0.0.1:1080 指向容器自身而非宿主机;且 Go 对 socks5:// 走本地解析 DNS,本机 DNS(Clash)把上游域名解析成 fake-ip(198.18.x.x),把 fake-ip 交给代理必然连不通(实测 socks5:// 失败、socks5h:// 成功) 两处一起修:① 隧道改监听 0.0.0.0:1080,代理地址用 LAN IP 192.168.31.31,firewalld 限制来源为 docker 网段;② Docker daemon.json 设公共 DNS,让容器解析出真实 IP;容器需重建才能应用 DNS,已纳入幂等判定
10 预热脚本并发传参错误(invalid reference format) xargs -I@ 与 printf 组合的参数传递不稳 改用 bash 数组 + jobs -rp 控制并发的实现
11 预热整体极慢(30 分钟仅 44 MB) Harbor 主机经 Clash 出口访问海外源实测 ~24 KB/s 引入香港跳板 SOCKS5 隧道(用户提供),实测 1.0~2.4 MB/s

8. 回滚方式

8.1 节点侧(最常触发,秒级、无损)

# 单台回滚:删除接入文件即回到直连(Harbor 与已有缓存不受影响)
ssh root@192.168.31.13 'rm -rf /etc/containerd/certs.d/ghcr.io \
  /etc/containerd/certs.d/registry.k8s.io /etc/containerd/certs.d/quay.io \
  /etc/containerd/certs.d/registry.gitlab.com /etc/containerd/certs.d/docker.io'
# 或整体恢复改动前快照
ssh root@192.168.31.13 'tar -xzf /root/harbor-nodeaccess-backup/<ts>/certs.d.tar.gz \
  -C /etc/containerd/certs.d'

工程入口:main.py rollback(需 --force 才真删;--dry-run 仅预演)。

8.2 Harbor 侧

ssh root@192.168.31.31 'cd /opt/harbor && docker compose down'   # 停服务,数据保留

彻底回滚需另加:删除 /data/harbor、/opt/harbor、/etc/systemd/system/harbor-upstream-tunnel.service(并 systemctl disable --now)、还原 /etc/yum.repos.d/rocky*.repo 与 /etc/docker/daemon.json 的时间戳备份。这些为不可逆动作,属于「卸载」,须先出方案再执行。

状态:本节步骤已写入工程实现(node_access.rollback()),但端到端「删除后确认回到直连」尚未实测(对应 A6),续做时须补。


9. 遗留项与后续建议

# 遗留项 说明与续做方式
1 ~~存量镜像预热~~ 已完成 57/58;缺口已归档 58 条中 57 条入库。唯一缺口 quay.io/minio/minio:RELEASE.2025-04-22T22-12-26Z 上游不可得 → 已从 k8s-node-01 离线导出为 62 MB tar,存 192.168.31.31:/data/offline-images/(含 README 台账与 sha256 55d508b0…e4d903),节点丢失时用 ctr -n k8s.io images import 恢复。预热过程见 ../../services/harbor/upstream-direct-20261001/SOP.md
2 ~~A1~A7 验收~~ 已完成,全项通过 见 §6。方法学的三条坑务必保留:探针须走 CRI、清理须用 --sync、A1 须用较大镜像
3 ~~A3 变更后快照~~ 已完成 Pod 64→64,无消失 / 新增 / 重启数增加
4 ~~A6 回滚实测~~ 已完成 移除 hosts.toml 后 Harbor 计数未增长,文件已恢复
5 升级路径未演练 Harbor 升级:改 config/default.yaml 的 version / release_url / offline_sha256 后重跑 deploy-harbor(走官方 install.sh 升级流程)。升级前须手工备份 Harbor 数据库(/data/harbor/database),实现体只自动备份 harbor.yml
6 上游加速通道的外部依赖 Harbor 拉取上游依赖香港主机 rainchen.top。若其不可用:已有缓存与内网拉取不受影响,仅新镜像首次入库变慢(回落直连,约 24 KB/s)。关闭方式:upstream_proxy.enabled: false 后重跑 deploy-harbor
7 监控未接(R8) 建议按现有 textfile → node_exporter:9100 路线接入 Harbor 存活与缓存命中指标,本次未做
8 Docker 官方源换成了阿里云镜像 已在实现体中配置化(harbor.docker_repo_url),如需回到官方源改配置即可
9 集群内 GitLab 自建 registry 未纳入 按 K7 保持不动

来源:/home/rain/Documents/k8s_ins/ghcr-slow-20261001/HARBOR-RESULT.md | 最后更新:2026-10-01 15:25(由 build/sync.py 同步,请勿在站点侧修改)