在 Cloudflare 上构建面向百万仓库的可定制 CI/CD 平台

2026-08-04 44 预计阅读时间: 1 分钟
来源: blog.cloudflare.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

当 CI/CD 平台需要服务数百万个代码仓库时,问题不只是“如何执行一条构建命令”。平台还必须处理任务隔离、构建产物、失败重试、权限边界、流程定制,以及不同团队对流水线的差异化需求。将复杂 YAML 配置拆成 TypeScript 工作流步骤,再结合 Cloudflare Workflows、Artifacts 和 CI SDK,可以把流水线从静态配置变成可编排、可观察、可扩展的执行系统。

从 YAML 配置转向 TypeScript 工作流

YAML 适合描述简单的声明式流程,但当流水线包含条件分支、动态矩阵、外部服务调用、失败恢复和上下文传递时,配置文件很快会变成难以维护的“程序”。TypeScript 工作流步骤可以直接使用类型检查、函数抽象和测试工具,把构建逻辑放回熟悉的应用代码中。

一个典型的 CI 工作流可以拆成以下步骤:

  1. 检出指定仓库和提交。
  2. 创建隔离的构建沙箱。
  3. 安装依赖并执行测试。
  4. 生成并保存 Artifacts。
  5. 发布状态,或将失败交给修复代理处理。

下面是一段可改造的示意代码。具体导入路径和方法名需要以当前 Cloudflare Workflows、Artifacts 与 CI SDK 版本为准,但编排结构可以直接作为项目骨架使用:

// src/workflows/ci.ts
// 示意代码:请按当前 Cloudflare CI SDK 的实际 API 调整 import 和方法名。
import { WorkflowEntrypoint, WorkflowStep } from "cloudflare:workers";
import { createSandbox } from "@cloudflare/ci";
import { artifacts } from "@cloudflare/artifacts";

type Env = {
  CI_REPOSITORY: string;
  CI_TOKEN: string;
};

type TriggerInput = {
  repository: string;
  commit: string;
  command?: string;
};

export class PullRequestCI extends WorkflowEntrypoint<Env, TriggerInput> {
  async run(event: TriggerInput, step: WorkflowStep) {
    const source = await step.do("checkout", async () => {
      return {
        repository: event.repository,
        commit: event.commit,
      };
    });

    const result = await step.do("test in isolated sandbox", async () => {
      const sandbox = await createSandbox({
        network: "restricted",
        timeoutSeconds: 900,
        environment: {
          CI: "true",
          CI_COMMIT_SHA: source.commit,
        },
      });

      await sandbox.checkout(source.repository, source.commit, {
        token: this.env.CI_TOKEN,
      });

      const command = event.command ?? "npm ci && npm test";
      return sandbox.exec(command);
    });

    await step.do("save build artifacts", async () => {
      await artifacts.upload({
        name: `ci-${source.commit}`,
        files: ["coverage/**", "dist/**", "test-results/**"],
        metadata: {
          repository: source.repository,
          commit: source.commit,
          exitCode: result.exitCode,
        },
      });
    });

    return {
      repository: source.repository,
      commit: source.commit,
      passed: result.exitCode === 0,
      logs: result.stdout,
    };
  }
}

这段流程的关键不在于把所有步骤写进一个函数,而在于让每个步骤具备清晰的边界。Workflows 可以负责步骤编排和持久化执行状态,CI SDK 可以负责沙箱内的代码执行,Artifacts 则负责保存测试报告、覆盖率文件和构建输出。

沙箱是多租户 CI 的边界

在平台化 CI/CD 中,仓库代码并不完全可信。构建脚本可能会读取环境变量、访问网络、消耗大量 CPU,甚至尝试修改宿主环境。因此,执行构建任务时应把沙箱视为平台的主要安全边界,而不是一个可选优化。

可以把以下约束作为默认策略:

  • 只注入任务真正需要的环境变量,避免把平台凭据全部暴露给构建进程。
  • 默认限制外部网络访问,只开放依赖下载和必要的服务地址。
  • 为每个任务设置超时、CPU、内存和磁盘限制。
  • 将仓库代码、缓存、日志和 Artifacts 分开管理。
  • 将仓库、提交和执行 ID 写入元数据,便于审计和追踪。

