Cloudflare全家桶实战:零运维搭建个人云操作系统

1. 项目概述

1.1 "cloudflare-os"到底是什么

第一次看到cloudflare-os这个词,我以为是Cloudflare出了个官方操作系统,点进去才知道被"标题党"带偏了。所谓cloudflare-os,并不是Cloudflare推出的定制Linux发行版,而是大家把Cloudflare全家族产品重新组合之后,抽象出来的一套"云上操作系统"。

说白了,你可以把Cloudflare的Dashboard当成桌面,Workers/Pages当成应用程序运行时,R2当成一块硬盘,Tunnel当成网络通道,Access当成门禁系统,WAF当成防火墙,D1当成数据库。这些东西拼在一起,刚好具备一个操作系统该有的全部能力:登录认证、进程执行、文件存储、网络通信、安全隔离。很多折腾过自建服务的人,最终都会走向这一套组合拳,因为它免费额度高、全球边缘节点多、运维成本几乎为零。

这篇内容适合谁看?一是手里有域名但只把Cloudflare当DNS用的人,二是被VPS续费、被暴力破解、被证书过期折磨过的人,三是对Workers/R2/Tunnel有兴趣但不知道从哪里下手的人。我会把整套架构拆开,从为什么这么设计,到每一步怎么操作,再到踩过的坑,全部摆出来。看完之后你应该能自己搭出一个"个人云操作系统"。

1.2 我的使用场景与最终目标

先交代背景。我自己有一套长期在跑的小型个人服务集群:一个个人Blog、一个文件分享面板、一个定时任务脚本、一个内网NAS的Web界面。以前这些服务分散在VPS和家用服务器上,管理起来非常零散。VPS要操心系统更新、防火墙规则、证书续期;家用服务器虽然有公网端口映射,但线路质量不稳定,IP还会变;定时任务脚本跑在本地电脑上,关机就断。

接触Cloudflare全家桶之后,我把这套体系做了整体迁移。最终的目标非常明确:

  • 所有对外入口全部收敛到Cloudflare边缘,不再直接暴露源站IP;
  • 动态内容交给Workers/Pages,静态附件交给R2,数据库用D1;
  • 内网服务通过Tunnel映射到公网,配上Access做身份认证;
  • 域名解析、证书、防火墙、访问控制全部在一个控制台里完成。

这套组合拳落地之后,我基本上可以只管业务代码本身。域名解析出问题看Cloudflare,证书过期看Cloudflare,有人扫端口看Cloudflare,甚至攻击流量也在边缘就被拦掉了。你要做的只是打开Dashboard看几眼状态,仅此而已。

2. 整体设计与技术选型思路

2.1 为什么选择Cloudflare全家桶而不是传统VPS

在聊具体配置之前,先说说选型逻辑。很多人一听到"自己搭服务"第一反应是买一台VPS,装上Nginx、MySQL、Redis、Node/Python环境,然后开始漫长而痛苦的运维。这条路不是不行,只是对个人项目来说成本太高了。

VPS方案意味着你要承担的东西包括:系统补丁更新、Nginx配置维护、SSL证书三个月续期(现在虽然有自动化,但出问题的概率依然不低)、CC攻击防护(很多人直接被刷死)、数据备份策略(自己写cron)等等。一个服务还好,三四个服务叠加起来就是一场灾难。我身边不少朋友最后放弃自建,不是因为技术不行,而是被琐碎运维耗尽了精力。

Cloudflare全家桶的逻辑完全相反。它把自己定位成"你站点的边缘网关",把安全、加速、证书、路由这些原本属于基础设施的问题全包了。你的服务只需要跑在自己的角落里,对外统一走CDN。这意味着源站在哪里已经不重要了:可以是一台便宜VPS,可以是一台家里的旧电脑,甚至直接是Cloudflare自己的边缘运行时(Workers)。这种"代码跑在边缘、业务逻辑全球化分发"的思路,比传统VPS模式更适合个人和小团队的轻量级需求。

