My First Impressions of Nix

Michael Lynch

我对 Nix 的初印象

Nix 是一款根据源文件来配置软件环境的工具。最近,我在 Hacker News 和 Twitter 上越来越频繁地看到关于 Nix 的讨论。它的理念很吸引我,所以在过去几周里我一直在摆弄它。

我与 Infrastructure as Code(基础设施即代码) 的渊源

十年前,我发现了 Salt,这是一款可以用源代码来定义计算机系统配置的工具。我很喜欢用一个 Git 仓库来定义我的电脑和虚拟机上安装了哪些服务这个想法。我可以清空电脑,重新运行配置工具,就能恢复到之前相同的状态。

我摆弄了几年 Salt,直到发现了 Ansible,我觉得它把同样的理念执行得更好。

我所有的开发工作都是在家用实验室服务器上的虚拟机里完成的。我为每个项目都创建了一个独立的虚拟机,并用 Ansible 来统一管理它们。

Ansible 的问题

Ansible 最大的问题是速度慢得令人痛苦。在我的一台虚拟机上运行一次 Ansible 通常需要 10 到 15 分钟。

假设我想安装一个新的 apt 软件包 foo。我是直接运行 sudo apt install --yes foo 在 5 秒内装好?还是打开我的 Ansible 角色,修改配置以添加安装 foo 的步骤,然后运行 playbook,再等待 15 分钟?显然,我更多时候会选择前者,所以我的环境会逐渐偏离本应描述它们的 Ansible 文件。

另一个问题是 Ansible 的变更不向后兼容。所以,如果我更新某个 playbook 以使用 Ansible 的新特性,就必须把所有 playbook 都更新到兼容状态。但我从来没有动力去重写并重新测试所有的 playbook,所以就一直卡在旧版本上。我现在还在用三年前发布的 Ansible 2.9。

Nix 的吸引力

我看到越来越多的人在讨论 Nix 和 NixOS。许多我感兴趣的开发者都在谈论他们对 Nix 的尝试。

对我影响最深的一次推荐来自 Mitchell Hashimoto(米切尔·哈西莫托),他是 Hashicorp 的联合创始人,这家公司创建了许多广为使用的开源基础设施工具,如 Vagrant、Packer、Consul 和 Terraform。他称 Nix 是“我近年来学到的影响最积极的第一大技术”。

到目前为止我的 Nix 之旅。我依然坚持认为它是近年来我学到的影响最积极的第一大技术。

Nix 的理念在很多方面和 Ansible 很像。Nix 让你用代码定义配置,然后将系统置于该状态。

Nix 与 Ansible 的对比

Nix 在几个我觉得很有意思的重要方面与 Ansible 不同。

Nix 比 Ansible 更快

Ansible 没有“状态”的概念。如果在一台虚拟机上运行 Ansible 需要 15 分钟,那么一分钟后再运行同一个 playbook,大概还需要 10 分钟。两次调用之间可能没有软件包更新,所以能省一点时间,但它仍然在做几乎所有相同的工作。

Ansible 永远不会说:“哦,我刚刚才配置过这台机器,所以现在没什么要做的了。”Ansible 必须重新执行每一项配置,因为自上次运行以来可能发生了任何变化。

而另一方面,Nix 是有状态概念的。如果你在 200 行的 Nix 配置中只改了一行,它不需要重做另外 199 行的工作。它可以对照配置文件评估系统状态,并识别出只需应用这一行的变更。而这个变更通常在几秒钟内就能完成。

编辑(2023-06-19):一位更有经验的读者澄清说 Nix 并不像我以为的那样是有状态的

Nix 之所以快,并不是因为它是有状态的,而是因为它是函数式且可复现的,这使得它可以在不牺牲正确性的前提下进行缓存。

我曾以为 Nix 会记录是哪些任务把我带到了哪种系统状态。例如,假设执行任务 X 和 Y 会进入状态 A,而执行任务 X、Y 和 Z 会进入状态 B。我以为处于状态 A 的 Nix 系统会知道只需执行任务 Z 就能到达状态 B。

我现在新的理解是,如果你要求处于状态 A 的 Nix 系统进入状态 B,Nix 会执行任务 X、Y 和 Z,但 Nix 缓存了任务 X 和 Y 的结果,所以它们几乎是瞬间完成的。

Nix 针对本地配置进行了优化

Ansible 旨在通过网络配置系统。你仍然可以把 localhost 指定为目标,但那并不是 Ansible 优化的场景。

Nix 则是为配置其所在的环境而设计的。使用 Nix,你定义环境中应该包含什么,然后 Nix 会就地为你创建该环境。使用 NixOS,你定义整个操作系统,NixOS 会让操作系统进入该状态。你可以更改文件系统、Linux 内核或引导加载程序等底层内容。

