跳转至

🧱 AgentMeshOS 内部镜像缓存仓库(2026-10-26)

📌 结论摘要

AgentMeshOS 已把构建缓存从分散的 Runner 本地层和外部远程缓存改为 102 上的内部 OCI Registry,正式地址为 http://100.64.0.4:5000。构建仍由 101/102 自建 Runner 执行,最终镜像继续推送 GHCR;内部 Registry 保存 BuildKit 缓存、固定摘要的基础镜像,以及经摘要校验的 transfer/sha-<40位SHA> 部署传输副本。GHCR 始终是生产镜像权威,内部传输 tag 只缩短 102 standby 的部署拉取路径。依据是 102 已运行容器 agentmeshos-internal-registry,101 可访问 /v2/,101/102 的 github-runner 都完成了跨 builder CACHED 回读,内部基础镜像和 ctr --plain-http 导入均通过真实测试。影响是缓存、基础层和控制面镜像可以走局域网复用,减少对 GHCR 和 Docker Hub 的长尾等待。

内部 Registry 是共享缓存后端。 本方案不改变项目代码权威、生产发布权威或 GHCR 最终镜像边界。102 只增加一个 Tailnet-only 基础服务;Registry 不可用时,构建失败关闭,不回退到旧的 GHA/GHCR buildcache 模型。

🗂️ 目录

🧩 背景与目标

此前慢构建的主要问题不是最终镜像推送,而是每次需要重新获取基础镜像层和重建依赖层。Runner 本地 Docker 缓存虽然存在,但至少有三个限制:Job 可能落到 101 或 102;GitHub Actions 工作目录和 BuildKit builder 可能被替换;本地镜像清理、节点重启和不同 builder 之间无法保证共享。单纯依赖某台 Runner 的本地缓存,会让同一提交在不同节点上重复完整构建。

目标固定为四项:

  1. 缓存和固定摘要基础镜像可以在 101 与 102 Runner 之间共享。
  2. 缓存不进入公网,也不改变 Tailnet 作为控制面的边界。
  3. 最终镜像仍以完整提交 SHA 的 GHCR 镜像为准,内部缓存不能替代可发布产物。
  4. 缓存目录、日志、磁盘水位、垃圾回收和回滚都有可执行入口。

📊 采用方案与取舍

方案 优点 缺点 结论
只保留 Runner 本地 Docker/BuildKit 缓存 无新增服务,速度最快时接近本地命中 Job 落到其他节点或 builder 被重建时失效;无法证明跨节点共享 保留为第一层,但不作为唯一方案
102 内部 OCI Registry 跨 101/102 和跨 builder 共享构建缓存;可保存固定摘要基础镜像;Tailnet-only;不依赖 GitHub Cache;缓存故障时仅跳过缓存 多一个宿主服务、存储目录和运维入口 采用
GitHub Actions Cache / GHCR buildcache 托管、无需自建服务 网络和平台依赖更强,现有构建曾受远程缓存慢影响 退役为缓存后端;GHCR 仅保留最终镜像
Docker Hub 自有缓存仓库 工具成熟 需要额外账户、认证、网络和可见性治理 不采用

内部 Registry 使用固定镜像 public.ecr.aws/docker/library/registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373,避免 2.8.3 浮动 tag 在重启或重部署时悄悄换层。容器只绑定 100.64.0.4:5000,UFW 只允许 100.64.0.2 和 100.64.0.4。这意味着 Registry 没有应用层认证,安全性依赖 Tailnet 网络身份和精确来源规则;不得把它发布到公网、局域网泛地址或其他 Tailnet 节点。

🔧 运行与工作流配置

十个 Docker 构建工作流统一使用:

CACHE_REGISTRY: ${{ vars.AGENTMESHOS_INTERNAL_REGISTRY || '100.64.0.4:5000' }}