2.2 把Cloudflare租件类比成操作系统模块

cloudflare-os这个词有点戏谑,但用"操作系统类比"来理解这一整套体系其实非常精准。我整理了一张对照表,把Cloudflare生态里的产品映射到传统操作系统的概念上,这样理解起来会快很多。

操作系统概念Cloudflare对应产品作用
桌面/控制台Cloudflare Dashboard统一管理所有服务和配置
统一认证Cloudflare Access控制谁能进入你的应用
进程/运行时Workers & Pages Functions执行自定义逻辑,跑代码
应用商店Pages/Git集成从仓库直接发布Web应用
文件存储R2存图片、视频、备份、静态文件
数据库D1保存结构化数据
网络协议栈Tunnel & 智能路由安全连接源站和边缘节点
防火墙WAF + 安全规则拦截恶意流量和攻击
域名服务Cloudflare DNS全球解析、负载均衡
流量监控Analytics & Logs查看请求日志,分析访问趋势

这套类比不是硬凹,它确实说明了一个事实:Cloudflare已经把应用开发所需的底层资源全包了。你要做的不再是"租一台机器再安装环境",而是"在边缘平台上直接组装服务"。这个心智模型的转变,正是cloudflare-os最大的价值。

2.3 为什么要把源站隐藏到Tunnel后面

整个架构里最关键、也最反直觉的一步,是把源站彻底藏起来。传统VPS部署方法是把域名解析到服务器IP,然后在服务器上配SSL、配防火墙。这样做的最大隐患是IP被人扫到之后,攻击者可以绕过CDN直接打源站。

Cloudflare Tunnel的方案是:源站主动向Cloudflare边缘发起一条安全的加密隧道(本质是outbound连接,80/443端口根本不需要对外开放),公网请求通过边缘节点转发进入隧道回到源站。这种方式下,源站完全隐藏在Cloudflare网络背后,扫描不到、爆破不了、DDoS打到边缘也会被消化掉。

我在实际操作中最喜欢的一点是:Tunnel部署完成后,你在防火墙里根本不需要再配置任何入站端口。以前我在家用NAS上开放端口给别人访问时,每天晚上都会有各种IP来扫22端口和445端口。换成Cloudflare Tunnel之后,Nginx完全监听在内网,路由器不用映射任何端口,这些扫描流量直接在边缘就被挡掉了,省心太多。

3. 核心细节解析与实操要点

3.1 第一步:Cloudflare Tunnel的基础部署细节

先把最容易让新手卡住的Tunnel部署讲透。在Cloudflare Dashboard左侧菜单找到Zero Trust,进入Networks -- Tunnels,创建一条隧道。创建时Cloudflare会给你一个token,格式类似eyJhIjoi...,这个token就是隧道和账户绑定的凭证,泄露了等于把自己的内网服务公开了,一定要保管好。

本机安装cloudflared之后,执行:

cloudflared service install <token>

注意Windows和Linux安装方式略有不同。Linux下我建议用cloudflared service install装成系统服务,这样开机自启、崩溃自动重启都由systemd接管了。我最早是手动跑进程,结果重启机器之后忘了启动,导致整个内网应用"神秘消失"了整整一个周末。装成服务之后就不再背这个锅。

一条隧道可以配置多个公网入口。比如你的homepage路由到本机80端口,你的nas路由到内网另一台机器的5000端口。配置在隧道的Public Hostname页面里再加一条记录就行,不需要再建新隧道。我家里有三台设备,现在共用一条隧道,省去了建立多条隧道的维护成本。

3.2 第二步:用Access给Tunnel应用加一把锁

隧道打通之后,一个关键问题马上浮现:任何拿到你域名的人都能访问你的内网服务。R2里的文件还好说,NAS里的家庭相册、内部工具如果直接暴露在公网上,光想想就头皮发麻。所以我强烈建议,所有走Tunnel的应用必须加Cloudflare Access保护。

