1 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • NixOS macht es leicht, allein per Konfiguration eine VM oder ein ISO zu bauen, aber selbst ein nahezu minimales Live-Image wurde von Beginn an mit 458 MiB erzeugt und lag damit deutlich über dem Alpine-VM-ISO mit etwa 66 MiB
  • Den Großteil der Größe belegte nix-store.squashfs; darin steckten Python 3.13.13, Linux-Module, systemd, Perl, GRUB, Dokumentation und Nix-bezogene Abhängigkeiten
  • Mit nix.enable = false, documentation.enable = false und dem Entfernen von register-nix-paths schrumpfte das ISO von 458 MiB → 384 MiB → 360 MiB, und auch die Boost-Abhängigkeit verschwand
  • Nach dem Entfernen des OpenSSH-Clients, der Standardpakete, der GRUB-Installationswerkzeuge, der Kernel-Module zur Laufzeit und des Perl-basierten Aktivierungspfads fiel die endgültige Größe auf 183 MiB
  • Für kleine experimentelle Boot-Images ist das nützlich, aber da dabei viele benötigte Funktionen entfernt werden, eignet es sich kaum unverändert für Desktops oder wichtige Umgebungen

ISO aus einer NixOS-Konfiguration bauen

  • NixOS kann auf Basis einer Konfiguration leicht eine VM erzeugen
    • nixos-rebuild build-vm erstellt eine VM aus der aktuellen Systemkonfiguration
    • Mit pkgs.nixos lässt sich auch aus einer beliebigen Konfiguration eine VM erzeugen, selbst wenn sie nicht die Systemkonfiguration ist
  • Das Grundbeispiel erzeugt eine minimale VM, die nur system.stateVersion = "26.05" und services.getty.autologinUser = "root" setzt
  • Diese VM arbeitet als thin VM
    • Im Disk-Image liegen nur Dateien, die direkt innerhalb der VM erstellt wurden
    • /nix/store und der Rest werden vom Host-OS eingehängt
  • Wenn auf dem Host kein Nix vorhanden ist oder das System auf einem entfernten Host bzw. einem normalen Hypervisor laufen soll, braucht man ein eigenständiges ISO
  • Importiert man in NixOS das Modul iso-image.nix, kann man ein ISO bauen
    • Mit image.baseName = lib.mkForce "nixos" wird der Name des erzeugten ISO festgelegt
    • Ein Beispielaufruf sieht so aus: qemu-system-x86_64 --cdrom .../nixos.iso -m 1G --accel kvm
    • Außerhalb einer modernen Linux-Umgebung auf amd64 müssen möglicherweise Architektur oder Beschleunigungsmethode angepasst werden

Ausgangspunkt: 458-MiB-ISO

  • Das Ergebnis des Standard-ISO-Builds lag bei 458 MiB
  • Dieses Image enthielt noch nicht einmal vim
    • Nach dem Booten ergab vim nur command not found
  • Als Vergleich diente ein Alpine-VM-ISO mit etwa 66 MiB
  • Damn Small Linux wird als Beispiel erwähnt, das bei viel kleinerer Größe trotzdem eine brauchbare Desktop-Umgebung bot
  • Das Ziel war nicht, das Niveau von Damn Small Linux zu erreichen, sondern zu prüfen, ob sich ein NixOS-ISO überhaupt spürbar verkleinern lässt

Größenanalyse des ISO-Inhalts

  • Nach dem Mounten des ISO zeigte du folgende Aufteilung
    • nix-store.squashfs: 416 MiB
    • initrd: 26 MiB
    • Kernel: 13 MiB
    • gesamtes ISO: 458 MiB
  • Der entscheidende Faktor war also nix-store.squashfs, also der eigentliche Userspace
  • Nach dem Mounten des squashfs fanden sich darin Pfade wie in einem Nix-Store
    • python3-3.13.13: 128 MiB
    • linux-6.18.35-modules: 144 MiB
    • systemd-260.1: 60 MiB
    • perl-5.42.0: 56 MiB
    • grub-2.12: zusammen über mehrere Einträge etwa 62 MiB
    • Auch Dokumentation wie nix-manual-2.34.7 und nixos-manual-html war enthalten
  • Da das ISO auf dem Host gebaut wird, lassen sich die Store-Pfade aus dem ISO auch im Host-/nix/store nachvollziehen
  • Mit nix why-depends wurde die Herkunft der Abhängigkeiten untersucht
    • Boost kam über den Pfad des Nix-Daemons hinein
    • Über nix-daemon.conf, nix und libnixutil.so führte die Kette bis zu boost-1.89.0