每个工作流使用独立的 agentmeshos/cache/<service>:buildcache 标签,并在 BuildKit cache 参数中固定 registry.insecure=true,ignore-error=true。显式使用 setup-buildx-action 的工作流必须配置 driver-opts: network=host,否则 BuildKit 位于 Docker bridge 网络时无法访问 Tailnet Registry,会出现 import 30 秒、export 约 2 分钟的超时。前者只表示 Tailnet HTTP Registry,不允许把 Registry 改绑公网;ignore-error=true 保证缓存故障不会阻断镜像构建。最终 push: true 的目标仍是 GHCR 的 sha-<40位SHA> 镜像,GHCR 推送失败仍会阻断发布。

固定基础镜像

Runner 访问内部 HTTP Registry 时,setup-buildx-action 通过 buildkitd-config-inline 注入以下 BuildKit 配置:

[registry."100.64.0.4:5000"]
  http = true

Python 和 Nginx 基础镜像固定为 Linux amd64 单平台摘要,来源与目标摘要记录在 deploy/internal-registry/base-images.json:

用途 内部镜像摘要 原来源索引摘要
Python 100.64.0.4:5000/agentmeshos/base/python@sha256:a249c9f47e05708dd367f3fe8ada03cf347390fad66fb8b0518c0ef55ae3cb84 python@sha256:09f7da3bc104798d0afb40bc08d23ab2da20a76130cec1f2ef170848f5d85217
Nginx 100.64.0.4:5000/agentmeshos/base/nginx@sha256:62223d644fa234c3a1cc785ee14242ec47a77364226f1c811d2f669f96dc2ac8 nginx@sha256:65645c7bb6a0661892a8b03b89d0743208a18dd2f3f17a54ef4b76fb8e2f2a10

CI 通过 AGENTMESHOS_BASE_PYTHON_IMAGE 和 AGENTMESHOS_BASE_NGINX_IMAGE 变量传入内部摘要;仓库变量未设置时使用上述固定摘要。Dockerfile 的 PYTHON_BASE_IMAGE / NGINX_BASE_IMAGE 默认值保留同样的上游固定摘要,便于仓库外构建。更新基础镜像时先在 101 选定并测速来源、docker pull 到本地,再执行:

python3 scripts/deploy/internal-registry/mirror-base-image.py \
  --source python:3.12-slim \
  --registry http://100.64.0.4:5000 \
  --repository agentmeshos/base/python \
  --tag 3.12-slim

随后必须回填 base-images.json、Dockerfile 默认摘要和 CI 固定摘要,并重新执行真实构建。内部 tag 只用于人工发现,CI 实际引用 manifest 摘要;不得用可变 tag 代替摘要。

备用控制面不复建已存在镜像。三个独立工作流必须发布与 standby 相同的 sha-<40位提交SHA> 标签;standby 在 GitHub-hosted fan-in Job 中等待三项完成并并行检查 manifest,全部存在时只运行部署 Job。目标 SHA 同时写入 run 标题,用于精确识别同一部署目标,不能按 workflow_run 包装运行的 head_sha 去重。目标 SHA 在准备和部署前都必须等于当前 main,旧提交不得占用自建 Runner 或在后续提交之后继续部署。仅 standby 脚本变更不再由普通 push 自动触发,使用 workflow_dispatch,避免与镜像工作流并发补建三套镜像。

控制面镜像传输副本

Auth、Console、API 构建同时发布:

仓库 标签 作用
GHCR sha-<40位提交SHA> 唯一生产镜像权威与长期回滚来源
102 内部 Registry agentmeshos/transfer/<service>:sha-<40位提交SHA> 与 GHCR 同 SHA 的不可变局域网传输副本

GitHub-hosted fan-in 只检查 GHCR 同 SHA 标签,避免从公网 Runner 访问 Tailnet Registry。随后短时 verify Job 在自建 Runner 上并行比较 GHCR manifest 摘要和内部 Docker-Content-Digest;只有两者完全相等才允许部署或跳过补建。102 通过 ctr -n moby images pull --plain-http 导入内部镜像,再由 Docker 以同一 digest 启动容器。内部传输 tag 也使用完整 SHA,旧工作流无法覆盖新版本;GHCR 继续承担正式版本、回滚和审计权威。