Access的配置路径是Zero Trust -- Access -- Applications,创建一个Self-hosted应用,填入你的子域名。接下来设置Access Policy,核心选项有两个:Session Duration和Authentication Method。前者决定登录态保持多久,我建议默认给的24小时就够用,不要图省事设置成30天,因为临时借电脑给别人用的时候,登出状态管理会很混乱。后者选择你的邮箱域名做One-time PIN验证即可,Cloudflare会给该邮箱发一次性验证码,不需要额外搭建身份服务。

有一点很多人不知道:Access控制的是"会话级别"的访问,不会影响自建应用里的用户体系。我一开始以为套了Access之后,还得处理内部系统的登录逻辑,其实不用。Access在边缘层做拦截,合法用户通过之后,请求才会转发到源站,源站里的登录逻辑该怎么跑还怎么跑。对外呈现的就是"先过Cloudflare验证,再进应用本身",叠加了两层安全,操作上却几乎零负担。

3.3 第三步:Workers与Pages的选择逻辑和部署细节

计算层是整个cloudflare-os的大脑。Workers和Pages都能跑JavaScript/TypeScript,区别在于Workers更适合独立API和轻量后台任务,Pages则是"静态站点+边缘函数"的一体化平台,适合博客官网这类有前端又有动态接口的场景。

我在实际项目中基本上遵循这个原则:如果是一个完整的Web应用,带界面带路由,用Pages把静态资源托管了,API接口通过Pages Functions实现;如果只是内部调用的工具脚本、Webhook处理、定时通知任务,用Workers单独维护。

Pages部署最简单的方式是直接连Git仓库。你只需要在Pages里创建一个项目,选择GitHub仓库,Cloudflare会自动识别构建命令。每次git push到main分支,线上就自动更新了。我第一次跑通这个流程后,回头看曾经SSH上服务器手动部署的日子,确实觉得那时候自己在用铁锹挖院子,而面前是一条现成的柏油马路。

说到Workers,微信里经常被问"怎么用Workers做API转发"。以一个最基础的API服务为例,在Workers里只需要写:

export default { async fetch(request, env) { // 只允许GET请求 if (request.method !== "GET") { return new Response("Method Not Allowed", { status: 405 }); } // 读取绑定变量 const apiKey = env.MY_API_KEY; const url = new URL(request.url); const name = url.searchParams.get("name") || "world"; return new Response(`Hello, ${name}! Your API key is ${apiKey}`); } }

部署上去之后,Cloudflare会给你分配一个xxx.workers.dev子域,配合GPU一样的全球网络,请求会从离访客最近的边缘节点直接返回。如果业务量大了、超了免费额度,再往Pages迁移也不难,因为同属一个生态,代码结构几乎不用改。

3.4 第四步:R2存储挂了之后,注意公开配置的细节

R2在这套体系里就是那块硬盘。我主要用它存三样东西:博客图片、自动备份压缩包、不常访问但必须保留的文件。R2的免费额度对个人项目来非常宽裕,10GB存储、每天100万次读请求,对于个人博客和家庭服务来说基本上是"用不完"。

但权限配置有几个细节特别容易踩坑,我在这里展开聊。

R2默认创建的bucket是私有读写的,需要设置公开访问才能拿到直链。操作路径是R2 -- 你的bucket -- Settings -- Public access。里面有一个选项是"允许创建R2.dev域名"。这里要提醒一下:R2.dev域名Cloudflare官方早就宣布不保证长期可用,且绑定在免费计划下的服务随时可能变化。所以我推荐自备一个自定义域名绑定到bucket上,路径为Settings -- Custom Domains,绑定一个子域如files.example.com,之后所有文件就能通过https://files.example.com/xxx.png访问。

公开读有另一个隐患:公开读意味着任何人都能列出你bucket里的文件列表(如果开启了列表权限)。我在一次测试中发现,只要访问https://files.example.com/,Cloudflare会直接返回所有文件的JSON列表,等于把整个目录结构暴露了。所以我在配置公开访问时特意关闭了列出权限,只允许通过已知路径读取具体文件。这个操作路径是bucket的Settings -- Permissions -- Public access,把"允许列出对象"开关关掉。

4. 实操过程与核心环节实现

4.1 搭建个人站点的边缘层配置

现在开始一步步走完整流程。首先在Cloudflare Dashboard里添加你的域名,确保DNS记录代理状态是橙色的云朵(Proxied),这样请求就会先经过Cloudflare边缘。接下来配置SSL/TLS模式,我建议直接选Full (strict),这要求你的源站也必须配置有效的SSL证书。很多人以为源站可以裸HTTP,其实Full (strict)模式下Cloudflare会严格校验源站证书,没配证书的话会报526错误。

我的源站是Nginx,直接用acme.sh生成Let's Encrypt证书,但有一个坑:源站证书有效期很短,自动续期的cron如果不注意就会失效,导致整个站点显示526。后来我换了方案:源站直接用Cloudflare Origin Certificate。这个证书在Cloudflare Dashboard的SSL/TLS -- Origin Server里可以一键生成,最长15年有效期,不需要续期,且只有源站和Cloudflare之间的链路用它验证,外部根本看不到。对个人用户而言,一劳永逸比什么都重要。

Cache Rules方面,我给静态资源(图片、CSS、JS)设置了Cache Everything加上Edge Cache TTL为一个月。给HTML设置了标准缓存,避免动态页面被错误缓存导致更新不生效。Cloudflare默认对静态文件就已经能识别Content-Type并缓存,加上规则只是给个更明确的表达。配合上一节说的R2存储,静态资源走边缘缓存、动态接口走Workers/Pagess,整个站点的响应速度比原来VPS直连快了一个档次。

4.2 配置Workers连接D1数据库

动态应用的核心数据放在D1里存。创建D1数据库后,在Workers/Pages项目的Settings -- D1里绑定数据库实例,然后需要把表结构初始化进去。D1初始化可以在Cloudflare Dashboard的D1控制台里直接执行SQL,我以建一张访客留言表为例:

CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, content TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );

然后Workers代码里通过env.DB拿到数据库句柄。常见操作有三类:

查询:

const { results } = await env.DB.prepare("SELECT * FROM messages ORDER BY created_at DESC LIMIT 10").all();

插入:

await env.DB.prepare("INSERT INTO messages (name, content) VALUES (?, ?)") .bind(name, content) .run();

更新:

await env.DB.prepare("UPDATE messages SET content = ? WHERE id = ?") .bind(newContent, id) .run();

D1和传统关系型数据库基本兼容,SQL语句直接写,能平滑迁移过来。有一点要注意:免费版的D1每天有写操作行数限制,大概是500万行读、10万行写,我个人的博客留言量完全没压力。如果哪一天真的不够用,D1的付费计费单位也不贵,不需要换数据库架构。

4.3 把一套完整API发布到Pages Functions

Pages Functions是Pages项目的"隐形API层",直接在项目目录下创建/functions/api.js文件(或.ts),Pages会自动把它打包成API路由。比如我的博客搜索接口就放在/functions/api/search.js:

export async function onRequest(context) { const { request, env } = context; const url = new URL(request.url); const query = url.searchParams.get("q") || ""; if (!query) { return Response.json({ results: [] }); } const { results } = await env.DB.prepare( "SELECT id, title FROM posts WHERE title LIKE ? LIMIT 10" ).bind(`%${query}%`).all(); return Response.json({ results }); }

这段代码部署到Pages之后,你不需要单独开一个API服务和前端域名分开部署,静态页面在边缘,API也在边缘,整个项目一个仓库搞定。这是我迁移之后最强烈的爽点:原先VPS部署方式需要维护"前端目录"和"后端目录"两个项目,现在合二为一,push一次代码前后端全上新版本。

4.4 用Scheduled Workers处理定时任务

个人服务里少不了定时任务,比如每天抓取一次天气数据、每周清理一次R2里的旧备份。Cloudflare的Scheduled Workers可以用Cron触发,免费试用版有三档频率选择(每分钟一次、每小时一次、每天一次),实际上这三个档位对个人项目已经够用。

我的清理任务长这样:

export default { async scheduled(event, env, ctx) { // 每天凌晨两点执行 const yesterday = Date.now() - 24 * 60 * 60 * 1000; const oldObjects = await env.BUCKET.list({ startAfter: new Date(yesterday) }); for (const obj of oldObjects.objects) { await env.BUCKET.delete(obj.key); } } };

Scheduled Workers最大的好处是它跑在边缘,不依赖任何常驻机器。以前我用crontab在本地跑脚本,电脑一关任务就失灵;现在脚本在Cloudflare的全球节点上执行,彻底干掉了"机器开关机"这个变量。

需要注意Cron频率限制:免费计划Scheduled Workers不能自定义任意间隔,只能从平台提供的三个选项里选。如果你需要"每隔5分钟跑一次"这种高频任务,免费计划不行,可以考虑Workers结合外部触发,或者直接用平台自带Triggers功能里的HTTP endpoint,自己从外部发起请求。个人项目最常用的是"每天一次",三档足够覆盖了。

5. 常见问题与排查技巧实录

我整理了自己搭建过程中遇到的高频问题,按"现象--原因--解法"的结构做成速查表,直接抄作业就行。

现象排查思路解决办法
站点显示526错误源站证书失效检查源站SSL证书,Full strict模式用Origin CA证书就不会过期
访问站点返回1010错误浏览器指纹或IP被安全规则拦截登录Dashboard检查Security Events,看是哪个规则命中了,适当放行
Tunnel显示Connected但访问不了服务公网Hostname映射地址写错检查Public Hostname配置里的URL指向,注意http和https要写对
R2文件可以预览但下载的文件名乱码缺Content-Disposition响应头在Worker/S3 API里手动设置响应头,或用自定义metadata
D1查询很慢没有建索引给WHERE条件字段建索引,如CREATE INDEX idx_name ON messages(name)
Access验证邮件收不到邮箱One-Time PIN被当垃圾邮件检查垃圾箱;推荐直接配置企业邮箱或改用Social IdP(GitHub登录)
Workers日志查不到没有打开实时日志Dashboard里打开Workers -- 你的项目 -- Logs,开启Live tail

5.1 我踩过且现在每次都会提醒别人的三个坑

第一个坑是Tunnel的DNS记录冲突。创建Tunnel时Cloudflare会自动添加一条CNAME记录指向<tunnel-id>.cfargotunnel.com。如果你之前在DNS面板里手动创建过一条A记录指向同一主机名,就会产生冲突,导致域名解析时随机打向两个目标。换句话说,部署Tunnel之前,先检查目标子域名有没有旧记录,有的话删掉再继续。

第二个坑是Pages的Functions目录结构。Pages里每个文件对应一个路由,如果创建了/functions/api/search.js,那访问路径就是/api/search,不是/api/search.js。很多人第一次部署时发现404,就是因为不理解这个映射关系。而且Pages Functions只能部署在Pages项目里,独立Workers项目不支持这种方式。我一开始试图在Workers里按Pages的目录结构写,结果发现怎么都不对,查文档才搞清楚两者差异。

第三个坑是R2的S3兼容接口需要另配API Token。在Cloudflare Dashboard里操作R2是不需要单独创建Token的,但如果你要用rclone同步文件或者用S3 SDK做自动化上传,就必须在My Profile -- API Tokens里创建一个带有R2读写权限的Token。我第一次用rclone备份数据时被AccessDenied卡了好久,后来才发现是忘了建这个专用API Token。

5.2 一次真实排查:Dashboard显示一切正常但服务离线

有一回我早上起来发现自己的博客打不开,但Dashboard面板里Workers显示健康、Tunnel显示Connected、DNS记录也是橙色云朵代理状态。这种"面板一切正常但实际不可访问"的情况最让人抓狂。

我的排查流程是:先看Access应用的访问日志,发现目标服务的请求量是0,说明请求根本没有到达边缘。然后检查Tunnel的公共主机名配置,发现之前手滑把nas子域名的服务地址从http://192.168.1.10:5000改成了http://localhost:5000,而Tunnel是跑在另一台机器上的,这个localhost指向的是Tunnel所在机器而不是NAS。查到这里真相大白:改完配置保存之后,面板看起来一切正常,但实际上流量被导到了错误地址。

这个教训让我养成了一个习惯:每次改Tunnel的配置,都要在保存之后打开终端,访问一下对应域名,确认响应正常再离开。依赖面板状态这种"仪表盘安全感"是不太靠谱的,真实访问是最底层的验证方式。

6. 架构扩展方向与个人经验总结

6.1 还能往这个系统里塞什么

cloudflare-os的核心组件搭好之后,这套架构的可扩展性比想象中大很多。如果你愿意折腾,以下几个方向都是顺手就能加的:

一是队列任务。Cloudflare Queues可以和Workers配合,把耗时的后台任务丢进队列异步执行。个人项目里能用来做图床缩略图生成、Webhook事件重放、批量数据导入等。对个人项目来说,这个功能比自己在VPS上部署一套消息队列简单太多,不需要维护任何持久化中间件。

二是AI能力。Cloudflare Workers AI可以在边缘调用开源大模型,免费额度不大但够玩玩个人助手、摘要生成之类的东西。把它接在之前搭好的API里,几行代码就能让"个人云操作系统"具备初步的AI接口能力。不需要管理GPU服务器,不需要处理GPU驱动、CUDA环境这些繁琐内容。

三是Email Routing。Cloudflare的Email Routing能帮你免费处理自定义域名的邮件转发。个人域名收到的邮件可以转发到任意邮箱,配合Workers还能做邮件自动处理。只不过注意一下常规邮件服务的合规使用规则,不要用来发垃圾邮件。

6.2 这套方案适合什么样的人

聊了这么多,如果你正在纠结"要不要把服务迁到Cloudflare全家桶",我按经验给个判断清单:

  • 你的服务主要是个人使用或小团队使用,不追求极高的并发和商业化SLA;
  • 你已经受够了VPS的证书续期、防火墙规则、备份脚本这些繁琐工作;
  • 你的服务形态以Web应用、API、静态站点为主,没有特殊的底层依赖;
  • 你愿意接受"逻辑部署在边缘、数据存储在当时当地"这套新思维,而不是始终认为必须有一台属于自己的服务器才算"自建"。

如果几条都符合,那cloudflare-os这套打法会给你带来一种"把复杂度外包了"的轻松感。如果已经上了这套方案,建议多做一步:开启Cloudflare的免费WAF规则,把"托管规则集"打开,同时定期在Security Events里看看拦截日志,了解你的站点在被谁扫描、谁攻击,心里有底。

说实话,这一整套东西我陆陆续续折腾了两三个周末才完全理顺。期间走弯路踩坑,但最终跑通之后,运维成本真的降到了"打开Dashboard看一眼"的程度。你不需要会运维,不需要懂证书原理,甚至不需要一台24小时开机的电脑——Cloudflare把服务器这件事变成了"边缘服务形态"。我个人的体会是:cloudflare-os并不是一个真实存在的操作系统,但它把"我该怎么组织自己的线上服务"这个问题重新问了一遍,而答案比我想象中要轻得多。