- Dockerイメージをすでにデプロイ単位として使っている環境なら、Nixはワークフローを大きく変えずに決定論的ビルドと依存関係の固定を試せる入口になる
- 一般的な
docker build はUbuntuリポジトリや外部ダウンロードのような公開インターネットの状態に依存しがちで、時間が経ったあとに同じイメージを再現するのが難しい
- Nixは必要な依存関係をパッケージ単位で把握しているため、
dockerTools.buildLayeredImage で変更されたレイヤーだけを再アップロードする構成が可能
- Goベースの
douglas-adams-quotes の例は、pkgs.buildGoModule でバイナリを作り、nix build.#docker、docker load <./result で通常のDockerイメージのようにロードしてデプロイできることを示している
- モノレポ内の複数サービスがレイヤーとNixキャッシュを共有すれば、過去コミットのイメージ再現や反復的なデプロイでビルド時間とコストを削減できる
DockerイメージビルドにNixを組み合わせる理由
- Nixはパッケージマネージャー、言語、オペレーティングシステムという3つの性格をあわせ持つ
- Nix言語でパッケージのビルド指示を書く
- Nixパッケージマネージャーはその指示をもとに、ソフトウェア、ツール、NixOSイメージ、コンテナイメージなどを作れる
- 既存のCI/CDパイプラインにNixを導入するのは簡単ではない
- Nixは多くの開発者が慣れている方法とは異なる
- 新しいスタートアップやホームラボのようにすべてを一から始められる環境でなければ、導入のハードルは高い
- すでにDockerを使っている組織は、コンテナイメージのビルドからNixを適用することで、既存のデプロイフローを大きく壊さずに試せる
Dockerビルドが揺らぐポイント
- Dockerとコンテナ化は広く採用されており、Dockerイメージはインターネット上の事実上の汎用パッケージ形式のように使われている
- Fly.io、Railway、Renderのようなプラットフォームは、任意のVMイメージやtarball形式のLinuxプログラムではなくDockerイメージを使う
- Dockerビルドは常に決定論的とは限らない
- インターネットでよく見かけるDockerfileのほとんどは問題なくビルドされるが、失敗するわずかな割合が運用上の問題につながり得る
- 最大の弱点の1つは、Dockerビルドが公開インターネットにアクセスする点である
- Ubuntuリポジトリからパッケージをダウンロードするために必要になる
- 後で同じイメージを再生成する必要があるとき、当時のUbuntuリポジトリの正確な状態を戻すのは難しい
- Ubuntu 18.04はその年にサポート終了を控えており、何がそのバージョンに依存しているのか、障害が起きてから初めて分かる可能性がある
- 単純な方法でDockerイメージにパッケージを追加すると、無駄な容量が生じることがある
- ビルドの初期に
apt-get upgrade を実行すると、コンテナイメージ内のファイルが置き換えられる可能性がある
- 置き換え前のファイルがレイヤーに残り、シャドウコピーのように蓄積されることがある
NixがDockerレイヤーを扱う方法
- Nixは必要な依存関係を事前に把握し、それらをできるだけ少ないDockerレイヤーに分けられる
- コードを1行変えたからといって、
apt や npm が依存関係を再インストールする必要はない
- 実際に変わった最小限の内容だけをイメージ更新に反映できる
dockerTools は、Nixパッケージとその依存関係をDockerイメージに入れるためのツール群である
- Nixで作るDockerイメージは大きく2つの方式に分かれる
- 非レイヤーイメージ: プログラム、依存関係、TLSルート証明書のような追加項目を1つのフォルダに入れ、単一レイヤーのイメージとして公開する
- レイヤーイメージ: 各依存関係を別々のイメージレイヤーに配置し、実際に変更された部分だけをアップロードできる
- Docker内部にもコンテンツベースのストレージはあるが、
docker build だけではそれを十分に活用しにくい
- Nixのレイヤーイメージはパッケージごとのレイヤーを使うため、
glibc のような依存関係は一度だけアップロードすればよい
- ただし、そのライブラリに修正が必要な場合、そのレイヤーは再アップロードされる必要がある
Goサービスの例: Douglas Adams Quotes
- 例のサービスはDouglas Adamsの引用を提供するGoプログラムである
- Goモジュールプロジェクトは
pkgs.buildGoModule でNixパッケージを定義する
bin = pkgs.buildGoModule {
pname = "douglas-adams-quotes";
inherit version;
src = ./.;
vendorHash = null;
};
- この定義はGoモジュールテンプレートを使い、Goコンパイラ、CGo用Cコンパイラ、外部依存関係のダウンロードを設定する
pname はパッケージ名である
version はサービスのGitコミットから自動生成される
src = ./.; は現在の作業ディレクトリのソースコードを使う
- 標準ライブラリ以外の依存関係がなければ、
vendorHash = null を置ける
- 外部依存関係がある場合は、すべての依存関係のハッシュを指定するか、
gomod2nix を使える
- パッケージは次のコマンドでビルドする
nix build .#bin
- Dockerレイヤーイメージは
dockerTools.buildLayeredImage で作成できる
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
config.Cmd にビルド済みのサーバーバイナリを指定すると、必要な項目も一緒にコピーされる
- CAルート証明書のような追加パッケージは
contents に入れられる
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
contents = with pkgs; [ cacert ];
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- イメージは次のコマンドで作成し、Dockerデーモンにロードする
nix build .#docker
docker load < ./result
dive で開いてみると、各レイヤーがnixpkgsの別々のパッケージを追加し、最後に項目がイメージルートへシンボリックリンクされる
モノレポでレイヤーを共有する効果
- 同じリポジトリに複数のサービスがある場合、Nixで作ったDockerイメージはレイヤーを共有できる
- 追加設定なしで共有される
- Dockerだけでこれを行おうとすると、共通ベースイメージを複数作ることになり、一部サービスが使わないツールや不要な内容が入る可能性がある
- 例として
Xe/x リポジトリは、10年分のサイドプロジェクト、実験、ツールが集まったモノレポである
- 複数のプロジェクトがおよそ3つのプラットフォームにデプロイされている
- 共通のデプロイ基盤へ収束させるため、Dockerイメージに入れる作業を進めてきた
- あるサービスのアップデートをプッシュすると、他のサービスの大半と共有されるアップデートの一部も一緒にプッシュされる
- この方式は複数のプロジェクトで時間とコストを節約する
Dockerキャッシュだけでは追いつきにくい部分
- Dockerキャッシュを使えば、理論上はNixと同じくらい効率的にイメージをビルドできる場合もある
- しかしDocker方式で同じ効果を出そうとすると、ビルド手順の保守が難しくなり得る
- 共有ライブラリを別レイヤーにインストールしなければならない
- ビルド中にネットワークスタックを再び有効にする必要があるため、再現性が弱くなる
- 検索パス、コンパイラフラグ、CGo関連設定を再調整しなければならない
- Nixの
dockerTools.buildLayeredImage は、パッケージをコンテナに入れる細部を代わりに処理し、設定をシンプルにする
過去時点の再現とNixキャッシュ
- Nixの大きな利点の1つは、過去時点のソフトウェアを正確に再ビルドできる点である
- 後で特定の顧客環境のバグを再現する必要があるとき、同じDockerイメージを再び作れる
- パッケージビルドは、ソフトウェアと依存関係全体の状態をある時点に固定する役割を果たす
XeDN は数年にわたって取り組んできたプロジェクトであり、14か月前のバージョンは次のコマンドでビルドできる
nix build github:Xe/x/567fdc2#xedn-docker
- このコマンドは
GitHub repo Xe/x の特定コミットから xedn-docker ターゲットをビルドする
- Dockerデーモンにロードすると、当時と同じバイト列のイメージが作られる
- 例にはGo 1.19も含まれる
- 通常のDockerビルドだけで同じ再現を行うには、多くのストレージコストが必要になる可能性がある
- NixキャッシュはNixコマンドの出力を保存し、後で再ビルドしなくて済むようにする
- 開発者のノートPCごとに
nokogiri のようなパッケージを新しくビルドしなくてもよい
- すでにクラウドでビルドされた結果を受け取れる
- GarnixはすべてのflakesプロジェクトのCIに使われ、コミットごとにビルド状態を報告する
- ホームラボのマシン設定もGarnixでビルドし、毎晩アップデートするときに自前でビルドする代わりにGarnixキャッシュから最新設定を取得する
デプロイ成果物としての結論
- NixはDockerイメージビルダーとして、Dockerのイメージビルダーより優れていると見なせる
- Nixは結果を指定させ、その結果に到達するための細かな手順を直接列挙させない
- すでにDockerを使っている環境では、DockerイメージビルドからNixを適用する方法が現実的な導入経路になる
- Nixで作ったDockerイメージは、モノレポの複数部分の間でレイヤーを共有できる
- バイナリキャッシュのおかげで、過去にすでにビルドされたコードを再ビルドしなくて済む
- 最終的な成果物は、AWS、Google Cloud、Fly.ioのようなプラットフォームにデプロイ可能な通常のコンテナイメージである
1件のコメント
Hacker News のコメント
Nix を好きになろうと何度も試みましたが、もう諦めるべきだという気がしています
Nix を動かしているシステムが2台ありますが、触るのが怖く、以前はどちらも壊れて最初から再インストールしたこともあります。理論上、Nix は冪等で決定的ですが、実際には何に対して決定的なのかを理解する必要があり、依存しているすべての部分の挙動を深く知らないと、奇妙な結果や不可解で役に立たないエラー、あるいはフィードバックがまったくない状況に遭遇します
ドキュメントは完全で技術的に正しくても、ひどく難解で、チュートリアルは80%の道のりまでしか連れていってくれません。そこから先は自分で作る必要がありますが、それには何年分もの経験知と挫折が必要で、もう耐えたくありません。Docker の利点はむしろ混沌そのもので、シェルとディストリビューションのパッケージマネージャをざっくり知っているだけでほとんど何でも作れますし、問題が起きても何十年も使われてきたツールのおかげで診断と修正がずっと簡単です
Nix は Emacs のように、忍耐力と深く先見的な知識さえあれば何でもできますが、完全にのめり込むか、距離を置いて眺めるしかないように見えます
同じマシンで続けて2回ビルドすれば同じ結果になりますが、時間が経ってパッケージマネージャとベースイメージタグが更新されると、1か月後の再ビルド結果はまったく別物になります。チームが管理するコンテナが40個ほどになると、コンテナの修正が業務の大きな部分になります
そのため理論上は Nix が完璧なはずですが、あまりにも違いすぎてベンダー製ツールは Nix 上で動かず、エラーが出ても Web で素早く解決策を見つけるのが難しいです。そのもどかしさから https://www.stablebuild.com を作り、Ubuntu、Debian、Alpine コンテナ上で Docker ベースの決定的ビルドを提供しています。不変の Docker Hub プルスルーキャッシュ、Ubuntu/Debian/Alpine パッケージリポジトリの日次フルコピー、人気の PPA と PyPI インデックスの日次コピー、任意のファイル/URL の不変キャッシュで構成されています
実務では使いやすくデバッグしやすく、ソフトウェア互換性も広いため、StableBuild に移行したコンテナでは非決定性によって起きた問題は0件でした
nix developがドキュメントどおりに動作しない理由と、そう動作させる方法を理解するのに時間を費やしたことがあります。ドキュメントでは基本的にビルド時の環境に入るとされていますが、実際のデフォルトは密閉されておらず、コマンドラインオプション名も紛らわしいため、ソースから知識を掘り出す必要がありました再現性を壊すエッジケースも、もっと明示的に扱ってほしいです。浮動小数点コードは演算順序に敏感で、OS のプリエンプションによって状態が漏れ出すこともあります。明白なことでも明示しなければ、人々は自分で痛い目を見ることになります
“Erase your darlings” 設定を使うと、ユーザーアカウント外の再現不可能な状態をほとんどなくせます。少し面倒ですが、NixOS で面倒でないものなどあるのか、という感じです
https://grahamc.com/blog/erase-your-darlings/
ユーザーアカウント内部は気にしておらず、Home Manager は好きではありません
Docker は悪くありませんが、Mac では性能がいまひとつです。一方 Nix は複数のマシンに同じ方法でインストールして動かせるのが些細な作業で済むので気に入っています。Nix のドキュメントはひどいですが、ChatGPT-4 は Nix の問題解決には非常に役立ちました
Nix の問題の90%は「Nix 流」ではないことをしようとしたために起きていた印象です
ほぼすべてのディストリビューションのパッケージマネージャを使ってきましたし、特別なことをしなくても何らかの形ですべて壊したことがあります。Fedora Kinoite はレイヤーの追加/削除、毎日のアップデート、Silverblue からのリベースまでしてもよく耐えています。個人的には rpm-ostree が Nix を置き換えることになりそうです
NixとNixOSは、GitHub以前のgitに近い状態だと思う
基本的なアイデアは、従来方式であるSVNやDockerよりも、より本格的な計算機科学に基づいていて、内部の配管にはまだ問題があるものの、それ以上に悪いわけではない。ただ、ユーザー向けのツールやドキュメントは主流として採用される水準にはない
floxの登場で、これは変わったかもしれない: https://flox.dev。ほとんどシームレスで、DE Shaw出身なら驚きでもない
Nixはflakesと
nix-commandなしではあまり意味がないが、どちらも実験的だとドキュメント化されており、デフォルトでもオフになっている。ドキュメントは改善されてきているものの、まだ不十分で、nixlangはきちんと学べば素晴らしいが、主流への参入障壁としては受け入れられない。nix-env -iA fooは実際には望む動作でないことがほとんどなので、Nixは宣伝されているようなパッケージマネージャーというより、社内の専門知識を抱えられる企業にとっての秘密兵器に近いfloxは「一度試してすぐにより良い体験を得る」ためのハードルを下げてくれる。Nix/NixOS、あるいはそれに近い何かは、いずれDockerをSubversionのように後方へ押しやるだろうが、GitHubのような瞬間が来るまでは起きず、来たら一気に起きるはず
このスレッドにあるNixへの不満の大半は、技術的には間違っているが十分に理解できるし、さらに重要なのはユーザーのせいではないということだ。git/GitHubのない世界を知る世代が減っているのは分かっているが、初期Googleの採用基準を通過した人たちを前に、Linusが、なぜ複雑に感じるツールに関心を持つべきなのかを説明しているのを聞いてみるといい
https://youtu.be/MjIPv8a0hU8?si=QC0UnHXRdMpp2tI4
しばらくこれを作ってきたので、より良くするために優先すべきことがあれば聞きたい
gitはそうだが、Nixはまだそうではないので、GitHubのような瞬間が助けになるのかはよく分からない
このブログ記事では、共有Dockerレイヤーがなぜ有用なのかの説明が抜けている
理由はキャッシュで、より多くのイメージが同じレイヤーを共有するほど、より多くのものをキャッシュできるため、コンテナの起動が速くなる
Dockerがここで弱い理由は、キャッシュの利点を得るには、イメージをビルドするときに既存のレイヤーとできるだけ同じレイヤーが生成される必要があるからだ。たとえば今日
apt-get install python3を実行して新しい更新がなければ、昨日とまったく同じレイヤーが生成されるべきだが、Dockerレイヤーはファイルのハッシュでキャッシュされるため、作成時刻のようなメタデータまで含めて、すべてのファイルが完全に同一でなければならないNixはすでに依存関係をハッシュで保存しているため、同じバージョンと同じ設定ならレイヤーは常に同じになる
依存関係は通常ツリーではなくグラフを形成するため、すぐに面倒になる。Nixや、おそらくBazelのような代替ツールにはこの制約がなく、依存関係グラフをDockerレイヤーにマッピングして細粒度のキャッシュを実現できる。Dockerfileでは表現できない方法だ
apt-getを含むレイヤーが自動的に無効化されることはない--no-cacheを使うか、上位レイヤーに変更がある場合にだけ変わるここ2〜3日、Darwin で Docker イメージをビルドしようと格闘していたので、この記事は宇宙が自分をからかっているように感じる
Nix は目指すゴールには間違いなく最高のツールだが、魂を吸い取られるような暗く見捨てられた片隅がある。好きではあるけれど、時々 Rick のコンパイラランドの冒険に連れて行かれた Morty になった気分になる
Docker は本質的に Linux バイナリを収める牢獄に近い。Linux ではバイナリをビルドしてコンテナに入れれば終わりで、Nix のコードも単純。コードをビルドできるなら、コンテナ作成はもう一段階にすぎない
しかし Docker は Linux バイナリを要求し、Mac では Docker Desktop が Linux VM を立ち上げ、すべての作業をその中で行ったうえで、その事実を隠している。Nix はそうしないので、選択肢は2つある
1つ目はクロスコンパイルする方法だが、glibc までクロスコンパイル可能でなければならず、コミュニティ依存関係の大半はできるとしても、一部のパッケージは作者がクロスコンパイルを考慮していないかもしれない。さらに、標準の Nix キャッシュを埋める Hydra はクロスコンパイルビルドを行わないため、長時間ビルドした末に失敗することもある
2つ目は、Mac に Linux ビルダーを接続し、
x86_64-linuxのビルドジョブをそちらに送る方法。物理マシン、VM、さらには NixOS Docker コンテナでも可能1番は正しいやり方に見えるが、2番のほうが実用的。遭遇している問題は、おそらく1番を試しているからである可能性が高く、これは Nix だけでなくクロスコンパイルの経験もかなり要求される。Hydra が Darwin から Linux へのクロスコンパイルもビルドしてキャッシュを提供し、壊れないようにしてくれればよいが、コストも増えるだろう。2番の解決策を試すほうがよさそう
公式の解決策があった気がする: https://ryantm.github.io/nixpkgs/builders/special/darwin-bui...
一部のスタックではクロスコンパイルがあまり堅牢ではないため、Docker で
{aarch64,amd64} x {linux,darwin}をクロスコンパイルしている。Darwin aarch64 上で Docker を複数動かして全部コンパイルしており、体験は良かったhttps://github.com/gytis-ivaskevicius/high-quality-nix-conte...
バグが1つ2つあるが、主にボリューム関連のようで、Docker イメージのビルドは問題なく処理してくれる。より荒い部分は速度で、ハードウェアと Docker が Linux VM をエミュレートしなければならないという事実が重なって、かなり効いてくる
NixでJavaアプリケーション用のDockerイメージをビルドした経験は、あまり良くなかった
gradle2nixが廃止された後は、GradleベースのJavaアプリケーション向けの明確な代替手段がないように見える。以前、友人にシンプルなSpring Bootアプリケーションのできるだけ小さいDockerイメージを作ってみようと持ちかけたところ、Nixで作った成果物はNixなしで作ったイメージの2倍のサイズになったコードはここで見られる: https://github.com/jossephus/Docker_challenge/blob/main/flak...
zuluと、gradleが
jdk引数として含めるJDKが一緒に入る。nixpkgsのgradleGenを見ると意味が分かるはず。gradle2nixについては申し訳なく思っていて、よりハックっぽくない改善作業を進めているだいたいこんな感じだった: https://gist.github.com/takeda/17b6b645ad4758d5aaf472b84447b...
すべてをmuslでリンクし、Pythonをコンパイルする際にアプリケーションで使わないパッケージをすべて無効化し、boto3/botocoreで使わない部分を削除した。boto3/botocoreだけでも100MBを超える
Nixパッケージは基本的にNixOSオペレーティングシステムを対象にしているため、ディスク容量に余裕がある通常の状況では全機能が有効になっていることが望まれる。そのため不要な依存関係が多く付いてくる。一方でAlpineイメージはDocker向けに設計されており、パッケージの付加機能をオフにすることを目標としているので、結果がより小さくなる
小さいイメージを作るには
overrideで不要なものをオフにする必要がある。例えばzuluにはalsa、fontconfig、freetype、xorg、cups、gtk、cairo、ffmpegといったものが入る。友人は必要なファイルだけを慎重に抽出してコンテナに入れたが、Nix側はzuluパッケージ全体とすべての依存関係をバンドルしていたようなものまずJDKを1つだけ含めるように修正し、その後、上の方法でJDKサイズをさらに減らせる。
openjdk_headlessを使うともっと簡単かもしれないhttps://github.com/NixOS/nixpkgs/blob/master/pkgs/developmen...
https://github.com/GoogleContainerTools/jib/tree/master/jib-...
openjdk_headlessはSpringに不要なGTKとXの依存関係をスキップするZuluの代わりにNixpkgsのheadless OpenJDKビルドを基準に変更してGUIライブラリの依存関係を取り除き、
pkgs.jre_minimalとjlinkでカスタムの最小JREを作ったイメージサイズは161MBで、
demo_jlinkイメージより少し大きい。実際にはアプリケーション実行に必要なすべてのモジュールを含めているため、JREが約90MBになるからだ。Dockerfile_jlinkのjdeps呼び出しはすべてのモジュールを検出できておらず、java.baseだけでJREを作っていた。私の最小JREもjava.baseだけを入れればJREサイズは約50MBになり、壊れたコンテナイメージはPodman基準で117MBになるdockerTools.buildImage呼び出しで誤っていたcopyToRootも削除した。config.Cmdの文字列コンテキストだけでアプリはイメージに入るのに、既存コードはアプリをもう一度コピーしていた。また、dockerTools.buildLayeredImageに変更して各store pathを個別のイメージレイヤーに入れた。複数のコンテナイメージ間で依存関係を共有する場合の容量スケーラビリティには有利だが、単一イメージの実験には影響しない残っているのは主にJREサイズの最適化だ。
glibcが次に大きい依存関係で約30MBだが、Nixpkgsが多くのロケールと文字エンコーディングをサポートするようにglibcをビルドしているためと思われる。これを別のderivationやoutputに分けて選択可能にすることが可能なのか、実用的なのかは分からない。複数のイメージをdockerTools.buildLayeredImageで作れば、同じNixpkgsコミットを使う前提でglibcと残りの依存関係はすべて共有されるhttps://github.com/max-privatevoid/hackernews-docker-challen...
すでに Nix を導入しているなら素晴らしいし、Nix や Guix のような宣言的パッケージ管理がもっと広く普及するとよいと思う
すでに Docker を使っているが Nix を段階的に導入したいなら、この発表のアプローチもある: https://youtu.be/l17oRkhgqHE
設定とコンテナビルドをいきなりすべて Nix に移すのではなく、Dockerfile を維持したまま Nix の設定をビルドさせることができる。最大の欠点はレイヤーをまったく活用できない点で、利点は Dockerfile を段階的に変えながら既存の Docker インフラや自動化を再利用できる点だ
[1] https://mitchellh.com/writing/nix-with-dockerfiles
ただ、その非再現性のかなりの部分は、Docker レイヤーのいずれかが今後も利用可能である保証がないことによるのではないかと思う。だとすると、Nix ではパッケージバージョンがリポジトリに永遠に残る保証があるのかも気になる
唐突だが、このスライドの図を作ったイラストレーターの Annie Ruygt は、YC スタートアップ 2 社である RethinkDB(rethinkdb.com) と Pachyderm(pachyderm.io) のロゴやブランドアートも手がけている
Pachyderm で働いていたことがあり、Pachyderm は元 RethinkDB エンジニアたちが創業した会社だ。作品が本当に良い
比較的最近、インフラチームの勧めで Nix を使って CI ベースイメージをビルドしようと半日ほど費やしたが、イメージは巨大で、リンクの問題のせいで一部が動作しなかった
特に苛立たしかったのは、マルチアーキテクチャイメージを作るときに別アーキテクチャの何かを実際に実行しようとし、qemu のハードウェア仮想化しかサポートしていない点だった。ビルドマシンとワークステーションは VM なのでそれがなく、
binfmt-miscはある。arm64 のmkdirを fork/exec して/tmpを作っていたなら動いたはずだが、Docker レイヤーは単なる tar ファイルなので、次のようにディレクトリを作れるecho "tmp uid=0 gid=0 time=0 mode=0755 type=dir" | bsdtar -cf - @-この正確なレイヤーはどこかにすでに存在している可能性も高く、ユーザーがダウンロードする必要すらないかもしれない
Nix を試すたびに、あと数か月もすれば日常的に使えるようになりそうだという感覚を受ける。nixpkgs には欲しいパッケージがほぼすべてあり、ワークステーションには問題なくインストールできる。しかし「bash、python、build-essential、Bazel が必要だ」という要件は、Docker イメージビルダーの目標のようには見えない。Go バイナリ 1 つを Docker イメージに入れる用途なら Nix は不要だ。distroless を持ってきてアプリケーションを tar ファイルに入れればコンテナになる。個人的には Bazel の
rules_ociを使っているが、内部でやっていることもその程度で、複数アーキテクチャ向けのバイナリをビルドし、イメージインデックス YAML を作ってレジストリにプッシュする程度の知能が少し加わっているだけだもちろん、パッケージのビルドファイルがそれをサポートしている必要はある。qemu がハードウェア仮想化しかサポートしていないという話も正しくないように見える。ソフトウェアエミュレーションしかできないアーキテクチャにも qemu は使える
最小例はここで扱っている: https://discourse.nixos.org/t/how-do-i-get-a-shell-nix-with-...
pkgCrossを使うくらい単純であるべきだ、とまで言うつもりはないが、一般的な手順の中でどんな具体的な問題に遭遇したのか気になるhttps://nix.dev/tutorials/cross-compilation.html
いくつかのプロジェクトで数年間使ってきたが、サイズが問題なら、記事で触れられている方式で、指定したものだけが入った非常に小さなイメージを作れる
[1] https://hub.docker.com/r/nixos/nix/tags
musl まで使い、どんなツールで作ったものよりも小さいイメージを CI で高速かつ一貫してビルドでき、キャッシュもうまく動作した。何が実行されたという話なのか、よく分からない
buildFHSEnvを使う必要があったのか気になるNix には、既存システムから部分ごとにスムーズに移行する方法を説明する記事がもっと必要だと思う
プラットフォームエンジニアとして Nix を好きになりたいが、ほかのみんなにとって簡単というわけではない
開発者体験もまだかなり悪いと思う。たとえば
devbox add python@3.11のようにパッケージを追加できるので、devbox の開発者体験のほうを好んでいる120行の
flake.nixを見ても、正直「より簡単」とは言いにくいhttps://github.com/Xe/douglas-adams-quotes/blob/main/flake.n...
リンク先の flake は Go バイナリのビルド方法を定義し、そのバイナリをエントリーポイントとして使う Docker イメージを定義し、Go バイナリを実行する systemd サービスを作る NixOS モジュールも定義している。さらに、その NixOS モジュールが期待どおりに systemd サービスを作るか確認する NixOS テストまで含まれている
NixOS のテストフレームワークはかなり印象的で、テストは NixOS を一緒に起動する QEMU VM の中で動く。リンク先の記事に関係があるのは、Go バイナリと Docker イメージの定義、そして周辺のボイラープレートの一部だけだ
また、多くの行はインラインの systemd サービス記述と1対1で対応しているので、どんなシステムを使ってもなくならない。複数のシステムを override で宣言する洗練された方法も入っている
この例は「些細なことを大規模で本格的なプロジェクトでやるようにやってみよう」に近く、単発として扱うなら40行程度まで減らせそうだ
そのバイナリと設定ファイルは、Linux で得られる「flat pack」に最も近い形だが、この例は、私たちが森と木のどちらを見落としているのかをよく示している
Guix にも
guix pack -f dockerという簡単で良い Docker オプションがあるGuix には、独自の専用言語ではなく、すでに使われている言語である Guile/Scheme を使うという利点もある
[1] https://guix.gnu.org/manual/en/html_node/Invoking-guix-pack....