在 NixOS 中对某些包进行一些自定义修改时,如果当前 store 里没有可用的二进制结果(即没有缓存 / 没有从 binary cache 下载到),Nix 会走正常构建流程去生成它——这就会触发编译。而本地机器性能通常有限,这时候就需要用到分布式构建,这里记录一下 NixOS 分布式构建的配置和验证过程。
假设/先决条件
- 在两台机器(远程和本地)上系统级安装NixOS;
- 对远程机器高度信任;
- 机器之间的 SSH 连接;
- 对 NixOS 和 Nix 语言有一些经验。
配置过程
本地机器配置
因为两台机器之前是通过 SSH 连接,所以需要生成 SSH Key:
# ssh-keygen -f /root/.ssh/<name of ssh key file>
这个命令会在 /root/.ssh
目录下生成名为 <name of ssh key file>
的 SSH 公钥/私钥对,执行命令时只需一路回车使用默认值或留空即可。注意,不要为 SSH 密钥添加密码短语,因为这个连接需要无密码连接。
为了方便 SSH 连接,可以创建 SSH 配置文件 ~/.ssh/config
:
Host <remote alias>
HostName <remote ip>
User <remote username>
Port <remote port>
IdentityFile /root/.ssh/<name of ssh key file>
也可以直接在 configuration.nix
中配置在 programs.ssh.extraConfig
中:
{ config, lib, pkgs, ... }:
{
#...
# SSH configuration for distribute build
programs.ssh.extraConfig = "
Host <remote alias>
HostName <remote ip>
User <remote username>
Port <remote port>
IdentityFile /root/.ssh/<name of ssh key file>
";
#...
}
其中:
Host <remote alias>
:用于标识某个特定的配置,可随意设置;HostName <remote ip>
:远程机器的 IP 地址;User <remote username>
:用于 SSH 连接的用户名;Port <remote port>
:远程机器的 SSH 端口;IdentityFile /root/.ssh/<name of ssh key file>
:前面生成的 SSH 私钥路径。
然后开始编辑 /etc/nixos/configuration.nix
如下:
{ config, lib, pkgs, ... }:
{
#...
# You can see the resulting builder-strings of this NixOS-configuration with "cat /etc/nix/machines".
# These builder-strings are used by the Nix terminal tool, e.g.
# when calling "nix build ...".
nix.buildMachines = [{
# Will be used to call "ssh builder" to connect to the builder machine.
# The details of the connection (user, port, url etc.)
# are taken from your "~/.ssh/config" file.
hostName = "<remote username>@<remote alias>";
# CPU architecture of the builder, and the operating system it runs.
# Replace the line by the architecture of your builder, e.g.
# - Normal Intel/AMD CPUs use "x86_64-linux"
# - Raspberry Pi 4 and 5 use "aarch64-linux"
# - M1, M2, M3 ARM Macs use "aarch64-darwin"
# - Newer RISCV computers use "riscv64-linux"
# See https://github.com/NixOS/nixpkgs/blob/nixos-unstable/lib/systems/flake-systems.nix
# If your builder supports multiple architectures
# (e.g. search for "binfmt" for emulation),
# you can list them all, e.g. replace with
# systems = ["x86_64-linux" "aarch64-linux" "riscv64-linux"];
system = "x86_64-linux";
# Nix custom ssh-variant that avoids lots of "trusted-users" settings pain
protocol = "ssh-ng";
# default is 1 but may keep the builder idle in between builds
maxJobs = 3;
# how fast is the builder compared to your local machine
speedFactor = 2;
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
mandatoryFeatures = [ ];
}];
# required, otherwise remote buildMachines above aren't used
nix.distributedBuilds = true;
# optional, useful when the builder has a faster internet connection than yours
nix.settings = {
builders-use-substitutes = true;
};
# Only use remote machine to build
#nix.settings.max-jobs = 0;
#...
}
nix.buildMachines
中需要注意的是,hostName
以 <remote username>@<remote alias>
格式指定远程连接,<remote alias>
就是上方 SSH 配置中指定的别名,也可以直接填写 IP 和端口,即 <remote username>@<remote ip>:<remote port>
,不过这样还需要单独通过 <sshKey>
指定 SSH 私钥路径。
另外,每个 builder 都声明了一组 supportedFeatures
,如果 builder 缺少派生类的 requiredSystemFeatures
之一,它将被忽略。以下是 nixpkgs 中使用的一些功能:
kvm
:在虚拟机内部构建的所有内容,例如 NixOS 测试;nixos-test
:机器可以运行 NixOS 测试;big-parallel
:kernel config、libreoffice、evolution、llvm 和 chromium;benchmark
:机器可以生成评估分数(意味着构建通常需要相同的时间)。
以及,如果不想使用本地机器进行构建,可以本地配置最大 job 数为 0 (nix.settings.max-jobs = 0;
)。
远程机器配置
远程机器最好单独创建一个用户用于 SSH 连接,编辑远程机器的 /etc/nixos/configuration.nix
如下:
{ config, lib, pkgs, ... }:
{
#...
# Define a user accout.
users.users.<remote username> = {
description = "NixOS distrubute builder"; # Can be anything
isSystemUser = true;
createHome = false;
openssh.authorizedKeys.keys = [
"the .pub ssh key from local machine"
];
openssh.authorizedKeys.files = [
"path to a copy of the .pub file"
];
# choose one of this two options
uid = 500;
group = "<remote username>";
useDefaultShell = true;
};
};
users.groups.<remote username>= {
gid = 500;
};
nix.settings.trusted-users = [ "<remote username>" ];
# Enable the OpenSSH daemon.
services.openssh.enable = true;
#...
}
远程机器的配置比较简单,只是添加用户时需要注意将前面在本地机器中 ssh-keygen
生成的公钥 (<name of ssh key file>.pub
) 内容粘贴在 openssh.authorizedKeys.keys
中,或者将公钥文件拷贝到远程机器中,然后将其路径填写在 openssh.authorizedKeys.files
,这两种方式任选其一即可。
另外,需要注意不要忘记开启远程机器的 SSH 连接 (services.openssh.enable = true;
)。
测试
为了确认配置是否有误,首先可以测试 SSH 连接是否正常,如下:
$ sudo nix store info --store ssh-ng://<remote username>@<remote alias>
Store URL: ssh-ng://
Version: 2.28.4
Trusted: 1
进一步,可以构建测试项目 hello
,首先在远程机器上删除该软件包(如果已经有的话):
$ nix store delete /nix/store/*hello*
2 store paths deleted, 1.19 MiB freed
然后,在本地机器上开始执行构建:
$ nix build nixpkgs#hello -v --rebuild --max-jobs 0
checking outputs of '/nix/store/qa8is0vmvak3c5l0krb5zsmqpm0q1nd0-hello-2.12.1.drv' on 'ssh-ng://<remote name>@<remtoe alias>'...
copying path '/nix/store/pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz' from 'https://cache.nixos.org'...
copying 0 paths...
copying path '/nix/store/m1r53pnnm6hnjwyjmxska24y8amvlpjp-hello-2.12.1' from 'https://cache.nixos.org'...
在编译 log 中可以看到 on 'ssh-ng://<remote name>@<remtoe alias>'...
字样就说明构建执行在远程机器上,在构建完成后也可以在远程机器上看到构建结果:
$ ls /nix/store | grep hello
m1r53pnnm6hnjwyjmxska24y8amvlpjp-hello-2.12.1
pa10z4ngm0g83kx9mssrqzz30s84vq7k-hello-2.12.1.tar.gz