Cloudflare Pulumi 架构模式实战:从组件化 Worker 到版本化发布
2026/9/12 15:51:36 网站建设 项目流程

Cloudflare Pulumi 架构模式实战:从组件化 Worker 到版本化发布

【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills

本篇指南以仓库内 patterns.md 为骨架,系统讲解如何用@pulumi/cloudflare(v6.x)编排 Cloudflare Workers 平台资源,覆盖组件化封装、全栈绑定、多环境、队列处理、微服务、事件驱动与蓝绿/金丝雀发布等十种高频架构模式。读完本文,你将能直接复刻这些模式,把 Pulumi 的 IaC 能力与 Workers 生态结合起来,搭建可维护、可演进、可灰度发布的云上应用。

前置认知:本文模式所依赖的 Pulumi 基础

所有模式都建立在同一个 Pulumi Provider 之上,在动手前先确认以下前提(详见 README.md 与 configuration.md):

  • 包与版本:TypeScript/JS 使用@pulumi/cloudflare,本文全部示例基于 v6.x API;
  • 认证:优先使用 API Token(CLOUDFLARE_API_TOKEN环境变量),而不是遗留的 API Key;
  • 配置:accountId应放入 stack 配置(Pulumi.<stack>.yaml)统一管理,大多数资源都要求它;
  • 代码形态:使用 ES modules 时务必设置module: true,并始终设置compatibilityDate锁定 Worker 行为;
  • 依赖管理:通过*Bindings(KV/D1/R2/Queue 绑定)把存储资源连接到 Worker,绑定名必须与 Worker 代码中env访问的名称完全一致(大小写敏感)。

这些原则在下面的每个模式中都会被反复用到,是理解代码的钥匙。

组件化资源:用ComponentResource封装 Worker 应用

当应用由 KV 命名空间、Worker 脚本和自定义域名三件套组成时,可以将其封装为一个 Pulumi 组件资源,实现“一处定义、多处复用”:

class WorkerApp extends pulumi.ComponentResource { constructor(name: string, args: WorkerAppArgs, opts?) { super("custom:cloudflare:WorkerApp", name, {}, opts); const defaultOpts = {parent: this}; this.kv = new cloudflare.WorkersKvNamespace(`${name}-kv`, {accountId: args.accountId, title: `${name}-kv`}, defaultOpts); this.worker = new cloudflare.WorkerScript(`${name}-worker`, { accountId: args.accountId, name: `${name}-worker`, content: args.workerCode, module: true, kvNamespaceBindings: [{name: "KV", namespaceId: this.kv.id}], }, defaultOpts); this.domain = new cloudflare.WorkersDomain(`${name}-domain`, { accountId: args.accountId, hostname: args.domain, service: this.worker.name, }, defaultOpts); } }

要点拆解:

  • 类型标识super("custom:cloudflare:WorkerApp", ...)中的custom:前缀是自定义组件资源的约定,Pulumi 会以此作为该组件在状态中的类型;
  • 父级关系{parent: this}把内部资源挂到组件下,删除组件时会级联清理其子资源,pulumi up的输出也会按树形展示,便于查看;
  • 统一命名:KV、Worker、域名均以组件name为前缀生成唯一资源名,避免命名冲突;
  • 对外暴露kvworkerdomain作为组件属性,调用方可以继续读取.id.name等输出(Output)用于后续组装;
  • 域名绑定WorkersDomain需要service: this.worker.name指向 Worker 脚本名,这就是 Worker 与自定义域名的连接点(详见 configuration.md 中 Workers Domains/Routes 一节)。

封装完成后,创建一套完整应用只需要一行:

const app = new WorkerApp("api", {accountId, workerCode: code, domain: "api.example.com"});

全栈 Worker 应用:KV / D1 / R2 一站式绑定

一个典型的全栈应用同时需要 KV(缓存/配置)、D1(关系型数据)与 R2(对象存储)。模式代码如下:

const kv = new cloudflare.WorkersKvNamespace("cache", {accountId, title: "api-cache"}); const db = new cloudflare.D1Database("db", {accountId, name: "app-database"}); const bucket = new cloudflare.R2Bucket("assets", {accountId, name: "app-assets"}); const apiWorker = new cloudflare.WorkerScript("api", { accountId, name: "api-worker", content: fs.readFileSync("./dist/api.js", "utf8"), module: true, kvNamespaceBindings: [{name: "CACHE", namespaceId: kv.id}], d1DatabaseBindings: [{name: "DB", databaseId: db.id}], r2BucketBindings: [{name: "ASSETS", bucketName: bucket.name}], });

