把 Cloudflare 当作边缘操作系统:个人云架构设计与实践全记录
1. 先说说我为什么把这个项目叫作“cloudflare-os”
如果你平时爱折腾 Cloudflare,大概率会发现一个很有意思的现象:这个平台的免费额度,其实已经足够撑起一个小型个人云的全部核心能力。域名托管、DNS 解析、CDN 缓存、边缘函数、KV 存储、R2 对象存储、Tunnel 内网穿透,这些服务单拆开看都不稀奇,但把它们放进同一个账户、用同一套策略统一调度,效果就很像一台跑在边缘网络上的“操作系统”。
所谓 cloudflare-os,其实不是一个官方产品,而是我给自己这套方案起的内部代号。它的核心思路很简单:把 Cloudflare 的边缘网络当作一台分布式的“裸机”,上面跑的每一行配置、每一个 Worker 脚本、每一条 Tunnel 规则,就是这台虚拟主机的“内核模块”和“用户态程序”。域名相当于设备标识,DNS 是地址簿,CDN 是预取缓存,Workers 是事件处理器,KV/R2 是持久化存储,Pages 是前端构建产物,而 Tunnel 则负责把家里的实体服务器安全地“挂载”到这台云主机上。
这个方案解决了我两个很实际的痛点。第一,以前自己搭服务总要买一台 VPS,既要操心系统补丁、进程守护,又要担心流量被打爆;现在大部分请求在边缘就被处理掉了,真正回源的只剩下必须动态计算的内容。第二,家里有 NAS、有树莓派、有跑着各类脚本的小主机,想在外部安全访问它们一直很头大,Cloudflare Tunnel 帮我把内网服务和公网域名打通,不需要端口转发,也不需要暴露真实 IP,TLS 证书全部由平台接管。
这篇文章适合谁看?如果你正在研究个人云基础设施、想把 Cloudflare 的全家桶项目统一管理起来,或者正处于“手上有一堆服务但入口太散”的阶段,这篇文章里的架构思路、配置步骤和踩坑记录应该能省下你不少时间。我会从设计思路讲到具体实现,再放上我在真实使用过程中遇到的典型问题和排查方法,尽量做到看完就能自己动手。
2. 整体架构与关键组件选型
2.1 三层模型:DNS、CDN、边缘计算是怎么分工的
一台传统的服务器,处理一次用户请求大致要经过 DNS 解析、Web 服务接收请求、应用层逻辑计算、数据库读取、返回响应这几个环节。Cloudflare 做的事情,是把其中一部分环节向用户侧“提前”完成。我在设计 cloudflare-os 时,把整个系统按职责拆成了三个层次。
第一层是 DNS 与路由层。域名托管在 Cloudflare 之后,所有解析记录都在同一个控制台里维护。这里的重点不是解析速度,而是“代理状态”开关。橙色云开启时,流量会先经过 Cloudflare 边缘网络再回源;灰色云关闭时,记录直接指向源服务器。对于需要走 CDN 和边缘能力的业务,必须开启橙色云;涉及 SSH 这类非 HTTP 协议,则只能走灰色云或者靠 Tunnel 转发。
第二层是缓存与分发层,就是 CDN。Cloudflare 的节点会把静态资源缓存到距离用户最近的数据中心,图片、CSS、JavaScript 这些内容根本不会回源。规则从哪里定?可以在控制台配置 Cache Rules,也可以用 Workers 在请求级别动态控制缓存策略。我的习惯是:凡是能缓存的响应一律设置 Cache-Control 头,并在 Worker 里显式声明缓存行为,而不是依赖默认策略。
第三层是边缘计算层,也就是 Workers。它运行在 Cloudflare 的 V8 隔离环境中,相当于把一小段应用代码部署到全球 300 多个城市的数据中心。用户请求到达哪个节点,代码就在哪个节点执行。这样处理一些轻量逻辑时,延迟可以压得很低。选择 Workeres 而不是自己买服务器跑后端,最大的原因是它没有冷启动焦虑,也没有“这台机器挂了怎么办”的运维问题。
这三层配合的最理想效果是:静态资源从 CDN 走,轻量逻辑在 Workers 执行,只有像用户认证、数据写库、文件处理这类必须集中处理的操作才会回源或落到持久化存储。能用边缘解决的就不往家里服务器引,这是整个项目设计的一条主线。
2.2 数据层怎么选:KV、D1、R2 还是 Queues
刚开始搭这套系统时,最容易犯的错是想用一个存储方案解决所有问题。实际情况是 Cloudflare 的每个存储产品都有明确的边界,选错后面就得折腾数据迁移。我根据自己的使用场景做了一个简单的划分。
KV 适合读多写少、对最终一致性容忍度高的数据。比如说网站配置项、功能开关、用户会话的缓存副本、临时标记位。KV 的读取延迟很低,配合 Workers 使用非常顺手,但它不是关系型数据库,别指望用它做复杂的条件查询。
D1 是 Cloudflare 提供的 SQLite 兼容数据库,适合存结构化数据。比如用户的账单记录、任务清单、评论列表。它支持 SQL 语法,也有 migrations 工具,对于轻量级的关系型需求完全够用。D1 的查询性能受边缘节点到数据库存储位置的影响,跨地域访问会有一定延迟,设计时要考虑数据本地性。
R2 是对象存储,对标的是传统厂商的对象存储服务。它的一个显著优势是不收出口带宽费,这意味着我可以放心地把图片、视频、备份存档放在 R2 上,配合 CDN 对外分发。免费额度是 10GB 存储,对个人项目来说前期很够用。
Queues 是消息队列,适合处理异步任务。比如用户上传了一张图片,需要生成缩略图;或者某个定时任务要批量刷新缓存。把这些工作塞进队列,由后台 Consumer 慢慢消费,用户界面不会卡顿。
我的选择逻辑很直白:配置类数据放 KV,业务类数据放 D1,文件类数据放 R2,耗时任务放 Queues。不是越多越好,而是让每类数据落在最适合它的存储里。搭配起来以后,整个方案的存储成本和维护成本都很低。
| 存储类型 | 数据特点 | 典型场景 | 免费额度 |
|---|---|---|---|
| KV | 键值对,读多写少,最终一致 | 配置项、开关、会话缓存 | 每天 10 万次读取 |
| D1 | 关系型,SQL 查询 | 业务数据、用户列表、订单 | 5GB 数据库 |
| R2 | 对象存储,大文件 | 图片、视频、备份存档 | 10GB 存储 |
| Queues | 异步消息 | 缩略图、邮件、任务队列 | 每月 100 万次操作 |
2.3 Tunnel:把本地服务接入边缘网络的正确姿势
cloudflare-os 里的一个关键角色是 Tunnel。简单说,它是在你本地设备和 Cloudflare 边缘之间建立一条安全的出站连接,本地设备不暴露任何入站端口。边缘收到请求后,通过这条连接转给本地服务,然后把响应返回给用户。
这个方案的实用价值很明显。以前如果想在家里跑一个服务给外面访问,要么申请公网 IP,要么做端口映射,还要自己处理域名证书。现在只需要在本地运行一个 cloudflared 进程,它主动连出去,所有公网入口都在 Cloudflare 侧,安全性和易用性都大幅提升。
我在实际架构里把 Tunnel 用在两个地方。一是把自己家里的 NAS 和媒体服务暴露出去,只对可信域名开放,并且加上 Access 策略做额外身份校验。二是把本地的开发环境接入边缘网络,比如临时给客户展示一个 demo 页面,不用重新部署,直接通过 Tunnel 映射到公网域名。整个过程配置文件都可以版本化管理,重装系统后一条命令拉起来。
当然,Tunnel 也不是万能的。它适合 HTTP 服务,对于裸 TCP 场景支持方式相对受限,而且本地到边缘之间的带宽上限取决于你家庭宽带的上行速度。如果本地服务没有公网访问需求,完全没必要套一层 Tunnel,别把它当成默认选项。
3. 从零到一实现 cloudflare-os 的完整过程
3.1 准备工作:域名接入与账号初始化
开始搭之前,我建议先把域名迁入 Cloudflare 管理。如果你已经在 Cloudflare Registrar 注册域名,这一步可以跳过,因为 DNS 天然由平台托管。如果域名是在其他注册商那里买的,需要做一次 DNS 托管迁移。
操作路径是:登录 Cloudflare 控制台,点击“添加站点”,输入你的域名,选择免费计划,然后系统会给出两个 NS 地址。你要去注册商那边把域名的 NS 记录改成这两条。改完之后,DNS 记录会在几小时到一两天内逐步生效,具体速度取决于注册商和 TTL 设置。
等状态变成“活跃”后,第一件事就是把基础记录建好。我通常会先建一个根域名的 A 记录指向本地服务的占位 IP,或者直接建一个用于验证的 CNAME。注意这里先别急着开橙色云,等核心服务都跑通了再统一开代理,不然中间排查问题会多一层干扰。
账号初始化这块,我强烈建议开启两步验证。因为后面你会把很多核心资产挂在这个账号下,包括域名控制权、所有 DNS 记录、边缘函数和存储桶。一旦账密泄漏,影响面比单一服务器被攻破还要大。另外建议把 API Token 的最小权限原则用起来,不同用途用不同 Token,避免一个 Token 拥有全部权限。
3.2 第一个 Worker:边缘函数怎么跑起来
当 DNS 和域名都稳定以后,就可以从最简单的 Worker 开始体验边缘计算。进入控制台,找到 Workers 菜单,点击创建,会有一个在线编辑器,直接在里写代码就行。
我第一次跑通的 Worker 很简单,就是一个返回 JSON 的 API。代码如下:
export default { async fetch(request, env, ctx) { const data = { service: 'cloudflare-os', status: 'running', timestamp: Date.now() }; return new Response(JSON.stringify(data, null, 2), { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' } }); } }部署后,Cloudflare 会分配一个*.workers.dev子域名,访问它就能看到返回结果。这一步就是把代码部署到了边缘网络,所有经过的请求都由离用户最近的节点处理。响应里加上 Cache-Control 头之后,这个接口会被 CDN 缓存,后续请求根本不会执行 Worker 逻辑,直接从缓存返回。
如果你想用自己的域名访问,可以在 Worker 的“设置”里添加自定义域名。Cloudflare 会自动帮你完成路由配置,不需要额外改 DNS。这里有个细节:自定义域名必须在同一个账号下,且域名的 NS 托管在 Cloudflare,否则无法绑定。
Worker 真正强大之处是可以处理请求路径、修改响应头、鉴权、访问存储。比如给接口加一个简单 Token 校验,逻辑并不复杂:
export default { async fetch(request) { const token = request.headers.get('Authorization'); if (token !== 'your_token_here') { return new Response('Unauthorized', { status: 401 }); } return new Response('Hello from edge!'); } }上面只是演示,真实项目里 Token 应该放在 Worker 的 Secrets 环境变量里,而不是硬编码到源码中。在控制台的“设置 - 变量”中可以添加加密变量,代码里通过env.YOUR_VAR读取。
3.3 R2 存储桶:给静态资源找一个归宿
静态资源是整个云操作系统里占比很大的一块。图片、文档、备份文件,都需要放到对象存储里。Cloudflare R2 的创建流程很顺滑,控制台左侧找到 R2,点击创建存储桶,输入名称和区域位置即可。
存储桶创建完成后,默认会得到一个https://<bucket>.<account>.r2.cloudflarestorage.com这样的私有端点。但直接暴露这个地址既不方便也不好看,建议绑定自己的自定义域名。步骤是进入存储桶的“设置”页面,找到“自定义域”,添加一个已经托管在 Cloudflare 的域名,比如cdn.example.com。平台会自动创建一条 CNAME 记录,并为你签发 SSL 证书。
绑定域名后,上传的文件就可以通过https://cdn.example.com/xxx.jpg访问。这里还有一个 CORS 配置值得注意。如果你在前端页面里直接调用 R2 的 API 上传文件,必须配置允许的来源域名,否则浏览器会拦截跨域请求。配置界面在存储桶的“设置 - CORS”里,直接粘贴规则即可:
{ "CORS": [ { "AllowedOrigins": ["https://your-app.example.com"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], "AllowedHeaders": ["*"] } ] }上传文件可以直接用控制台,也可以装 Wrangler CLI 或用 SDK。我更喜欢命令行方式,脚本化之后方便批量处理。用 Wrangler 操作 R2 的示例命令大致是这个样子:
wrangler r2 object put cloudflare-os-bucket/path/to/file.jpg --file=./local.jpg如果想在 Worker 里动态写入文件,比如处理用户上传的头像,可以通过env.MY_BUCKET.put()完成。R2 的 API 兼容原有接口习惯,做过对象存储开发的人基本没有学习成本。我实际用下来,最舒服的点还是不计费出口流量,大文件分发不用担心带宽账单,这才是 R2 最打动我的原因。
3.4 Tunnel 实战:让家里的服务安全上云
Tunnel 是我这套架构里最有“打通任督二脉”感觉的部件。实现前先在控制台左侧找到 Zero Trust 菜单,进入 Networks 下的 Tunnels 页面,创建一个隧道。创建过程中会要求你安装 cloudflared,并给出安装命令。
安装完成后,在本地机器上运行验证命令,确保 cloudflared 进程能正常连接边缘。接着就是配置 tunnel 的 ingress 规则。我一般直接新建一个config.yml文件,内容大致如下:
tunnel: <your-tunnel-token> credentials-file: /home/user/.cloudflared/<your-tunnel-id>.json ingress: - hostname: nas.example.com service: http://192.168.1.10:5000 - hostname: photo.example.com service: http://192.168.1.10:8000 - service: http_status:404这个文件表达的意思很清楚:把 nas 子域名的请求转发到本机局域网里那台 NAS 的 5000 端口;把 photo 子域名的请求转发到 8000 端口;没有匹配到的其他请求一律返回 404。配好后运行cloudflared tunnel run <隧道名>启动服务即可。
启动成功后,你还需要在 Cloudflare 控制台的 Tunnel 页面给每个子域名添加 Public Hostname。这一步本质上是在边缘创建一条路由规则,让请求能通过隧道转发进来。之后通不通,直接访问子域名就能验证。
把 cloudflared 设置成系统服务是另一个重要环节。我用 systemctl 管理它,写一个简单的服务文件,开机自启,进程异常自动重启。这样就可以在重启机器后不用手动操作,整套服务自动恢复。
4. 我在 cloudflare-os 上实际跑了什么
4.1 个人仪表盘:每天打开的第一个页面
cloudflare-os 搭建完成后的第一个应用,是一个个人导航页兼仪表盘。它部署在 Pages 上,入口是根域名,内容包括时间、天气、服务状态链接、常用工具入口。这些数据有一部分是静态展示的,只有天气和状态检测是动态获取。
天气数据我通过一个 Worker 做转发,避免前端直接暴露第三方 API 密钥。状态检测则是在 Worker 里发起 HEAD 请求检查各个服务是否在线。这套逻辑完全跑在边缘,仪表盘打开时只向边缘发起一次请求,速度快,服务器端零压力。
Pages 的部署体验很不错,连接 Git 仓库后,每次推代码都会自动构建发布。配合 Cloudflare 自带的 CI/CD,省掉了整套规范的发布流程。前端代码里不需要关心服务器环境,因为它就是一组静态文件加若干 Worker 接口。
4.2 图片托管与相册服务
另一个实际运行的项目是家庭相册。我的需求是:上传照片后可以在线查看,并自动生成缩略图。上传入口是一个简单的前端页面,图片文件直接传到 R2 存储桶,上传成功后把一个任务塞进队列,后台 Worker 处理缩略图生成并把小图写到另一个前缀下。
因为是个人使用,流量不大,R2 的免费额度完全够用。缩略图生成是异步的,用户上传后先显示原始文件名,后台处理完成缩略图后自动刷新,体验上没有明显等待。对象存储的好处就是不需要自己维护磁盘和目录结构,扩展性天然存在。
中途遇到过一个坑:直接通过 Worker 上传大文件时,请求体大小可能超限。解决方法是在前端用 R2 的预签名 URL 方式直传,即 Worker 只负责生成一个带权限的上传地址,浏览器直接传给 R2。这样大文件流量不经过 Worker,速度更快也更稳定。
4.3 自托管应用的统一入口
家里运行着一些自托管服务,比如媒体服务器、下载工具、代码仓库。以前访问这些服务需要记 IP 加端口,还经常因为忘记开端口导致连不上。现在所有服务都通过 Tunnel 接入 cloudflare-os,统一使用二级域名访问。首页仪表盘上直接列出每个服务的入口和状态,点一下就能跳转。
为了让安全性更好一点,我在部分敏感服务前面加了 Access 策略。用户需要经过邮箱验证或者硬件密钥验证才能打开页面,等于给自托管应用统一加了一层身份认证。这个体验比自己去各个应用里单独配置登录页要顺畅得多,而且不论什么应用,认证方式都一致。
这套方案跑到现在,最大的变化是我不再纠结某台服务器是否稳定了。边缘能力消化了大部分流量,家里的机器只承担真正需要它承担的职责。对于没有高可用要求的个人项目来说,这种复杂度控制刚刚好。
5. 常见问题与排查经验实录
5.1 DNS 解析明明改了为什么半天不生效
我遇到过不少次,给域名加了新记录,控制台显示已经是“有效”状态,但访问时还是旧地址。原因通常有几个层面。一是本地 DNS 缓存没刷新,操作系统都会缓存解析结果,可以用dig或在线 DNS 检查工具确认权威服务器返回的状态。二是在你真正访问时,本地运营商递归服务器也会有一层缓存,这个只能等 TTL 到时间或更换公共 DNS 验证是否正常。
另外还有一些“看似 DNS 问题实际是代理问题”的场景。比如你开了橙色云代理,边缘会缓存旧的源 IP,即使你已经改了源站地址,响应可能还是旧的。处理方式是去 DNS 设置里先点掉代理状态,等解析完全更新后再重新开启。排查这类问题时,记得先看响应头里的server和cf-ray字段,判断请求是否真的经过了边缘节点。
5.2 缓存命中率不高,是什么在捣乱
CDN 的命中率直接决定了边缘能挡住多少回源请求。我的经验是,缓存命中率低大多是响应头策略的原因。默认情况下 Cloudflare 不会缓存 HTML 页面,除非显式设置了 Cache-Control 或者匹配了缓存规则。对于 API 接口,如果你返回的是动态数据但允许短时缓存,也要明确标一个max-age,否则每次请求都会回源。
调试时可以打开浏览器开发者工具,查看响应头里的cf-cache-status。如果是DYNAMIC,表示没有走缓存;如果是MISS,说明缓存里没有,但策略上允许缓存;HIT则说明成功命中。看到 MISS 就检查一下请求的查询字符串是否携带了随机参数,URL 完整变化也会导致缓存命中困难,可以通过 Cache Rules 里的“忽略查询参数”来优化。
5.3 Worker 执行超时,如何处理
Workers 免费套餐的 CPU 时间限制是 10 毫秒,付费套餐是 50 毫秒。这里说的 CPU 时间不是处理时长,而是代码占用 CPU 的执行耗时。对于阻塞式操作、多重循环、大量字符串拼接这类逻辑,很容易超时。遇到超时不要硬扛,解决思路是拆任务。耗时操作丢给 Queue,后台异步处理;需要实时返回的接口只做轻量转发。
另外注意,Worker 里调用外部 API 也会计入 CPU 时间吗?准确说 CPU 时间是执行时间,不包括等待外部请求的时间,但等待过程也占用连接资源。所以对于外部请求,尽量合并、减少次数,或者把调用方的逻辑迁移到边缘的ctx.waitUntil()里执行,让用户响应先返回,后台慢慢处理。
5.4 Tunnel 掉线和访问异常
Tunnel 本身比较稳定,但偶尔也会因为本机网络切换、进程被系统杀掉等原因掉线。排查第一步看本机 cloudflared 进程是否存活,如果服务状态正常但还是连不上,就去 Tunnel 页面看连接状态是否显示健康。如果你有多个副本或者多台设备同时运行同一个隧道名,也可能出现路由冲突,尽量一台设备承担一个隧道。
还有一次遇到访问提示 1033 错误,排查下来是本地 TCP 连接不能顺利到源服务。Tunnel 的流量回源到本机时走的是随机端口,某些严格防火墙环境会拦截,可以尝试更换 argo 隧道协议版本或调整本机防火墙策略。总之,Tunnel 的问题多为本地网络环境引发,根源不在 Cloudflare 这一侧。
6. 一些真实的心得与建议
把 cloudflare-os 这套方案跑起来之后,我最深的感触是:个人云系统的复杂度不一定来自硬件,而往往来自软件配置的散乱。Cloudflare 的各个组件功能清晰,但真正把它们有机组合起来是需要设计感的。每一层的边界在哪里、数据放哪里、请求在哪里终止、哪里回源,这些决定要在写代码之前就想清楚,不然到后面就是到处打补丁。
如果你刚开始尝试,建议不要一上来就复刻整套全家桶。先选一个真实痛点,比如图片托管,或者内网服务访问,把它跑通,再逐步加组件。我是从 Worker 加 R2 起步的,后来加了 KV,再后来用 Tunnel 把家里的服务接进来。每加一层,都会对前面几层的设计产生新的理解。
最后分享一个我一直在用的技巧:所有配置文件、Worker 脚本、Tunnel 配置都放进 Git 仓库维护。Cloudflare 的界面操作很方便,但 GUI 点出来的配置复制不到第二套环境,版本化之后随时可以重建整个系统。写wrangler.toml时,把环境变量和 secrets 分开,内部变量直接写在文件里,密钥放进.dev.vars并被 Git 忽略。这样既能保证可复现性,又不会泄露敏感信息。这套“个人边缘云”构建思路,会随着你的业务扩展越来越值得维护下去。