Nix让你的团队成员不再受环境问题困扰

🤓谢本壹

2024.10
https://xieby1.github.io/nix_config/docs/slides/2023.nix-env.slides.html

1 Nix/NixOS简介

1.1 Nix/NixOS是什么

Nix

  • 包管理器 (Package Manager)

NixOS

基于Nix的Linux发行版 (Distro)

1.2 Nix/NixOS发展简史

  • 2006: 博士论文[1]
  • 2012: Github[2,3]
  • 2021: 100%可重现[4]

  • [1]: Dolstra, Eelco. “The purely functional software deployment model.” (2006).
  • [2]: https://github.com/NixOS/nixpkgs
  • [3]: https://github.com/NixOS/nix
  • [4]: Nixos-unstable’s iso_minimal.x86_64-linux is 100% reproducible! https://discourse.nixos.org/t/nixos-unstable-s-iso-minimal-x86-64-linux-is-100-reproducible/13723

1.3 Nix/NixOS特点

  • Imperative(命令式)/Declarative(声明式)
  • 函数式编程语言 (Purely Functional)
  • 可重现 (Reproducible) & 确定性 (Deterministic)

1.4 Nix/NixOS特点-续

  • Imperative(命令式)/Declarative(声明式)
  • 🟢Imperative
    • apt install vim
    • nix-env -iA nixpkgs.vim
  • 🟢Declarative即需要编程
  • 函数式编程语言 (Purely Functional)
  • 🟢在nix配置代码中:home.packages = [nixpkgs.vim];
  • 可重现 (Reproducible) & 确定性 (Deterministic)
  • 🔴apt install,不同时间安装的内容不一样

1.5 Nix/NixOS vs APT/Ubuntu

Nix/NixOS APT/Ubuntu
使用用户 root(system wide), 普通用户 仅root(system wide)
版本升级 随时升级channel 跨版本升级难
安装位置 全局/各用户:/nix/store配合符号链接 全局:/bin, /usr, …
包的版本 能够同时安装多个版本的包 单一版本
包的数量 >80,000[1] 72,514[2]
更新速度 每天几百个commits[3] -
开放程度 开放、可去中心化 Canonical Ltd维护
历史回滚 支持 -
交互方式 Declarative/Imperative 仅Imperative


  • [1]: https://search.nixos.org/packages
  • [2]: apt list | wc -l
  • [3]: git log --date=short --pretty=format:%ad | sort | uniq -c

1.6 Nix/NixOS vs Docker

Nix/NixOS Docker
确定性 是 (确定性derivation) 否 (非确定性layer)
融入现有环境 轻松:nix-env/nix-shell 难:本质各种隔离,bind/network/…

1.7 学习Nix/NixOS

学习曲线陡峭📈,但也别担心😸

遇到问题

2 Nix展示

2.1 Nix使用模式

nix-env

  • 永久的(Permanent)
  • 和其他包管理器差不多

nix-shell

  • 稍纵即逝的(Ephemeral)
  • 类似python的venv


nix-shell适合创建开发环境

2.2 nix-shell使用场景实例

同学壹🤓


搞了一个

gem5项目

同学贰🫠


在ubuntu22上

死活编译不成功

同学叁😨


在服务器(ubuntu16)上

好多库都没有


项目一开始就体验极差😖😤😱

2.3 nix-shell使用场景实例-续

shell.nix

let
  pkgs = import (fetchTarball {
    url = ...; # url of nixpkgs
    sha256 = ...;
  }) {};
in pkgs.mkShell {
  packages = with pkgs; [
    # for compilation
    python3 scons zlib ...
  ];
  shellHook = ''
    PYTHONPATH+=":..."
    export PYTHONPATH
  '';
}
  • 包集合(nixpkgs)的版本
  • 装哪些包
  • 环境变量

2.4 nix-shell使用场景实例-续

shell.nix

let
  pkgs = import (fetchTarball {
    url = ...; # url of nixpkgs
    sha256 = ...;
  }) {};
in pkgs.mkShell {
  packages = with pkgs; [
    # for compilation
    python3 scons zlib ...
  ];
  shellHook = ''
    PYTHONPATH+=":..."
    export PYTHONPATH
  '';
}
  • 包集合(nixpkgs)的版本
  • pkgs = import (fetchTarball {
      url = ...; # url of nixpkgs
      sha256 = ...;
    }) {};
  • 装哪些包
  • 环境变量

2.5 nix-shell使用场景实例-续

shell.nix

let
  pkgs = import (fetchTarball {
    url = ...; # url of nixpkgs
    sha256 = ...;
  }) {};
in pkgs.mkShell {
  packages = with pkgs; [
    # for compilation
    python3 scons zlib ...
  ];
  shellHook = ''
    PYTHONPATH+=":..."
    export PYTHONPATH
  '';
}
  • 包集合(nixpkgs)的版本
  • 装哪些包
  • packages = with pkgs; [
      # for compilation
      python3 scons zlib ...
    ];
  • 环境变量

2.6 nix-shell使用场景实例-续

shell.nix

let
  pkgs = import (fetchTarball {
    url = ...; # url of nixpkgs
    sha256 = ...;
  }) {};