几个关键细节:

  • 内容来源content直接读取构建产物./dist/api.js,Pulumi 不会替你做打包——它会把读到的内容原样上传(见 gotchas.md 中 “No bundler/build step” 一节),因此生产环境必须先用npm run build产出dist目录;
  • 绑定即依赖namespaceId: kv.iddatabaseId: db.id这类 Output 引用会自动建立隐式依赖,Pulumi 会保证存储资源先创建、Worker 后创建,无需手写dependsOn
  • 绑定命名规范:绑定名CACHEDBASSETS是 Worker 代码中env.CACHEenv.DBenv.ASSETS的入口,必须与代码一致,否则运行时会得到env.XXX is undefined
  • module: true:以 ES module 形式上传脚本,这是现代 Worker 的推荐形态。

完整存储资源定义(KV 键值写入、R2 的location、D1 迁移)可以参考 configuration.md。

多环境管理:按 Stack 隔离部署

Pulumi 的 Stack 天然适合区分devstagingprod环境,通过pulumi.getStack()获取当前栈名并注入资源命名:

const stack = pulumi.getStack(); const worker = new cloudflare.WorkerScript(`worker-${stack}`, { accountId, name: `my-worker-${stack}`, content: code, plainTextBindings: [{name: "ENVIRONMENT", text: stack}], });

模式价值:

  • 同一份代码通过pulumi up -s dev/pulumi up -s prod即可分别创建my-worker-devmy-worker-prod,互不干扰;
  • plainTextBindings把栈名作为普通文本绑定注入 Worker,代码里通过env.ENVIRONMENT即可感知当前环境,用于日志标记、配置分支等;
  • 不同环境的差异化配置(如accountId、域名、配额)放在各自的Pulumi.<stack>.yaml中,由 stack 配置统一管理。

队列驱动处理:Producer / Consumer 解耦

面向异步处理的场景,cloudflare.Queue配合生产端queueBindings与消费端queueConsumers构成完整的队列模式:

const queue = new cloudflare.Queue("processing-queue", {accountId, name: "image-processing"}); // Producer: API receives requests const apiWorker = new cloudflare.WorkerScript("api", { accountId, name: "api-worker", content: apiCode, queueBindings: [{name: "PROCESSING_QUEUE", queue: queue.id}], }); // Consumer: Process async const processorWorker = new cloudflare.WorkerScript("processor", { accountId, name: "processor-worker", content: processorCode, queueConsumers: [{queue: queue.name, maxBatchSize: 10, maxRetries: 3, maxWaitTimeMs: 5000}], r2BucketBindings: [{name: "OUTPUT_BUCKET", bucketName: outputBucket.name}], });

各字段含义:

  • queueBindings:把队列绑定到生产端 Worker,代码中通过env.PROCESSING_QUEUE.send(message)投递消息;
  • queueConsumers:把 Worker 注册为队列消费者,queue传入队列名;maxBatchSize控制单批最大消息数(示例为 10);maxRetries控制失败重试次数(示例为 3);maxWaitTimeMs控制最长批等待时间(示例为 5000ms);
  • 消费端组合:消费 Worker 可以同时绑定其他资源——示例里处理器把处理结果写入 R2OUTPUT_BUCKET,体现“消费 + 产出”的流水线组合。

微服务与服务绑定:Worker 间直接调用

多个 Worker 组成微服务时,用serviceBindings在服务间建立直接调用通道,避免走公网:

const authWorker = new cloudflare.WorkerScript("auth", {accountId, name: "auth-service", content: authCode}); const apiWorker = new cloudflare.WorkerScript("api", { accountId, name: "api-service", content: apiCode, serviceBindings: [{name: "AUTH", service: authWorker.name}], });
  • service字段引用目标 Worker 的name(如auth-service),与代码中env.AUTH.fetch(request)的调用方式一一对应;
  • 与直接拼接 URL 相比,服务绑定走内部网络、时延更低,且由平台负责服务发现;
  • 绑定建立后,apiWorkerauthWorker之间即形成隐式依赖,Pulumi 会保证被调服务先部署。

事件驱动架构:统一事件总线

当多个下游需要对同一事件流做出反应时,可以引入事件队列作为总线,生产端只负责投递,消费端按需订阅:

const eventQueue = new cloudflare.Queue("events", {accountId, name: "event-bus"}); const producer = new cloudflare.WorkerScript("producer", { accountId, name: "api-producer", content: producerCode, queueBindings: [{name: "EVENTS", queue: eventQueue.id}], }); const consumer = new cloudflare.WorkerScript("consumer", { accountId, name: "email-consumer", content: consumerCode, queueConsumers: [{queue: eventQueue.name, maxBatchSize: 10}], });

与“队列驱动处理”模式的区别在于意图:这里的事件队列承担总线角色(event-bus),可以挂载多个语义不同的消费者(邮件通知、指标统计、审计日志等),新增下游只需新增一个带queueConsumers的 Worker,而不改动生产端——这正是事件驱动架构的松耦合红利。

v6.x 版本化部署:蓝绿与金丝雀发布

v6.x 引入“Worker + WorkerVersion + WorkersDeployment”三段式资源,把代码版本流量分配分离,从而支持按百分比灰度:

const worker = new cloudflare.Worker("api", {accountId, name: "api-worker"}); const v1 = new cloudflare.WorkerVersion("v1", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v1.js", "utf8"), compatibilityDate: "2025-01-01"}); const v2 = new cloudflare.WorkerVersion("v2", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v2.js", "utf8"), compatibilityDate: "2025-01-01"}); // Gradual rollout: 10% v2, 90% v1 const deployment = new cloudflare.WorkersDeployment("canary", { accountId, workerId: worker.id, versions: [{versionId: v2.id, percentage: 10}, {versionId: v1.id, percentage: 90}], kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], });

模式要点(与 configuration.md、api.md 的版本化章节一致):

  • Worker:版本容器,本身不含代码,只定义name
  • WorkerVersion:不可变的代码 + 配置快照(contentcompatibilityDatecompatibilityFlags);
  • WorkersDeployment:把若干版本按percentage切分流量,并在部署层统一声明绑定(KV/D1/R2 等),示例实现 10% v2 / 90% v1 的金丝雀;调整percentage到 100% 即完成全量切换(蓝绿);
  • 适用场景:金丝雀发布、A/B 测试、蓝绿部署;
  • 不适用场景:绝大多数单版本应用应使用cloudflare.WorkerScript——它会自动完成版本管理,无需手动维护三段资源(详见 gotchas.md 中 “v6.x Worker versioning confusion” 一节)。若发现部署后 Worker 未接到流量,先检查是否误用了缺少WorkersDeployment的三段式写法。

生成 wrangler.toml:桥接 IaC 与本地开发

Pulumi 部署时不会读取wrangler.toml(见 gotchas.md 中 “wrangler.toml not consumed” 一节),本地wrangler dev与云端配置因此容易漂移。解决思路是反向生成:用@pulumi/command在资源创建后自动写出wrangler.toml,让本地开发与生产使用同一套绑定:

import * as command from "@pulumi/command"; const workerConfig = { name: "my-worker", compatibilityDate: "2025-01-01", compatibilityFlags: ["nodejs_compat"], }; // Create resources const kv = new cloudflare.WorkersKvNamespace("kv", {accountId, title: "my-kv"}); const db = new cloudflare.D1Database("db", {accountId, name: "my-db"}); const bucket = new cloudflare.R2Bucket("bucket", {accountId, name: "my-bucket"}); // Generate wrangler.toml after resources created const wranglerGen = new command.local.Command("gen-wrangler", { create: pulumi.interpolate`cat > wrangler.toml <<EOF name = "${workerConfig.name}" main = "src/index.ts" compatibility_date = "${workerConfig.compatibilityDate}" compatibility_flags = ${JSON.stringify(workerConfig.compatibilityFlags)} [[kv_namespaces]] binding = "MY_KV" id = "${kv.id}" [[d1_databases]] binding = "DB" database_id = "${db.id}" database_name = "${db.name}" [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "${bucket.name}" EOF`, }, {dependsOn: [kv, db, bucket]}); // Deploy worker after wrangler.toml generated const worker = new cloudflare.WorkerScript("worker", { accountId, name: workerConfig.name, content: code, compatibilityDate: workerConfig.compatibilityDate, compatibilityFlags: workerConfig.compatibilityFlags, kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}], d1DatabaseBindings: [{name: "DB", databaseId: db.id}], r2BucketBindings: [{name: "MY_BUCKET", bucketName: bucket.name}], }, {dependsOn: [wranglerGen]});