102 的持久化目录为 /srv/agentmeshos-internal-registry/data,配置目录为 /etc/agentmeshos/internal-registry。容器使用 local 日志驱动,单文件上限 20 MiB,最多保留 5 个文件。部署和检查统一由 scripts/deploy/internal-registry/agentmeshos-internal-registry-deploy.sh 管理:

操作 命令 作用
部署/更新 deploy 固定摘要启动、设置来源规则、健康检查
只读检查 check 验证容器、固定摘要、Tailnet 监听、UFW、101 连通和磁盘水位
状态 status 显示容器、缓存目录和文件系统
回收预览 gc-plan 维护窗口短暂停写并运行 dry-run,不删除对象
回收 gc 维护窗口删除未引用层和旧的无标签缓存清单
回滚 rollback 删除容器、配置和 UFW 规则,保留缓存目录

🧪 已验证证据

验收项 证据 结论
102 运行态 docker inspect 显示容器 running,镜像 ID 为 sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373 固定摘要镜像已实际运行;重部署后仍需再次回读
101 到 102 101 请求 http://100.64.0.4:5000/v2/ 成功 Tailnet 客户端路径可用
缓存目录 更新部署并通过固定摘要检查后,/srv/agentmeshos-internal-registry/data 约 31 MiB,根文件系统使用率 32% 当前容量没有压力
101/102 Runner 写缓存 两端都以 github-runner 身份创建独立 builder A,并成功写入各自的 cache-runner*-smoke:buildcache 两个 Runner 用户都具备 Docker 和 Registry 写路径
101/102 Runner 读缓存 删除各自 builder A,再创建全新的 builder B;两端日志均显示 importing cache manifest、CACHED 跨节点和跨 builder 命中真实成立,不是本地旧缓存
内部基础镜像 102 新建 builder 从内部 Python/Nginx 摘要拉取,Python 最大层 29.79 MB 和 Nginx 最大层 15.52 MB 均在约 0.1–0.7 秒完成;101 实际上构建设站点和认证镜像成功 内部基础镜像可直接用于项目 Dockerfile
基础层外部对比 同一 Docs Nginx 15.52 MB 层从 Docker Hub 曾耗时 221.6 秒 基础镜像内网化直接消除了该长尾
控制面 GHCR 拉取长尾 2026-10-26 实跑中 Auth 从 GHCR 到 102 的单次拉取约 9 分 45 秒,成为发布主瓶颈 增加同 SHA transfer 局域网传输副本和摘要对等门禁
ctr 内部导入 102 从内部 Nginx 摘要导入约 0.1 秒,随后 docker image inspect 可见同一 image ID 证明 ctr -n moby 与 Docker 镜像存储可协同,不需要重启 Docker 或放开 daemon-wide insecure registry
最终生产提速 fe9db86c 发布总时长 8分20秒,相对 eb3d2ba 的 14分27秒 缩短约 6分07秒;standby build=skipped、deploy=1分35秒 fan-in、基础镜像和内部传输三项收益共同生效
GC 预览 gc-plan 输出 24 blobs marked, 1 blobs and 1 manifests eligible for deletion,随后 Registry 恢复并通过 101 健康检查 删除流程可预演,当前没有删除任何对象
工作流契约 internal_registry_cache_contract=passed workflows=10 十个目标工作流均未保留旧 GHA/GHCR cache 回退
缓存故障降级 101 Runner 对不可达 127.0.0.1:5999 实测 ignore-error=true,BuildKit 给出缓存错误但构建仍成功 缓存是加速层,不能阻断 GHCR 镜像构建
BuildKit 网络 Actions 日志显示 bridge 网络下 Registry import/export 持续超时;手工 network=host builder 可命中 CACHED 七个显式 setup-buildx 工作流统一使用 host 网络