in pkgs.mkShell {
  packages = with pkgs; [
    # for compilation
    python3 scons zlib ...
  ];
  shellHook = ''
    PYTHONPATH+=":..."
    export PYTHONPATH
  '';
}
  • 包集合(nixpkgs)的版本
  • 装哪些包
  • 环境变量
  • shellHook = ''
      PYTHONPATH+=":..."
      export PYTHONPATH
    '';

2.7 nix-shell使用场景实例-结果

同学壹🤓


搞了一个gem5项目

cd ~/Codes/gem5/
写好了nix-shell.nix
# 提交到gitlab仓库

同学贰😍


在ubuntu22上

cd ~/Codes/gem5/
nix-shell
# 开始编译

同学叁🥰


在服务器(ubuntu16)上

cd ~/Codes/gem5/
nix-shell
# 开始编译


成功编译🎉🎉🎉

并且结果一模一样!

一模一样具体是指?

2.8 一模一样具体指:二进制级别一样!

同学壹🤓

同学贰😍

同学叁🥰

md5sum ~/Codes/gem5/build/X86/gem5.debug

一模一样!

3 Nix原理

3.1 多版本

  • 抛弃FHS (Filesystem Hierarchy Standard): /bin, /lib, …
  • 所有的包将被hash,全部放在/nix/store/
  • 命名方式/nix/store/<hash>-<pname>-<version>
bap4d0lpcrhpwmpb8ayjcgkmvfj62lnq-bash-interactive-5.1-p16
bjp36ahgqs5m2j29kp3ni8x8zb3s7crc-bash-interactive-5.1-p16
f01r6lmbi7mg0xg80mvzdwqhlmrzvy4v-bash-interactive-5.2-p15
gmz9kyy7m7dvbp34wjpmqjyir58z0xch-bash-interactive-5.1-p16

混乱的用户环境?

3.2 多版本-统一的用户环境

符号链接

以可执行程序为例

PATH=~/.nix-profile/bin:...

所有用户需要程序会被符号链接到改文件夹

$ which vim
~/.nix-profile/bin/vim
$ realpath ~/.nix-profile/bin/vim
/nix/store/9gzxr4ij4i0h1zyvkai8k7jq2dpprr2k-neovim-0.8.1/bin/nvim

3.3 多版本-总

3.4 正确的依赖关系

  • 用户视角只能看到单一的
    • ~/.nix-profile/bin/
    • ~/.nix-profile/lib/
  • 某个包依赖某个特定版本的可执行程序?
  • 某个包依赖某个特定版本的动态链接库?

3.5 正确的依赖关系-解决

  • 某个包依赖某个特定版本的可执行程序?

写绝对路径

  • 某个包依赖某个特定版本的动态链接库?

ELF文件Dynamic section的RUNPATH

可使用readelf -d查看

动态链接器ld.so会根据寻找RUNPATH去找动态链接库

3.6 其他的原理等待你来探索

  • 多用户
  • 回滚
  • 垃圾回收

4 🆕应用:Nix与程序切片

4.1 切片(Checkpoint)

为什么要切片

  • 为了测试处理器的性能
  • 测试程序时间运行长
    • SPEC CPU 2017 ref verilator几年
  • 仅采样测试程序的一些片段

什么是切片

  • 动词:获取片段
  • 名字:获取到的片段

有哪些采样方法

  • 平均切片
  • SimPoint聚类

4.2 基于SimPoint聚类的切片

  1. 收集程序所有的片段包含哪些指令块(插桩(QEMU)运行一片测试程序)
  2. 根据指令块执行频率对片段聚类,选出有代表性的片段(运行SimPoint)
  3. 根据代表性的片段,生成可片段运行的上下文(再次插桩(QEMU)运行测试程序)

以上步骤,每次运行结果都不一样

4.3 香山的切片流程

  • 十几个流程
    • 每个流程都有较高门槛
  • 文档驱动
    • 每个人的环境都不一样,没办法完全自动化,只能文档
    • 但文档不足

4.4 切片恼人的场景

  • 🤔切片出问题,想找原来完整的程序,想复现一下
    • ❗但找不到了完整的程序了
    • ❗重新编译生成的也不一样
    • ❗重新切片也不一样
  • 🤔服务器切片跑出问题了
    • ❗在我的环境下无法复现
    • ❗在你的环境下也无法复现

4.5 确定性切片(deterministic_checkpoints)

就做了两件事:

  1. 流程复杂、文档驱动 => 自动化
  2. 难以复现 => 确定性

大致涉及内容:

  • Nix: 测试程序、QEMU、SimPoint这些程序二进制级别一样
  • 改QEMU、内核,控制SimPoint等:运行结果一样

项目链接:https://github.com/xieby1/deterministic_checkpoints

4.5.1 确定性切片(deterministic_checkpoints)用法

# 准备源码spec2006.tar.gz # TODO: 更智能化
git clone https://github.com/xieby1/deterministic_checkpoints
cd deterministic_checkpoints
nix-build -A checkpoints

4.6 Nix的更多应用场景等你来探索

  • 确定性构建香山
  • 确定性构建Gem5
  • 确定性的性能评估
  • 确定性CI
  • 确定性的…

5 总结