Nixってなに?
Nixの説明と簡単な使いかたを紹介したいと思います。
NixというとパッケージマネージャやOSとしてのNixOSの意味がありますが、ここではパッケージマネージャとしてのNixを説明します。
公式のマニュアルはこちらです。
Nixをパッケージマネージャとして使う場合は手元のWindows, Mac, LinuxでOSのインストールなしに利用することができます。
パッケージマネージャだけでなく、開発環境を含めた開発フローについてpythonを使った例を示します。
NixではパッケージのことをDerivationと呼びますので注意してください。Nixでは独特の用語が多いので用語集をつけました。参考にしてください。
OSのバージョンを気にしないソフトのインストール
あるソフトを使う場合にOSのバージョンを気にしたり、関連するソフトをインストールするかと思います。
MacのユーザーとLinuxのユーザーの開発環境が違っていてつらいという状況もあるでしょう。
同じソフトだけどバージョン違いのものをいれて試したいこともあるでしょう。
Nixをつかうとシステムを汚さずに使いたいソフトをどこででもインストールできます。
例えば、gccをインストールしたい場合はMacやLinuxにかかわらず、次のコマンドでインストールできます。
nix-env -i gcc
どこにインストールするのか?
プログラムをインストールケースは次のようなものがサポートされています。
- システムのユーザー全員
- /etc/nixos/configuration.nixに設定を書く。
- システムのあるユーザー
- nix-envコマンドで直接インストールする。
- ~/.config/nixpkgs/config.nixに設定を書く。
- あるユーザーのある開発環境だけ
-
nix-shellコマンドを使う。例えばpythonが欲しいときは
nix-shell -p python
とすればpythonがあるshellの環境ができます。exitをするとそのpythonへの参照が消えます。
-
nix-shellコマンドを使う。例えばpythonが欲しいときは
ハッシュによるバージョン管理
Nixはパッケージの依存関係をハッシュで管理しています。
パッケージを作るのに必要なパッケージのリストとパッケージのソースコードのアーカイブのハッシュをつくってそれをもとに出力先を決めます。詳しくはこちらを参照してください。ハッシュによって再現性を担保します。
ハッシュでつくられたパッケージを中央のレポジトリ(https://cache.nixos.org) でキャッシュすることでバイナリのパッケージを配布しています。個人的なパッケージのキャッシュにはローカルのストレージ、AWSのS3、cachixが使えます。
パッケージ作成が簡単
Nixはもっとも簡単にパッケージが作れものじゃないかなと思っています。 単にシェルの結果だけを生成するパッケージは次のようなものです。
runCommand "hello" {} ''
mkdir $out
echo hello > $out/hello.txt
'';
まとめ
Nixの利点をまとめると以下のようになります。
- 再現性
- バイナリキャッシュ
- 独自パッケージの提供が簡単(中央のリポジトリがなくてもパッケージを提供できる。)
- MacでもLinuxでも同じパッケージを利用可能
いいことばかりだけではなく、Nixではなにがつらいのか以下に紹介しておきます。
- 使いたいパッケージが公式サイトで提供がない。
- コミュニティーが小さい。
- 既存のパッケージのコードがよくわからないが、読めないと生きていけない。
- nix expressionのデバッグが難しい。
- ストレージを多く使う傾向がある。
- セキュリティパッチを当てられない。セキュリティの対応にはソフトの再インストールが必要。
- 成熟してない。次から次に新しいパッケージの管理ツールがでてくるので学習コストが高め。
Dockerとの違いは?
再現性というとDockerを思い浮かべるでしょう。Nixも同じような用途で使うことができますが、Nixはパッケージ管理ソフトです。時にはHomebrewのように時にはDockerのように使えます。
細かいDockerとの違いはこちらの記事や下記の表が参考になるでしょう。
Nix | Docker | |
---|---|---|
実行環境 | 通常のユーザー環境 | コンテナ |
実行権限 | ユーザーと同じ権限 | ルート権限 |
MacOSでの利用 | MacOSのネイティブ(homebrew的な立ち位置で使える。) | コンテナ内のLinux |
再現性の担保方法 | 依存するファイルのハッシュで担保(実行したときによらず同じ結果になるはず) | ビルドしたときのスナップショットで担保 |
ビルドの再現性 | いつ実行しても同じものができる。 | 実行したときの最新のファイルがインストールされる。ダウンロードするファイルが違う可能性がある。 |
ビルドプロセス | Nix言語で記述:aptやyumは使えない。 | Dockerfileで記述:aptやyumが使える。 |
キャッシュする単位 | パッケージごと | スナップショット全体(各ビルドのステップごとにキャッシュができますが、一部を変更するとそれ以降はすべてやり直しになります。) |
Bazelとの違いは?
Bazelはgoogleが開発したビルドツールですね。
Googleでは非常によく使われているようで、例えばGoogle ChromeのビルドにBazelは使われています。
Bazelはビルドに必要とするファイルをすべて洗い出してサンドボックス環境でビルドを行います。
そのため、再現性が高く、効率よくビルドした結果をキャッシュするビルドツールです。この考えはnixのビルド方法に近いです。
こちらの記事にあるようにBazelを使って再現性が高い機械学習のパイプラインを作った例があります。
https://www.spotdraft.com/engineering-blog/how-we-used-bazel-to-streamline-our-ai-development
Bazelはビルドツールとして設計されていますが、Nixはパッケージマネージャとして設計されています。
使うターゲットの違いがあります。
どんな用途使うといいと思っているか?
以下のようなケースでnixを使うといいと考えています。
nixではカスタマイズしたツールをインストールするのが簡単です。
nixでは開発環境の構築はnix-shellを実行するだけです。
環境構築が複雑になってきたらnixをつかうことを検討してもいいかもしれません。
コンテナのほうが向いている場合は無理に使う必要はないです。
- エディタなどのツールの設定ファイルの管理(.vimrcや.emacs)
- セットアップが複雑なプロジェクト(まずaptでなにかインストールして、それから言語特有のプログラムを実行してとか。)
- 複数の言語やライブラリを利用するプロジェクト(rustで作られたライブラリをpythonから利用するとか)
インストール方法
下記を実行するだけです。LinuxでもMacでも同様です。Windowsの場合はWSL2上で実行しましょう。
$ curl -L https://nixos.org/nix/install | sh
アンインストール方法
- ~/.profile や ~/.bash_profile の
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
の行を消す。 rm -rf $HOME/{.nix-channels,.nix-defexpr,.nix-profile,.config/nixpkgs} sudo
rm -rf /nix
- マルチユーザー用にインストールしているならnixbld[0-9]のユーザーを削除
パッケージの管理の仕組み
パッケージは次のフローで作成されます。
通常はnix-buildコマンドで次の手順が一度に実行されます。
- nix言語で書かれたものをdrvファイルにコンパイルします。その結果/nix/store/*.drvファイルができます。この時点では実際のビルドはなにも始まりません。
- drvファイルをつかってパッケージの中身を作成します。bashとかpythonとかいろいろなコマンドを使って実際のビルドが行われます。その結果、/nix/store/*(drvがついてないディレクトリ)ができあがります。
パッケージマネージャの使い方
パッケージマネージャとしてのコマンドを紹介します。
こちらのコマンドはユーザーの環境内でグローバルに設定するために使います。
開発環境で使うツールはshell.nixファイルを作りnix-shellコマンドを起動して利用します。
パッケージのインストールにこれまでnix-envを使ってきましたが、nix profileというコマンドが試験的に導入されています。二つの使い方を紹介しますが、互いに互換性がないので初めてつかうかたはnix profileから始めてもいいかもしません。
- nix-envをつかう場合
- インストール
- nix-env -i "パッケージ名"
- 特定のチャネルのパッケージをインストール
- nix-env -f https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz -iA firefox
- アンインストール
- nix-env -e "パッケージ名"
- インストール済みのパッケージの一覧
- nix-env -q
- インストール可能なパッケージの一覧
- nix-env -qa
- インストール
- nix profileを使う場合
- インストール
- nix profile install nixpkgs# "パッケージ名"
- 特定のチャネルのパッケージをインストール
- nix profile install nixpkgs/release-20.09#firefox
- アンインストール
- nix profile listで削除したいパッケージのpositionを調べて下記を実行
- nix profile remove "position"
- インストール済みのパッケージの一覧
- nix profile list
- インストール
ディスクがなくなってきたときはnix-collect-garbage
を実行してGCをします。
パッケージの構成
パッケージの作り方
Nix Expression Language(Nix言語)
パッケージを作る前にパッケージを書くためのNixに慣れる必要があります。
詳細はこちらのwikiにありますが、特徴は次のようなものです。
- 関数型言語
- 遅延評価
- 動的型付け
- ファイル全体が一つの式
- 式の型は次がすべて
- 文字列型
- 整数型
- 浮動小数点型
- パス
- ブーリアン
- null
- リスト
- 辞書型
- 関数(ラムダ)
- derivation
例えば次のようなdefault.nixファイルがある場合を考えます。
ファイルですが、一つの関数になっていて引数は{}
がついているので辞書型で
その辞書の要素(キー)にpkgsというのがあります。
pkgsというパッケージの辞書の中でstdenvのキーのところにmkDerivationというパッケージ(derivation)をつくる関数があって、引数として辞書をとります。
この場合はnameという要素(キー)にpackage1
という文字列をいれて、パッケージ(derivation)の名前をpackage1
として宣言しています。
{pkgs}:
pkgs.stdenv.mkDerivation {
name = "package1";
##以下省略
}
Derivation
Sandbox
ファイル構成
再現性の高いパッケージの作り方(NivとFlakes)
Nixによるパッケージは再現性が高いですが、nixpkgsなど関連するパッケージを固定しておかないと環境によってはインストールされるものが違ってきます。
固定する方法は二通りあって、nivとflakesです。
-
niv
- gitで管理されたレポジトリをまとめてくれて、nix上で使えるようにするツール。
-
flakes
- nixコマンドに統合された次世代型パッケージ管理ツール。
- 従来のdefault.nixと互換性がない。
ユーザー環境の管理方法
開発環境の作り方
開発環境の作り方はいくつかあります。
-
nix-shell -p "パッケージをスペース区切りで列挙"
- 例、
nix-shell -p sqlite gcc
- 例、
- shell.nixまたはdefault.nixファイルを置き、derivationを出力するようにする。
- 例として次のshell.nixを作りnix-shellコマンドを実行します。
{pkgs ? import <nixpkgs> {}}: # nix-shellへの引数(?は引数がない場合のデフォルトで今回はnixpkgsのパッケージ(システムのデフォルトが使われる。))
pkgs.mkShell { # mkShellはソースの無いderivationを作るために使う便利関数
buildInputs = [ pkgs.sqlite pkgs.gcc ]; # 開発環境で使うパッケージのリスト
}
- flake.nixファイルを置き、devShellをターゲットにderivationを出力するようにする。
- 例として次のflake.nixを作りnix developコマンドを実行します。
{ # ソース: https://nixos.wiki/wiki/Flakes
description = "my project description";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let pkgs = nixpkgs.legacyPackages.${system}; in
{
devShell = import ./shell.nix { inherit pkgs; };
}
);
}
Pythonの使い方
-
nixpkgsから直接使う
-
公式の標準の方法
-
pros
- 使い方が簡単
- 単にパッケージを並べるだけでパッケージをインストールずみのpythonが使えます。
- サンプル:
nix-shell -p 'python38.withPackages(ps: with ps; [ numpy toolz requests ])' --run python3
- インストールそのもので失敗しにくい。
- wheelも扱える。
- 使い方が簡単
-
cons
- cache.nixos.orgに機械学習系のパッケージがキャッシュされてないのでフルスクラッチからビルドが走りインストールに時間がかかかる。たとえば、transformersとpandasを使うプロジェクトで12時間くらいかかる。(どういう条件でキャッシュされるのかはよくわかっていません。)
-
-
mach-nix
- requirements.txtを使うプロジェクトの場合
-
poetry2nix
- pyproject.tomlを使うプロジェクトの場合
- pros
- cache.nixos.orgにキャッシュがなくてもwhlなどを利用でき、インストールが他より速い。(nixをつかわない場合と同じくらい?)
- cons
- poetryそのものでパッケージのインストールができないとできない。
カスタマイズしたツールの使い方(Sixel対応のScreenのインストール)
nixpkgsが提供していないソフトのパッケージの仕方を見ていきます。
その例としてsixelに対応したgnu screenのパッケージを行います。
- nixpkgsに類似のパッケージがないか探す。
- flake.nixの雛形の生成をするために
nix flake init
を実行 - flake.nixのinputsのところにインストールしたいソフトのURLをはる
- flake.nixの
defaultPackage.x86_64-linux
にnixpkgsのmkDerivationのところを貼り付け、srcのところinputsのものに変更する。 -
nix profile install github:<ユーザーID>/<レポジトリ名>
でインストールnix profile install github:junjihashimoto/screen-nix
CUDAの使い方
CUDAのためのドライバのセットアップ方法はNixOSとそれ以外では違います。
Ubuntu上でNixを使っている場合はドライバの設定は不要ですが、次のライブラリのセットアップを行ってください。
NixOS以外の場合
nixではnvidiaのライブラリ(libcuda.soなど)を/run/opengl-driver/libは以下に置くことになっています。
CUDAを使う前に下記のコマンドを実行しましょう。systemdで起動時に設定したい場合はこちらを参照してください。
sudo mkdir -p /run/opengl-driver/lib
sudo ln -s /usr/lib/x86_64-linux-gnu/libcuda* /run/opengl-driver/lib/
sudo ln -s /usr/lib/x86_64-linux-gnu/libnvidia-* /run/opengl-driver/lib/
WSL2の場合は次のようになります。
sudo mkdir -p /run/opengl-driver/lib
sudo ln -s /usr/lib/wsl/lib/lib*.* /run/opengl-driver/lib/
NixOSの場合
OpenGLの使い方
NixOS以外の場合
OpenGLを動かす場合も/run/opengl-driver/libは以下に置くことになっているのですが、上記のCUDAのやりかたではうまく動かないのでnixGLを使いましょう。
NixOSの場合
OpenGLの使い方
デバッグの仕方
nix-buildに関すること
- 修正してないのにビルドが走る場合
- 修正前後のdrvファイルを/nix/store/*から見つけてきてnix-diffコマンドで差分を確認する
- 特定のdrvファイルのビルドに失敗する場合
- nix-shell "drv ファイル"を実行し、(source $stdenv/setup ; genericBuild)コマンドを実行する。
- ビルドの失敗したところのシェルに入りたい。
- ビルドのログを見たい。
- nix log *.drvファイル
- nix log "/nix/store/以下の成功したビルドのディレクトリ"
- nix log
readlink -f result
# nix buildで生成されたresultディレクトリをつくったログを見る場合。
- *.nixファイルのnix expression自体をデバッグしたい。特に途中の式になにが入っているのかわからない。
- nix replを使う。
- 起動後、
:l <nixpkgs>
やsrc = import ./default.nix {}
などしてファイルを読みんで式を評価して確認 - flakesの場合
-
:lf .
を実行後にpkgs = import inputs.nixpkgs {}
を実行して読み込む。 - https://github.com/NixOS/nix/pull/5027
-
- 起動後、
- traceを使って途中の式を評価して表示する。
- nix replを使う。
- derivationの設定を書き換えたい
- drvの生成でどんなコマンドが動くのか確認する
- nix show-derivation "drvファイル" | jq '.[] | .["env"]'
- nixのbuildの中でネットワークにアクセスしたい。
-
--option sandbox false
のオプションをつける.- デフォルトはsandbox環境でderivationのビルドが行われるがsandboxをオフにしてビルドすることでネットワークにビルド中にアクセスできる。
- このオプションをつけてもアクセスできない場合は、nix.confにtrusted-users=<ユーザー名>を追加する。
- gitからネットワークにアクセスしているならgitのラッパーを書く。
- sandbox環境でないとビルドの再現性が落ちるため、あまりsandboxをオフにするのは好まれない。
- gitのようにネットワークにアクセスするコマンドをビルド中に使う場合はコマンド自体のラッパーを書いてネットワークアクセスを回避するパターンがある。下記はその一例。
- サンプル
-
__impure = true;
をderivationにつける。- nix 2.8の新機能
-
--option sandbox false
をつけてビルドするとすべてのderivationのビルドがsandbox環境でないところでビルドされるが、nix 2.8の新機能で特定のderivationだけsandboxを外すことができるようになった。 - https://discourse.nixos.org/t/nix-2-8-0-released/18714
-
キャッシュ
別のディレクトリにキャッシュを保存する方法
キャッシュの参照先の追加方法
NFSで/nix/storeを共有する方法(シングルユーザーモード)
https://nixos.wiki/wiki/NFS
こちらにあるようにシングルユーザーモードのときは/nixのディレクトリをNFS上に置くことができます。
これにより、すべてのサーバーでパッケージが共有できます。
fstabに次のような設定をいれるといいようです。
<host_or_ip>/nix /nix nfs nofail,x-systemd.device-timeout=4,local_lock=all 0 0
NFSで/nix/storeを共有する方法(マルチユーザーモード)
マルチーユーザーモードで単純にNFSで/nixをマウントすると、nix-daemonというデーモンと通信するための/nix/var/nix/daemon-socket/socketのファイルが作られるため、マシンごとにこのソケットのファイルがつくれなくなり、nix-daemonが起動できなくなります。
なので、次のようにbindの設定をいれるとローカルの/var/run/nix-daemon-socketのディレクトリにソケットのファイルができるので、nix-daemonが起動できるようになります。
<host_or_ip>/nix /nix nfs nofail,x-systemd.device-timeout=4,local_lock=all 0 0
/var/run/nix-daemon-socket /nix/var/nix/daemon-socket none bind 0 0
分散ビルド
複数のマシンをつかってビルドを高速にする場合には、https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html を参照ください。
わかりにくい気がしますが、次の手順でセットアップします。
- sshできるnixをインストールしたサーバーを用意。
- /etc/nix/machinesに分散ビルド用のサーバーを列挙
/etc/nix/machinesのフォーマット
#sshのユーザー名@ホスト マシンのタイプ sshの鍵 並列化の数 優先度 (kvmをつかうかどうかのフラグ)
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvm
nix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2
nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark
ビルドは一台のホストにビルド結果を集約しつつ、必要に応じてリモートのサーバーにファイルをコピーしてリモートのサーバーでビルドし、それをまたホストにコピーするという手順で行うようです。
セキュリティ
- パッケージの信頼性を担保するか?
- キャッシュのセキュリティ
- バイナリキャッシュを秘密鍵で暗号化して、ユーザーは公開鍵で利用するモデル。
- https://www.tweag.io/blog/2019-11-21-untrusted-ci/
用語集
- nix : パッケージマネージャのコマンド
- nixpkgs : nixのパッケージ一覧をまとめたもの。 https://github.com/nixos/nixpkgs で提供しています。
- derivation : パッケージのこと、debianであればdebファイルのこと。
- NixOS : nixをパッケージマネージャとしてつかうlinuxのディストリビューション
- Nix言語 : パッケージを記述する言語、nix expressionともいう。
- チャンネル:パッケージ一覧のセットorバージョン、ubuntuだったら18.04とか20.04とか。
- Nixストア: パッケージをインストールしているディレクトリ(
/nix/store
) - drvファイル
- *.nixファイルから生成したderivationをつくるためのファイル。
- https://nixos.org/guides/nix-pills/our-first-derivation.html
参考文献
- https://serokell.io/blog/what-is-nix
- https://nixos.wiki/wiki/Flakes
- https://github.com/Tokyo-NixOS/Tokyo-NixOS-Meetup-Wiki/wiki/terminology
- https://nixos.org/manual/nixos/stable/
- https://nixos.org/guides/nix-pills/
- https://nixos.org/manual/nix/unstable/
- https://discourse.nixos.org/t/is-there-much-difference-between-using-nix-shell-and-docker-for-local-development/807/4
- https://github.com/nmattia/niv
- https://www.tweag.io/blog/2020-05-25-flakes/
- https://nixos.wiki/wiki/Flakes
- https://serokell.io/blog/practical-nix-flakes
- https://discourse.nixos.org/t/debug-a-failed-derivation-with-breakpointhook-and-cntr/8669