模式收益与前提:

  • wrangler dev与生产环境使用完全相同的绑定(KV/D1/R2 名称与 ID 均来自 Pulumi Output),杜绝配置漂移;
  • 单一事实来源:一切以 Pulumi 配置为准,wrangler.toml只是派生物;
  • pulumi.interpolate负责把 Output 安全地插值进 heredoc 字符串;dependsOn确保 KV/D1/R2 创建完成后再生成配置文件,Worker 则等配置文件就绪后再部署;
  • 反向模式:如果你的团队以 wrangler 为事实来源(配置文件由人维护),则反过来在 Pulumi 中读取wrangler.toml,保持同一方向上的同步即可。二者选其一,不要两套并存互不同步。

构建与部署流水线:先构建、后上传

由于 Pulumi 不会打包 Worker 代码,构建步骤必须显式编排进 IaC。用command.local.Commandnpm run build纳入资源图:

import * as command from "@pulumi/command"; const build = new command.local.Command("build", {create: "npm run build", dir: "./worker"}); const worker = new cloudflare.WorkerScript("worker", { accountId, name: "my-worker", content: build.stdout.apply(() => fs.readFileSync("./worker/dist/index.js", "utf8")), }, {dependsOn: [build]});
  • 构建命令在./worker目录执行,产出./worker/dist/index.js
  • content通过build.stdout.apply(...)延迟读取构建产物,dependsOn: [build]保证顺序正确;
  • 这是 gotchas.md 中 “No bundler/build step” 问题的标准解法:直接把src/index.ts原样上传会导致 Worker 报 “Cannot use import statement outside a module”,务必先构建、后部署。

内容 SHA 强制更新:打破“误判无变更”

Pulumi 通过内容哈希判断是否需要更新 Worker。当代码仅发生空白符、注释等不影响哈希的变化时,会出现“代码改了但pulumi up显示无变更”的假象。强制更新的惯用技巧是注入一个随每次发布变化的版本号绑定:

const version = Date.now().toString(); const worker = new cloudflare.WorkerScript("worker", { accountId, name: "my-worker", content: code, plainTextBindings: [{name: "VERSION", text: version}], // Forces deployment });
  • 每次pulumi up都会生成新的时间戳文本,导致绑定值变化,从而强制触发一次新部署;
  • 附带收益:env.VERSION让 Worker 运行时能上报自身构建版本,便于排障与日志追踪;
  • 代价是每次部署都会产生新的绑定差异,适合需要确定性“每次都部署”的场景(详见 gotchas.md 中 “False no-changes detection” 一节)。

模式落地时的常见陷阱与最佳实践

把上述模式组合进真实项目时,请对照 gotchas.md 自查:

  1. 绑定名大小写敏感:Pulumi 侧kvNamespaceBindings: [{name: "MY_KV", ...}]必须与 Worker 代码env.MY_KV完全一致,否则运行时报env.MY_KV is undefined
  2. D1 迁移不进pulumi upD1Database只负责建库,不会执行 SQL。需要用一个command.local.Command(如wrangler d1 execute ${db.name} --file ./schema.sql)并dependsOn: [db],再让 WorkerdependsOn: [migration],保证迁移先于部署;
  3. API Token 权限:若遇到authentication error (10000),说明 Token 缺少权限,需要至少授予Account.Workers Scripts:EditAccount.Account Settings:Read
  4. 导入后资源“异常变更”pulumi import后若pulumi preview显示变更,是状态与真实资源属性不一致,需调整 Pulumi 代码与真实资源对齐(可参考 api.md 中的导入命令清单);
  5. 始终设置compatibilityDate:锁定 Worker 运行时行为,避免 Cloudflare 平台升级引发破坏性变更。

相关参考

本文对应仓库中的完整参考集:

  • pulumi/README.md:Provider 总览、认证方式、核心原则与阅读顺序;
  • pulumi/configuration.md:Worker/KV/D1/R2/Queue/Pages/DNS 等资源完整配置;
  • pulumi/api.md:Output、依赖、数据源、动态 Provider、导入与密钥管理;
  • pulumi/gotchas.md:常见错误、最佳实践与平台限制;
  • cloudflare-deploy/SKILL.md:Cloudflare 部署技能总览,含决策树与产品索引;
  • 横向对比可参考 terraform(另一套 IaC 方案)与 wrangler(CLI 部署方案)。

从单一 Worker 到事件驱动微服务、再到金丝雀发布,这十种模式覆盖了 Cloudflare 平台上从“能跑”到“可灰度、可演进”的完整路径。按需选取、组合运用,即可用纯代码把整套云上基础设施管理起来。

【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询