ITADN
Mic92/tribuchet
Mic92/tribuchet · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

Tribuchet

用于 Nix 的远程构建执行,基于实验性的 external-builders 特性:一个位于 nix-daemon 旁边的中心节点将构建任务 分派给远程工作节点,这些工作节点在各自的沙箱中执行构建,并将 日志和输出流式传输回等待的 nix build

Architecture

状态:实验性。 它依赖于 Nix 的实验性 external-builders 特性(外加一个用于 uid-range 构建的小补丁) 并且协议和配置可能仍会发生变化。

为什么不是 --builders / SSH 构建钩子?

经典的远程构建协议需要能够 SSH 访问每个 构建器,在其上安装 Nix,并且在不进行调度的情况下复制闭包。 tribuchet 从 Nix 接收完整的构建环境,并自行负责 传输、调度和执行。

特性

  • Workers 通过 gRPC 使用双向 TLS 与 hub 建立连接,因此它们可以位于 NAT 之后,并注册其服务的系统和功能。
  • Hub 调度采用按系统划分的队列和能力匹配 (kvm, uid-range, big-parallel, …);相同的提交共享 同一个构建。
  • 仅缺失的输入路径会被传输,以 zstd 压缩的 NAR 形式;输出 作为由 worker 签名(ed25519)的 NAR 返回,且不进行 store-path 重写。
  • 构建在 hub 和 worker 重启/重新加载后依然存活,因此部署不会 终止进行中的构建;当客户端断开时,它们会被取消。
  • 沙箱机制与 Nix 自身的等效(Linux 命名空间 + 按构建划分的 cgroup 限制,macOS 下按构建用户运行的 Seatbelt),外加 uid-range 构建以及 跨系统用户态模拟。
  • 固定输出派生项通过 presto-pasta (嵌入式用户态 NAT)在一个否则隔离的网络命名空间中获取网络, 并带有可选的允许/拒绝流策略(fod-network in worker.toml)。
  • 跨重新加载/重启的实时构建日志,带有 max-log-size、 max-silent-time 和超时强制执行。
  • 针对这两个服务的 NixOS 和 nix-darwin 模块。

快速入门

tribuchet 是一个包含四个子命令的二进制文件:hubworkerattach(Nix 执行的 shim)以及 ca

1. 证书

Worker 使用来自私有 CA 的客户端证书向 hub 进行身份验证:

$ tribuchet ca init   --dir ./ca
$ tribuchet ca issue hub     --dir ./ca   # SAN must match the hub address workers dial
$ tribuchet ca issue worker  --dir ./ca   # one per worker

Hub 从 <config-dir>/ca 读取 ca/{hub.crt,hub.key,ca.crt} (默认 /etc/tribuchet/ca);每个 worker 获得 ca.crt 及其自身的 密钥对(默认 /var/lib/tribuchet/tls/)。

或者,在两端设置 auth = "tailscale" 以完全跳过 TLS: worker 拨号 http://<hub-tailnet-name>:7437,hub 在每次连接时针对 tailscaled 的 LocalAPI 查找对等节点 (因此任何不在 tailnet 上的节点都会被拒绝),并使用节点名称 作为 worker 身份。使用 tailscale-allowed-tags = ["tag:tribuchet-worker"] 将注册限制为特定的 ACL 标签。

2. Hub(在运行 nix-daemon 的机器上)

/etc/tribuchet/hub.toml:

socket = "/run/tribuchet/hub.sock"   # for tribuchet attach
listen = "0.0.0.0:7437"              # for workers
config-dir = "/etc/tribuchet"

将 Nix 指向 nix.conf 中的 attach shim:

experimental-features = external-builders
external-builders = [{"systems":["x86_64-linux","aarch64-linux"],"program":"/path/to/tribuchet-attach"}]

其中 tribuchet-attach 是一个包装脚本:

#!/bin/sh
exec tribuchet attach "$1" --socket /run/tribuchet/hub.sock

可选地在 /etc/tribuchet/trusted-signing-keys 中固定 worker 签名密钥(每行一个 name:base64,语法与 trusted-public-keys 相同)。

3. Workers

Workers 需要拥有自己正在运行的 nix-daemon(输入通过它导入,并通过临时根目录从垃圾回收中保护)。 worker 以非特权方式运行,并将每个构建租约分配给按 uid 划分的 agent 服务(由 NixOS 和 nix-darwin 模块设置),该服务拥有构建器进程及其 uid 块。

/etc/tribuchet/worker.toml:

hub = "https://hub.example.org:7437"
max-jobs = 4
max-log-size = 67108864

[emulate]
aarch64-linux = "/path/to/static/qemu-aarch64"
$ tribuchet worker --config /etc/tribuchet/worker.toml

两个文件的完整选项集记录在 crates/tribuchet/src/config.rs

工作进程的 TLS 路径可以通过 TRIBUCHET_CA_CERTTRIBUCHET_CERTTRIBUCHET_KEY 环境变量进行覆盖,例如 指向由 systemd LoadCredential 提供的密钥(NixOS 模块的 services.tribuchet-worker.keyFile 会执行此操作)。

NixOS

导入 tribuchet.nixosModules.default(flake 输入 github:Mic92/tribuchet)并启用服务:

{
  # hub machine
  services.tribuchet-hub.enable = true;
  # optional: route this machine's nix-daemon builds through the hub
  services.tribuchet-hub.externalBuilders = {
    enable = true;
    systems = [ "x86_64-linux" "aarch64-linux" ];
  };

  # worker machines
  services.tribuchet-worker = {
    enable = true;
    settings = {
      hub = "https://hub.example.org:7437";
      max-jobs = 4;
    };
  };
}

