☰
用NixOS复刻Cloudflare OS:构建不可变边缘节点系统
2026/10/28 13:32:34 网站建设 项目流程

第一次看到 cloudflare-os 这个名字,很多人和我一样,第一反应是“Cloudflare 真的做了一个操作系统?”实际上它并不是从零写内核的那种 OS,而是一整套构建边缘计算节点系统镜像的方法论与工具链。Cloudflare 的服务器遍布全球几百个城市的数据中心,几万台异构硬件的系统要保证一致、安全、可快速替换,这件事靠常规 Linux 发行版很难做漂亮。Cloudflare OS 的核心价值就是把操作系统变成一种“可复现、可审计、可回滚”的软件制品,每次升级都是一次声明式的构建和发布,而不是登录到一台台机器上改配置。

这篇文章我会把这套系统的设计思路掰开揉碎,从内核定制、不可变根文件系统、原子更新,到如何用 Nix 在本地复刻一个 Mini 版原型。内容适合云平台工程师、SRE、以及所有对构建自定义 Linux 系统感兴趣的人。我会尽量用我实际踩过坑之后的理解来讲,配合可直接运行的配置和命令,让你看完不只是有个概念,而是能自己动手跑起来。

1. 项目背景:为什么 Cloudflare OS 值得专门讲

1.1 现成发行版为什么不够用

很多人会问:数据中心服务器用 Debian 或 CentOS 不就行了?在只有几十台机器的规模下确实行,但到了 Cloudflare 这种规模,问题就变得很尖锐。

首先是最基础的版本一致性问题。机器数量一旦上千,你没法保证每一台机器都被同样地配置过。一个人工的apt upgrade漏了某台机器,这台机器的内核和邻居差了好几个版本,问题排查时你会发现同一套代码在不同的节点上表现完全不一样。其次是升级的安全窗口,边远机房现场没人,系统出问题不能靠人插键盘,必须有一套机制让机器在无人干预的情况下自动恢复。

另一个让大厂选择自研系统的重要原因,是硬件层面的定制需求。CDN 边缘节点主要做 DDoS 防护、HTTP 缓存、网络转发,这些场景对 Linux 内核的网络栈、XDP/eBPF 处理路径有极高要求,需要深度裁剪内核模块和系统服务。拿到一个通用的 Ubuntu Server,里面有大量边缘节点根本用不到的驱动、固件、桌面组件,这些多余部分不仅占空间,还扩大攻击面。Cloudflare OS 的思路是:系统就是代码仓库里的一个构建产物,任何节点上的系统都可以从同一个 commit 重建出来,行为完全一致。

1.2 不可变基础设施的思维模型

如果你接触过容器,可以把 Cloudflare OS 想象成“给整台物理机做的 Docker 镜像”。容器镜像一旦构建完成就只读不可改,部署只是把新镜像拉下来、换掉旧容器。Cloudflare OS 对服务器操作系统做了同样的事:根文件系统只读,运行时所有可变数据都放到专门的数据分区或远程存储;更新不是在某台机器的文件系统上调包,而是构建一个完整的新系统快照,通过切换启动入口来完成升级。

这种设计的核心是放弃了“服务器是宠物,需要定期登录去照顾”的旧观念,转而采用“服务器是牲畜,坏了直接杀掉换新的”的现代运维哲学。任何一种配置漂移都是不允许的,因为整台机器的状态都由构建系统描述。它带来的直接好处是:升级失败的恢复时间从几小时人工排障降低到一次重启;安全漏洞修复可以同时推送到全网节点;审计人员想查“某年某月某日某节点上跑的到底是哪个内核”,只需要查当时的构建记录和镜像哈希。

2. 整体设计拆解:一套边缘操作系统的关键模块

2.1 内核定制与启动链

边缘节点的操作系统内核首先要解决性能和安全两个问题。Cloudflare 的流控、防火墙和负载均衡大量依赖 XDP 和 eBPF,这意味着内核版本必须足够新,且相关编译选项得在构建时打开。公开资料里他们提到过使用 Bazel 构建整个系统镜像,内核同样作为一个 Bazel target 参与编译,从源码、配置、补丁到最终二进制,每一步都有确定的输入输出。

启动链一般遵循 UEFI -> GRUB/systemd-boot -> initrd -> rootfs 的顺序。在不可变系统里,initrd 的作用会被放大:它要负责在多块磁盘里找到正确的系统快照分区,校验签名或哈希,然后切换到真正的根文件系统。Cloudflare OS 在这条链路上做了很严格的安全设计,所有启动组件都要求签名,密钥放在硬件信任根里,防止有人物理接触服务器后替换引导程序。