这解决了我在使用 Ansible 时遇到的问题:把一个 Ansible playbook 升级到更新版本的 Ansible,就要求我升级系统上的所有 playbook。你可以让许多运行着不同版本 Nix 的 Nix 系统共存,且都能正常工作。如果我有一些需要 Ansible 2.9、一些需要 2.10、还有一些需要 2.14 的 Ansible 文件,那么要同时兼顾它们会非常麻烦。

Nix 的变更是原子性的

使用 Ansible 时,很容易在配置进行到一半时失败,使系统处于未定义的状态。

而使用 Nix,变更是原子性的。Nix 要么让你的系统进入期望的状态,要么回滚到你尝试更改配置之前的状态。

对我有帮助的 Nix 资源

我看到的关于 Nix 最大的抱怨之一是它文档不足、错误或质量低下。我的体会是,现有文档感觉像是面向有经验的 Nix 用户的。

我找到的很多 Nix 文档都会说这样的话:“只需添加这几行!”

哈?

哪个文件?在文件的哪个位置添加这几行?

以下是我目前找到的最好的资源:

失败的尝试 #1:在虚拟机中安装 NixOS

我在 Proxmox 虚拟机上按照“NixOS for the Impatient”教程操作,一开始一切正常。然后,我按照教程修改了主机名并重启以使其生效。但虚拟机进入了一种奇怪的状态,看起来能启动却无法登录。在登录界面输入密码后,屏幕就直接卡住了。

NixOS 已在我的 Proxmox VM 服务器上成功安装,但在第二次启动登录后卡住了。

失败的尝试 #2:在 Raspberry Pi 4 上安装 NixOS

既然虚拟机行不通,我觉得裸机就是下一个合乎逻辑的选择。我手头正好有一台备用的 Raspberry Pi 4,我觉得在 Pi 上做实验会很有趣。

我找到了两份看起来很官方的在 Raspberry Pi 4 上安装 NixOS 的教程:

这两份教程的问题在于,它们都假设你已经在运行 Nix 环境了。我正试图用我的主力电脑来准备 microSD 卡,而那是一台 Win10 系统,所以我还没有安装 Nix。

NixOS 下载页面列出了一个 64 位 ARM 镜像。Raspberry Pi 4 支持 64 位 ARM,所以我想试试这个。

我打开了 Balena Etcher,这是我首选的 microSD 烧录工具。第一个危险信号是 Etcher 基本上在说:“嘿,你在想什么?那根本不是一个可启动镜像。”

缺少分区表。看起来这不是一个可启动镜像。该镜像似乎不包含分区表,可能无法被你的设备识别或启动。

我还是继续了!但当我尝试从 microSD 启动时,Pi 也认为这不是一个可启动镜像,所以就直接卡住了。

Pi 启动界面显示“进度:正在尝试启动模式 USB-MSD”

我尝试使用官方的 Raspberry Pi Imager 工具烧录同一镜像,但结果相同。

更新:我最终让它成功运行了

成功:在 Dell Mini 电脑上安装 NixOS

我工作中大部分测试都是在一台 Dell Optiplex 7040 上进行的。这是我手头唯一一台可以清空重装的裸机,所以我就在它上面试了试。

一切都和“NixOS for the Impatient”中描述的完全一样。整个安装从开始到结束大约花了 10 分钟。其中 7 分钟只是在复制文件。由于这只是一台测试设备,我想省去每次重启都要输入密码的步骤,所以跳过了加密。

在 Dell Optiplex 7040 上安装 NixOS(我加速了文件复制部分)

最后,我得到了一个完整、可用的 NixOS 安装!

失败的尝试 #3:再次在 Raspberry Pi 4 上安装 NixOS

既然现在我有了一台可用的 NixOS 机器,我想再给 Raspberry Pi 一次机会。之前的阻碍是没有可用于准备 microSD 镜像的 Nix 环境,但现在我有了。

我按照 nix.dev 教程操作,这次比第一次进展更远,但仍然无法启动。Pi 只会显示一个多彩的屏幕,然后就卡住了:

当我从 NixOS 系统烧录 NixOS Pi aarch64 microSD 镜像并用它启动 Pi 时,它卡在了多彩屏幕上。

更新:我最终让它成功运行了

任务 1:获取 SSH 访问权限

好了,回到我在 Dell Optiplex 上可用的 NixOS 安装。

我需要从主力机器 SSH 登录到 NixOS 系统。要做到这一点,我得把我的 SSH 密钥放到系统上。

在 NixOS 中,我打开了 Console 应用,然后输入 sudo nano /etc/nixos/configuration.nix。接着,我找到 environment.systemPackages 并添加了以下几行:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

要使更改生效,我运行了:

sudo nixos-rebuild switch

现在,我已经有了 vimcurl,所以我可以从 GitHub 拉取我的 SSH 公钥:

sudo mkdir -p /etc/nixos/ssh

GITHUB_USERNAME='mtlynch'
curl "https://github.com/${GITHUB_USERNAME}.keys" | \
  sudo tee --append /etc/nixos/ssh/authorized_keys

接下来,我运行 sudo vim /etc/nixos/configuration.nix 并添加了以下几行:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;
  users.users.mike.openssh.authorizedKeys.keyFiles = [
    /etc/nixos/ssh/authorized_keys
  ];

最后,我重新构建并重启了系统。我不确定重启是否严格必要:

sudo nixos-rebuild switch && sudo reboot

成功了!之后,我就可以从主力电脑 SSH 登录到我的 NixOS 系统了。

任务 2:移除 Gnome 杂乱组件

我对这个操作系统第一印象是里面有很多杂乱的东西。它内置了许多我不想要的应用,比如通讯录和天气应用:

我搜索了如何摆脱它们,发现它们是作为 Gnome shell 一部分的默认应用。你可以通过在 /etc/nixos/configuration.nix 中添加这一行来禁用它们:

services.gnome.core-utilities.enable = false;

或者你可以逐个移除每个工具:

 environment.gnome.excludePackages = with pkgs.gnome; [
    baobab      # disk usage analyzer
    cheese      # photo booth
    eog         # image viewer
    epiphany    # web browser
    gedit       # text editor
    simple-scan # document scanner
    totem       # video player
    yelp        # help viewer
    evince      # document viewer
    file-roller # archive manager
    geary       # email client
    seahorse    # password manager

    gnome-calculator
    gnome-calendar
    gnome-characters
    gnome-clocks
    gnome-contacts
    gnome-font-viewer
    gnome-logs
    gnome-maps
    gnome-music
    gnome-screenshot
    gnome-system-monitor
    gnome-weather
    gnome-disk-utility
    pkgs.gnome-connections
  ];

我选择了“全部禁用”的选项并重新构建:

sudo nixos-rebuild switch

瞧!所有杂乱的东西都消失了:

任务 3:恢复 System Monitor(失败)

我觉得唯一值得保留的 Gnome 工具是 System Monitor。我尝试把它添加到我的 environment.systemPackages 列表中,但重新构建失败了:

$ sudo nixos-rebuild switch
building Nix...
building the system configuration...
error: undefined variable 'gnome-system-monitor'

       at /etc/nixos/configuration.nix:130:5:

          129|     curl
          130|     gnome-system-monitor
             |     ^
          131|   ];
(use '--show-trace' to show detailed location information)

我尝试了其他可能的名字,如 gnome-shell-system-monitor,但始终没弄明白如何安装它。

编辑(2023-06-19):感谢指出正确包名是 gnome.gnome-system-monitor 的读者。我所缺失的一环是可以在 search.nixos.org 上搜索软件包。

接下来想了解的事情

我对使用 Nix 和 NixOS 的最初几天感到满意。这和我听说的差不多。当使用得当时,它似乎会非常强大,但需要大量的前期投入和四处搜集信息。

我只是浅尝辄止,所以以下是我接下来想了解的关于 Nix 的事情。

在 NixOS 系统上使用 VS Code Remote SSH

我所有的开发都是通过远程 SSH 在 VS Code 中完成的。当我尝试从 VS Code 远程连接到我的 NixOS 系统时,安装失败了。VS Code 必须在目标系统上安装某种服务器,而 VS Code 可能不知道如何在 NixOS 上安装。

有一个 nixos-vscode-server 的 Git 仓库,那可能就是我需要的解决方案。我还没有尝试过。

Nix 的主要概念如何协同工作

我看到像“flakes(flake)”和“derivations(derivation)”这样的词,目前还不知道它们是什么意思。我还不理解 Nix 的语言语法,但它和 JavaScript 和 Python 足够相似,让我目前还能勉强应付。但要有效地使用 Nix,我显然需要学习这门语言。

确定性何时发生?

当我看到关于 Nix 的讨论时,其中最常被提及的特性之一就是 Nix 具有确定性。

到目前为止,我还不明白它是如何确定性的。当我指定要安装的软件包时,我既没有指定完整性哈希,甚至连版本号都没有指定。如果一年后我运行同样的 Nix 配置,我猜我会得到一个不同的系统,因为它会安装不同版本的我指定的 vimcurl 软件包。

我想一定有更精确地指定软件包版本的方法,但我还没有学到。

我在信任谁?

当我指定软件包时,只是一个普通的软件包名称列表:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

要让我能够像上面那样指定软件包,Nix 肯定是从默认仓库拉取软件包的。是否存在多个仓库?我该如何选择使用哪个仓库?

原文由 Michael Lynch 发布

本文章由 muse-spark-1.2-contributor 进行翻译