ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Arch Linux打造Cloudflare定制开发系统cloudflare-os

基于Arch Linux打造Cloudflare定制开发系统cloudflare-os 用了差不多一周的碎片时间我把手头几台开发机统一重装成了一个基于 Arch Linux 的定制系统并且给这套东西起了个名字叫 cloudflare-os。起因其实很简单我日常大部分工作都在跟 Cloudflare 的生态打交道经常要开新机器、配环境、装工具链每次都得重复折腾一遍 Wrangler、cloudflared、Workers 脚手架这些零碎东西。与其每次都从头来过不如直接打一个包做成一个开箱即用的 Web 开发环境镜像。这篇文章就完整记录一下我在设计、构建和实际使用 cloudflare-os 过程中的思路、命令、参数和踩过的坑希望能给同样在这条路上折腾的人一点参考。1. 项目定位与整体设计思路1.1 为什么我需要一个“云厂商定制”的操作系统说实话最开始我没有想到要去做一个“操作系统”这么重的东西。我的原始需求只是“想要一套装完就能直接写代码、直接连 Cloudflare 的环境”。但在真实场景里这个需求一旦展开就会牵扯出一连串的问题系统装什么发行版、用什么包管理器、要不要桌面环境、Cloudflare 的 CLI 工具怎么装、Tunnel 怎么开机自启、防火墙要不要为 cloudflared 放行、Docker 和 Wrangler 的版本怎么共存。这些问题每一个看起来都不难但组合在一起就是一个典型的“环境交付”问题。我在团队里经常要给同事交付一套统一的开发环境过去用 Docker 镜像或者脚本的方式做过但总觉得隔了一层尤其是涉及 Cloudflare Tunnel 这种需要常驻进程、需要系统级守护进程的服务容器的方案体验并不好。所以我决定走一条更彻底的路线直接定制一个系统镜像把 Cloudflare 工具链、Node.js 运行时、常用数据库客户端、代理调试工具全部预置进去任何一台机器装上之后开机就能进入工作状态。这套系统的代号就叫 cloudflare-os。1.2 基于 Arch Linux 而不是 Debian/Ubuntu 的原因选择基底发行版的时候我在 Arch Linux 和 Debian/Ubuntu 之间犹豫过一阵。多数云服务器默认给的是 Ubuntu Server生态成熟文档多心理安全感强。但最终我选了 Arch Linux理由主要有三个。第一Arch 的滚动更新机制能保证 Cloudflare 的 CLI 工具比如 Wrangler 和 cloudflared始终拿到比较新的版本。Cloudflare 的产品迭代速度非常快Workers 的运行时、wrangler 的命令参数经常会有调整如果基座是一个版本锁定、更新保守的发行版很容易出现“系统的包太老跟云端 API 对不上”的尴尬情况。第二Arch 的 KISS 哲学很适合做垂直定制系统。它的 base 包非常精简不会像 Ubuntu Server 那样预装一堆我用不到的固件、驱动和服务。对于 cloudflare-os 这种面向 Web 开发的专用系统我更希望从内核之上的一切都在掌控之中尽量做到最小化安装。第三Arch Wiki 和 AUR 仓库在开发者工具这块覆盖非常全wrangler、cloudflared 这类工具在 AUR 里通常都有现成的包即使没有手动打包成 pkgbuild 也不难。不需要像 Ubuntu 那样添加各种莫名其妙的第三方 PPA 源信任边界清晰得多。当然Arch 不是没有缺点滚动更新偶尔会带来不稳定性我后面会在系统配置里加入一些锁定机制来缓解这个风险这个细节到实操部分再讲。1.3 整体架构与模块划分cloudflare-os 的整体架构我划分成四个层基础系统层、开发运行层、Cloudflare 工具层、以及日常运维层。每一层负责的事情非常明确。基础系统层包括 Arch Linux base 系统、内核、网络管理NetworkManager、SSH 服务、sudo 和基础 shell 环境。这一层负责让机器“能开机、能联网、能登录”。开发运行层包括 Node.jsLTS 版本、npm/pnpm/bun 等包管理器、Git、Docker可选组件、以及常用的调试工具如 httpie、jq、ripgrep。这一层负责让机器“能写代码、能跑代码”。Cloudflare 工具层包括 wranglerWorkers 部署工具、cloudflaredTunnel 客户端、以及相关的配置文件模板和 systemd 服务单元。这一层是系统的灵魂负责让机器“能和 Cloudflare 云平台无缝协作”。日常运维层包括一些我自己写的小脚本比如环境自检脚本、日志收集脚本、系统更新脚本。这一层负责让机器“长期健康运行”。这样的分层逻辑让整个系统的可维护性变得很高。我在实际使用中如果需要往某个层里加东西只需要更新对应的清单文件重新走一遍构建流程即可不会出现“装一个软件导致整个系统状态变得不可复现”的问题。2. 核心细节解析与关键技术选型2.1 systemd 服务单元让 cloudflared 开机自启cloudflare-os 里最核心的常驻服务就是 cloudflared Tunnel。Tunnel 的机制是在本地机器上运行一个出站连接跟 Cloudflare 的边缘网络建立长连接这样不需要在公网开放任何入站端口就能把本地服务暴露到域名上。这个机制对家庭宽带、NAT 网络、以及没有公网 IP 的开发机特别友好。但在实际部署中Tunnel 必须保证 7x24 小时在线不能依赖用户手动敲命令所以必须用 systemd 来管理。我在系统镜像里预置了一个 service 文件内容大致是这样[Unit] DescriptionCloudflare Tunnel Agent Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple ExecStart/usr/bin/cloudflared tunnel --config /etc/cloudflared/config.yml run Restartalways RestartSec5 Usercloudflared Groupcloudflared NoNewPrivilegestrue PrivateTmptrue ProtectSystemfull ProtectHometrue [Install] WantedBymulti-user.target这里有几个细节值得说一下。首先Afternetwork-online.target配合Wantsnetwork-online.target的意思是cloudflared 必须在网络真正就绪之后才能启动而不是只要网卡 up 就启动。很多人在配置自启动服务时忽略了这一点结果 Tunnel 在 DHCP 还没拿到地址之前就尝试建立连接直接失败然后陷入反复重启的循环。其次Restartalways和RestartSec5保证了偶发的网络抖动或者云端连接中断之后进程能在 5 秒内自动恢复不需要人工干预。我实际跑了一个多月这个参数组合下的稳定性非常好最长连续在线时间超过 20 天最后是因为我自己升级内核才断的。再有就是安全加固相关的参数NoNewPrivilegestrue、PrivateTmptrue、ProtectSystemfull、ProtectHometrue这些是 systemd 提供的沙箱化选项用来限制进程的权限和文件系统访问范围。cloudflared 是常驻在公网连接上的进程攻击面相对大能给它加一层隔离就加一层成本很低收益却很实在。2.2 Wrangler 版本管理用 nvm 而不是系统包Wrangler 是 Cloudflare Workers 的官方 CLI它的版本迭代非常勤有时候一周能发好几个小版本。而且 Wrangler 对 Node.js 的版本也有要求新的 Wrangler 3.x 要求 Node.js 版本至少是 16.x 以上推荐 18.x 或 20.x。在 cloudflare-os 里我没有直接用 pacman 安装 wrangler而是通过 npm 全局安装并且把 Node.js 本身交给 nvmNode Version Manager来管理。原因有两个一是 Arch 仓库里的 nodejs 包是滚动更新的可能会在某个时刻跳到奇数版本比如 21.x而奇数版本属于非 LTS不适用于生产环境二是 wrangler 的全局升级非常频繁通过 npm 管理更符合它的发布节奏。具体的操作思路是先在系统层面安装一个 nvm然后在.zshrc或.bashrc里写入初始化脚本让每次打开终端时自动加载当前机器默认的 Node.js 版本。默认版本我会固定在某个 LTS 版本上比如 20.x这样既兼容了 Wrangler 3.x也能跑 Next.js、Nuxt 这些主流框架。这里有一个小坑如果直接用sudo npm i -g wrangler安装npm 的全局目录是/usr/lib/node_modules权限归 root 所有后续升级时需要 sudo而且在 nvm 切换 Node 版本之后全局命令可能找不到。所以我习惯的做法是在用户的 home 目录下完成所有 npm 全局安装不跟系统目录纠缠。这样 nvm 切到哪个 Node 版本全局工具就跟随哪个版本整个心智模型简单了很多。2.3 防火墙与端口策略cloudflare-os 的设计原则里有一条很重要尽量不开放入站端口。既然已经有了 cloudflared Tunnel那么本机的 Web 服务完全可以通过 Tunnel 暴露出去不需要在防火墙里开 80/443 端口给公网访问。这正好契合 Cloudflare 官方的安全建议源站越隐蔽越好。所以在系统层面我用了 nftables 作为默认防火墙只放行 SSH22 端口、本地回环流量以及局域网访问按需。出站流量不做限制因为 cloudflared、npm、pacman 都需要访问外网。防火墙配置在系统里是预置的安装完成之后直接生效不需要用户手动配置。这个设计带来的好处很明显即使你在一台公网服务器上装 cloudflare-os也不用担心因为忘了配置防火墙而把开发服务器裸奔到公网上。3. 实操过程与核心环节实现3.1 准备安装介质与基础系统需要准备的东西很简单一个 U 盘容量大于 2GB 就行一台目标机器一条稳定的网络连接。从 Arch 官网下载 ISO 后用dd把镜像写入 U 盘dd ifarchlinux-2024.xx.xx-x86_64.iso of/dev/sdX bs4M statusprogress进入 Live 环境之后第一步是连接网络。如果用有线网络DHCP 通常会自动配置好如果用无线网络需要用iwctl工具来连接。我给 cloudflare-os 的安装流程做了一个自动化的引导脚本脚本的核心逻辑是使用archinstallArch 官方提供的安装脚本完成基础安装自动化程度高能节省大量时间。在 archinstall 的配置文件中指定语言、时区、键盘布局选择 ext4 文件系统。最小化安装 base 和 linux-lts 内核不装任何多余的固件包。这里说一个重要的决策为什么选linux-lts而不是最新的linux内核。对于一台跑开发服务、Tunnel 常驻的机器来说稳定压倒一切。LTS 内核不会频繁引入新的驱动变更配合 Arch 的滚动更新周期反而能获得一个相对稳定的体验。我用这个组合跑了几个月没有遇到过内核相关的问题。3.2 基础系统安装后的核心配置系统进入 chroot 环境之后有几个关键配置必须手动确认。第一是/etc/locale.gen需要启用en_US.UTF-8然后执行locale-gen第二是设置 root 密码和创建普通用户第三是安装引导程序我在 BIOS 环境下用grub在 UEFI 环境下用grub加efibootmgr。这些步骤做完之后就是 cloudflare-os 的专属配置阶段了。我把所有需要安装的软件包放在一个文本清单里这样无论是全新安装还是后期维护都能保持环境的一致性pacman -S --noconfirm \ base-devel git curl wget \ networkmanager openssh \ nftables sudo vim zsh \ nodejs-lts-hydrogen npm \ docker docker-compose \ jq ripgrep httpie这里的nodejs-lts-hydrogen对应 Node.js 18 系列配合 nvm 使用完全没有冲突——我系统里预置了一个 nvm 安装脚本它会在用户首次登录时把 nvm 装上并自动安装 Node.js 20 LTS。这样即使系统包有更新也不会影响用户空间的 Node 版本选择。等待安装结束之后启用关键系统服务systemctl enable NetworkManager systemctl enable sshd systemctl enable nftables systemctl enable docker systemctl enable cloudflared这一步的意义在于让所有核心服务在首次启动时就进入可用状态。如果不 enable 的话每次重启机器都要手动去启动服务这就违背了 cloudflare-os“开机即用”的初衷。3.3 Cloudflare 工具链的安装与验证系统基础就绪之后接下来就是把 Cloudflare 工具链装进去并把配置文件写好。先在用户环境下安装 wranglernpm install -g wrangler wrangler --version这里注意一点wrangler 需要登录 Cloudflare 账号才能完成部署操作。安装完之后执行wrangler login它会打开浏览器引导完成 OAuth 认证。如果目标机器是没有浏览器的纯服务器环境可以改用 API Token 的方式通过环境变量CLOUDFLARE_API_TOKEN传给 wrangler效果是一样的。然后是 cloudflared。我提供了两种安装路径一是直接用 pacman 安装官方仓库里的cloudflared包二是下载 Cloudflare 官方发布的二进制文件放到/usr/local/bin。后者更适合那些想要固定版本、避免系统更新导致意外变更的场合。我在 cloudflare-os 里默认走的是 pacman 路线同时在配置文件的注释里说明了固定版本的安装方法。安装完成后验证 cloudflared 是否能正常建立连接。我先用最快速的方式做了一个连通性测试cloudflared tunnel --url http://localhost:8080这个命令会创建一个临时的 Tunnel并把本地 8080 端口映射到一个随机分配的trycloudflare.com子域名上。如果这个命令能跑通说明本地环境与 Cloudflare 边缘节点之间的网络通道是正常的。等到确认无误之后再把它切换成正式的命名 Tunnel 并配置持久化的 systemd 服务。3.4 配置正式 Tunnel 并设置开机自启正式 Tunnel 的配置步骤稍微复杂一些需要在 Cloudflare Dashboard 或者通过命令行完成。我的操作路径是在一台已经登录的机器上执行cloudflared tunnel create my-dev-tunnel这个命令会生成一个 Tunnel 的 ID 和对应的证书文件保存到~/.cloudflared/目录。拿到 Tunnel ID 之后把对应的 credentials 文件拷贝到系统的/etc/cloudflared/目录然后编写/etc/cloudflared/config.ymltunnel: my-dev-tunnel credentials-file: /etc/cloudflared/tunnel-id.json ingress: - hostname: web.example.com service: http://localhost:8080 - hostname: api.example.com service: http://localhost:3000 - service: http_status:404这个配置的意思是发往web.example.com的请求通过 Tunnel 转发到本地 8080 端口发往api.example.com的请求转发到 3000 端口其余所有未匹配的域名请求统一返回 404。然后在 Cloudflare Dashboard 的 DNS 设置里把web.example.com和api.example.com这两个域名分别添加 CNAME 记录指向tunnel-id.cfargotunnel.com。这一步是很多新手容易漏掉的Tunnel 本身不是在公网新开端口而是依赖 DNS 记录来匹配入口流量到对应的 Tunnel 连接。到这一步之后就可以启动并启用 cloudflared 服务了。后续如果要修改路由规则我只需要改动config.yml然后systemctl restart cloudflared整个过程不超过十秒钟。3.5 构建脚本的设计与使用为了让 cloudflare-os 具备可复现性我还写了一个构建脚本把从零到完整系统的所有步骤都脚本化。脚本的主要流程是执行 archinstall 完成最小化系统安装。chroot 进入新系统安装基础软件包。创建普通用户设置 shell 为 zsh。复制预置的 dotfiles包括 nvm 初始化、zsh 配置、git 配置。安装并配置 cloudflared、wrangler。写入 nftables 防火墙规则。设置 systemd 服务的开机自启。这个脚本并不能做到完全无人值守因为硬件差异和目标机器的网络环境不同需要一定的人工干预。但它把大部分重复劳动都自动化了我在两台配置不同的机器上分别跑了一次整个过程从进制作到进入桌面环境大约只需要 25 分钟。4. 常见问题排查与避坑心得4.1 cloudflared 频繁重启网络依赖没写对我在第二台机器上部署的时候遇到了 cloudflared 反复重启的问题现象是systemctl status cloudflared显示进程一直在 active (restarting)。排查之后发现原因是那台机器用的是无线网络NetworkManager 管理接口的启动时机比 systemd 服务晚而我的 service 文件里只写了Afternetwork-online.target没有确认 NetworkManager 已经完全接管了接口。解决的办法是在 service 文件里增加一行依赖声明让 cloudflared 在 systemd-networkd 或者 NetworkManager 的 waiting 状态结束后再启动。通用的做法是WaitForNetworktrue再配合 ExecStartPre 里加一小段检查脚本确保网络已通过 ping 检测后再启动 cloudflared否则就一直等。这个坑其实很隐蔽因为在一台网络环境简单的服务器上看network-online.target基本是秒通的很难复现问题。但一旦到了家用网络、无线网卡这种场景问题就暴露出来了。4.2 Wrangler 登录状态与多账号切换如果你像我一样需要同时维护多个 Cloudflare 账号比如工作账号和私人账号那么 wrangler 的登录状态管理就是一个需要认真对待的问题。wrangler 默认把登录 tokens 存在~/.wrangler/config/default.toml或者通过环境变量读取多个账号切换起来非常容易犯错。我在 cloudflare-os 里的做法是启用配置文件的环境变量方式每个项目目录下放一个.env文件这里面写入当前项目对应的CLOUDFLARE_API_TOKEN和CLOUDFLARE_ACCOUNT_ID然后用dotenv或者 direnv 这类工具在进入目录时自动加载。这样就不会出现“在这个项目里跑部署命令结果部署到了另一个账号”的事故。另外一个经验是尽量少用wrangler login的 OAuth 方式来做服务器端的自动化部署。OAuth 登录会写入本地的 refresh token虽然方便但如果服务器被攻破token 的泄露面比较大。相比之下API Token 可以设置具体的权限范围和有效期最小权限原则在自动化场景下永远是对的。4.3 防火墙规则写错导致 Tunnel 断连有一次我调整 nftables 规则想限制 SSH 的来源 IP结果误操作把出站流量也一并限制住了导致 cloudflared 无法主动向外建立连接。这个问题的排查难度不大——只要意识到 cloudflared 是出站长连接而我的规则没放行出站 TCP 流量问题就定位了。修改后的规则里我在output链上显式放行了需要的服务nft add rule inet filter output ct state established,related accept nft add rule inet filter output oifname lo accept这段规则的意思是回应的数据包对应 established 连接和回环网卡上的流量都放行。cloudflared 的连接因为是主动出站所以需要保证握手阶段的 SYN 包能发出去。严格地讲防火墙默认对出站放行就不会有问题但如果你跟我一样喜欢把规则写得“严丝合缝”就要特别留意这条。4.4 包管理器源的选择与国内加速Arch Linux 默认使用全球的软件源在国内网络环境下访问速度很不稳定。安装系统的时候archinstall 会让我们选择镜像服务器我通常在交互式界面里手动指定离自己最近的镜像源或者直接编辑/etc/pacman.d/mirrorlist把延迟最低的源放到最前面。判断镜像源快慢有一个实用的工具reflector它会按照 HTTPS 和同步状态自动生成一个按速度排序的 mirrorlist。cloudflare-os 里我把 reflector 的优化命令写进了系统构建脚本每次执行就能自动完成源的排序避免手动挑选的麻烦reflector --protocol https --latest 10 --sort rate --save /etc/pacman.d/mirrorlist执行完这条命令之后pacman -Syyu的下载速度会有肉眼可见的提升尤其是在晚高峰时段差距非常明显。4.5 docker 与 cloudflared 共存时的网络细节最后一个值得记录的坑是 Docker 和 cloudflared 的共存问题。cloudflare-os 里会预装 Docker 来跑一些数据库或者测试服务但 Docker 默认创建的docker0网桥会改变主机的路由表和 iptables 规则有时候会导致 cloudflared 连不上远程网络。我遇到的具体情况是启动一个容器之后cloudflared 报告connection reset但停止所有容器之后又恢复正常。排查后发现是 Docker 的iptables规则和 cloudflared 建立的 UDP 连接产生了冲突。解决办法有两个方向一是在 cloudflared 的 service 文件里加上IPAddressDeny设置二是调整 Docker 的 daemon.json把 iptables 的接管行为改成可预测的方式。我最终选择了更简单的办法把 docker 的 bridge 网络显式设定为一个指定的子网避免和 host 路由的默认网关冲突。具体配置存放在/etc/docker/daemon.json里{ bip: 172.26.0.1/24 }这个方案本质上就是划清主机和容器的 IP 网段边界让两边的路由表各走各的互不干扰。改完之后 cloudflared 再也没有因为 Docker 的启动而断连过。5. 系统更新策略与日常维护建议5.1 滚动更新的节奏控制Arch Linux 的滚动更新是双刃剑频率过高容易踩到上游包的 bug频率太低又可能错过安全补丁。我在 cloudflare-os 里定了一个比较稳妥的节奏一周至少更新一次尽量在周末执行因为周末出问题有更充足的时间处理。更新前先看一眼 Arch 官网的 news 页面一旦有重大变更比如 python 版本升级、openssl 升级就额外谨慎必要时会推迟几天。更新的命令很简单sudo pacman -Syu如果工作区里有正在运行的关键服务比如生产环境依赖的 Tunnel我一般先执行systemctl status cloudflared确认连接健康更新完之后再检查一次。如果pacman更新了内核那么需要重启机器但我会安排在服务低峰期进行。5.2 备份与恢复的轻量方案云原生时代很多人会忽略本地开发机的备份觉得环境坏了重新装就行。但 cloudflare-os 这种重度定制的系统如果配置文件、Tunnel 凭证、Wrangler 登录状态丢了光是恢复配置就能折腾大半天。我的方案是写一个简单的备份脚本定期把/etc/cloudflared/、~/.cloudflared/、~/.wrangler/、以及项目的.env文件打包加密推送到对象存储里。恢复的时候只需要做两件事解开加密包恢复到对应路径然后重新执行一次服务的 enable 和 start。整个过程大概五分钟就能把一个全新的 cloudflare-os 变成和原来一样的工作环境。这个备份机制我认为是对“定制系统”这类实践最有价值的锦上添花——它让系统真正变成了可以随时推倒重来的产物。5.3 日志轮转与数据清理Tunnel 常驻进程和 Docker 容器都会产生日志文件如果不加管理/var/log和 Docker 的数据目录会逐渐膨胀。我在系统里配置了logrotate对 cloudflared 和 systemd journal 做了大小限制具体来说journalctl --vacuum-size200M这条命令会把 systemd 日志压缩并限制在 200MB 以内。对于 Docker我设置了 daemon.json 的日志驱动为json-file并限制单个容器的日志大小避免某个调试容器疯狂打印日志导致磁盘打满。这些细节平时不起眼但往往决定了系统能不能长时间稳定运行。6. 一套可以复用的工作流模板cloudflare-os 的最终目标不是做一个“安装完就放着不管”的系统而是要让开发、部署、调试的全流程都围绕 Cloudflare 生态跑顺。我这里整理了一套基于 cloudflare-os 的典型工作流模板供参考。在这个系统上我通常把项目仓库存放在~/work/目录下每个项目先建一个.env文件配置好 CLOUDFLARE_API_TOKEN 和 account ID然后直接通过 wrangler 初始化 Workers 项目wrangler init my-worker初始化完成之后本地开发用wrangler dev它会自动起一个本地服务器并监听需要调试的端口。如果要把本地服务临时暴露到公网给同事联调就跑一条 Tunnel 快速命令。等到代码稳定了再更新正式 Tunnel 的 ingress 规则把流量切到本地端口的指定版本上。整个流程里我几乎不需要手动登录云控制台所有操作都在这台机器的终端里完成。这种流畅感才是 cloudflare-os 存在的意义。按我个人的使用体会来说定制的操作系统听起来是件很“重”的事情但一旦你把这层体验打磨顺了后面省下来的时间是非常可观的。也不必一步到位哪怕只把你常用的那几款工具、几个配置文件和自启动服务固定下来就已经比大多数人的环境要可靠得多。
返回列表