Nix und Dokumentation entfernen

  • Mit nix.enable = false wurde versucht, Nix selbst aus dem Image zu entfernen
  • Mit documentation.enable = false wurde auch die Dokumentation deaktiviert
  • Das erste Ergebnis war eine Schrumpfung von 458 MiB → 384 MiB
  • Boost war jedoch weiterhin vorhanden
    • register-nix-paths.service wollte beim Booten den Inhalt des ISO-Stores registrieren
    • Dieser Pfad zog erneut Nix und Boost herein
  • Mit systemd.services.register-nix-paths = lib.mkForce {} wurde dieser Dienst geleert und entfernt
  • Danach war das ISO 360 MiB groß, und nix why-depends zeigte keine Boost-Abhängigkeit mehr

OpenSSH und Standardpakete entfernen

  • Auf ähnliche Weise ließ sich auch environment.defaultPackages leeren
  • Das Entfernen von ssh war komplizierter
    • modules/programs/ssh.nix fügt OpenSSH zu environment.corePackages hinzu
    • Eine Option wie programs.ssh.enable zur Steuerung ließ sich nicht finden
    • services.openssh.enable ist eine Servereinstellung und keine Option zum Entfernen des Clients
  • Zwar ließ sich programs/ssh.nix mit disabledModules ausschließen, aber andere Module erwarteten weiterhin das Vorhandensein von programs.ssh, was zu Folgefehlern führte
  • Die Lösung war ein separates Modul mit einer Stub-Option, das zwar programs.ssh bereitstellt, aber nicht verwendet
    • options.programs.ssh = lib.mkOption {};
    • Danach wurde das eigentliche SSH-Modul mit disabledModules = [ "programs/ssh.nix" ]; ausgeschlossen
  • In diesem Schritt wurden außerdem folgende Einstellungen gesetzt
    • documentation.man.enable = false
    • networking.firewall.enable = false
    • environment.defaultPackages = lib.mkForce []

Notiz zur Struktur von NixOS-Modulen

  • NixOS-Module bestehen grob aus drei Teilen
    • Einträgen auf Modulebene: imports, disabledModules
    • Optionsdefinitionen: options.*
    • Implementierung: config.*
  • Module, die keine Optionen definieren, können eine Kurzform verwenden und Implementierungseigenschaften ohne config.-Präfix schreiben
  • Um diese Kurzform im übrigen Setup beizubehalten, wurde die Stub-Option programs.ssh in ein separates importiertes Modul ausgelagert

GRUB-Installationswerkzeuge entfernen

  • Einer der verbliebenen großen Posten waren GRUB-bezogene Dateien mit etwa 62 MiB
  • Der Bootloader selbst wird benötigt, aber es erschien unnötig, sämtliche Installationswerkzeuge mitzuliefern
  • Das NixOS-ISO-Preset bündelt sowohl UEFI- als auch BIOS-Versionen von GRUB
  • Da es dafür keine klare Abschaltoption gab, wurden auf gröbere Weise folgende Werte zurückgesetzt
    • system.extraDependencies = lib.mkForce []
    • environment.systemPackages = lib.mkForce config.environment.corePackages
  • environment.systemPackages wurde nicht vollständig geleert
    • Ohne bash kann getty in eine Crash-Schleife geraten, daher wurden die corePackages beibehalten, damit die Shell zumindest grundlegend funktioniert

Kernel-Module entfernen

  • linux-6.18.35-modules war 144 MiB groß und machte etwa ein Viertel der Gesamtgröße aus
  • In NixOS schien es keinen guten Hook zu geben, um die zur Laufzeit nutzbaren Kernel-Module zu begrenzen
  • Stattdessen wurde im System-Output der Ordner kernel-modules entfernt
    • system.systemBuilderCommands = lib.mkAfter "rm $out/kernel-modules";
  • Dadurch wird das Laden von Laufzeitmodulen praktisch deaktiviert
    • Benötigte Module müssen in boot.initrd.kernelModules oder availableKernelModules aufgenommen werden
  • Nach dieser Änderung funktionierte das Booten weiterhin, aber die Möglichkeit, auf eine angenehmere Bildschirmauflösung umzuschalten, ging verloren
  • Die ISO-Größe sank auf 197 MiB

Perl entfernen und experimentelle Ersatzfunktionen

  • Es war weiterhin Perl mit 56 MiB enthalten
  • Mit nix why-depends zeigte sich, dass Perl bei der Systemaktivierung für Benutzer und /etc verwendet wurde
  • Auf die Einrichtung von Benutzern und /etc konnte nicht vollständig verzichtet werden
  • Stattdessen wurden diese Pfade durch experimentelle Funktionen ersetzt
    • Für die Verwaltung von /etc wurde ein Overlay-Ansatz verwendet
    • Für die Benutzerverwaltung kam das native userborn zum Einsatz
  • Verwendet wurden folgende Einstellungen
    • system.etc.overlay.enable = true
    • system.etc.overlay.mutable = false
    • services.userborn.enable = true
  • Die endgültige ISO-Größe betrug 183 MiB