对我们做复刻原型的人来说,没必要重新发明启动链。NixOS 天然支持生成内核、initrd、引导配置一整套内容,我们只需要声明boot.kernelPackages = pkgs.linuxPackages_latest并指定一些内核参数,构建系统就会处理好其余细节。它在哲学上跟 Cloudflare OS 很接近:整个启动过程是声明式的、可重复生成的。

2.2 只读根文件系统与原子更新

所有人关注 cloudflare-os 时,最感兴趣的点几乎都是它的升级机制。升级的本质不是一个“补丁覆盖”的过程,而是一个新的系统 generation 被写入到独立的存储位置,然后下一次启动时引导加载器直接选择新的 generation。如果新版本启动失败,节点可以自动回退到上一个 generation。

NixOS 把这个机制做成了内建能力。系统中所有文件都链接到 /nix/store 下的哈希目录,每个软件包、每个内核、每个配置文件都有唯一哈希;nixos-rebuild switch所做的就是把一个名为“system”的新 profile 切换到当前系统,同时生成对应 GRUB 菜单项。构建完成前旧的系统完全不受影响,切换失败的恢复只是重新选择菜单项而已。

这里有一个常见的认知误区:NixOS 的根文件系统并不是默认只读的。Nix 通过把有状态数据清零、把系统路径全部符号链接到 /nix/store,在效果上实现了“大部分文件不可变”。如果要更贴近 Cloudflare OS 的强只读模型,还需要在挂载选项里给根系统加上ro,并把 /var/log、/var/lib 等有状态目录移到独立数据分区。后面我会在实操部分演示这个配置思路。

2.3 用户空间服务的极简化

普通发行版会附带一堆便利服务,边缘 OS 则希望用户空间越小越好。小意味着出问题的面小,也意味着启动更快、内存占用更低。Cloudflare OS 上通常只保留 systemd、网络栈相关工具、日志采集 agent、节点管理 agent、SSH 服务,以及实际承载业务的边缘代理进程。其它组件一个都不装。

我在复刻时也遵循这个原则。NixOS 的environment.systemPackages里只放最必需的工具,能不加就不加。这样做还有一个附带好处:构建产物小,分发到几千台节点上的带宽成本、部署时间都会显著下降。如果你想查看一个 NixOS 系统最终包含了哪些 store paths,可以执行nix path-info -r /run/current-system,输出会很长,但每一行都能追溯到声明式配置里的某一行,这种可追溯性是传统包管理器给不了的。

2.4 配置、遥测与安全加固

操作系统本身做得再干净,没有良好的运维通道也无法支撑大规模节点。Cloudflare OS 的节点通常不会开放很多端口,对外管理走的是基于零信任的隧道,SSH 也默认禁用密码登录。节点的健康状态通过内置的遥测 agent 上报到中心,包括 CPU、内存、磁盘、网络包量、系统日志等。任何事情都不需要节点主动拉取,全部由中心下发给 agent 执行。

基于 NixOS 模拟这套运维模型时,我会在配置里开启 SSH 并配置好公钥,同时把PasswordAuthentication设为 false,把UseDNS关掉避免登录延迟。对于更真实的生产要求,可以在节点上部署 Tailscale 之类的组网工具作为远程管理通道,也可以配置 Prometheus node_exporter 上报指标。这些服务在 NixOS 里都是一个布尔开关加几行配置就能搞定,不需要手工写 systemd unit。

3. 实操:用 Nix 复刻一个 Mini 版 Cloudflare OS

3.1 环境准备:安装 Nix 与启用 Flakes

动手之前需要准备一台 Linux 虚拟机或实体机,最好有 root 权限。我这里以 Ubuntu 24.04 为例,单用户安装 Nix:

sh <(curl -L https://nixos.org/download.html) --no-daemon

安装完成后,当前用户需要重新加载 profile:

. ~/.nix-profile/etc/profile.d/nix.sh

然后开启 Flakes 和新的 nix-command 特性。如果你用的是 NixOS,可以直接写在配置里;如果是在普通 Linux 上,可以修改~/.config/nix/nix.conf:

experimental-features = nix-command flakes

验证环境是否就绪,执行:

nix --version nix flake --help

我建议再配置一下缓存镜像源,国内环境直接访问官方 Cache 经常超时。NixOS 上在 configuration.nix 里写:

nix.settings.substituters = [ "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store" "https://cache.nixos.org" ];

普通 Linux 则在 /etc/nix/nix.conf 里加同样内容。这一步能省掉大量等待时间,属于实操中非常实用的经验。

3.2 编写 edge 节点的 NixOS 配置

现在建立工作目录,里面放两个文件。第一个是 flake.nix,负责描述外部输入和系统构建入口:

# flake.nix { description = "Mini Cloudflare OS:基于 NixOS 的边缘节点最小系统"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; }; outputs = { self, nixpkgs }: { nixosConfigurations.edge = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ./edge.nix ]; }; }; }

第二个是 edge.nix。我在这里做了一份面向虚拟机验证的配置,同时保留了生产环境下需要调整的注释:

# edge.nix { config, pkgs, lib, ... }: { nix.settings.experimental-features = [ "nix-command" "flakes" ]; # 使用最新稳定内核,并做精简启动参数 boot.kernelPackages = pkgs.linuxPackages_latest; boot.kernelParams = [ "quiet" ]; # 虚拟机里用 GRUB 引导,设备指向虚拟磁盘 boot.loader.grub.enable = true; boot.loader.grub.device = "/dev/sda"; # 节点主机名和网络 networking.hostName = "edge-node-001"; networking.useDHCP = true; # SSH 远程管理,仅允许公钥登录 users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxx your@email" ]; services.openssh = { enable = true; settings = { PermitRootLogin = "prohibit-password"; PasswordAuthentication = false; UseDNS = false; }; }; # 模拟不可变基础设施:数据卷独立挂载 fileSystems."/" = { device = "/dev/disk/by-label/EDGE"; fsType = "ext4"; options = [ "defaults" ]; }; fileSystems."/var/log" = { device = "/dev/disk/by-label/EDGE-VAR"; fsType = "ext4"; }; # 尽量保持干净的用户空间 environment.systemPackages = with pkgs; [ vim tmux curl htop ]; system.stateVersion = "24.11"; }

生产环境里/dev/disk/by-label/EDGE和/dev/disk/by-label/EDGE-VAR可以是两块不同的物理盘或云盘;在虚拟机上要验证启动,则需要把根分区的 device 改为/dev/sda。这个细节很容易被忽略,很多人在本地一跑发现找不到磁盘,其实就是这里的问题。

3.3 构建并启动虚拟机验证

有了配置,接下来构建一个可运行的 QEMU 虚拟机。在项目目录执行:

nixos-rebuild build-vm --flake .#edge

第一次构建会拉取大量 Nix 包,耗时取决于网速和 CPU。构建完成后,当前目录会出现result符号链接,里面包含一个启动脚本:

./result/bin/run-edge-vm

启动后系统会进入虚拟机终端。默认情况下 NixOS 的 build-vm 启动的虚拟机里,系统状态是临时的,适合用来验证配置是否能够正常引导和启动服务。

如果想把系统导出为可烧写到物理机或云盘的镜像,有更直接的办法。在 NixOS 生态里可以安装 nixos-generators,然后一行命令生成多种格式镜像:

nix run nixos-generators -- -f raw --configuration ./edge.nix

生成出来的raw格式镜像放到 U 盘或用 qemu-img convert 转成虚拟盘都可以直接启动。这个工具链本身就是很多人在没有 Cloudflare 内部基建的情况下,用来模拟类似能力的最方便路径。

3.4 部署切换与回滚验证

现在进入最有意思的部分:验证系统的原子切换与回滚机制。

先在虚拟机里确认当前系统 generation:

nixos-rebuild list-generations

这条命令会显示当前系统的历史记录,每条对应一个配置文件描述的系统快照。接下来修改 edge.nix,比如加一个nginx到environment.systemPackages,然后重新执行:

sudo nixos-rebuild switch --flake .#edge

切换完成后再次查看 generation 列表,你会看到多了一项。此时 GRUB 菜单里也同时保留了新旧两个 entry。回滚命令则非常直观:

sudo nix-env -p /nix/var/nix/profiles/system --list-generations sudo nix-env -p /nix/var/nix/profiles/system --rollback

执行完回滚后,当前系统会切回上一个 generation。如果要验证“启动失败自动回退”,可以给配置里故意写一个错误的内核参数或者破坏性的 modules,然后重启,观察 GRUB 出现新旧选项后选择旧版本。在真机上,Cloudflare 的运维系统会配合健康检查和心跳机制来自动完成回退,而不再依赖人工在屏幕上选择菜单。

这套“构建新系统快照 -> 切换入口 -> 失败回滚”的流程,通过 NixOS 在大约半小时内就能完整体验一遍。理解了它,也就理解了 cloudflare-os 里最核心的更新哲学。

4. 常见问题与排查经验

4.1 构建阶段的坑

Nix 构建失败排在第一位的问题永远是网络。如果你用的 nixpkgs 输入是最新的 unstable 版本,构建时需要的 source 可能来自 GitHub、GitLab 等多处,下载中断会导致报错显示 hash mismatch 或者 curl 超时。我的建议:固定一个稳定分支,比如 nixos-24.11,并且把 flake.lock 提交到代码仓库,构建时保证输入可复现。

第二个常见问题是error: cannot lookup flake ...。这通常是因为本地 flake registry 和缓存没有刷新。执行nix flake update或者删除~/.cache/nix目录下对应缓存能解决。国内网络如果频繁超时,参照前面配置 substituters 为镜像源,同时尽量让所有构建依赖都从 Nix 的二进制缓存命中,而不是从源码重新编译。

第三个问题比较隐蔽:GRUB device 配置不对。在物理机上,你需要找到实际的磁盘设备名,比如/dev/sda、/dev/nvme0n1,在虚拟机上又可能是/dev/vda。写错了启动器装不上,系统构建本身却不会报错,只有启动时才会发现引导失败。提前用lsblk或者blkid确认磁盘路径,能省很多时间。

4.2 启动与运行阶段的坑

虚拟机启动后系统不断重启,多半是根文件系统没有正确挂载。注意 build-vm 镜像的设备名跟配置里的 device 字段必须一致。另外,NixOS 默认使用 systemd 作为 init 系统,如果 initrd 阶段缺少某个模块导致无法识别磁盘,可以在boot.initrd.availableKernelModules里补充ata_piix、sd_mod、virtio_blk这类基础驱动。

SSH 登录不上也是高频问题。先确认服务有没有起来:systemctl status sshd。然后看防火墙,NixOS 默认防火墙是关闭的,但如果你在生产配置里开了networking.firewall.enable = true,就必须显式放行 22 端口。

登录速度慢通常不是网络问题,而是 sshd 做 DNS 反向解析超时,已经在配置里加过UseDNS = false的话可以排除。还有一种情况是有数据分区挂载失败,比如 /var/log 指向的分区不存在,sshd 在运行时会因为无法创建会话目录而异常。把有状态目录放到独立分区之前,一定要先确认分区已经格式化并正确写入 fstab 或者 NixOS 的 fileSystems 声明。

4.3 经验清单:哪些建议真实有用

我在复刻这类系统过程中,沉淀出几条值得保留的经验,分享给你。

第一,永远不要把节点的有状态数据放在系统根分区里。数据库、日志、缓存的宿目录都要映射到独立数据盘。因为不可变系统的升级和回滚只保证系统文件还原,回滚不会自动恢复业务数据,放在一起会让回滚变得极其危险。

第二,系统配置尽量保持“一个节点一个 profile”的单一入口。不要今天手动改一份配置,明天在机器上临时装一个包,后天又从配置中心下发一部分散装指令。NixOS 的哲学是最终状态完全从声明式配置推导,任何手工操作都在污染这个模型。

第三,在灰度发布时先拿一小批节点验证。大规模边缘操作系统最怕的就是全网一次性升级,然后发现新内核在某种老旧网卡上有 bug。Cloudflare 的做法一定是分批次、分区域灰度。NixOS 的系统 profile 机制天然支持这个流程:同一个 flake 构建出的系统,可以先部署到 test 节点,稳定后再推给 prod 节点。

第四,把日志和监控配置当成系统的一部分,而不是事后附加。这样每次系统版本变更,日志采集参数、指标上报规则也跟着一起原子变更,不会出现新版系统与旧版监控 agent 不兼容的情况。NixOS 里用 services 定义这些 agent,升级时它们会跟系统一起切换,非常干净。

最后说一个很多人都忽略的点:题目里的 cloudflare-os 并不是一个你下载回来就能装的发行版镜像,而是一套“如何把系统当成代码来管理”的思路。Cloudflare 作为一家 CDN 公司,能做到几千台边缘节点以极快的速度完成固件级更新,依靠的是从内核到启动器、再从网络配置到监控采集全链路的构建化和自动化。这套思路最大的门槛不在工具本身,而在于你是否愿意接受一个更严格的运维模型:机器不再被单独配置,状态被集中管理,一切以代码为唯一事实来源。对于团队规模不大、机器数量还没到数百台的场景,这套哲学同样适用,越早引入越省事。NixOS 是这个模型最容易上手的载体,希望你在本地跑通之后,能对“操作系统即构建产物”这件事有更具体的感受。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询