hub 单元采用套接字激活;worker 单元在包或配置变更时执行重载而非重启,因此正在运行的构建在部署过程中得以保留。

macOS (nix-darwin)

tribuchet.darwinModules.default 为 launchd 提供相同的两个服务:hub 从 launchd 接管其套接字,worker 守护进程通过一个稳定的符号链接执行,该链接在激活时切换并发送 SIGHUP 信号,同样确保构建在升级过程中保持存活。

容器

对于没有 Nix 的主机,flake 会构建一个 OCI 镜像, packages.x86_64-linux.worker-image。它自带 Nix 存储, 启动 nix-daemon 和 worker,并根据 worker.toml 中的 spawn-agentsagent-uid-base 生成其构建代理。无需添加 任何 capabilities,但沙箱会创建命名空间和 挂载点,而默认运行时 seccomp 配置禁止这些操作。请使用 packages.x86_64-linux.seccomp-profile 中的配置并取消 /proc 的掩码:

$ podman run -d --name tribuchet-worker \
    -v /etc/tribuchet:/etc/tribuchet:ro \
    -v tribuchet-nix:/nix \
    --security-opt seccomp=$(nix build --print-out-paths .#seccomp-profile) \
    --security-opt unmask=ALL \
    tribuchet-worker:latest

沙箱所需的额外 syscall 规则也可单独在 nix/seccomp-additions.json 中获取, 以便追加到您选择的任何基础配置中。

对于 docker,请将 unmask=ALL 替换为 systempaths=unconfined。在 Kubernetes 上,将配置作为 Localhost seccomp 配置文件分发,或者回退到 Unconfined,并设置 procMount: Unmasked

与 NixOS 模块相比,代理不会获得委托的 cgroup,因此 build-memory-max 和 uid-range 构建不可用。/nix 卷仅是一个缓存。守护进程通过镜像中内置的 min-freemax-free 设置对其进行垃圾回收。

固定输出网络策略

在具有 /dev/net/tun 的 Linux 工作节点上,固定输出构建运行在 私有网络命名空间中,并通过嵌入式 presto-pasta 用户态 NAT 获得出站连接。 工作节点的环回服务和抽象套接字从那里永远不可达。在此基础上, 可选的 fod-network 设置会过滤此类构建可以连接的目标。它位于工作节点的自由格式设置中,因此 在使用 NixOS 模块时,它就是普通的 Nix:

services.tribuchet-worker.settings.fod-network = {
  # action when no rule matches (default: "allow")
  default = "allow";

  # ordered rules, first match wins
  rules = [
    {
      action = "deny";
      dst = "private"; # loopback, RFC 1918, link-local, ULA, CGNAT, ...
    }
    {
      action = "allow";
      dst = "10.20.0.15"; # single IP or CIDR, IPv4 or IPv6
      ports = [ "443" ];
    }
    {
      action = "deny";
      proto = "tcp"; # "tcp", "udp" or "any" (default)
      dst = "any";
      ports = [
        "25"
        "465"
        "587"
        "8000-8999"
      ];
    }
  ];
};

如果没有该模块,相同的结构将以 [fod-network] 表的形式写入 worker.toml,其中包含 [[fod-network.rules]] 个条目。

每条规则都匹配新出站连接的目的地:

  • dst: "any""private"(所有非公共地址:环回、 RFC 1918、链路本地、ULA、CGNAT、组播)、单个 IP 或类似 "192.0.2.0/24" / "2001:db8::/32" 的 CIDR。
  • proto: "tcp""udp""any"
  • ports: 目的地端口,单个("443")或包含范围 ("8000-8999");为空或省略表示任意端口。

在创建主机套接字之前,规则按顺序对每个新流进行评估;被拒绝的连接根本不会离开沙箱(TCP SYN 不会收到响应)。规则基于 IP 是有意为之——基于主机名的规则仅适用于连接时名称解析到的目标, 并且可以通过自行解析名称的构建轻松绕过。DNS 查询由 presto-pasta 独立于这些规则转发到主机解析器, 因此 deny 规则不会破坏名称解析。

构建流程

  1. Nix 执行 tribuchet attach build.json;shim 通过 unix socket 将构建提交给 hub。
  2. Hub 验证并去重请求,将其排队给服务于该系统和功能集的 worker。
  3. 路径协商:worker 报告其已拥有的输入路径;hub 将缺失的路径作为带有 Nix db 元数据的 zstd NAR 流式传输,并通过 worker 的 nix-daemon 在 worker 上导入。
  4. Worker 在其沙箱中运行 builder;日志实时流式传输回客户端。
  5. 输出被打包、签名、由 hub 验证,并在 Nix 提供的 scratch 路径上解包;Nix 完成哈希和注册,就好像构建是在本地运行的一样。

DESIGN.md 详细描述了架构、沙箱、安全模型和故障处理。

开发

$ nix develop            # rust toolchain + protobuf
$ cargo test
$ cargo clippy --all-targets
$ nix build .#checks.x86_64-linux.nixos-test   # end-to-end VM test (hub + worker)

VM 测试涵盖远程构建、构建过程中的 hub/worker 重启与重新加载、 取消操作、日志限制、uid-range 和模拟构建,以及 固定输出网络。

License

MIT