Nixを使うと何が嬉しいのか
ソフトウェアをinstallしたり、複数のversionの整合性や依存関係を管理する
他のpackage managerと異なる点は、環境の再現性の精密さと、汎用性の高さである
installするものを宣言的に設定ファイルに記述し、
それを読み込むと必ず一意の環境を再現できる
この「1つの入力に対して、必ず同じ出力を返す」という特性を指して、「Pure Functional Package Manager」と呼ばれる
Nixとは、ざっくりいうとこれで全てである
しかし、この能力を使うことで様々な場所で恩恵を受けることができる
ここでは、それらをユースケース視点で列挙していく
前提
ここでは以下のことは書いていない
Nixの導入方法
具体的な内部実装や詳細
「嬉しさ」だけを表面的に列挙することを目的としている
一部わかりやすさのために正確な説明を端折っている
詳細が知りたければ、ググるなり、リンク先を見るなりして欲しい
一応、用語の説明を加味して上から読むように書いている
が、どの節から読んでもさほど問題ない
対象読者は、何かしらのpackage managerを使ったことがある人
npmでも、homebrewでも、Nixでもなんでも
Nixのことを全く知らない人も読めるように書いている
mrsekut.iconはNixを知ったのは最近なのであまり詳しくない
誤った記述があったら@msekutにリプを飛ばしていただけるとありがたいです 自分で触れたことがないことについても書いている
mrsekut.iconはMacOSを使っている
global環境を汚さずに、一時的にlibraryを試すことができる
どこかで目にしたlibraryを、どんなものかと触ってみたい機会はよくある
例えば、Rustで実装されたlsコマンドにexaというものがあるらしい lsコマンドと違って、色を付けてくれたり、tree表示してくれらしい
しかし、良い口コミのあるlibraryも自分で触ってみないことにはわからない
Homebrewのようなpackage managerでこれを試すためには一度installする必要がある
触ってみて不要だったら、$ brew uninstall exaとする
しかし、この際に対象のexa以外に、依存関係のlibraryもinstallされることがある
その場合、それも特定してuninstallしないとglobal環境にゴミが溜まっていく
Nixを使うと、一時的にlibraryを試す環境が与えられる
$ nix-shell -p exaを実行すると、exaだけが入った環境が与えられる
その環境の中でいくらかexaを試してみる
十分試したら$ exitでその環境を抜ける
これで環境はリセットされ、それ以降は$ exaにアクセスできなくなる
globalなPATHを汚さずに手軽にlibraryを試すことができた
チーム間でのprojectの環境構築をコマンド1つで行うことができる
Gitを使ったrepository(以下、repoと呼ぶ)を利用する際に楽に環境構築をしたい
複数人でチーム開発をしている時
OSSで新規に参加したい時
OSSのframeworkを楽に使い始めたい時
一人で開発する時も同様に、repoごとに環境構築をしたい
ここで、問題は3つある
環境構築がだるい
チーム開発をしているなら、全員が同じ環境を揃える必要がある
使用しているlibraryのversionが異なっていると困る
かといって仕様書を用意して、順番に揃えていくのも煩わしい
仕様書もメンテしていく必要が出てくる
新しいframeworkを使用する際に、チュートリアルを読んでセットアップしたくない
Aをinstallして、次にBをinstallして、とやっていくのは面倒くさい
Dockerを使うという方法もあるが、問題もある
パフォーマンスの問題
Dockerやdocker-syncそれ自身のversionも合わせる必要がある
Dockerfileの書き方に依っては同じ環境が再現されない
どんな環境もコマンド一発で揃って欲しい
projectごとに異なるversionのものを使用すると一手間増える
普段の開発で以下のようなことは頻繁に起きる
こっちではNode.js v14を使い、別ではNode.js v15を使う
こっちではPython v3.5を使い、別ではPython v3.9を使う
解決するために各言語ごとのversion管理ツールが必要になる
使うツールの数だけ、そのversion管理ツールも入れないといけない
だるい
global環境を汚したくない
あるrepoで、Pythonを使っているからといってglobalにPythonを入れたくない
携わるrepoが増えるごとにglobalにものを入れていくと、どんどんゴミが溜まる
そのrepoで開発しているときにだけ使えれば良い
Nixを使うと、コマンド一発で環境が揃う
少し設定をすればコマンド一発も不要で、$ cdでそこへ移動するだけで環境が揃う
repoにshell.nixという設定ファイルを置いておきさえすれば良い
ここにNix言語という関数型DSLで設定を記述する (このユースケースに限らず)Nixは全ての環境の設定をNix言語で宣言的に記述する
上記3つの問題も生じない
複数人でも、repoのshell.nixを見て環境構築すれば同じ環境が再現される
Pythonの異なるversionを併用していても、pyenvを使わずに使用できる
repoごとにshell.nixを置けば、わざわざglobalにPythonを入れる必要はない
projectごとに、独立した環境が整うので互いに干渉しない
そのprojectを触らなくなったら、そこでしか使わないものは消える
例えば、frameworkの環境構築にNixを使っているものにIHPがある
IHPは、HaskellでWeb Applicationを書くframework
これはHaskellありきでIHPをinstallしているのではない
Haskellの入っていないマシン上で、コマンド1発で全て整えているのである
自分の書いたソフトウェアを再現性を保って簡単にBuildできる
Nixにはbuild toolとしての側面もある
自分の書いたソフトウェアをbuildしてderivationにする
一般的な「package」のような概念を、Nixの文脈では「derivation」と呼ぶ
derivationには固有のhashが与えられるため、build生成物の一意性が得られる
コードや、依存したlibraryのversion(というかhash)が同じなら、同じhashが得られる
どんなタイミングで実行しても全く同じbuild成果物が得られることが保証される
これの応用例として本番環境でdeployする際などがある(後述)
build環境の設定が楽
普通は、C言語コードをbuildするためにはCコンパイラが必要
ここで、Nixを使うことでbuildの過程でCコンパイラを用意してくれる
そのため、build環境にわざわざgccをinstallしておかなくて良い
これは、
build環境の構築が楽という意味でもあるし、
build環境の構築の再現性も担保される、という意味でもある
例えば、どのversionのgccでコンパイルするのかなども確定させることができる
Nix言語から再現性のあるDocker Imageを作れる
Nix言語で宣言的にDockerfile的なものを書き、そこからDockerfileを生成する
code:例.nix
buildImage {
name = "redis";
tag = "latest";
fromImage = someBaseImage;
fromImageName = null;
fromImageTag = "latest";
contents = pkgs.redis;
runAsRoot = ''
mkdir -p /data
'';
config = {
WorkingDir = "/data";
Volumes = { "/data" = { }; };
};
}
通常のDockerfileと異なり、ベースとなるDocker Imageを使わない
Redisを入れたい時は、Docker ImageのRedisを入れるのではなく、Nix経由で入れる
故にDocker Imageのlayerは作られない
Docker Containerを作る際に使用するlibraryのversionもNixで厳密に管理できる
以上が、Nix単体の話
Nixはできることが単純な分、汎用性も凄まじい
再現性を保証したpackage managementや、build systemは様々なことに応用できる
以下ではNixを基盤にしたいくつかのツールを、同じくユースケース視点で列挙する
各ツールのpackage managerを全てNixに統一できる
例えば、JavaScriptのprojectを作る際にはnpmを使うことが多い
npmを使ってReactやTypeScriptをinstallし、version管理を行う
Haskellではcabal、Rustではcargo、PHPではcomposer、PureScriptではspagoを使う
こういった各言語のpackage managerをNixで代替できる
各種package managerを使い分けるのではなくNixに統一できる
Nixの醍醐味である再現性もしっかり担保される
依存するlibaryのversionはきっかり固定される
上述したようにNixはbuild toolとしても使えるので、build scriptを埋め込むこともできる
例えばcabalなら、cabal2nix
例えばspagoなら、spago2nix
Build時間を無視できる
それらには一意のhashが与えられる
使用するlibraryや、その依存関係が全く同じものを指しているならhashは一致する
そこで、hashをkeyに、build生成物をvalueにして他者とbuild生成物の共有ができる
既に誰かが同じ内容のものをbuild済みで、それをどこかにcacheしておく
次にそれを使う人は自分でbuildするのではなく、そのcacheを使用すれば良い
buildにかかる時間を無視できる
buildに失敗することもない
小さいproductならばbuildも一瞬で済むのでわざわざ共有する必要はないが、
GHCやgccやglibcのような大きめのprojectをlocalでbuildするのは大変
Nixを使えばその時間を無くすことができる
既にbuildした先人がいれば恩恵を受けられる
他者のbuildしたcacheを使用できる
業務で開発したproductでも前回のbuild成果物を利用できる
自分が先人のケースmrsekut.icon
この生成物は、どこにでも置いておける
S3のようなRemote Storage系のサービスに置いても良いし
Cachixのようなhosting serviceに置いても良い dotfilesを管理して、別のマシンでも同じ環境を簡単に再現できる
これはNixとは関係ない一般的な概念mrsekut.icon
例えば、.vimrcや.gitconfigのような設定ファイルのことをdotfileと呼ぶ
家のPCで書いた.vimrcを、会社のPCでも参照すれば同じ設定のVimを使える
新しいPCを買った時の設定も楽になる
GitHubに「dotfiles」という名前のrepoを作って育てていくのがよくある慣習
このrepoにありとあらゆる種類のdotfileをぶち込んで使う
この管理をNixのエコシステム上で行える
dotfilesの管理やユーザー環境の設定をやってくれる
ここでの「dotfilesの管理」には、「globalに入れるソフトウェアの管理」も含む
どのPCでもexaやfzfをglobalに入れたいときは、それをdotfilesに書いておけば良い Nixを使わずに、例えばhomebrewを使って、dotfiles管理をした時に生じる問題がある
目的のソフトウェアの依存関係を管理できない
dotfilesを作っていない人もglobalにソフトウェアを入れるのは良くするだろう
$ brew install pythonみたいに。
例えば、$ brew listで入っているものを一覧してみると良い
「何に使ってるんだこれ?」みたいなのが大量にあったりする
しかし、どこで使われているのかわからないので安易に消すこともできない
dotfilesをhomebrewのinstallコマンドで羅列していると再現性がない
$ brew install hogeが実行されるタイミングに依って入るものが変わりうる
仮に1月1日にマシンAで$ brew install hogeをし、
2月1日に別マシンBで$ brew install hogeをしたとする
この間にhoge自体や、hogeの依存しているもののversionが上がっていたりする
そうすると、同じdotfilesを見ていようと、同じ環境は再現されていないことになる
home-managerでdotfilesを管理すると、こういった問題がなくなる
globalに入れるソフトウェアを宣言的に管理する
Nixの威力によって、あるマシンの環境を別マシンに正確に簡単に移植できる
使用したいものをhome.nixに1行追加し、$ home-manager switchで反映するだけ
例えば、zshや、zshのpluginやstashipのようなshell系のものだったり、
vimやemacsなどのeditorだったり、
exaやghqのような各種コマンドだったり
何でも入れられる
rollbackができるので、「〇〇を入れる前に戻したい」も簡単
諸々の設定をNix言語で記述できる
各ソフトウェアのDSLの文法を覚えずに全てNix言語で書けてしまう
以下は、本来は.gitconfigに書くような設定をNix言語で書いている
code:nix
programs.git = {
enable = true;
userName = "mrsekut";
userEmail = "hogehoge@example.com";
extraConfig = {
fetch.prune = true;
};
aliases = {
ssb = "status --short --branch";
co = "checkout";
};
};
}
これで.config/git/configを生成してくれる
Nixを使用するとそもそもglobalに何かを入れようという機会は減るmrsekut.icon
repoごとに環境構築できるからglobalにあまり追加しない
だからdotfilesはそれほど膨らんでいかない
というかinstallすらせずに実行できる
commaを使うとinstallせずにlibraryを実行できる 例えばcowsayが入っていない環境で$ , cowsay 'hey!'とすれば実行できる
$ npm iせずに、$ npxで使えるのに似ているmrsekut.icon
installがうまくいかなかったので試していないmrsekut.icon
2021/11/29現在ちょうど調整中っぽいのでまた時間をあけて見てみよう
dotfilesをMacなどのシステムレベルで管理できる
Macを買い替えた時に設定したいのは何もlibraryのinstallだけではない
Mac自体の設定も、マシン間で共有したい
例えば、以下のようなことをNix言語で記述する
dockの配置やサイズの調整
キーボードの連続して入力するまでの間隔
trackpadでクリックの固さの調整
など
新しいMacを買った時の環境構築がまた楽になった
ただし、全ての設定を記述できるわけではないと思うmrsekut.icon
OSレベルでNixの恩恵を受けることができる
上記のnix-darwinは、MacOS上のシステム設定を一部Nixで管理するというものだった
それをもっと極めてOS上の全ての設定をNixで管理できるようにしたものがNixOS NixOSは、package managerのNix上に構築した、Linuxのdistribution
1つの設定ファイルからOSがbuildされる
その1つの設定ファイルを、ある2人がbuildしたら全く同じ環境が再現される
全てのシステム設定も、package管理も宣言的に記述できる
今までの話と同様にシステムのrollbackもできる
バグって動かなくなったときや、前に戻したい時に安心して簡単に戻せる
この性質を活かして、CI環境や、インフラ環境に使うと便利(後述)
普段の開発環境として使っている人もチラホラいる
わかりやすさの都合上、この順序で説明したmrsekut.icon
実際は、NixOSはNix関連のprojectの中でもかなり主要なもの
NixOSでやっているようなことをMac上でもやりたい → nix-darwin作りました
というのが順序的には正しいはず(知らんけど)
Build環境も管理してCIする
HydraというNixを基盤としたCIツールがある 例えばJenkinsやTravis CIの代替物になる
他のCIツールと異なる点は、buildする環境も含めてversion管理をできるところ
Hydraでは、使用するcompilerなどの環境も管理する
dev環境とprod環境の切り替え、などのbuild optionも正確に管理する
そのため、複数のbuild環境で厳密に同じ環境の再現ができる
他のCIツールと同様に自動テストなどができる
他のCIツールではyamlとかで書くところをNix言語で記述する
あまり詳しくないし、触ってないのでちゃんと書けないmrsekut.icon
ネットワーク管理やプロビジョニング設定もNix言語で記述できる
NixOpsを使ってNix言語で宣言的にネットワーク管理ができる cloud上でインスタンスを作ったり
依存関係をinstallしたり
Serviceを開始したり、停止したり
apacheやngixの設定を書いたり
類似物はAnsibleやChefなど
NixOSの設定を書いてEC2などにdeployできる
同じ設定を見てdeployしたものは同じ結果になる
あまり詳しくないし、触ってないのでちゃんと書けないmrsekut.icon
最後に、嬉しくないところ
良いところばかり言っていてもアレなので。
学習コストがややある
情報が少ない
manualは割と整備されている
日本語の情報が少なすぎる
これは、学習コストの高さに繋がる
でもTwitterのTLではよく見かけるので意外とユーザーはそこら中にいるっぽい(?)
できることが多いので、組み合わせの数が多い
初見だとこれがきつい
誰かのdotfilesのコードを見て参考にしたり、ブログ記事を読んだりするが、
様々な要素を絡めて説明されるので、
どれが今の自分にとって必要な情報なのか、
どれが今は理解を保留しても大丈夫なのか、
の判断が難しいことがある
これは、学習コストの高さに繋がる
独自の用語が多い
Nix言語
derivation
nix store
NIX_PATH
etc.
これは、学習コストの高さにつながる
学習コストが問題だが、ほぼ情報量の問題だと思うmrsekut.icon
まとめ
どれもやっていることは同じ
以下のような性質はどのユースケースに対しても当てはまる
Nix言語で宣言的に設定を書く
そこから生成されたものは全く同じ結果になる
rollbackができる
以上のユースケースの1つでも良さそうと感じたものがあれば触ってみてはいかがでしょうか