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)。表象极具欺骗性:文件在、
containerdactive、节点侧校验项全过 —— 但接入实际未生效。 证据: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.ioproject 对应的 endpointhttps://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 中制品存在 |
判定与测量方法学(本轮踩到的坑,均会产生假结论):
- 接入是否生效,只能看 Harbor 入口日志:
docker logs nginx中出现来自节点的/v2/<registry>/...请求才算生效。节点侧"文件在 + containerd active"证明不了任何事。 - 探针必须走 CRI:
ctr images pull不读 CRI 的config_path(除非显式传--hosts-dir), 完全不经过 certs.d 接入,用它测出来的是直连上游的速度。已改用crictl pull。 - 清理必须用
ctr images rm --sync:crictl rmi只删镜像记录、层仍在 content store; 而 containerd 2.x 的ctr content prune不清理内容(执行时只打印 help)。清理不彻底时 pull 命中本地,测不出真实下载。 - 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同步,请勿在站点侧修改)