生产环境中还需要区分“构建沙箱”和“控制平面”。控制平面负责接收 webhook、决定执行哪些步骤和更新状态;沙箱只负责运行不可信代码。不要让构建脚本直接获得能够创建其他工作流、读取任意 Artifacts 或修改平台配置的权限。

Artifacts 不是日志的替代品

日志适合实时查看过程,Artifacts 适合保存需要在任务结束后继续使用的结果。两者应当分开设计:

  • 日志:编译输出、测试过程、失败堆栈和诊断信息。
  • Artifacts:覆盖率报告、二进制文件、容器清单、测试结果和调试包。
  • 元数据:仓库、提交、分支、运行 ID、工具版本和退出码。

例如,可以用统一的目录约定让不同语言项目都能上传结果:

set -eu

mkdir -p ci-artifacts

if [ -d coverage ]; then
  cp -R coverage ci-artifacts/coverage
fi

if [ -d dist ]; then
  cp -R dist ci-artifacts/dist
fi

find . -maxdepth 3 -type f \
  \( -name "junit*.xml" -o -name "test-results*.xml" \) \
  -exec cp --parents {} ci-artifacts/ \; 2>/dev/null || true

tar -czf ci-artifacts.tar.gz ci-artifacts
printf 'artifact=%s\n' "ci-artifacts.tar.gz"

这类脚本可以作为不同项目的默认收集步骤,再通过 CI SDK 的上传能力保存到 Artifacts。平台还可以为每个 Artifact 设置保留时间和访问权限,避免构建产物无限累积,或被不相关的仓库读取。

让 AI 代理负责诊断,而不是直接放权

自愈流水线的价值不在于“失败后自动重跑”,而在于系统能够识别失败类型,收集足够上下文,并执行有限、可审计的修复动作。AI 代理可以分析测试日志、最近的代码变更和历史运行结果,然后提出修复建议或生成补丁。

一个实用的代理提示词可以明确输入、输出和权限边界:

你是 CI 诊断代理。

输入:
- 仓库:{{repository}}
- 提交:{{commit}}
- 失败步骤:{{step}}
- 最近 200 行日志:{{logs}}
- 相关 Artifact:{{artifact_manifest}}

任务:
1. 判断失败属于代码错误、依赖错误、基础设施错误还是暂时性错误。
2. 给出证据,引用日志中的具体信息。
3. 只有在判断为暂时性错误时,建议一次重试。
4. 只有在获得显式授权时,才生成代码补丁。

输出 JSON:
{
  "category": "code|dependency|infrastructure|transient|unknown",
  "confidence": 0.0,
  "evidence": ["..."],
  "action": "retry|comment|create_patch|stop",
  "reason": "..."
}

代理的执行结果应回到工作流中,由确定性的策略决定下一步,而不是让模型直接调用任意平台 API。例如,retry 可以限制为一次;create_patch 只能创建临时分支和 Pull Request;stop 则保留原始日志和诊断结果。这样既能利用 AI 的分析能力,又不会把生产控制平面交给不可预测的自动化逻辑。

落地时的检查清单

  • 用 Workflows 表达长时间运行、可重试和可观察的步骤。
  • 用 TypeScript 抽象公共流程,把仓库差异收敛为输入参数或插件。
  • 用 CI SDK 在隔离沙箱中执行仓库代码。
  • 用 Artifacts 保存可复用的构建结果,并配置保留策略。
  • 为每次运行建立仓库、提交、运行 ID 和步骤状态的关联。
  • 对 AI 代理设置明确的工具权限、输出格式和人工审批边界。
  • 先从测试和静态检查开始,再逐步加入发布、部署和自动修复。

这种架构的主要收益是定制能力和平台控制力,但代价也很清楚:平台团队需要维护沙箱生命周期、资源配额、缓存策略、凭据管理和失败诊断。对于规模较小、流程高度固定的团队,现成 CI 服务可能更简单;当你需要把 CI/CD 变成产品能力,并让大量仓库共享一套可编程执行平台时,Cloudflare 上的 Workflows、Artifacts 和 CI SDK 才更有发挥空间。


相关推荐