Endstand und Grenzen

  • Vom Startwert 458 MiB sank das ISO auf 183 MiB, also auf fast ein Drittel der Ausgangsgröße
  • Trotzdem wird das Ergebnis nicht als wirklich „gut“ bewertet
  • Für Desktops oder wichtige produktive Umgebungen ist es nicht geeignet
    • Alle entfernten Funktionen haben einen Grund, überhaupt vorhanden zu sein
  • Wenn man ein kleines experimentelles Boot-Image braucht und nur sehr kleine Aufgaben ausführen will, kann das als Anhaltspunkt dienen
  • Wer die finale Konfiguration unverändert übernimmt, dem könnten genau die Funktionen fehlen, die für den eigenen Zweck nötig sind

Weiteres Potenzial zur Verkleinerung

  • Diese Arbeit konzentrierte sich auf Teile, die sich „einfach entfernen“ ließen oder für die es relativ klare Ersatzwege gab
  • Bereiche mit tieferem Eingriff bleiben weiterhin offen
    • Derzeit werden sowohl systemdMinimal als auch systemd mitgebündelt
    • Der Versuch, eines von beiden zu entfernen, ließ andere Build-Pfade scheitern
  • Auch kleinere Posten ließen sich weiter abbauen, und in Summe könnte das noch relevant werden
  • Für weitere Optimierungen wären zusätzliche Untersuchung und Experimente nötig

1 Kommentare

 
GN⁺ 4 시간 전
Lobste.rs-Meinungen
  • Dafür gibt es ein Modul, das genau für diesen Zweck gemacht wurde. Es erfordert zwar einiges an Kompilierung, kann aber ein vollständig eigenständiges initrd inklusive des gesamten NixOS-Userlands mit zstd-Komprimierung auf etwa 80 MiB bringen
    Das ist nicht nur auf eigenständige initrds beschränkt, sondern kann genutzt werden, um jedes NixOS zu verkleinern. Vermutlich ließe sich das auch auf eine Installations-ISO anwenden
    https://github.com/wucke13/minimal-nixos/

  • Das Basissystem von TinyCore Linux ist Core mit 17 MB
    Wenn man X und FLTK/FLWM will, gibt es TinyCore mit 23 MB, und wenn man noch mehr Fenstermanager und Apps möchte, gibt es CorePlus mit 248 MB
    http://www.tinycorelinux.net/downloads.html

    • Ich weiß nicht, was das mit deklarativer Konfiguration oder reproduzierbaren VMs zu tun hat
  • Ich empfehle den Vortrag auf der NixCon, in dem NixOS als Yocto-Alternative stark verkleinert wurde: https://youtu.be/AsXY61laNb8
    Er war nicht so detailliert, wie ich gehofft hatte, aber das, was ich auf der Konferenz direkt von Óli und Matthew gehört habe, war großartig. Ich frage mich, ob es dazu irgendwo eine Zusammenfassung gibt

  • Es ist bei NixOS immer etwas frustrierend, kleine Installations-Setups zu bauen
    Die Größe rund um SSH lässt sich mit den folgenden Einstellungen wohl reduzieren

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

    Man kann auch "${nixpkgs}/nixos/modules/profiles/minimal.nix" importieren. Dort sind einige der im Artikel genannten Optimierungen bereits enthalten

    • Im Anwendungsfall, der mich ursprünglich zu dem Artikel gebracht hat, war ssh selbst eigentlich kaum nötig
      Trotzdem ist dieser Ansatz in den meisten Fällen wahrscheinlich sinnvoller
      "${nixpkgs}/nixos/modules/profiles/minimal.nix" hatte ich mir früher schon einmal angesehen und fand es weniger hilfreich als erwartet, daher kam mir beim Start meiner Untersuchung nicht in den Sinn, es einzubeziehen. Als ich später wieder daran dachte, war ich schon etwa halb fertig, und es noch nachträglich an den Anfang zu setzen, wo es eigentlich hingehört hätte, fühlte sich etwas unehrlich an
  • Es ist seltsam, wie oft Perl sich in Systeme hineinzieht. Selbst bei kleinen ISOs ist Perl dabei, und wenn man von Grund auf etwas Ordentliches kompilieren will, landet man schnell bei openssl -> Perl

  • Noch bevor ich es gelesen habe, habe ich vermutet, dass wieder irgendein dummes Perl-Skript, das niemand in C neu geschrieben hat, daran schuld ist
    Korrektur: Genau so war es

  • NixOS verwendet ab 26.05 im Standard-initrd systemd, weil es viele initrd-Anwendungsfälle gibt, die moderne Betriebssysteme unterstützen müssen
    systemdMinimal ist ein systemd-Binary, das mit weniger Flags und Abhängigkeiten kompiliert wurde, was hilft, das initrd kleiner zu halten
    Wenn das Ziel allerdings eine minimale ISO ist, könnte man wohl auch beide vom selben Binary abhängig machen