Cloudflare开发者平台实战:用TypeScript工作流构建下一代CI/CD

Cloudflare开发者平台的全面演进

2026年8月,Cloudflare在开发者平台领域动作频频:从将CI流水线变为TypeScript工作流,到推出Agent Tracing,再到将日处理90亿请求的cdnjs迁移至自有开发者平台,Cloudflare正在从一家CDN公司全面转型为开发者基础设施平台。

这一系列发布共同描绘了一个清晰的愿景:构建一个以Workers为核心、以TypeScript为语言的全栈开发者平台。对于开发者而言,这意味着从边缘计算到CI/CD、从静态资源托管到AI代理可观测性,所有能力都可以在一个平台内完成。

Cloudflare CI:用TypeScript定义CI/CD流水线

从YAML到TypeScript的范式转变

2026年8月17日,Cloudflare发布了cloudflare/ci——一个CI SDK,允许开发者用TypeScript在Cloudflare Workflows之上定义CI流水线。这标志着CI/CD流水线定义方式从声明式配置(YAML)向命令式编程(TypeScript)的转变。

传统CI流水线使用YAML定义,虽然简洁但缺乏表达力——无法使用条件逻辑、循环、函数复用等编程语言特性。TypeScript定义的CI流水线则可以:

  • 使用完整的TypeScript语言特性(类型检查、异步/等待、错误处理)

  • 复用代码逻辑,避免重复配置

  • 动态生成步骤,根据条件决定执行路径

  • 集成npm生态系统的工具库
  • 核心特性

    typescript
    // cloudflare/ci 流水线定义示例
    import { Pipeline, Step } from "@cloudflare/ci";
    
    const pipeline = new Pipeline({
      name: "build-and-deploy",
      trigger: { on: "push", branches: ["main"] }
    });
    
    // 步骤1:安装依赖
    pipeline.addStep(new Step({
      name: "install",
      run: async (ctx) => {
        await ctx.exec("npm", ["ci"]);
        // 保存依赖缓存,下次构建可复用
        await ctx.cache.save("node_modules", { 
          key: "deps-{{checksum package.json}}" 
        });
      }
    }));
    
    // 步骤2:并行执行 lint + test + build(默认并发)
    pipeline.addStep(new Step({
      name: "lint",
      run: async (ctx) => {
        await ctx.exec("npm", ["run", "lint"]);
      },
      concurrent: true  // 默认就是true,显式标注
    }));
    
    pipeline.addStep(new Step({
      name: "test",
      run: async (ctx) => {
        await ctx.exec("npm", ["test", "--", "--coverage"]);
        // 上传覆盖率报告作为构建产物
        await ctx.artifacts.upload("coverage/");
      },
      concurrent: true
    }));
    
    pipeline.addStep(new Step({
      name: "build",
      run: async (ctx) => {
        await ctx.exec("npm", ["run", "build"]);
        // 保存构建产物快照,用于后续步骤或失败回放
        await ctx.snapshot.save("dist/");
      },
      concurrent: true,
      dependsOn: ["install"]
    }));
    
    // 步骤3:条件部署
    pipeline.addStep(new Step({
      name: "deploy",
      run: async (ctx) => {
        // 只有main分支才部署到生产
        if (ctx.branch === "main" && ctx.status === "success") {
          await ctx.exec("npx", ["wrangler", "deploy"]);
          // 通知部署完成
          await ctx.notify({
            channel: "deployments",
            message: `Deployed ${ctx.commit.sha.slice(0, 7)} to production`
          });
        }
      },
      dependsOn: ["lint", "test", "build"]
    }));
    
    export default pipeline;

    关键特性对比

    | 特性 | 传统CI(GitHub Actions) | Cloudflare CI |
    |------|--------------------------|---------------|
    | 流水线定义 | YAML | TypeScript |
    | 类型安全 | 无 | 完整TypeScript类型 |
    | 步骤并发 | 需显式配置 | 默认并发 |
    | 重试机制 | 需手动实现 | 内置持久重试 |
    | 状态恢复 | 有限支持 | Sandbox快照缓存 |
    | 回放能力 | 不支持 | 支持步骤重放 |
    | 运行时 | 临时Runner | Cloudflare Workers |
    | 依赖复用 | cache action | 内置缓存 |

    持久化重试与回放

    Cloudflare CI的核心优势在于持久化执行模型。传统的CI流水线中,一个步骤失败后需要从头重跑整个流水线。Cloudflare CI基于Cloudflare Workflows的持久化能力,提供了:

  • 步骤级重试:失败的步骤可以单独重试,无需重跑整个流水线

  • 快照缓存:Sandbox快照保存了步骤执行的中间状态,重试时可以从快照恢复

  • 步骤回放:可以回放特定步骤的执行过程,便于调试和理解失败原因
  • 实践:从GitHub Actions迁移

    typescript
    // 从GitHub Actions迁移到Cloudflare CI
    // 原始GitHub Actions workflow(YAML):
    // jobs:
    //   build:
    //     steps:
    //       - uses: actions/checkout@v4
    //       - uses: actions/setup-node@v4
    //       - run: npm ci
    //       - run: npm run build
    //       - run: npm test
    
    // 迁移后的Cloudflare CI流水线
    import { Pipeline, Step } from "@cloudflare/ci";
    
    const pipeline = new Pipeline({
      name: "ci",
      trigger: { on: "push" }
    });
    
    // checkout在Cloudflare CI中自动完成
    // setup-node使用Sandbox环境中的Node.js
    
    pipeline.addStep(new Step({
      name: "install",
      run: async (ctx) => {
        await ctx.exec("npm", ["ci"]);
        await ctx.cache.save("node_modules", {
          key: `deps-${ctx.checksum("package.json")}`
        });
      }
    }));
    
    // build和test默认并发执行
    pipeline.addStep(new Step({
      name: "build",
      run: async (ctx) => {
        await ctx.exec("npm", ["run", "build"]);
        await ctx.snapshot.save("dist/");
      },
      dependsOn: ["install"]
    }));
    
    pipeline.addStep(new Step({
      name: "test",
      run: async (ctx) => {
        await ctx.exec("npm", ["test"]);
        await ctx.artifacts.upload("test-results/");
      },
      dependsOn: ["install"]
    }));
    
    // 添加lint步骤(原workflow中没有,迁移时增强)
    pipeline.addStep(new Step({
      name: "lint",
      run: async (ctx) => {
        await ctx.exec("npm", ["run", "lint"]);
      },
      dependsOn: ["install"]
    }));
    
    export default pipeline;

    迁移的核心变化是从声明式YAML转向命令式TypeScript。虽然学习曲线略陡,但带来的类型安全、代码复用和调试便利性是YAML无法比拟的。

    Agent Tracing:代理可观测性

    功能概述

    2026年8月15日,Cloudflare推出了Agent Tracing功能,为在Workers上运行的AI代理添加了追踪能力。新增的追踪跨度(spans)覆盖:

  • 代理调用(Agent invocations):每次代理被调用的记录

  • 模型调用(Model calls):LLM API调用的详细记录

  • 工具运行(Tool runs):工具执行的输入输出

  • 审批流程(Approvals):需要人类审批的决策点
  • 会话可以逐轮(turn by turn)重放,便于调试多轮代理对话。

    使用示例

    typescript
    // 在Workers代理中启用Tracing
    import { Agent, withTracing } from "@cloudflare/agents";
    
    export class ResearchAgent extends Agent {
      async handleRequest(request: Request) {
        // withTracing包装器自动记录以下span:
        // 1. agent.invocation - 整个请求处理
        // 2. model.call - LLM调用
        // 3. tool.run - 工具执行
        // 4. approval.request - 审批请求(如需要)
        return withTracing(this, async (span) => {
          span.setAttributes({
            "agent.name": "research-agent",
            "request.type": "search",
            "session.id": request.headers.get("x-session-id")
          });
          
          try {
            // 模型调用会被自动追踪
            const response = await this.model.generate(request.body);
            
            // 记录模型调用的详细信息
            span.addEvent("model_response_received", {
              tokens_used: response.usage.total_tokens,
              model: response.model
            });
            
            // 工具调用也会被自动追踪
            const result = await this.tools.execute(response.tool_calls);
            
            span.setAttributes({
              "tools.executed": response.tool_calls.length,
              "result.status": "success"
            });
            
            return Response.json(result);
          } catch (error) {
            span.recordException(error);
            span.setStatus({ code: "ERROR", message: error.message });
            return Response.json(
              { error: "Agent execution failed" },
              { status: 500 }
            );
          }
        });
      }
    }

    注意事项与最佳实践


  • 非无损记录:官方文档明确警告追踪不是无损的,载荷(payload)可能被截断。不要依赖追踪数据作为完整的事务日志

  • 框架差异:不同框架的载荷记录默认值不同,需要根据使用的框架调整配置

  • 计费影响:从2026年10月1日起,每个span都将作为计费事件。需要合理设置采样率,避免不必要的追踪开销

  • 敏感数据:追踪数据可能包含用户输入和模型输出,需要注意脱敏处理
  • typescript
    // 追踪配置最佳实践
    import { TracingConfig } from "@cloudflare/agents";
    
    const tracingConfig: TracingConfig = {
      // 采样率:生产环境建议10-20%
      samplingRate: 0.15,
      
      // 载荷截断设置
      maxPayloadLength: 4096,  // 限制单条payload长度
      
      // 敏感字段脱敏
      redactFields: ["api_key", "password", "token", "ssn"],
      
      // 采样规则
      samplingRules: [
        // 错误请求100%采样
        { condition: "status == 'ERROR'", rate: 1.0 },
        // 慢请求50%采样
        { condition: "duration > 5000", rate: 0.5 },
        // 默认10%采样
        { condition: "*", rate: 0.1 }
      ]
    };

    cdnjs迁移:90亿请求/日的架构重构

    迁移背景

    Cloudflare将cdnjs——其开源的JavaScript和CSS CDN——迁移到了自有开发者平台。新架构整合了多个Cloudflare组件:

    | 组件 | 用途 | 规模/说明 |
    |------|------|-----------|
    | Workers | 边缘计算 | 全球分布,低延迟响应 |
    | R2 | 对象存储 | 包内容存储,零出口费 |
    | KV | 键值存储 | 元数据缓存,快速查询 |
    | Workflows | 编排 | 发布流水线,版本管理 |
    | Queues | 消息队列 | 异步任务处理 |
    | Durable Objects | 有状态协调 | 版本一致性保证 |
    | Containers | 长时间任务 | 构建与打包 |

    迁移在保持包内容、URL和SRI哈希不变的前提下,完成了基础设施的全面重构,日处理请求量达90亿次。这证明了Cloudflare开发者平台具备承载超大规模生产负载的能力。

    架构启示

    cdnjs的迁移展示了如何用Cloudflare开发者平台组件构建生产级CDN服务:

    typescript
    // cdnjs新架构的核心逻辑(简化版)
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url);
        const packagePath = url.pathname;
        
        // 1. 从KV缓存读取元数据
        const metadata = await env.PACKAGE_KV.get(
          packagePath, 
          "json"
        ) as PackageMetadata;
        
        if (!metadata) {
          return new Response("Not Found", { status: 404 });
        }
        
        // 2. 检查缓存有效性
        const cached = await caches.default.match(request);
        if (cached) {
          return cached;
        }
        
        // 3. 从R2获取包内容
        const object = await env.PACKAGE_R2.get(metadata.r2Key);
        if (!object) {
          // 4. 触发异步重建任务(通过Queues)
          await env.REBUILD_QUEUE.send({
            package: metadata.name,
            version: metadata.version
          });
          return new Response("Package rebuilding", { status: 503 });
        }
        
        // 5. 构建响应并设置缓存
        const response = new Response(object.body, {
          headers: {
            "content-type": metadata.contentType,
            "cache-control": "public, max-age=31536000, immutable",
            "x-package-version": metadata.version
          }
        });
        
        // 写入边缘缓存
        ctx.waitUntil(caches.default.put(request, response.clone()));
        
        return response;
      }
    };

    Cache Response Rules:源站后缓存控制

    传统Cache Rules的局限

    此前,Cloudflare的Cache Rules只能基于请求属性操作——即只能根据URL、Header等请求信息决定是否缓存。这限制了对动态内容的精细缓存控制。

    Cache Response Rules的创新

    2026年8月15日,Cloudflare引入了Cache Response Rules——一个在源服务器响应之后、内容写入缓存之前运行的规则引擎。它评估源站响应,决定哪些内容应该被缓存。

    javascript
    // Cache Response Rules 配置示例(Wrangler配置)
    // wrangler.toml
    [cache_response_rules]
      [[cache_response_rules.rules]]
        name = "cache-api-json"
        # 只缓存成功的JSON响应
        expression = "response.headers.content_type == 'application/json' && response.status == 200"
        cache = true
        edge_ttl = 3600      # 边缘缓存1小时
        browser_ttl = 60     # 浏览器缓存1分钟
        
      [[cache_response_rules.rules]]
        name = "no-cache-auth"
        # 带认证头的响应不缓存
        expression = "request.headers.authorization != null"
        cache = false
        
      [[cache_response_rules.rules]]
        name = "cache-static-long"
        # 静态资源长期缓存
        expression = "response.headers.content_type.startsWith('image/')"
        cache = true
        edge_ttl = 86400     # 边缘缓存24小时
        browser_ttl = 3600   # 浏览器缓存1小时

    最佳实践:构建全栈应用

    typescript
    // 综合示例:使用Cloudflare开发者平台构建全栈应用
    import { Worker, DurableObject } from "cloudflare:workers";
    
    interface Env {
      ASSETS_BUCKET: R2Bucket;
      PAGE_CACHE: KVNamespace;
      API_DO: DurableObjectNamespace;
      QUEUE: Queue<JobMessage>;
    }
    
    // Workers入口
    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext) {
        const url = new URL(request.url);
        
        // 静态资源从R2提供
        if (url.pathname.startsWith("/assets/")) {
          const key = url.pathname.slice(8);
          const object = await env.ASSETS_BUCKET.get(key);
          if (object) {
            return new Response(object.body, {
              headers: {
                "content-type": object.httpMetadata?.contentType || "application/octet-stream",
                "cache-control": "public, max-age=31536000, immutable"
              }
            });
          }
        }
        
        // API路由到Durable Object
        if (url.pathname.startsWith("/api/")) {
          const id = env.API_DO.idFromName("default");
          const stub = env.API_DO.get(id);
          return stub.fetch(request);
        }
        
        // 页面从KV缓存读取
        const cached = await env.PAGE_CACHE.get(url.pathname, "text");
        if (cached) {
          return new Response(cached, {
            headers: { 
              "content-type": "text/html; charset=utf-8",
              "cache-control": "public, max-age=60"
            }
          });
        }
        
        // 未命中缓存,触发异步页面生成
        await env.QUEUE.send({
          type: "generate_page",
          path: url.pathname
        });
        
        return new Response("Generating page...", { status: 202 });
      },
      
      // 队列消费者:异步任务处理
      async queue(batch: MessageBatch<JobMessage>, env: Env) {
        for (const message of batch.messages) {
          if (message.body.type === "generate_page") {
            const html = await renderPage(message.body.path);
            await env.PAGE_CACHE.put(message.body.path, html, {
              expirationTtl: 3600
            });
          }
          message.ack();
        }
      }
    };
    
    // Durable Object 处理有状态API逻辑
    export class ApiObject extends DurableObject {
      async fetch(request: Request) {
        const url = new URL(request.url);
        
        if (url.pathname === "/api/state") {
          // 从Durable Object存储读取状态
          const state = await this.ctx.storage.get("appState") || {};
          return Response.json(state);
        }
        
        if (url.pathname === "/api/state" && request.method === "POST") {
          const data = await request.json();
          await this.ctx.storage.put("appState", data);
          return Response.json({ success: true });
        }
        
        return new Response("Not Found", { status: 404 });
      }
    }

    结语

    Cloudflare在2026年8月的这一系列发布——从CI工作流到Agent Tracing再到cdnjs迁移——展示了一个清晰的愿景:构建一个以Workers为核心、以TypeScript为语言的全栈开发者平台。

    对于开发者而言,Cloudflare开发者平台已经从"CDN加速"进化为"全栈应用部署平台"。从CI/CD流水线到边缘计算,从静态资源托管到AI代理可观测性,所有能力都可以在同一个平台内、用同一种语言完成。这种统一性带来的开发效率提升是不容忽视的。

    值得注意的是,Cloudflare CI目前仍依赖于尚处于私有测试阶段的Artifacts功能,因此其可迁移的经验在于持久化步骤模型的设计理念,而非直接的CI替代方案。但这一设计理念——用TypeScript定义CI流水线、步骤级重试和快照回放——无疑代表了CI/CD工具发展的方向。

    💬 评论区 (0)

    暂无评论,快来抢沙发吧!