不可变性(Immutability)是 Nix 体系的基石之一。它确保了所有软件包、依赖和环境在安装后不会被修改,从而带来了安全性、可复现性、回滚能力和多版本共存的特性。

传统包管理Nix(不可变)
软件可以被修改,可能导致不可预测的行为软件不可变,保证一致性
升级可能破坏系统,很难回滚升级不会影响旧版本,可随时回滚
不同软件可能冲突(lib 版本问题)所有软件独立存储,不会相互干扰
依赖可能被其他软件意外更新所有依赖固定,不会意外更改
安装影响全局系统每个用户可以拥有自己的环境,不影响其他人

1. 为什么不可变性重要?

在传统的软件管理方式(如 aptyumbrew)中,软件安装在系统的全局目录(如 /usr/bin/lib),这种方式带来了一些严重的问题:

  • 版本冲突:如果 A 依赖 libX v1.2,B 依赖 libX v2.0,你可能无法同时安装 A 和 B。
  • 升级破坏系统:升级某个软件可能会影响甚至破坏其他程序,因为它修改了共享库。
  • 难以回滚:如果更新后出问题,你无法轻松回到旧版本。
  • 不可复现:即使你使用相同的安装命令,不同时间点的安装可能会导致不同的结果。

Nix 通过不可变性彻底解决了这些问题,所有软件都安装到一个唯一的、基于哈希的目录中,从而避免了冲突和意外修改。

2. 不可变性在 Nix 中如何实现?

Nix 采用内容寻址存储(Content-Addressable Storage),确保每个软件包的路径是唯一的、不可变的。

(1) Nix Store

所有软件包都被存储在 /nix/store 目录中,每个软件的路径包含一个基于内容的哈希值

/nix/store/<hash>-<package-name>-<version>

例如:

/nix/store/7l5jybz5wv2pl6b1zfmj43jzpfvhw3m4-python-3.9.2
/nix/store/s97kf8hx1lzn6ajyq3zvbfzqflpzx9bv-openssl-1.1.1

这个 <hash> 是通过源码、依赖、构建环境计算得出的,确保:

  • 相同输入 相同输出
  • 不同的依赖或编译参数 生成不同的哈希
  • 所有软件都是不可变的,一旦存入 /nix/store,不会被修改

如果你重新构建 Python 但修改了 OpenSSL 依赖,它会生成一个新的 Python 版本:

/nix/store/abc123-python-3.9.2  # 旧的
/nix/store/xyz789-python-3.9.2  # 重新编译后的

旧版本不会被覆盖,确保不会破坏已有软件

(2) 依赖的不可变性

传统 Linux 依赖:

libX.so -> /usr/lib/libX.so (可能被覆盖)

Nix 依赖:

/nix/store/xyz789-libX.so  (永远不会被覆盖)
  • 在 Nix 中,每个软件直接指向其依赖的 /nix/store 路径。
  • 由于 /nix/store 下的文件不会修改,所有依赖关系是完全稳定的。
  • 即使某个库升级,旧版本的库依然可用,不会影响已有软件。

(3) 用户级软件管理

传统 Linux:

sudo apt install firefox
  • 需要管理员权限
  • 会修改 /usr/bin/firefox
  • 可能影响其他用户

Nix:

nix-env -i firefox
  • 不需要管理员权限
  • 不会修改全局系统,仅影响当前用户
  • 不同用户可以安装不同版本的 Firefox,而不会互相影响

(4) 原子性 & 回滚

因为 Nix 不会直接修改软件,所以可以实现原子性操作和回滚

  • 升级时,旧版本仍然存在,如果新版本有问题,可以立即回滚

    nix-env --rollback
    
  • 配置环境也是原子的,你可以定义完整的 Nix 环境:

    nix-shell -p python3 git
    

    退出 nix-shell 后,环境就会恢复,不影响全局系统。

  • 升级软件不会导致系统崩溃

    • 传统升级:

      apt upgrade glibc
      

      可能会破坏整个系统。

    • Nix:

      nix-env -u glibc
      

      旧版本仍然可用,系统不会被破坏。

(5) 垃圾回收(Garbage Collection)

由于 Nix 不会主动删除旧版本,因此你可以在需要时手动清理

nix-collect-garbage -d
  • 只删除不再被引用的软件,确保回滚能力

  • 你可以随时恢复被删除的软件:

    nix-env --restore
    

3. 真实场景中的不可变性

(1) 数据科学

问题:如果你的 Python 依赖 NumPy v1.20,而新安装的软件要求 NumPy v1.22,传统方式可能导致环境崩溃。

Nix 解决方案

nix-shell -p python3 numpy
  • 你可以创建一个独立的 Python 运行环境,不影响系统的其他 Python 版本。

(2) DevOps & CI/CD

问题:同一个 apt install 命令,不同时间可能安装不同版本的软件,导致 CI/CD 失败。

Nix 解决方案

nix-build
  • Nix 锁定所有依赖的哈希,即使过了一年,你的 CI/CD 仍然构建相同的软件。

(3) 服务器运维

问题:升级 glibc 可能导致服务器崩溃,传统方式很难回滚。

Nix 解决方案

nix-env -u glibc
nix-env --rollback  # 如果有问题,立即回滚
  • 不会影响正在运行的进程,确保服务器稳定性

5. 总结

  • Nix 采用不可变的软件管理,所有软件存储在 /nix/store,不会修改。
  • 多版本共存,避免依赖冲突(libX v1.2libX v2.0 可以同时存在)。
  • 原子性升级/回滚,防止系统崩溃,支持随时回滚。
  • 不同用户可以有不同的环境,互不影响。
  • 垃圾回收确保空间利用率,只删除不再使用的软件。

你对不可变性有更深入的疑问吗?还是想看看更多的实际应用?