proxy:轻量级多生态缓存包proxy
nesbitt.io 发布了一款名为 proxy 的轻量级缓存中间件,支持跨多个生态系统(如不同编程语言或框架)进行高效缓存管理。该工具旨在简化分布式系统中的数据复用问题,提供统一接口以降低异构环境下的集成复杂度。
Andrew Nesbitt
构建需要与包注册中心通信的工具,意味着在迭代过程中会向 npm、RubyGems、PyPI 和 crates.io 发起数千次请求,久而久之就会产生心理负担。录制的 HTTP 测试桩会过期,手动模拟十六种不同的注册协议本身就是一项工程,而让测试套件直接指向真实服务则意味着每次红绿循环都会为他人付费的基础设施多带来数百次请求。我想要的是一个本地可运行的 git-pkgs 和 brief,它能像真实注册中心一样响应,但只会在首次安装时从上游获取数据。
proxy 是一个独立的 Go 程序,支持 npm、PyPI、RubyGems、Cargo、Go modules、Maven、NuGet、Composer、Hex、pub.dev、Conan、Conda、CRAN、Debian、RPM 以及 OCI 容器注册表等所有主流协议的底层通信。启动后,只需将包管理器指向 localhost:8080,首次安装会从上游获取并写入本地存储,之后的所有安装都直接从缓存提供。同时它会重写元数据响应,使 tarball 的 URL 指向代理而非原始源,这是大多数简单 HTTP 缓存最容易出错的部分。
proxy &
npm_config_registry=http://localhost:8080/npm/ npm install
GOPROXY=http://localhost:8080/go,direct go build
pip install --index-url http://localhost:8080/pypi/simple/ requests这原本就是我想要的全部功能——一个可以从测试中随意调用的本地注册端点,不会惊扰任何人。它至今仍是我的副项目,主要作为实验平台,让我能在真实协议处理器上测试包注册相关的新想法,而无需先说服十六个上游团队发布它们。
但事实证明,位于包管理器和其注册中心之间是个很有用的位置。一旦这些处理器就绪,它实际上就具备了成为 Artifactory 或 Nexus 的免费替代品的大部分条件——对只需要缓存镜像而不想要完整平台的人来说,一个单二进制方案就足够了。
CI
GitHub Actions 及其同类工具内置的依赖缓存是在文件系统层面工作的:将 ~/.npm 或 ~/.cargo 打包,以 lockfile 的哈希值作为键,下次运行时恢复。这在 lockfile 仅因一个包发生变化而导致整个缓存键失效时就不够理想,或者在矩阵构建中将相同依赖集分散到六个不同操作系统和运行时组合(每个组合都有自己的 tarball)时也会出现问题。
注册协议级别的缓存则更上一层楼,它以包坐标而非文件系统布局作为键,因此 lodash-4.17.21.tgz 只存储一次,无论 lockfile 中其他内容如何变化或哪个 runner 请求,都能被所有任务共享。将其配置为使用 S3 或 Postgres 替代默认的 SQLite 和本地磁盘,即可跨 runner 共享。从长远来看,我希望看到它直接集成到类似 Forgejo CI runner 的机制中,这样实例上的每个作业默认就能获得共享包缓存和冷却策略,而不是每个仓库都需要单独配置。
镜像
可以告诉 proxy 在有人请求之前就主动拉取包。proxy mirror 接受 PURL 或 CycloneDX/SPDX SBOM,并将其中列出的所有工件拉取到缓存中:
输入仓库的 SBOM,即可获得该项目的依赖树精确离线镜像——这正是气隙构建所需,或在 CI 集群开始拉取前预热缓存的理想形态。该操作也通过运行中的服务器暴露为 POST /api/mirror 接口,便于流水线驱动。
冷却期
由于 proxy 本身就在重写元数据响应,它也可以编辑这些响应。cooldown 设置会从返回的版本列表中剔除比设定年龄更新的版本:
cooldown:
default: "3d"
ecosystems:
npm: "7d"
packages:
"pkg:npm/lodash": "0"按照这种配置,从 npm 发布一小时前起,代理后端就认为该版本不存在,且一周内都不会存在。我写过一篇文章阐述为何我认为冷却期(cooldowns)是大多数项目尚未采用的最有效的供应链控制手段;简而言之,几乎所有恶意软件包事件都在发布后一两天内被发现,因此一个无法访问三天以内任何内容的构建过程从一开始就没有暴露风险。
自从我写那篇文章以来,已有几个包管理器原生支持此功能,但在代理层面实现意味着一条配置即可覆盖所有生态系统,包括那些尚未支持的。
Web UI
在 / 路径下有一个用于浏览缓存内容的 Web UI:按生态系统分类的包、命中次数、大小信息,以及无需解压即可读取缓存 tarball 内部文件的源码浏览器,还能逐文件对比同一软件包的两个缓存版本。其背后的增强 API (/api/package/{ecosystem}/{name}/{version}) 会返回指定坐标(coordinate)的许可证、发布日期、最新版本及任何 OSV 安全公告,这正是 git-pkgs 所需的查询内容,因此两者共享这部分代码。
UI 还有很多想做的事,大部分来自值得借鉴的 npmx 功能列表:依赖树构成分析、安装体积环形图、拼写混淆警告等。仅展示你实际安装过的包的注册表前端,与面向 npm 上四百万个全部包的方案相比属于不同的设计范畴,我还没完全想清楚它应该长什么样。
下一步
上游合并会将内部索引和公共注册表现在置于同一 URL 之下,内部名称会遮蔽公共名称——这是 pip 尤其缺乏原生的防御依赖混淆攻击的方案。我还希望代理能更广泛地强制执行依赖策略(如许可证白名单、禁止包名、最低版本限制),但目前尚无统一格式来编写这些策略,因此无论代理新增何种配置都会成为又一个独立方案,我更希望这个问题能在工具上游解决。
我最期待进行的实验是“包装模式”:proxy npm install express,此时二进制程序会启动或找到服务器,根据情况设置 NPM_CONFIG_REGISTRY、GOPROXY 或 PIP_INDEX_URL,然后执行底层命令。只需在你的 shell 中添加别名 alias npm="proxy npm",即可让机器上的每次安装都被缓存并进入冷却状态,全程无需触碰 .npmrc。
大部分繁重工作都集中在与 git-pkgs 其余工具链共享的模块中:清单和锁定文件解析器、PURL 处理、SBOM 读取器、OSV 客户端。每个新的注册表后端都只需在其基础上添加几百行协议处理器,再加上安装页面的配置片段。
非常欢迎对此项目的贡献,尤其是 README 中尚未打勾的注册表协议处理器(Helm、Swift、Alpine、Arch),以及让浏览 UI 变得更实用的功能。请在 Mastodon 或 issue tracker 上告诉我你想要的功能。
brew install git-pkgs/git-pkgs/proxy / github.com/git-pkgs/proxy
需要完整排版与评论请前往来源站点阅读。