如果你在 GitHub 上搜过 cloudflare-os,一定见过一堆把 Cloudflare 服务组合成“伪操作系统”的项目。我理解中的 cloudflare-os 并不是某个官方产品,而是一整套运行在 Cloudflare 边缘网络上的个人云系统:Workers 当 CPU,D1 当数据库,R2 当硬盘,Pages 当桌面,Cron 当定时器,DNS 和 WAF 当网络配置。这不是赶时髦,而是把服务器运维里最容易失眠的部分——升级、备份、扩容、防攻击——全部下沉到平台侧。我最近半年把博客、短链、私有文件柜、状态监控页都搬了上去,除了工具链偶尔需要折腾,整体稳定得让我意外。这篇文章就把这套 cloudflare-os 的搭建思路、关键配置和踩过的坑写出来,想少买一台服务器、又不想被机房 SLA 绑架的朋友,可以抄作业。
1. 项目定位与整体架构
1.1 我为什么要把 Cloudflare 当成操作系统用
说实话,一开始我也觉得“OS”这种叫法有噱头成分。但用着用着会发现,Cloudflare 提供的组件边界非常像一台 Linux 主机的分层:DNS 负责解析域名,对应/etc/resolv.conf;WAF 和 DDoS 防护对应防火墙和 SELinux;Workers 是无状态计算进程,类似 systemd 托管的小服务;KV、D1、R2 分别是缓存、数据库和对象存储,对应 Redis、MySQL、MinIO;Pages 则是静态资源目录,对应 Nginx 的站点根目录。当你把这些服务用 API 串起来,确实能得到一个“无主机”的操作系统。
这个方案解决的核心痛点是运维成本。传统服务器哪怕只是跑个博客,也要处理系统补丁、PHP 版本、SSL 证书续期、磁盘报警、备份策略。而在这套体系里,所有底层都由 Cloudflare 处理,我只需要关系自己的业务代码。第二个痛点是弹性,流量突增时不用提前买高配机器,免费额度内就能扛住日常流量,超了顶多加个付费计划,没有“半夜起来加带宽”的剧本。
1.2 整体架构和组件映射
下面这张表是我目前线上服务的组件映射,可以当作参考模板:
| 传统服务器角色 | Cloudflare 对应组件 | 我的实际用途 |
|---|---|---|
| 解析与路由 | Cloudflare DNS + CDN | 所有域名的入口,缓存静态资源 |
| Web 服务器 | Pages + Workers | 博客静态站点,API 与短链服务 |
| 数据库 | D1 | 短链的记录表、订阅关系 |
| 缓存 | KV | 热数据、访问计数、会话状态 |
| 对象存储 | R2 | 私有文件柜、备份归档 |
| 定时任务 | Cron Triggers | 每小时备份、每日数据汇总 |
| 消息通知 | Email Routing + Workers | 邮件收发自动转发到 webhook |
| 监控日志 | Workers Analytics + Logs | 异常请求和延迟追踪 |
组件之间通过 Wrangler 配置里的 bindings 互相访问,不需要在网络层面打通端口。这也意味着,原本一台服务器上各种进程的通信,变成了边缘节点内部的调用,减少了网络延迟和故障点。特别适合我这种喜欢用杂牌云服务拼生态的人。
注意:不要一上来就堆太多组件。我的建议是先跑通静态站点 + Workers,再逐步加 R2 和 D1。一次引入全部概念,很容易被配置项劝退。
2. 核心服务选型与细节
2.1 Workers:几行代码就能部署的边缘函数
Workers 是整个 cloudflare-os 的“计算大脑”。它的模型很简单:你导出一个名为fetch的异步函数,收到请求后返回一个Response。但它真正厉害的是可以在全球 300 多个节点上运行,用户访问时自动就近执行,不需要专门维护 Node.js 进程。
我的短链服务和 API 都跑在 Worker 上。一个 Worker 绑定了 D1 和 R2,代码量并不大,因为路由和响应逻辑全在脚本里:
export default { async fetch(request, env, ctx) { const url = new URL(request.url); if (url.pathname === "/api/shorten") { return handleShorten(request, env); } return new Response("Not Found", { status: 404 }); } };不要小看这个函数,它已经在边缘替我处理了几万次请求。部署时只需要在项目根目录执行wrangler deploy,Cloudflare 会把代码打包成运行在 V8 引擎上的快照,冷启动时间远低于传统容器。另外,env参数里能直接拿到 D1、KV 和 R2 的绑定实例,不需要环境变量或者连接池,这比传统云函数方便很多。
2.2 D1 和 R2 的数据分工
很多人会把 D1 和 R2 搞混。简单说,D1 是关系型数据库,基于 SQLite,适合存结构化数据;R2 是对象存储,适合存文件。我的选择是:短链的映射表(短码 ↔ 原 URL)用 D1,因为我要按索引查询和统计;文件柜里的图片和压缩包用 R2,因为不需要频繁改写,靠 S3 兼容 API 就能读写。
D1 的使用方式和 SQLite 几乎一样,直接用 SQL 建表:
CREATE TABLE IF NOT EXISTS links ( short_code TEXT PRIMARY KEY, target_url TEXT NOT NULL, created_at INTEGER DEFAULT (unixepoch()) );Worker 里执行查询时,用env.DB.prepare(sql).bind(...).first()这类接口。实践上有两个细节:第一,D1 的查询延迟通常在 10ms 到 50ms,对大多数场景没问题;第二,写入 D1 后可以立即读,但跨数据中心复制是异步的,所以在低优先级场景(比如访问统计)用 KV 更合适。
R2 则更像是“无限硬盘”,免费额度内可以存不少东西。它的优点是没有出口带宽费,下载请求的成本很低。我把备份文件、博客里的附件、监控截图都丢进 R2,再通过 Cloudflare CDN 分发,体验很顺滑。R2 提供 S3 兼容接口,我用rclone或 AWS SDK 就能上传下载,不需要额外写协议转换。
2.3 Pages 和 Workers 的取舍
Pages 是静态站托管服务,支持从 GitHub 仓库自动构建。如果你的项目是博客、文档、SPA 前端,用它比 Workers 更合适:内置 CDN、自动 HTTPS、自定义域名、分支预览。而 Workers 更适合需要动态响应 API 的场景,比如处理表单、数据库读写、请求外部接口。
我的做法是“页面归 Pages,接口归 Workers”。博客站点部署在 Pages,当页面里有动态组件时,前端 JS 请求 Workers 的/api/*地址。Pages 还支持_worker.js或functions/目录来写动态逻辑,但我把它和主站逻辑分开,这样升级时互不影响。如果你想一个项目里同时搞定静态和动态,可以试试 Pages Functions,不过复杂项目里我还是推荐拆开部署。
3. 实操过程与核心环节实现
3.1 环境准备与 Wrangler 初始化
实操部分基于当前 CLI 工具 Wrangler v3,Node.js 建议 18 以上。先安装并登录:
npm install -g wrangler wrangler login然后创建一个 Worker 项目:
npm create cloudflare@latest my-worker cd my-worker wrangler deploy登录后,wrangler login会打开浏览器授权,完成后会在本地生成用户凭证。如果你在 CI 里自动化部署,可以改用 API Token 配合环境变量,这里不展开。
创建完项目后,需要配置wrangler.toml,它相当于操作系统的“驱动配置文件”。我通常会把 D1、KV、R2 的绑定写清楚,方便本地开发时直接映射到资源。
name = "my-cloud-os" main = "src/index.js" compatibility_date = "2024-11-01" [[d1_databases]] binding = "DB" database_name = "shortlink-db" database_id = "your-database-id" [[kv_namespaces]] binding = "KV" id = "your-kv-id" [[r2_buckets]] binding = "FILES" bucket_name = "private-files"3.2 用 Workers 十几分钟搭一个短链服务
短链服务是 cloudflare-os 里最适合新手练手的项目,因为逻辑简单但涉及 D1、KV、Cron 和域名绑定,能覆盖大部分知识点。
先建表,我直接在控制台用 D1 的 SQL 终端执行:
CREATE TABLE links ( code TEXT PRIMARY KEY, url TEXT NOT NULL, hits INTEGER DEFAULT 0 );Worker 里定义两个 handler:一个是 POST/api/shorten,接收{"url":"https://..."}返回短码;另一个是 GET/s/:code,查询 URL 并跳转。为了让短码足够短,我用了crypto.randomUUID().slice(0, 8)生成 8 位随机码,虽然不够优雅但够用。
注意要处理几个边缘情况:URL 合法性校验、重复短码的数据库冲突、点击量更新。尤其点击量,如果用同步 UPDATE 会影响跳转性能,我的办法是先在 KV 里记一个瞬时计数,再让定时任务每小时把 KV 里的增量回写到 D1。这样跳转永远是“读 KV + 重定向”,延迟很低。
代码大致如下:
async function handleShorten(request, env) { if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 }); const body = await request.json(); const code = crypto.randomUUID().slice(0, 8); await env.DB.prepare("INSERT INTO links (code, url) VALUES (?, ?)").bind(code, body.url).run(); return new Response(`https://s.example.com/s/${code}`, { status: 200 }); } async function handleRedirect(request, env, ctx) { const code = new URL(request.url).pathname.split("/").pop(); const row = await env.DB.prepare("SELECT url FROM links WHERE code = ?").bind(code).first(); if (!row) return new Response("Not Found", { status: 404 }); // 更新缓存计数 await env.KV.put(`hits:${code}`, String((parseInt(await env.KV.get(`hits:${code}`)) || 0) + 1)); return Response.redirect(row.url, 302); }这段代码不复杂,但能完整展示一个事件驱动服务的样子。部署后,在 Dashboard 里配置自定义域s.example.com,Cloudflare 会自动绑定到 Worker。注意 SSL/TLS 加密模式我建议设为 Full(Strict),避免证书问题。
3.3 把静态博客迁到 Pages
我已经有一份 Hugo 的博客源文件,迁移到 Pages 极快。在 Pages 控制台选择“Connect to Git”,仓库里放好构建命令和输出目录:
- 框架预设:Hugo
- 构建命令:
hugo - 输出目录:
public - 环境变量:按需设置
配置完成后,每次 push 到主分支都会自动触发构建。之后设置自定义域名和 HTTPS,页面秒开。
这里有个省心小技巧:把博客里面所有图片、附件改用 R2 域名访问,可以大幅减少 Pages 的源站流量,并且通过 Cloudflare 的缓存策略自动热部署。我用 R2 公开桶 + 自定义域名/files/*路径,配合 Cache Everything 规则,体验和对象存储 CDN 没有区别。
3.4 配置定时任务与邮件路由
定时任务在 cloudflare-os 里很重要,相当于系统 crontab。在wrangler.toml里加:
[triggers] crons = ["0 * * * *"]然后导出的 Worker 里新增一个scheduled函数:
export default { async scheduled(event, env, ctx) { ctx.waitUntil(runBackup(env)); }, async fetch(request, env, ctx) { // ... } };我用它做两件事:每小时把 D1 里的短链数据备份成 JSON 扔进 R2,每天拉取 Workers 日志摘要并推送到 Telegram Bot。因为调度器本身不消耗用户的 HTTP 请求次数,所以免费额度下也能稳定跑。
邮件路由的配置更简洁:在 Cloudflare 控制台把自定义域名的邮箱地址设为转发到“Send to Workers”,然后 Worker 里监听email事件。我在 Worker 内部解析邮件主题,如果是“备份完成”就把内容存到 D1,如果是垃圾邮件就丢弃。这个功能很适合做轻量级的通知入口,比单独维护一个邮箱服务器省心得多。
3.5 用 R2 和 Workers 组一个私有文件柜
最后补一个比较有成就感的组件:私有文件柜。它把 R2 当做存储,Workers 做鉴权,专门用来存取个人配置、文档、截图和小体积安装包。
实现上,Workers 提供一个/files/list和/files/upload接口,上传时检查请求头里的 Authorization Token,然后把文件写入 R2。下载时用 Cloudflare 内置的临时签名 URL,避免公开暴露读写权限。R2 支持createMultipartUpload和getDownloadUrl,配合前端<input type="file">很快就能用。
认证这块千万不要自己存明文密码,我用 Cloudflare Access 或者简单的 HMAC 签名。个人用的文件柜,也可以在 Worker 里把请求头里自定义 Token 和 Cloudflare Secrets 里存的值比对,够用就行。上线后把 R2 配置成 S3 兼容存储,配合 Rclone 就能像网盘一样访问,手机和电脑都能直接挂载。
4. 常见问题与排查技巧实录
4.1 Worker 报错 1101 / 1102 / 1103 都是什么意思
Cloudflare 控制台或日志里常见的三个错误:1101 是 Worker 抛出了无法序列化的返回值,1102 是内存超限,1103 是 CPU 超时。遇到 1101 先看是不是Response里放了非法的 body,比如返回了一个数字而不是字符串;1102 则是长时间运行的循环或大文件读入内存;1103 需要检查是否在fetch里做了同步阻塞操作。
最常见的坑是没加ctx.waitUntil就异步执行了额外任务。比如我想在请求结束后更新统计,直接把 Promise 甩在背后,结果 Worker 在异步操作完成前就被冻结了。正确写法是把所有后台任务放进ctx.waitUntil(promise)里。我一开始没在意,导致日志里频繁出现奇怪的截断。
4.2 免费额度和配额容易碰到的边界
Cloudflare 免费计划下,Workers 每天有 10 万次请求的额度,CPU 时间也有上限。普通博客和短链完全够用,但要是有人用脚本狂刷短链,会很快耗尽配额。我的经验是把热门静态资源全部缓存到 CDN,动态请求尽量控制在几百次/天内。另外,D1 的读操作在免费版有限制,不要把高频页面直接查 D1,一定要在前面套 KV 缓存。
R2 的免费额度是 10GB 存储和每月 100 万次读,但写敏感操作会消耗另一部分配额。备份文件如果每小时覆盖一次,可以每次先写入新名字再删除旧对象,避免长时间占用写次数。
有个很隐蔽的坑:免费计划里 Cron Triggers 是支持的,但scheduled事件不计入 HTTP 请求数,所以你会看到“请求数没怎么动但 CPU 时间涨了”。排查时别只看请求数,要在 Analytics 里看 CPU 时间维度。
4.3 本地调试时连不上 D1 / R2
本地wrangler dev启动 Worker 后,默认会使用本地模拟的 D1 和 R2,不会影响线上数据。但如果你配置了--remote,那本地开发时的读写会打到云端资源。新手最容易在这里翻车:跑了半天wrangler dev --remote把线上短链表清空了,还以为只是测试。
建议开发环境永远不要加--remote。需要真实数据时,可以用wrangler d1 export导出线上表,再导入本地。R2 可以用rclone同步一个测试桶,保证生产桶只被正式 Worker 访问。
4.4 缓存导致的“修改不生效”
Pages 和 Workers 的静态资源默认会缓存,所以你改完 CSS 后,用户可能还在用旧文件。我在 head 里给资源加了版本号,/assets/app.css?v=20250101,CDN 就会当成新 URL 重新回源。Worker 返回的 API 响应如果带了Cache-Control头,也要注意更新后客户端和边缘节点是否还拿旧的。
有时候 Cloudflare Dashboard 里能看到缓存已清,但本机依然拿旧内容,那大概率是浏览器缓存或你本地的 DNS 解析缓存。在“Caching → Purge Everything”里清空边缘缓存后,再用无痕窗口测试,能快速定位是哪一层的问题。
5. 管理和进阶玩法
5.1 用 Wrangler 管理多环境
我把这套系统拆成了 preview 和 production 两个 Worker 环境。wrangler.toml里可以设置[env.production],不同环境绑定不同的 D1、R2 资源。Preview 环境收到 push 后自动部署,验证没问题再手动切换生产。这个流程和传统 CI 的测试/生产隔离差不多,但 Cloudflare 原生支持,省去 Jenkins 配置。
多环境的关键点是变量管理:数据库 ID、密钥这些不要写在代码里,用wrangler secret put保存。不同环境可以分别设置 Secrets,工具有时候会提示覆盖,但底层互相独立,不用担心串环境。
5.2 零成本的监控告警
有了 cloudflare-os,我额外盯上了“系统健康”。Workers 自带的 Analytics 能看到请求量、错误率、CPU 耗时。配合 Cron 定时任务,我每隔一段时间请求一次自己的 API 做健康检查,如果返回非 200 就触发 Telegram 推送。这个方案比第三方 uptime 监控更灵活,因为我可以拿到 Worker 内部的执行日志,定位问题根因更快。
再进一步,可以把 R2 里的日志文件用 Logpush 导入到自己的存储,或者直接可视化在 Grafana 里。不追求极致的话,控制台自带的 Logs 标签页已经能解决 80% 的排查需求,没必要引入复杂监控系统。
5.3 这套架构适合哪些场景
我验证下来最适合的三类场景:个人博客与内容站、内部工具接口与短链服务、轻量级的文件存储网关。不适合的场景包括:强一致性事务、超低频但超长耗时任务、依赖大量 CPU 计算的应用。如果需要处理音视频转码这类重计算,不如用专门的计算实例,Cloudflare 的 Worker 定位是轻量边缘计算。
如果你也打算搞一套“无服务器个人云”,我建议从短链服务开始,理由很简单:它小到一天就能写完,又能把你对 Workers、D1、KV、域名配置的陌生感全部消掉。等短链跑顺了,再逐步把博客、文件柜、定时任务接进来,你也会慢慢体会到 cloudflare-os 这套打法的爽感——不用操心底座,所有精力都能放在业务逻辑上。我自己踩过最大的坑就是一开始想一步到位,结果项目结构一团乱。从最小可行服务开始,是这套架构真正好用的秘诀。