1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • NixOSは設定だけでVMやISOを作りやすいが、最小に近いライブイメージでも最初から 458MiB あり、AlpineのVM ISO約 66MiB とは大きな差があった
  • サイズの大半は nix-store.squashfs が占めており、その中にはPython 3.13.13、Linux modules、systemd、Perl、GRUB、ドキュメント、Nix関連の依存関係が入っていた
  • nix.enable = false, documentation.enable = false, register-nix-paths の削除を経て、ISOは 458MiB → 384MiB → 360MiB まで縮小し、Boost依存も外れた
  • OpenSSHクライアント、デフォルトパッケージ、GRUBインストールツール、ランタイムのカーネルモジュール、Perlベースのアクティベーション経路まで取り除き、最終サイズは 183MiB まで下がった
  • 小さな実験用ブートイメージには参考になるが、多くの機能を削っているため、デスクトップや重要な環境にそのまま使うのは難しい

NixOS設定からISOを作る

  • NixOS は設定をもとにVMを簡単に作成できる
    • nixos-rebuild build-vm は現在のシステム設定のVMを生成する
    • pkgs.nixos を使えば、システム設定でなくても任意の設定でVMを作れる
  • 基本例では system.stateVersion = "26.05"services.getty.autologinUser = "root" だけを置いた最小VMを作成する
  • このVMは thin VM として動作する
    • ディスクイメージにはVM内で直接作成したファイルだけが入る
    • /nix/store など残りはホストOSからマウントされる
  • ホストにNixがない場合や、リモートホスト、一般的なハイパーバイザーで実行するには 自己完結型ISO が必要になる
  • NixOSの iso-image.nix モジュールを import するとISOをビルドできる
    • image.baseName = lib.mkForce "nixos" で出力ISO名を指定する
    • 実行例は qemu-system-x86_64 --cdrom .../nixos.iso -m 1G --accel kvm の形
    • amd64の現代的なLinux環境でなければ、アーキテクチャやアクセラレーション方式の変更が必要なことがある

出発点: 458MiBのISO

  • デフォルトのISOビルド結果は 458MiB だった
  • このイメージにはまだ vim すら含まれていない
    • 起動後に vim を実行すると command not found になる
  • 比較対象として挙げられたAlpineのVM ISOは約 66MiB
  • Damn Small Linuxは、はるかに小さいサイズで完成度の高いデスクトップ環境を提供していた例として登場する
  • 目標はDamn Small Linux級に到達することではなく、NixOS ISOをどの程度でも小さくできるかを確かめることにある

ISO内部のサイズ分析

  • ISOをマウントして du で確認すると、サイズは次のように分かれていた
    • nix-store.squashfs: 416MiB
    • initrd: 26MiB
    • kernel: 13MiB
    • ISO全体: 458MiB
  • サイズの主因は 主要ユーザー空間 である nix-store.squashfs だった
  • squashfsをマウントすると、Nix storeのように見えるパスが入っていた
    • python3-3.13.13: 128MiB
    • linux-6.18.35-modules: 144MiB
    • systemd-260.1: 60MiB
    • perl-5.42.0: 56MiB
    • grub-2.12: 複数項目の合算で約 62MiB
    • nix-manual-2.34.7, nixos-manual-html などのドキュメントも含まれていた
  • ISOはホスト上でビルドされるため、ISO内のstoreパスはホストの /nix/store でも追跡できる
  • nix why-depends で依存関係の出どころを確認した
    • BoostはNix daemonの経路から入っていた
    • nix-daemon.conf, nix, libnixutil.so を経由して boost-1.89.0 に到達した

Nixとドキュメントの削除

  • nix.enable = false でNix自体をイメージから外そうとした
  • documentation.enable = false でドキュメントも無効化した
  • 最初の結果は 458MiB → 384MiB だった
  • しかしBoostはまだ残っていた
    • register-nix-paths.service が起動時にISO storeの内容を登録しようとしていた
    • この経路が再びNixとBoostを引き込んでいた
  • systemd.services.register-nix-paths = lib.mkForce {} でそのサービスを空にして削除した
  • その結果ISOは 360MiB になり、nix why-depends でもBoost依存がないことを確認できた

OpenSSHとデフォルトパッケージの削除

  • 同様の方法で environment.defaultPackages も空にできた
  • ssh の削除はより厄介だった
    • modules/programs/ssh.nix がOpenSSHを environment.corePackages に追加していた
    • これを制御する programs.ssh.enable のようなオプションは見つからなかった
    • services.openssh.enable はサーバー設定であり、クライアント削除のためのオプションではない
  • disabledModulesprograms/ssh.nix を除外することはできたが、他のモジュールが programs.ssh オプションの存在を前提としており、連鎖的にエラーが発生した
  • 解決策は、programs.ssh オプションを使わない スタブオプション を別モジュールとして用意することだった
    • options.programs.ssh = lib.mkOption {};
    • その後 disabledModules = [ "programs/ssh.nix" ]; で実際のSSHモジュールを除外した
  • この過程で次の設定も併せて適用した
    • documentation.man.enable = false
    • networking.firewall.enable = false
    • environment.defaultPackages = lib.mkForce []

NixOSモジュール構造メモ

  • NixOSモジュールは大きく3つの部分から成る
    • モジュールレベルの項目: imports, disabledModules
    • オプション定義: options.*
    • 実装: config.*
  • オプションを定義しないモジュールは、config. 接頭辞なしで実装属性を書く短縮形を使える
  • 残りの設定でも短縮形を維持するため、programs.ssh のスタブオプションは別の import モジュールに分離した