上述证据只证明缓存基础设施和一条真实 Runner 路径可用。生产 Actions 日志仍需在首次统一构建后核对实际 cache-from/cache-to 行为。

💾 容量与垃圾回收

BuildKit 会覆盖相同服务名的 buildcache 标签。Registry 的默认垃圾回收不会删除未打标签的旧清单,因此如果不做维护,历史和层对象会持续增长。脚本把回收固定为人工维护操作,而不是定时自动执行,原因是 GC 扫描期间不能安全接受新的并行写缓存。

回收流程如下:

  1. 暂停可能写同一 Registry 的构建,或等待当前所有 Docker 工作流结束。
  2. 执行 gc-plan,读取日志中的可回收对象和 registry_disk_used_percent。
  3. 确认没有构建窗口后执行 gc。脚本会停止 Registry,运行 registry garbage-collect --delete-untagged=true,随后启动容器并执行健康检查。
  4. 回读 /v2/、缓存目录大小和下一次构建的 CACHED 状态。

磁盘预警线为 85%,硬门禁为 95%。达到预警线时必须安排回收或扩容;达到硬门禁时 check 失败,必须先处理容量。缓存数据不得直接删除;rollback 只删除运行对象并保留 /srv/agentmeshos-internal-registry/data。

🔁 回退方案

回退分两级。第一级只回退容器:执行 rollback,保留全部缓存数据;随后把十个工作流的 AGENTMESHOS_INTERNAL_REGISTRY 临时指向可用内部地址,或回退到上一版工作流文件。第二级恢复旧构建缓存:只有在内部 Registry 无法恢复且已记录故障原因时,才允许单独回退工作流到上一提交;不得让新旧缓存后端同时写两套标签,以免同一构建出现不可解释的缓存血缘。

DNS、Globalping、Console 和 102 只读备用控制面不属于本任务回退范围;它们各自使用独立回滚材料,不能因为 Registry 故障一起回退。

⚠️ 风险与未决项

风险 影响 处置
Tailnet-only 但没有应用认证 获得允许来源身份的主机可读写缓存 UFW 固定两个来源;不暴露公网;后续如需更强隔离再评估 token
Registry 单节点在 102 102 故障时共享缓存不可用 构建通过 ignore-error=true 回退到本地无命中构建;最终 GHCR 推送仍为硬门禁
内部基础镜像为单平台 当前只镜像 Linux amd64,不能服务多架构构建 101/102 Runner 均为 amd64;若未来增加 arm64,必须另建平台清单并更新摘要
内部基础镜像更新需回填摘要 tag 被覆盖不会影响已固定摘要,但也意味着更新不会自动生效 先执行 mirror,再更新 JSON、Dockerfile 和 CI 变量并完成真实构建;禁止直接覆盖后宣称已升级
内部传输镜像增长 每个完整 SHA 都保留 tag 与层,需要容量维护 延续 85%/95% 容量门禁和人工 gc-plan/gc;tag 不自动删除,GHCR 仍是长期权威
内部传输副本增长 未打标签旧 manifest 与层会占用磁盘 延续 85%/95% 容量门禁和人工 gc-plan/gc;current 不替代 GHCR 长期版本
GC 时并行构建 可能产生 layer 上传与删除竞态 GC 只在维护窗口人工执行,脚本停写并恢复
缓存持续增长 占满根文件系统影响 102 业务 check 执行 85% 预警、95% 硬门禁;按维护流程 GC
GHCR 仍为最终仓库 GHCR 故障时缓存命中也不能完成发布 保留为正式发布依赖,不在本任务内引入第二最终镜像仓库

📌 未决项 / 需要用户决策

当前没有需要用户选择的新增架构项。下一次多镜像生产发布需要记录排队、构建、standby 和部署时长,确认 fan-in 与基础镜像收益;若后续要求 Registry 双活或增加应用认证,应另立任务,不与本次单节点缓存基础设施改造混在一起。