
一台服务器上运行 60 个容器
一个裸金属服务器运行数十到数百个 Hoody 容器。KSM 和 BTRFS 去重使边际成本接近零。
矩阵 CI 扇出到三十个测试 runner,每个都需要同样的 800 MB 镜像。把 tarball 流式送入一个带 ?n=30 的管道路径,三十个 worker 一起 curl 同一个 URL。字节只走一次,服务器什么都不存,镜像仓库凭据也不必轮换。
# 镜像只流一次tar c ./image.tar | curl -T - https://pipe.hoody.com/build-12af?n=30左边一个发送端。右边三十个 GET 接收端。管道等到所有人都在听之后,字节才走一次。
管道是一个无磁盘的扇出路由器。发送端对 /api/v1/pipe/[path]?n=30 的 POST 会阻塞,直到三十个接收端用相同 n 连到同一 URL。然后字节从构建容器一直流到每个 runner,同时进行,以最慢接收端的速度为准。
tar c | curl -T - https://pipe/.../build?n=30构建容器把 tarball 直接管道送入 curl。不写文件,不推镜像仓库。
POST /api/v1/pipe/[path]?n=30服务器让发送端阻塞,直到三十个接收端全部连接。n 不匹配返回 400。提前连接的接收端没问题。
curl https://pipe/.../build?n=30 | tar x每个 runner 拿到完全相同的字节。反压来自最慢的接收端,而不是发送端的带宽。
什么都不持久,什么都不缓存。管道撮合连接后就让到一边。最慢的 runner 完成,传输就完成——URL 也随之消失。
朴素做法:同一个 800 MB tarball 拉三十次镜像仓库,三十个冷缓存,三十次网络往返。Pipe:一次出站、一次传输,最慢接收端定节奏。
12s
一次出站,以线速传输。最慢接收端定节奏,但没人需要重新下载。
1× / build
字节从构建机离开一次,在管道扇出。无 S3 GET 费用、无 Docker Hub 拉取。
0 bytes
管道在磁盘上什么都不存。无镜像仓库要清理,无缓存键要失效。
wall-time 数字假设三十路矩阵与构建容器在同一区域网络;跨区域传输瓶颈在区域间带宽,不在管道。
一旦构建变成一个 URL 加三十条 curl,一整堆 CI 脚手架就消失了。无产物存储要老化、无镜像仓库凭据要轮换、无 cache action 要调试。
反压内建在管道里。快的 worker 不用为慢的多走一次镜像仓库往返——大家都在管道处等,然后以同一速率喝水。没人重新下载。
什么都没推到镜像仓库,所以也不必对其鉴权。URL 本身就是凭据——短生命周期、限定一次传输、构建结束就被驱逐。
字节从构建机离开一次,管道做广播。每次构建只付一次出站,而不是每次矩阵跑三十次镜像仓库拉取。
管道按构建,不按键。没有 GitHub Actions cache 漏命中、没有 buildx 层迷案、也没有从上周 main 残留下来的旧 tarball。
同一模式适用于 node_modules、.pnpm-store、target/、wheel cache、数据集分片。能流的就能扇出。
一个发送端。三十个接收端。零 S3 账单。
原本要九十秒、消耗一次 S3 命中的 30 路推送,现在只要十二秒、一次出站。没人重新下载。镜像仓库凭据不用轮换。矩阵跑完,URL 自动消失。
矩阵 CI 流程平时要拼起来的零件——镜像仓库、cache action、镜像源、自定义上传步骤。管道把它们折进一个 URL。
别再把同一 tarball 推三十次。推一次。让三十条 curl 共享这条流。