Skip to content

常见问题 FAQ

NixOS 的回滚能力与 btrfs / zfs 系统快照回滚有何不同?

很多使用 Arch Linux / Ubuntu 等常规 Linux 发行版的用户,习惯于使用 btrfs / zfs 等文件系统 提供的快照作为「后悔药」,这样系统出了问题能回滚修复。而本书介绍的 NixOS 也提供了系统状态 回滚能力,因此很容易会有这样的疑问:这两种系统回滚功能有何不同?

这里简单解释下,主要区别在于,快照内容不包含如何从零构建这个快照的「知识」,是不可解释 的,而且其内容与当前硬件环境强相关,很难在其他机器上复现。

而 NixOS 的配置是一份从零构建出一个一模一样的 OS 的「知识」,是可解释的,而且可以通过 简单几行命令就自动完成这个构建。NixOS 配置本身既是一份记录你的 NixOS 系统都做过哪些变更的 文档,也可直接用于自动构建出你当前的 NixOS 系统。

NixOS 的配置文件就像是程序的源代码,只要源代码没丢,修改程序、审查程序,或者重新构建出 一个一模一样的程序都很简单。而系统快照就像是源代码编译出来的二进制程序,要对它做修改、审 查,都要难得多。而且这个快照很大,分享或者迁移它的成本都要比源代码高得多。

但这并不是说有了 NixOS 就不需要系统快照了,本书第一章就介绍了 NixOS 只能保证在声明式配置中 声明的所有内容都是可复现的,而其他未声明式配置覆盖到的系统状态是不受它管辖的。比如 MySQL/PostgreSQL 的动态数据、用户上传的文件、系统日志等等,用户 Home 目录下的视频、音乐、 图片等等,这些内容都还是需要文件系统快照或者其他手段来备份。

Nix 与 Ansible 等传统的系统运维工具相比有何优劣?

Nix 不仅可用于管理桌面电脑的环境,也有很多人用它批量管理云服务器,Nix 官方的 NixOps 与社区的 colmena 都是专为这个场景开发的工具。

Nix 与 Ansible 这类被广泛应用的传统工具比,主要优势就在:

  1. Ansible 这类工具一个最大的问题就是,它每次部署都是基于系统当前状态的增量修改。而系统的 当前状态就如同前面提到的系统快照,是不可解释的,也很难复现。而 NixOS 是通过配置文件声明 系统的目标状态,可以做到部署结果与系统当前状态无关,重复部署也不会导致任何问题。
  2. Nix Flakes 通过一个版本锁文件 flake.lock 锁定了所有依赖的 hash 值、版本号、数据源等信 息,这极大地提升了系统的可复现能力。而传统的 Ansible 等工具没有此功能,所以它们的可复现 能力很差。
    1. 这也是为什么 Docker 这么受欢迎的原因——它以较低的代价提供了 Ansible 等传统运维工具提 供不了的可在各种机器上复现的系统环境
  3. Nix 通过一层声明式的抽象屏蔽了其底层的实现细节,使用户只需要关心自己最核心的需求,从而 带来了高度便捷的系统自定义能力。而 Ansible 这类工具的抽象能力要弱得多。
    1. 如果你有使用过 terraform/kubernetes 等声明式配置工具,应该很容易理解这一点。需求越是 复杂的情况下,声明式配置带来的好处就越大。

Nix 与 Docker 容器技术相比有何优势?

Nix 与以 Docker 为代表的容器技术的应用场景也存在一定重合,比如说:

  1. 有很多人用 Nix 来管理开发编译环境,本书就对此做过介绍。但另一方面也有像 Dev Containers 这种基于容器搭建开发环境的技 术,而且非常流行。
  2. 目前整个 DevOps/SRE 领域基本已经是基于 Dockerfile 的容器技术的天下,容器中常用 Ubuntu/Debian 等发行版,宿主机也同样有很多成熟的发行版可选,改用 NixOS 有什么明显的优势 呢?

其中第一点「管理开发编译环境」,Nix 创建的开发环境体验非常接近直接在宿主机进行开发,这要比 Dev Containers 好很多,举例如下:

  1. Nix 不使用名字空间进行文件系统、网络环境等各方面的隔离,在 Nix 创建的开发环境中也可以很 方便地与宿主机文件系统(包括 /dev 宿主机外接设备)、网络环境等等进行交互。而容器要通过 各种映射才能宿主机文件系统互通,即使这样也可能会遇到一些文件权限问题。
  2. 因为没做啥强隔离,Nix 开发环境对 GUI 程序的支持也没任何问题,在这个环境中跑个 GUI 程序 的体验就跟在系统环境中跑个 GUI 程序没任何区别。

也就是说,Nix 能提供最接近宿主机的开发体验,不存在什么强隔离,开发人员能在这个环境中使用各 种熟悉的开发调试工具,过往的开发经验基本都能无痛迁移过来。而如果使用 Dev Containers,那开 发人员很可能会遭遇强隔离导致的各种文件系统不互通、网络环境问题、用户权限问题、无法使用 GUI 调试工具等等各种毛病。

而如果我们决定了使用 Nix 来管理所有的开发环境,那么在构建 Docker 容器时也基于 Nix 去构建, 显然能提供最强的一致性,同时所有环境都统一了技术架构,这也能明显降低整个基础设施的维护成 本。这就回答了前面提到的第二点,在使用 Nix 管理开发环境的前提下,容器基础镜像与云服务器都 使用 NixOS 会存在明显的优势。

error: collision between ... and ...

当你尝试在同一个 profile 中安装两个依赖于同一个库但版本不同的包时,就会出现这个错误。

比如说,如果你有如下配置:

nix
{
   # as a nixos module
   # environment.systemPackages = with pkgs; [
   #
   # or as a home manager module
   home.packages = with pkgs; [
     lldb

     (python311.withPackages (ps:
       with ps; [
         ipython
         pandas
         requests
         pyquery
         pyyaml
       ]
     ))
   ];
}

部署这份配置时,就会出现如下错误:

bash
error: builder for '/nix/store/n3scj3s7v9jsb6y3v0fhndw35a9hdbs6-home-manager-path.drv' failed with exit code 25;
       last 1 log lines:
       > error: collision between `/nix/store/kvq0gvz6jwggarrcn9a8ramsfhyh1h9d-lldb-14.0.6/lib/python3.11/site-packages/six.py'
and `/nix/store/370s8inz4fc9k9lqk4qzj5vyr60q166w-python3-3.11.6-env/lib/python3.11/site-packages/six.py'
       For full logs, run 'nix log /nix/store/n3scj3s7v9jsb6y3v0fhndw35a9hdbs6-home-manager-path.drv'.

解决方法如下:

  1. 将两个包拆分到两个不同的 profiles 中。比如说,你可以通过 environment.systemPackages 安装 lldb,通过 home.packages 安装 python311
  2. 不同版本的 Python3 被视为不同的包,所以你可以将你的自定义 Python3 版本改为 python310 以避免冲突。
  3. 使用 override 来覆盖包使用的库的版本,使其与另一个包使用的版本一致。
nix
{
  # as a nixos module
  # environment.systemPackages = with pkgs; [
  #
  # or as a home manager module
  home.packages = let
    custom-python3 = (pkgs.python311.withPackages (ps:
      with ps; [
        ipython
        pandas
        requests
        pyquery
        pyyaml
      ]
    ));
  in
    with pkgs; [
      # override the version of python3
      # NOTE: This will trigger a rebuild of lldb, it takes time
      (lldb.override {
        python3 = custom-python3;
      })

      custom-python3
  ];
}