Nix 远程构建与分布式构建:builders 协议、ssh-ng 与跨架构

Nix 远程构建实战:为什么要分布式构建、builder 协议与 ssh-ng 原理、/etc/nix/machines 与 buildMachines 配置、distributedBuilds 与调度策略、max-jobs/cores/system-features 的作用、x86 上编 aarch64 的跨架构构建、信任模型与签名、故障排查与性能调优、CI 中的远程构建编排。

引言

一台笔记本编译 LLVM 要四十分钟,一台 64 核服务器只要三分钟——Nix 的远程构建让你把编译任务丢给更强的机器,本地只接收结果。更关键的是跨架构:x86 开发机上直接产出 aarch64 的包,无需模拟、无需在树莓派上慢慢等。

本文从 builder 协议讲起,说清 ssh-ng 与传统 ssh 的区别、/etc/nix/machines 与 buildMachines 两种配置方式、调度与并发参数(max-jobs/cores/system-features)、跨架构构建的完整链路,以及信任模型、故障排查与 CI 编排。

前置:求值与构建性能、二进制缓存与替换器、CI 与 Cachix。


目录


1. 为什么要远程构建

1.1 三个真实动机

动机场景收益
算力笔记本 vs 64 核服务器编译时间数量级下降
架构x86 开发机编 aarch64无需模拟器,原生速度
平台Linux 编 Darwin 包跨系统产出

1.2 远程构建与二进制缓存的区别

两者常被混淆:

  • 二进制缓存(substituter):从别处下载已经构建好的产物。
  • 远程构建(remote builder):把构建任务发给别处执行,再取回产物。

实践中两者配合:远程构建机上同时跑缓存,构建完立刻推上去,别的机器就直接下载。

1.3 什么时候不值得

# ☐ 网络带宽远小于构建收益(大产物 + 慢网络)
# ☐ 构建本身很快(< 1 分钟),调度开销占比过大

记忆:远程构建解决算力、架构、平台三类问题——把编译任务发给更强的机器;它与二进制缓存(下载产物)不同但配合使用:远程机构建完推缓存、其他机器直接下载。


2. builder 协议与 ssh-ng 原理

2.1 两种传输协议

协议命令特点
sshssh://host老协议,把 nix-store 操作通过 ssh 转义
ssh-ngssh-ng://host新协议,走标准 Nix 远程 store 协议,更快更稳

ssh-ng 是推荐默认:它用 nix-daemon --stdio 建立一条标准的 store 连接,支持并发、错误信息完整、无需把命令层层转义。

2.2 一次远程构建的链路

本地 nix build
  → 本地求值出 .drv(derivation)
  → 查询本地 store:缺哪些输出?
  → 挑选一台 builder(按 system/features/负载)
  → 把 .drv 与输入闭包推给 builder,远端构建后把输出推回
  → 本地登记到 store,交给用户

2.3 builder 必须满足的条件

# 1) ssh 免密可达(key 认证):ssh builder.example.com nix-store --version
# 2) builder 的 trusted-users 包含发起方用户(或走 ssh-ng 受限协议)
# 3) builder 的 nix 版本与本地兼容(store 协议版本一致)

记忆:builder 协议分 ssh(老,转义)与 ssh-ng(新,标准 store 协议、推荐);一次远程构建 = 本地求值出 .drv → 挑选 builder → 推 .drv 与输入闭包 → 远端构建 → 取回输出;前提是 ssh 免密可达且版本兼容。


3. /etc/nix/machines 与 buildMachines

3.1 传统写法:/etc/nix/machines

# /etc/nix/machines 每行一台:<uri> <system> - <maxjobs> <speed> <features>
ssh-ng://builder1 x86_64-linux - 8 4 kvm,big-parallel
ssh-ng://builder2 aarch64-linux - 4 2 -

字段含义:

字段含义
uri连接串(ssh-ng://user@host)
system该 builder 的原生 system
ssh-key私钥路径(- 表示用默认)
maxjobs该机器最多并行几个构建
speed相对速度(调度权重)
features支持的 system-features

3.2 NixOS 写法:buildMachines

{
  nix.buildMachines = [
    { hostName = "builder1"; system = "x86_64-linux"; protocol = "ssh-ng";
      maxJobs = 8; speedFactor = 4; supportedFeatures = [ "kvm" "big-parallel" ]; }
    { hostName = "builder-arm"; system = "aarch64-linux";
      maxJobs = 4; supportedFeatures = [ ]; }
  ];
  nix.distributedBuilds = true;
  nix.settings.trusted-users = [ "root" "@wheel" ];
}

3.3 两种方式的取舍

方式优点缺点
/etc/nix/machines与 NixOS 无关、临时可用手工维护、不进配置
nix.buildMachines声明式、随系统配置仅 NixOS

记忆:builder 配置两种姿势——/etc/nix/machines(每行一台,字段为 uri/system/key/maxjobs/speed/features)与 NixOS 的 nix.buildMachines(声明式);后者要同时开 nix.distributedBuilds = true。


4. distributedBuilds 与调度

4.1 开关与生效条件

{
  nix.distributedBuilds = true;
  # 本地也参与构建(默认 true);关掉则只把任务发出去
  nix.settings.max-jobs = "auto";
}

远程构建只在本地无法满足或更慢时触发:本地能构建的包默认本地构建,除非显式把本地 max-jobs 设小以「强制外派」。

4.2 调度器怎么选 builder

# 1) 过滤:system 匹配 + 需要的 features 都满足
# 2) 排序:综合 speedFactor 与当前负载
# 3) 抢占:更快的空闲 builder 可以「抢走」队列里的任务

speedFactor 越高越容易被选中;构建机负载(正在跑几个 job)也参与排序。

4.3 强制外派与本地构建

# 本地并行度为 0:几乎所有任务都外派
nix build .#heavy --max-jobs 0
# 只对特定构建用远程(覆盖配置)
nix build .#heavy --builders 'ssh-ng://fast-builder x86_64-linux - 16 8'

记忆:远程构建默认「本地优先、够不着才外派」——distributedBuilds = true 打开、max-jobs = 0 可强制全外派;调度按 system/features 过滤、按 speedFactor 与负载排序。


5. max-jobs、cores 与 system-features

5.1 三个参数的分工

参数作用域含义
max-jobs单机同时跑几个 derivation
cores单个构建传给 NIX_BUILD_CORES,供 make -j 用
system-features单机声明支持的特性(kvm/big-parallel 等)

5.2 配置示例

{
  nix.settings = {
    max-jobs = "auto";   # 本机并行构建数;auto = 按 CPU 核数
    cores = 0;           # 每个构建可用的核数;0 = 用全部核
  };
  # 声明本机支持的 features(builder 匹配时用)
  nix.systemFeatures = [ "kvm" "big-parallel" "nixos-test" ];
}

5.3 参数如何影响远程构建

# max-jobs 是「本地」的并发上限;远程 builder 的并发由 machines 行的 maxjobs 决定
# cores 会被远程 builder 继承:本地 cores=4 → 远端构建也按 4 核调度
# features 不匹配时该 builder 直接被跳过(例如需要 kvm 的 NixOS 测试)

5.4 常见误配

# ☐ 把 max-jobs 设成 1 却期望多机并行(应为 auto 或较大值)
# ☐ builder 没声明 kvm,导致 nixosTest 全部落到本地

记忆:max-jobs 管「本机同时跑几个构建」、cores 管「每个构建用几个核」、system-features 管「声明支持的特性」;远程 builder 的并发由 machines 行的 maxjobs 决定,features 不匹配会直接跳过该机器。


6. 跨架构构建:x86 上编 aarch64

6.1 两条路线

路线做法速度适用
原生远程找一台真的 aarch64 机器当 builder最快有 ARM 硬件
本地交叉pkgsCross.aarch64-multiplatform中无 ARM 硬件
模拟QEMU binfmt 在 x86 上跑 aarch64慢应急验证

6.2 用远程 aarch64 builder

{
  nix.buildMachines = [
    {
      hostName = "arm-box";
      system = "aarch64-linux";     # 关键:声明目标 system
      maxJobs = 8;
      supportedFeatures = [ ];
    }
  ];
  nix.distributedBuilds = true;
}

之后 nix build .#pkg --system aarch64-linux 就会自动派给 arm-box。

6.3 无 ARM 硬件时的交叉构建

# 用本地交叉包集,x86 上直接产出 aarch64 产物
nix build nixpkgs#pkgsCross.aarch64-multiplatform.hello
file result/bin/hello    # 产物是 aarch64 ELF

6.4 模拟构建(不推荐长期使用)

{
  # 注册 binfmt,让 x86 内核能执行 aarch64 二进制
  boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
  nix.settings.extra-platforms = [ "aarch64-linux" ];
}

extra-platforms 声明「本机也能构建这些 system」,让本地参与远程构建的候选池——但模拟很慢,适合偶尔验证。

记忆:跨架构三路线——真 ARM 机器当远程 builder(最快)、pkgsCross 本地交叉(无硬件时用)、QEMU binfmt 模拟(应急);远程路线只需在 buildMachines 里把 system 写成目标架构,命令加 --system 即可。


7. 信任模型与签名

7.1 信任的两个方向

# 方向一:本地信任 builder —— builder 回传的产物被本地 store 接受
#         通过 trusted-users / trusted-public-keys 控制
# 方向二:builder 信任本地 —— 本地能把 .drv 推给它执行
#         通过 builder 的 trusted-users 包含发起用户

7.2 关键配置

{
  # 谁可以「以他人身份」操作 store(含远程构建的 .drv 推送)
  nix.settings.trusted-users = [ "root" "@wheel" "ci-runner" ];

  # 只接受签名过的产物(远程构建机需配 signing key)
  nix.settings.require-sigs = true;
  nix.settings.trusted-public-keys = [ "cache.example.com-1:AAAA..." ];
}

7.3 安全要点

要点说明
builder 视为可信远程机构建出的产物会进入本地 store
用独立账户给 CI 单独账户,别用 root
关闭 require-sigs 需谨慎无签名校验时任何可达 builder 都能注入

记忆:远程构建是「双向信任」——本地要信任 builder 回传的产物、builder 要信任本地推来的 .drv;用 trusted-users 白名单、require-sigs + trusted-public-keys 校验签名,builder 只在内网、CI 用独立账户。


8. 故障排查与性能调优

8.1 高频故障

现象原因排查
一直本地构建builder 未被选中检查 system/features 是否匹配
cannot connect to builderssh 不通手动 ssh host nix-store --version
产物回传失败磁盘满或权限builder 上 df -h 与 store 权限
构建结果不一致版本/配置不同对比双方 nix 版本与 nix.conf

8.2 调优清单

# 观察调度:-v 会打印「delegating build to ...」
nix build .#pkg -v 2>&1 | grep -i 'delegat\|builders'
# 探测 builder 连通性与版本
nix store ping --store ssh-ng://builder1
# 限制并发避免本地内存爆掉
nix build .#pkg --max-jobs 4

8.3 提高缓存命中

{
  # builder 上同时开二进制缓存,构建完立即推送
  nix.settings.post-build-hook = "/etc/nix/push-to-cache.sh";
}

记忆:排错先看「为什么没派出去」——system/features 不匹配是最常见原因;nix build -v 会打印 delegating build to 哪台;调优抓两点:给 builder 配 post-build-hook 推缓存、用 max-jobs 限制本地并发防内存爆。


9. CI 中的远程构建

9.1 典型拓扑

CI runner(轻量)→ 远程 builder 集群(重型)→ 二进制缓存
     求值 + 调度          并行构建             供所有机器下载

CI runner 本身可以是小机器,只负责求值与派发;真正的编译发生在 builder 上。

9.2 GitHub Actions 里的配置

- name: Configure remote builders
  run: |
    echo "ssh-ng://ci-builder x86_64-linux - 16 8 big-parallel" > ~/.config/nix/machines
    echo "builders = @$HOME/.config/nix/machines" >> ~/.config/nix/nix.conf
    echo "distributed-builds = true" >> ~/.config/nix/nix.conf
- name: Build
  run: nix build .#default -L

9.3 与 Cachix 的配合

# 构建完把结果推到缓存,后续任务直接下载
cachix push my-cache result
# 或让 builder 直接推(post-build-hook)

远程构建 + 二进制缓存 + Cachix 的组合是大型 flake 项目的标准加速栈:一次构建、全网复用。

记忆:CI 里的远程构建拓扑是「轻量 runner 求值派发 → 重型 builder 集群并行构建 → 二进制缓存供下载」;GitHub Actions 里写 machines 文件 + distributed-builds = true 即可,再配合 Cachix 做一次构建全网复用。


10. 速查表与一句话记忆

需求命令 / 配置一句话
声明 buildernix.buildMachinessystem 决定架构
打开远程nix.distributedBuilds = true本地优先、够不着才外派
强制外派--max-jobs 0几乎全外派
临时 builder--builders 'ssh-ng://h x86_64-linux - 8 4'命令行覆盖
跨架构builder 的 system 写目标架构加 --system
无硬件pkgsCross.aarch64-multiplatform本地交叉
模拟extra-platforms + binfmt应急验证
排查nix build -v 搜 delegating看派给了谁

一句话记忆:远程构建把编译任务发给更强的机器,解决算力/架构/平台三类问题;协议选 ssh-ng(标准 store 协议,优于老 ssh),配置用 /etc/nix/machines 或 NixOS 的 nix.buildMachines(字段 uri/system/maxjobs/speed/features),并打开 nix.distributedBuilds;调度按 system 与 features 过滤、按 speedFactor 与负载排序,max-jobs 管本机并发、cores 管单构建核数;跨架构只需把 builder 的 system 写成目标架构(无硬件则用 pkgsCross 或 binfmt 模拟);它是双向信任——本地信任回传产物、builder 信任推来的 .drv,用 trusted-users 与 require-sigs 收敛风险;排错先问「为什么没派出去」(多半是 features 不匹配),nix build -v 看 delegating;CI 里配轻量 runner + 重型 builder + 二进制缓存,一次构建全网复用。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「nix」更多文章

  1. nixpkgs 贡献与维护:从 by-name 到 backport
  2. Nix 中的 CUDA 与机器学习环境:cudaPackages 与 PyTorch
  3. nix-darwin:macOS 的声明式系统配置