基础设施篇:Spegel 镜像 P2P 分发
本文是全栈电商微服务从 0 到 1 再到 10系列的基础设施篇。上一篇:基础篇:通过软件提高开发效率;下一篇:基础设施篇:Reloader 让 Secret 变更自动滚动 Pod。
在电商平台里,代码发布不是把镜像推到仓库就结束了。Kubernetes 扩容、滚动更新或节点重建时,多个节点可能同时拉取相同的镜像。如果镜像仓库在跨地域网络中,或者集群出口带宽有限,拉取镜像本身就可能成为发布速度的瓶颈。
Spegel 解决的是这段链路:让已经拥有镜像层的节点为其他节点提供镜像层。它不构建镜像,也不取代镜像仓库,而是给集群增加一层 P2P 分发能力。
先看它位于哪一层
本系列的电商项目可以把镜像链路画成这样:
graph LR Code[代码提交] --> Build[GitHub Actions + Buildx] Build --> Scan[Trivy + Syft + Cosign] Scan --> Registry[TCR 镜像仓库] Registry --> Pull[Kubernetes 节点拉取] Pull --> Runtime[containerd] Pull -. 已有 layer .-> Peer[Spegel 节点] Peer --> Runtime Admission[Kyverno 准入校验] --> Runtime每个组件只负责自己的位置:
| 组件 | 负责什么 | 不负责什么 |
|---|---|---|
| Docker Buildx | 执行 Dockerfile,构建多架构镜像 | 不负责集群内 P2P 分发 |
| TCR / GHCR | 保存和发布镜像,提供权威来源 | 不负责节点间缓存协调 |
| crane | 通过 Registry API 查看、复制和打标签 | 不负责运行 Dockerfile 的 RUN 指令 |
| Spegel | 在 Kubernetes 节点之间分发已经存在的镜像层 | 不负责构建、签名或漏洞扫描 |
| containerd | 在节点上拉取、解包和运行镜像 | 不负责镜像发布策略 |
| Kyverno | 在准入阶段验证镜像策略和签名 | 不负责下载镜像层 |
如果还不熟悉 crane,可以先读基础篇:通过软件提高开发效率。crane 处理的是镜像仓库侧的元数据和 layer;Spegel 处理的是 Kubernetes 节点之间的拉取路径,两者不是同一种工具。
Spegel 是什么
Spegel 是运行在 Kubernetes 集群中的镜像分发缓存组件,通常以 DaemonSet 形式在每个节点运行。节点需要镜像时,Spegel 会尝试发现集群内是否有其他节点已经拥有对应的镜像 layer;如果有,就从节点间传输;如果没有,则回退到原始镜像仓库。
sequenceDiagram participant K as Kubernetes 节点 participant S as Spegel participant P as 已缓存 layer 的节点 participant R as TCR / 镜像仓库
K->>S: 请求镜像 layer S->>P: 查询其他节点是否拥有 layer alt 集群内已有 layer P-->>S: 通过 P2P 返回 layer S-->>K: 写入节点缓存 else 集群内没有 layer S->>R: 从权威仓库拉取 R-->>S: 返回 layer S-->>K: 写入节点缓存 end镜像由 manifest、config 和多个 layer 组成。Spegel 重点复用的是 layer,而不是重新构建完整镜像。例如,多个后端服务共同使用 alpine 基础层时,节点可以复用已经下载的基础层;新版本只增加一个应用层时,也不必重新传输所有旧层。
它能解决什么问题
扩容时减少重复下载
当新节点加入集群,或者多个 Pod 被调度到没有缓存的节点时,Spegel 可以让后续节点从已有节点获取相同 layer。节点间网络通常比节点到远端仓库的链路更快,也更容易控制。
滚动发布时降低仓库压力
一个 Deployment 更新到新 digest 后,多个节点可能在短时间内同时拉取相同镜像。Spegel 可以降低重复访问 TCR 或 GHCR 的请求量,尤其适合镜像较大、节点数较多的场景。
适应边缘或受限网络
如果 Kubernetes 集群位于边缘机房,节点到镜像仓库的出口带宽有限,但节点之间有稳定的局域网,P2P 分发可以把一次跨网络下载转化为多次集群内传输。
利用 layer 复用
镜像层按 digest 标识。相同基础层、运行时层和依赖层只需要在节点上保存一份,Spegel 可以围绕这些不可变 layer 做分发。
它不能做什么
Spegel 不是以下工具的替代品:
- 不是镜像构建器:不能执行 Dockerfile 中的
RUN apt-get ...、RUN apk ...或编译命令。 - 不是镜像仓库:首次进入集群的镜像仍需要从 TCR、GHCR 等权威仓库获得。
- 不是镜像签名工具:不生成 Cosign 签名,也不验证供应链证明。
- 不是漏洞扫描器:不会替代 Trivy、Syft 或 Gitleaks。
- 不是部署工具:不会替代 Helm、Argo CD 或 Kubernetes 控制器。
- 不是永久存储:节点重装、磁盘清理和 containerd 垃圾回收都可能清除缓存。
集群中没有任何节点拥有某个 layer 时,Spegel 没有可分发的数据;第一个节点仍然必须访问 Registry。这也是为什么 Spegel 应被看作拉取加速层,而不是镜像仓库高可用方案。
与 crane 的区别
两者都围绕 OCI 镜像工作,但工作位置不同:
| 对比项 | crane | Spegel |
|---|---|---|
| 运行位置 | 开发机或 CI | Kubernetes 集群节点 |
| 主要接口 | Registry HTTP API | 节点间镜像 layer 分发 |
| 典型动作 | copy、digest、manifest、tag | 发现并提供已缓存 layer |
| 是否构建镜像 | 否 | 否 |
| 是否运行容器 | 否 | 否 |
| 是否能跨仓复制 | 能 | 不是它的目标 |
| 是否降低节点重复拉取 | 间接 | 是 |
| 是否替代 Registry | 否 | 否 |
因此可以同时使用:CI 通过 Buildx 构建并推送 TCR,crane 做发布链的 digest 检查,Spegel 在集群节点侧加速拉取。它们解决的是同一条供应链上的不同环节。
放到电商项目里怎么看
当前项目的镜像发布方向是:GitHub Actions 只由发布 tag 触发多架构构建,镜像推送到 TCR 和 GHCR,构建后执行 Trivy、Syft 和 Cosign;集群侧以 TCR 作为主要镜像仓库。关于镜像与 Kubernetes 的基础环境,可以先读基础篇:通过软件提高开发效率;关于实际的 CI/CD 流程,也可以参考GitLab CI/CD 构建 Golang Web 项目。
Spegel 最适合插入节点拉取这一段:
代码 -> Buildx 构建 -> Trivy 扫描 / Syft 生成 SBOM / Cosign 签名 -> 推送 TCR -> Kubernetes 按不可变 digest 部署 -> Spegel 在节点间复用 layer它不会改变镜像的 digest,也不应该改变发布顺序。安全流程仍然是:
构建 -> 生成 SBOM -> 漏洞扫描 -> 签名 -> 推送 Registry -> 准入校验 -> 节点拉取节点从其他节点取得 layer,不等于这份 layer 绕过了镜像来源、签名和准入策略。生产部署仍应使用不可变 digest,并让 Kyverno 等策略在准入阶段验证制品。
什么时候值得采用
不要只因为 Spegel 支持 P2P 就直接安装。先观察以下信号:
- 多个节点经常在短时间内拉取相同镜像。
- 镜像较大,且不同服务共享较多基础层。
- TCR 到集群的链路成为发布或扩容瓶颈。
- 新节点长时间停留在
ContainerCreating,原因是等待镜像下载。 - 镜像仓库的出口流量或请求量明显增加。
- 集群节点之间有稳定、低延迟的网络。
相反,下面的情况不适合优先引入:
- 集群只有一个节点。
- 镜像很小,拉取时间不是问题。
- 每个镜像只使用一次,layer 几乎没有复用。
- 节点间网络不稳定或 NetworkPolicy 不允许通信。
- 节点磁盘空间已经紧张。
- Registry 访问才是主要故障,而集群中没有可复用缓存。
PoC 怎么做
PoC 的目标不是证明 Spegel Pod 能够 Running,而是证明镜像请求真的走了 P2P,并且失败时仍能安全回退到 Registry。
1. 选择测试对象
选择一个包含多个 layer、且会被两个节点拉取的测试镜像。记录完整的不可变 digest,不要用会移动的 latest 标签。
准备两类测试:
- 一个两个服务都使用的基础镜像,用于观察 layer 复用。
- 一个新构建的应用镜像,用于观察首次下载和后续节点拉取。
2. 测试冷缓存
在两个测试节点上清理目标镜像缓存,记录关闭 Spegel 时的基线:
- 第一个节点从 Registry 拉取的耗时。
- 第二个节点从 Registry 拉取的耗时。
- TCR 的请求量和出口流量。
- Pod 从调度到 Ready 的耗时。
清理缓存属于有影响的运维动作,只能在测试节点和维护窗口执行;不要在承载生产业务的节点上直接操作。
3. 测试 P2P 命中
安装 Spegel 后重复同一 digest 的拉取测试,并检查:
- 第二个节点的拉取时间是否下降。
- TCR 请求和出口流量是否下降。
- Spegel 日志是否记录 layer 发现和传输。
- 节点间网络是否出现对应流量。
- 目标镜像的 manifest 和 layer digest 是否保持一致。
同时做并发测试:让多个节点在同一时间拉取相同镜像,观察 Spegel 是否改善 Registry 峰值和 Pod 启动时间。
4. 测试回退
删除或选择集群中没有缓存的 digest,确认节点能从 Registry 正常拉取。再暂时阻断一条节点间路径,确认失败时不会让镜像请求永久卡住,并记录回退耗时和错误日志。
5. 验证安全链路
确认以下事实没有变化:
- 部署仍引用不可变 digest。
- Kyverno 仍然执行镜像准入策略。
- Cosign 签名和验证针对同一个 digest。
- SBOM 与 Trivy 报告仍对应同一个多架构 index 和平台 digest。
- Spegel 不能成为未签名镜像进入集群的旁路。
运维注意事项
节点间网络策略
Spegel 需要节点之间进行发现和 layer 传输。CiliumNetworkPolicy、云安全组和节点防火墙都可能阻断这条路径。配置网络策略时,要明确允许的节点来源、目标端口和协议,并在 PoC 中验证实际流量。
资源预算
Spegel 会占用节点的 CPU、内存、网络和磁盘缓存。三节点小集群尤其要测量它与 containerd、业务 Pod、监控组件之间的资源竞争,不能只看镜像拉取时间。
缓存生命周期
缓存不是制品真相源。节点升级、磁盘压力、镜像垃圾回收和 DaemonSet 重启都可能让缓存失效。Registry 必须始终可用,或者至少具备明确的故障恢复路径。
兼容性
接入前需要验证当前 containerd、CRI 配置、镜像解析路径和 Spegel 版本的兼容性。DaemonSet 就绪只表示组件进程启动,不表示每个镜像请求都命中了 P2P。
对本系列的结论
对当前电商项目,Spegel 更适合作为 P2 候选,触发条件是实际出现镜像拉取耗时、Registry 流量或跨网络带宽瓶颈。引入前先做两节点 A/B 测试,重点比较冷缓存、热缓存、并发拉取和 Registry 不可达时的行为。
如果 PoC 能证明后续节点的拉取耗时和 Registry 流量都明显下降,并且节点资源、网络策略和安全链路成本可接受,再把 Spegel 纳入 infrastructure/ 和 Helm 部署流程。否则,保持现有「Buildx 构建、TCR 存储、Cosign 签名、Kyverno 准入」方案,不为了引入 P2P 而增加一个常驻组件。