1. cloudflare-os到底算什么"操作系统"
第一次听到"cloudflare-os"这个名字的人,大概率会像我一样愣一下,忍不住问一句:Cloudflare什么时候出操作系统了?实际上这更像是一个玩笑,我给自己那套Cloudflare托管的边缘网络编排体系起的外号。这套体系把域名解析、访问控制、流量入口、轻量计算、对象存储全部收拢到Cloudflare的控制面下,而我管它叫"cloudflare-os",说到底,是方便在笔记、脚本和工具箱里用一句话区分"这是跑在我自己的机器上"还是"这是挂在Cloudflare边缘上的那一层"。
做这套东西的初衷,是想摆脱"家里或小机房放台机器,然后各种端口映射、IP白名单、反代配置一把梭"的旧模式。过去我管理一个服务,要先想清楚它跑在哪台机器上、监听哪个端口、外面怎么连进来,这实际上是把"网络拓扑"硬编码进了运维习惯里。一旦服务多了,这台机子要放端口转发、那台机子要改防火墙规则,再加上DNS换IP、证书续期,整个过程的维护成本会越来越高。把边缘作为一个轻量操作系统来用,思路就完全不一样:所有的域名、证书、访问策略、路由规则都由云端控制面统一编排,本地机器只承担一个角色——运行进程,连接出口。本机不再关心外面怎么访问,这些由Cloudflare这层系统处理。
有人可能觉得这是过度设计,毕竟一台小机器配个Nginx反代也能搞定。但"操作系统"和"装了一堆软件的机器"的区别,在于前者有统一抽象和清晰的资源模型。Cloudflare提供的Worker就是计算资源,R2是存储,DNS和Tunnel负责网络路径,Zero Trust负责身份与权限。你会发现,这些模块之间的配合方式,和操作系统的进程管理、虚拟内存、文件系统、用户权限确实有相似之处。换个思路,把一个典型前端项目的资源管理方式迁移到网络服务上,事情的复杂度就降下来了。
我把这套体系命名为"cloudflare-os"还有一个私心:希望自己在做技术选型时,把"能不能放进这套系统"当成一个默认思考维度。真正跑起来之后,我发现它不仅仅是远程访问工具,更是一套稳定的控制面,让我能够在几十个服务之间切换、收拢权限、做状态审计,而不需要每次都去登录某一台具体的机器。这套体系的核心价值就是把边缘能力抽象成了和服务本身解耦的统一层,这也是我写这篇东西的动机——想把我实际搭建和维护这套"操作系统"的过程、坑和配置方式完整还原出来。如果你是靠一台云服务器跑多个站点、或者手上有不少自托管服务的人,这篇文章多半能帮上忙。
2. 初始搭建:从裸域名到托管DNS、Tunnel和Worker入口
2.1 把域名迁到Cloudflare托管DNS,是整套体系的起点
"cloudflare-os"的第一步不是安装任何软件,而是把域名的DNS托管到Cloudflare。这一步看起来简单,实际上决定了后面所有路由方式的上限。只有当域名由Cloudflare的DNS权威服务器接管之后,你才能一键开启"代理模式",也就是让流量先经过Cloudflare边缘,再由配置好的规则决定回源位置或者直接由Worker处理。
我在迁移的时候,因为有几个域名散落在不同注册商那里,还栽了两次。不要忽略的是:即便某个域名已经交给了第三方DNS,只要把注册商那边的Name Server改为Cloudflare分配的ns地址,权威DNS切换通常几分钟到几小时生效。但随后就出现了一个经典问题,子域名的NS记录如果还留在旧的DNS服务商那里,那些子域名的记录会突然"消失",或者一直解析到旧地址,因为权威链已经断了。我的做法是把根域下的所有重要子域记录先在Cloudflare重建一遍,确认无误后再切Name Server,这样新的权威生效时,记录早就齐了。
域名加入Cloudflare之后,我第一件事就是把所有需要对外提供服务的记录设为"Proxied",也就是橙色云朵模式。这个选择的理由很直接:只有代理模式才会启用Cloudflare边缘的证书管理、DDoS防护和访问策略,而且后面配置Tunnel时也需要这种模式配合。DNS的TTL我反而没有做特殊调整,Cloudflare默认的自动TTL就够了,普通的解析请求不会有明显延迟。
2.2 Tunnel配置和路由:不用开端口,也不用暴露本机IP
DNS解决了"域名归谁管"的问题,接下来要解决"流量从边缘怎么去本机服务"的问题。传统做法是在服务器上装Nginx,做反向代理、挂证书。但这样做有绕不开的烦恼:家里或办公室网络没有公网IP,或者有公网IP但不想把端口暴露到全互联网,这种情况下申请人会很头痛。
Cloudflare Tunnel(官方客户端叫cloudflared)绕开了这个问题:从你的本机主动建立一条出站的长连接,到Cloudflare边缘;云端有了一个通道后,外部请求会经过这条连接,直接映射到你本机的某个端口。因为连接是"你主动出去"的,你完全不用开任何入站端口,NAT环境下也能正常工作。
我的cloudflared跑在一个普通容器里,配置核心就一段:
tunnel: 6f...-xxxx credentials-file: /etc/cloudflared/6f...-xxxx.json ingress: - hostname: blog.example.com service: http://localhost:8080 - hostname: stats.example.com service: http://localhost:9090 - service: http_status:404关键点在于最后一条兜底规则,没有匹配到任何hostname的请求会统一返回404。这个兜底必须写,不然cloudflared会拒绝启动,并且报错信息会比较隐晦,我第一次没写时直接卡在启动阶段,日志一直提示找不到可用的ingress规则,当时还以为是域名没解析对。想快速验证连接是否成功,可以在本机跑cloudflared tunnel list,或者在仪表盘里看该tunnel的运行状态,如果实时显示"HEALTHY",基本可以放心了。
2.3 Worker作为轻量路由层,干掉了那台"万能反代机"
在传统方案里,反代通常是Nginx干的活:按域名转发、按路径转发、加请求头、做简单缓存。入了cloudflare-os之后,我把多数这类工作搬到了Worker里,Nginx这层便逐渐退出了。
Worker的好处是"代码就近执行":你在边缘写一段逻辑,它可以在离用户最近的节点上直接处理请求,不需要等待回源。我把一个以前要跑一整台机器的"轻量API网关",改成了一段几十行的Worker脚本:根据URL路径前缀把请求转发给不同后端,并且在转发前统一加上必要的请求头。
当然,Worker和Tunnel不是互斥关系,反而是很好的搭配:Worker负责路由规则和请求变换,Tunnel负责把最终请求送回本机服务。举一个实际例子:我的/api路径直接透传到本机的某个服务,而/路径走了静态资源,这个判断在Worker里完成,配置成本比Nginx里的location块低很多。
不过这里有一个注意事项:Worker的免费额度对普通个人项目是够用的,但如果你在Worker里做大量重定向或者频繁访问KV,请求次数和耗时都会涨得很快,超了额度的成本不算高,但也不该无脑滥用。早期我把一个高访问量的接口也塞进了Worker,结果每日请求数轻松越过了免费线。后来我把这个接口改成直接从Tunnel透传,绕开Worker,费用立刻就回归正常了。这类取舍需要在真实流量下观察,纸上算不明明白白。
3. 访问策略与控制面:把"谁能进"变成一种声明式配置
3.1 Zero Trust Access策略,给站点套上身份认证层
如果只是开一堆带公网域名的Tunnel,那等于把本机的服务直接暴露到了公网。尤其是一些管理后台、文档系统、监控面板,这类系统通常自带的认证很弱甚至没有。cloudflare-os里解决这个问题的组件是Cloudflare Access(现在归在Zero Trust套件下),它能让你给某个域名套上一层身份认证,只有符合条件的用户才能访问到后面的服务。
配置过程非常简单:在Zero Trust面板里新建一个Access Application,填上域名(比如admin.example.com),然后添加Policy,指定允许访问的邮箱域、邮箱地址或身份提供方。保存之后,任何人访问这个域名都会看到一个Cloudflare托管的登录页,认证通过之间,流量根本不会到达你本机的服务。服务端只要信任"能被这个登录页放行的请求",就可以省去自己实现一整套账号密码体系。
我之前跑了一台Grafana监控面板,以前要么裸奔,要么在服务里挂Basic Auth,体验和安全性都很一般。放到Cloudflare Access后面之后,Grafana自己连密码都不用设置,因为所有请求只发给通过认证的用户。这不是"藏起来"的安全,而是每一条访问都要过身份校验,相当于给服务加了一个统一准入层。值得提醒的是:如果访问者已经在某些场景下使用企业邮箱登录过,Cloudflare会用已有的会话自动放行,减少重复登录的次数,体验会比较顺滑。
3.2 设备端证书和短时令牌,比长期密码更省心
Access策略里最常用的两种认证方式,一个是身份提供方登录(比如GitHub、Google),另一个是服务令牌。不管是哪一种,都不建议在客户端里放长期有效的静态密码。
我出门在外时需要用手机看一些接口状态,此时身份提供方登录是一个好选择,因为手机浏览器里登录一次,Token有过期时间,就算手机丢了,别人也没法通过一个"永久密码"直接进系统。而对于程序化访问的场景,比如脚本去拉某个接口,每次输邮箱密码不现实,这时候可以用Access服务令牌:在Cloudflare端生成一对Client ID和Client Secret,发起请求时加到请求头里,Cloudflare边缘会自己校验,请求不会再落到你的源站。
我建议为不同用途生成不同令牌,方便出了问题单独吊销。之前我把一个令牌同时用在了三四个脚本里,发现某台机器泄露后,只能把所有脚本里的令牌全部换一遍,非常被动。令牌这东西的密级应该和密码一样对待,不要把Client Secret写进代码仓库。
3.3 把管理后台也收进策略后面,避免"明晃晃的后门"
安全配置里最常见的疏漏,不是主域名没防护,而是那些不常见的二级域名或IP入口被漏掉了。很多人的服务不止一个入口:主站顶了Access,但有一个用于测试的子域名裸奔、一个用于API的入口没有策略、一台服务器还开了SSH暴露到公网。这些入口就像房屋的侧门,主门锁得再牢也没用。
在cloudflare-os里,我的原则是"所有能通过域名访问到的服务,默认都套Access策略,除非明确需要公开"。也就是说,公开策略必须是显式配置的,而不是默认行为。我甚至把自己的几台云主机的SSH访问也收拢到了Zero Trust策略后面,使用cloudflared提供的短时证书登录,本机不再开放22端口。这样一来,就算有人在公网扫描到我服务器的真实地址,也找不到可以连接的开放端口。
这种"默认锁定"思路一开始会让人觉得麻烦,但真的跑顺之后,你会感受到一种掌控感:所有的暴露面都来自一套策略文件,哪条公开、哪条锁定,一看便知。
4. 常见故障排查:我在维护cloudflare-os时翻过的几个车
4.1 Tunnel掉线、边缘显示故障,排查链路完整记录
Tunnel是这套系统里最关键的链路之一。维护了几个月之后,我遇到过一次比较典型的故障:某个服务的页面开始间歇性出现502,过一会儿又自动恢复,Cloudflare仪表盘里对应的Tunnel节点偶尔标记为"DOWN"。这类问题最怕的就是"时好时坏",因为它很难复现。
我的排查思路是这样的:
第一步,先直接看cloudflared进程的状态。用cloudflared tunnel list确认云端注册信息还在,再进容器看日志docker logs <container> --tail 200。日志里其实已经出现了线索,ERR级别的报错反复出现,提示连接被远端重置。这意味着不是配置错误,而是网络路径上确实有不稳定因素。
第二步,看本机的网络状况。因为我这台容器跑在一台家庭宽带的机器上,运营商晚上会做NAT行为,连接的活跃时间一长就会被回收。我尝试在服务端加心跳参数,降低连接的空闲时间,让cloudflared定期发送ping帧保持链路活跃。这里要注意:不是所有报错都能靠调参解决,把日志里每个关键时间点和本机的网络事件对比,往往能找到更真实的原因。
第三步,避免"进程假死"。
restart: unless-stopped容器加了自动重启之后,就算进程异常退出,也能尽快恢复。我还配置了监控,用UptimeRobot在外部持续检查一个固定的探活URL,一旦连续几次失败就给手机发通知。这套组合下来,再遇到网络抖动时,我至少能在几分钟之内知道问题,而不是等用户反馈。
4.2 Worker读KV配置总是拿到旧值,缓存不一致的根源
有一段时间,我把一些动态配置放进了Workers KV,由后台脚本定时更新。结果发现Worker读到的数据经常是旧值,有时候明明在仪表盘里改了配置,线上代码也要过一会儿才生效。
这个坑的本质是KV的"最终一致性"。Cloudflare KV不是一个强一致的数据库,它是分布式键值存储,写操作之后,各个边缘节点是异步同步的。所以Worker在东京节点读到的可能还是十分钟前的值,而在欧洲节点已经拿到了最新值。
解决方案有两个方向。第一,对于"必须读写一致"的数据,就不要用KV,改用D1或者通过Tunnel查源站数据库。第二,如果只是配置类数据,可接受短暂延迟,那KV完全够用,但不要奇怪为什么改了之后不是立刻生效。我后来把配置数据分了两类:一类是低频、可延迟的静态配置,放KV;另一类是跟用户操作直接相关的数据,直接写回源站,Worker只调用接口。分层之后,问题就从根上消失了。
4.3 客户端在局域网和外网之间切换时的访问失效
这是一类让人非常困惑的现象:同一台电脑,在公司能正常访问cloudflare-os里的服务,回到家却打不开,或者反过来。排查的时候DNS解析看起来也没有问题,但请求就是出不去。
出现这个问题的原因,是本机的网络出口环境变了。我在电脑上装了Cloudflare的本地客户端(配合Zero Trust使用),它会把部分流量交给本地代理来处理。在局域网里,请求可能直接走了内网路由;切到外网时,如果客户端没有正确接管,或者代理规则和当前网络冲突,访问就会失效。
我当时用的排查方法是分几步做的:先把客户端临时关掉,看服务能否访问,能访问说明问题出在客户端代理策略上;然后再检查客户端里的"排除规则"配置,确保内网网段和/或本地回环地址不经过代理;最后确认机器上只有一套代理链,不存在多套代理嵌套。因为一旦有两个代理同时生效,流量会来回绕,行为和故障都会变得非常不稳定。要把这套体系用得顺手,本地网络模式这一块值得花时间理清楚。
5. 常用配置参考:可以直接抄走的片段
5.1 用Docker Compose托管cloudflared
我的cloudflared是容器化部署的。Compose文件很精简:
version: "3" services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared restart: unless-stopped command: tunnel --config /etc/cloudflared/config.yml run cloudflare-os-tunnel volumes: - /path/to/cloudflared:/etc/cloudflared networks: - web networks: web: external: true这里有一个经验:不要让cloudflared和业务容器混在同一个Compose栈里,最好单独跑、单独管。原因很实际,当业务服务更新重启时,如果cloudflared和它们同生共死,整个入口会随业务一起抖动。单独网络、单独重启策略,可以有效隔离"业务挂掉"和"入口挂掉"两个故障域。
5.2 Zero Trust策略的JSON片段
Access策略在大规模场景下通常用terraform管理,小规模时在仪表盘点击即可。如果你想追求"可版本化配置",可以参考下面这种JSON结构,它对应一个允许admin@example.com访问的策略:
{ "name": "Allow admin", "decision": "allow", "rules": [ { "action": "allow", "match": { "selector": "email", "operator": "in", "value": ["admin@example.com"] } } ] }我这里不是让你照抄这个JSON,而是想说:把策略当成代码来管理,比在面板里点点点更可靠。你可以在Git仓库里保存当前线上策略的快照,出问题时能快速追溯,多人协作时分歧也更容易被发现。我自己后面学terraform就是为了解决这类"云端配置太多、手动管理不可控"的痛点,这个思路早做早省心。
5.3 R2结合Worker,做一个低成本静态站方案
cloudflare-os里,存储也是重要的模块。Cloudflare R2是对象存储,兼容S3 API,免费额度内的存储量和访问量对个人项目来说非常够用。
静态站点的经典组合是:R2放文件,Worker负责路由和响应头。下面这段Worker代码会把请求映射到R2里的对象,如果找不到再回落到index.html:
export default { async fetch(request, env) { const url = new URL(request.url); let path = url.pathname.slice(1) || "index.html"; const obj = await env.MY_BUCKET.get(path); if (!obj) { const index = await env.MY_BUCKET.get("index.html"); if (!index) return new Response("not found", { status: 404 }); return new Response(index.body, { headers: { "content-type": "text/html" } }); } return new Response(obj.body, { headers: { "content-type": obj.httpMetadata?.contentType || "application/octet-stream" } }); } }如果你有一堆静态资源要放,又不愿意每月为图床付钱,R2是一个实用方案。但它更适合冷热分离场景,对于需要极强一致性的数据库类诉求,不适合直接堆在这里。
5.4 用cloudflared访问只在本机监听的服务
有一次我需要访问一个只监听127.0.0.1的服务,公网完全没暴露。这个场景用Cloudflare Tunnel配上Access,很干净就解决了。在服务主机上再起一条映射,把local.example.com指向http://127.0.0.1:54321,加上Zero Trust策略后,只有我自己能看。这个方案比在服务器上临时开端口暴露要安全得多,而且关掉也快,删掉这条ingress并重启tunnel就行。
6. 这套体系的边界:别什么都往边缘塞,也别什么都自己扛
cloudflare-os用了一段时间后,我慢慢摸出了它的适用边界。它擅长的是"连接、路由、认证、分发"这一类任务,不适合的是"低延迟高频数据写入"和"对本地磁盘有强依赖的运算"。我曾尝试把一个小型数据库的直接查询放在Worker里,结果性能完全不如源站本地处理快,网络绕一圈的延迟已经成了瓶颈。
同时,所有的外部依赖都需要备份思维。不要觉得数据放在边缘服务商就万事大吉,R2里的重要文件我依然会定期同步到本地,配置也尽量做成"代码可重建"的形态。之前有次不小心在云端删了一个Bucket,虽然没有丢业务数据,但我意识到了"多余的备份"永远不是亏本买卖。
日志和成本也要关注。cloudflare-os会帮你屏蔽很多底层运维细节,但是代价是你不容易感知到"某个配置今天花了多少钱"。我养成的习惯是每周看一眼仪表盘里的流量和请求量,方便及时掌握异常增长。账单告警这类工具,越早配置越好,不要等月底看到数字才惊讶。
下一步我自己的方向有两个:一个是把更多的网络访问清单收敛到代码仓库里,做到"一条PR改全网配置";另一个是继续挖掘D1和Worker的配合,把一些轻量数据逻辑也放上边缘。这套系统还在迭代,不过到目前为止,它已经实打实地改变了我维护服务的姿势——我越来越不需要关心"这台机器开在哪个机房",取而代之的是"这套流程应该怎么设计"。这种抽离感,也许就是"操作系统"给人的最大价值。