GRUBインストールツールの削除

  • 残っていた大きな項目の1つが約 62MiB のGRUB関連ファイルだった
  • ブートローダー自体は必要だが、インストールツールをすべて含める必要はないと判断した
  • NixOSのISOプリセットはUEFI版とBIOS版のGRUBを両方バンドルする
  • これを無効にする明確なオプションはなかったため、より荒い方法で次の値を再設定した
    • system.extraDependencies = lib.mkForce []
    • environment.systemPackages = lib.mkForce config.environment.corePackages
  • environment.systemPackages を完全に空にはしない
    • bash がないとgettyがcrashloopし続ける可能性があるため、シェルがある程度動くよう corePackages は残した

カーネルモジュールの削除

  • linux-6.18.35-modules144MiB で、全体サイズの約4分の1を占めていた
  • NixOSにはランタイムで使うカーネルモジュールを制限する良いフックは見当たらなかった
  • そこでシステム出力から kernel-modules フォルダーを削除した
    • system.systemBuilderCommands = lib.mkAfter "rm $out/kernel-modules";
  • この方法は ランタイムのモジュールロードを事実上無効化 する
    • 必要なモジュールは boot.initrd.kernelModules または availableKernelModules に入れておく必要がある
  • この変更後、より快適なディスプレイ解像度に切り替える機能は失われたが、起動は可能だった
  • ISOサイズは 197MiB まで下がった

Perlの削除と実験的な代替機能

  • それでもなお 56MiBPerl が含まれていた
  • nix why-depends で確認すると、Perlはシステム有効化中にユーザーと /etc を構成するために使われていた
  • ユーザー管理と /etc 構成を完全に捨てることはできなかった
  • そこで実験的機能で既存経路を置き換えた
    • /etc 管理にはoverlay方式を使用
    • ユーザー管理にはネイティブな userborn を使用
  • 適用した設定は次のとおり
    • system.etc.overlay.enable = true
    • system.etc.overlay.mutable = false
    • services.userborn.enable = true
  • 最終的なISOサイズは 183MiB になった

最終状態と限界

  • 出発点の 458MiB から最終的に 183MiB まで減り、元のほぼ3分の1になった
  • それでも結果を「良い」と呼ぶのは難しいとしている
  • 実際に使うデスクトップや重要な環境には向いていない
    • 削除した機能にはすべて存在理由がある
  • 小さな実験用ブートイメージが必要で、ごく小さな作業だけを行えばよい場合には参考になる
  • 最終設定をそのままコピーして使うと、目的に必要な機能が欠けている可能性がある

さらに縮める余地

  • 今回の作業は、「単純に削除できる」か、比較的明確な代替手段がある項目に集中していた
  • より深い作業が必要な領域も残っている
    • 現在は systemdMinimalsystemd の両方がバンドルされている
    • どちらか片方を外そうとすると別のビルド経路が壊れた
  • 小さな項目もまだ削れる可能性があり、合算すれば意味のあるサイズになるかもしれない
  • 追加の最適化には、さらに多くの調査と実験が必要になる

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rs のコメント
  • まさにこの用途向けに作られたモジュールがある。かなりのコンパイルは必要だが、NixOS のユーザー空間全体を含む完全に自己完結した initrd を、zstd 圧縮で約 80MiB にできる
    この作業は自己完結型 initrd に限らず、どんな NixOS でも容量削減に使える。インストール ISO にもおそらく適用できるはず
    https://github.com/wucke13/minimal-nixos/

  • TinyCore Linux の基本システムは 17MB の Core
    X と FLTK/FLWM まで欲しければ 23MB の TinyCore、さらに多くのウィンドウマネージャやアプリまで欲しければ 248MB の CorePlus がある
    http://www.tinycorelinux.net/downloads.html

    • それが 宣言的設定 や再現可能な VM とどう関係あるのかは分からない
  • NixCon での、NixOS を Yocto の代替として小さくまとめた発表を勧める: https://youtu.be/AsXY61laNb8
    期待したほど詳しくはなかったが、カンファレンスで Óli と Matthew から直接聞いた話は素晴らしかった。まとめ記事があるのか気になる

  • NixOS で小さなインストール構成を作るのは、いつも少しもどかしい
    SSH 周りのサイズは次の設定で減らせそう

    programs.ssh.setXAuthLocation = false;  
    security.pam.services.su.forwardXAuth = lib.mkForce false;  
    fonts.fontconfig.enable = false;  
    

    "${nixpkgs}/nixos/modules/profiles/minimal.nix" を import することもできる。そこには記事で行っていた最適化の一部が入っている

    • 元の投稿を書くことになったユースケースでは、実際には ssh 自体がほとんど不要だった
      それでも、たいていの場合はこちらのほうが合理的である可能性が高い
      "${nixpkgs}/nixos/modules/profiles/minimal.nix" は以前に見て期待ほどではないと感じていたので、調査を始めたときには含めようと思わなかった。後から思い出したときにはもう半分ほど進んでいて、本来入れるべき初期段階に後付けするのは少し誠実でない気がした
  • 最近のシステムでは Perl が引き込まれる場面が多すぎて妙だ。小さな ISO にも Perl が入るし、何かまともなものを最初からコンパイルしようとしても openssl -> Perl となる

  • 読む前から、誰かが C に書き直していない間抜けな Perl スクリプトが原因だろうと思っていた
    修正: やはりその通りだった

  • NixOS は 26.05 からデフォルトの initrd で systemd を使う。現代の OS がサポートすべき initrd のユースケース が多いためだ
    systemdMinimal は、より少ないフラグと依存関係でビルドした systemd バイナリなので、initrd をより小さく保つのに役立つ
    ただし最小 ISO が目標なら、両方を同じバイナリに依存させることもできそうだ