ADMIN

2024

02

2024-01-30T12:00:00

Softwareverteilung und Patchmanagement

SCHWERPUNKT

076

Softwareverteilung

Patchmanagement

Systeme erzeugen und ausrollen mit NixOS

Wie am Fließband

von Dr. Holger Reibold

Veröffentlicht in Ausgabe 02/2024 - SCHWERPUNKT

Seit Monaten erfährt ein Tool für die Paketverwaltung und die Systemkonfiguration einen Hype: Nix. Kein Wunder, denn ein Nix-basiertes System unterscheidet sich fundamental von traditionellen Linux-Distributionen. Einer der zentralen Vorteile: Vorherige Systemzustände lassen sich problemlos wiederherstellen. Damit gehören Komplikationen etwa bei Updates der Vergangenheit an. Was NixOS zu bieten hat und wie Sie damit Images erstellen und verteilen, erfahren Sie in diesem Beitrag.

Heimlich, still und leise hat sich Nix­OS [1] zur größten Linux-Distribution entwickelt – zumindest in Bezug auf die Anzahl und die Aktualität der installierbaren Pakete [2]. Schon das wäre Grund genug, sich eingehender mit Nix­OS zu beschäftigen. Doch die Distribution hat noch weit mehr zu bieten. Sie setzt auf eine deklarative Systemkonfiguration, erlaubt atomare Upgrades mit vollständiger Rollback-Möglichkeit, verfügt über ein kongruentes Systemmanagement und bietet Reproduzierbarkeit sowie einfache Cross-Compilation.
Das Besondere an NixOS: Es verfolgt das Single-Source-of-Truth-Konzept und erlaubt die einfache Git-basierte Kooperation. Auch die Übertragbarkeit von Systemen ist gegeben, da es sich hierbei um ein Paket samt Runtime-Dependencies handelt. Als Ziele lassen sich VMs, vollständige Systeme oder ISO-Dateien generieren – auch hier zeigt sich NixOS sehr flexibel. Dabei profitieren die Anwender von dem Umstand, dass sich viele Services durch eine gute Vorkonfiguration auszeichnen. Auch die Zahlen sprechen für sich: Für NixOS stehen mehr als 80.000 Pakete und 10.000 Service-Optionen zur Verfügung.
Für deren Nutzung ist keine vollständige Migration notwendig, da sich der Nix-Paketmanager auf einer bestehenden Linux-Distribution installieren und bei Bedarf wieder entfernen lässt. Für den Einstieg in die Nix-Welt empfiehlt sich dieser Weg. Der Paketmanager läuft übrigens auch unter macOS. Alternativ bietet sich die Nutzung in einer VM an. Für Admins interessant: NixOS ist auch als Serverumgebung verwendbar.
Heimlich, still und leise hat sich Nix­OS [1] zur größten Linux-Distribution entwickelt – zumindest in Bezug auf die Anzahl und die Aktualität der installierbaren Pakete [2]. Schon das wäre Grund genug, sich eingehender mit Nix­OS zu beschäftigen. Doch die Distribution hat noch weit mehr zu bieten. Sie setzt auf eine deklarative Systemkonfiguration, erlaubt atomare Upgrades mit vollständiger Rollback-Möglichkeit, verfügt über ein kongruentes Systemmanagement und bietet Reproduzierbarkeit sowie einfache Cross-Compilation.
Das Besondere an NixOS: Es verfolgt das Single-Source-of-Truth-Konzept und erlaubt die einfache Git-basierte Kooperation. Auch die Übertragbarkeit von Systemen ist gegeben, da es sich hierbei um ein Paket samt Runtime-Dependencies handelt. Als Ziele lassen sich VMs, vollständige Systeme oder ISO-Dateien generieren – auch hier zeigt sich NixOS sehr flexibel. Dabei profitieren die Anwender von dem Umstand, dass sich viele Services durch eine gute Vorkonfiguration auszeichnen. Auch die Zahlen sprechen für sich: Für NixOS stehen mehr als 80.000 Pakete und 10.000 Service-Optionen zur Verfügung.
Für deren Nutzung ist keine vollständige Migration notwendig, da sich der Nix-Paketmanager auf einer bestehenden Linux-Distribution installieren und bei Bedarf wieder entfernen lässt. Für den Einstieg in die Nix-Welt empfiehlt sich dieser Weg. Der Paketmanager läuft übrigens auch unter macOS. Alternativ bietet sich die Nutzung in einer VM an. Für Admins interessant: NixOS ist auch als Serverumgebung verwendbar.
Begriffsverwirrung rund um Nix
Die mediale Diskussion um Nix ist durch eine gewisse begriffliche Unschärfe gekennzeichnet. Unter dem Sammelbegriff Nix werden das Betriebssystem NixOS, der Paketmanager und die Programmiersprache Nix subsumiert. Verschiedentlich ist auch von der "Deklarationstrinität" die Rede [3]. Meist ist mit Nix entweder der Paketmanager oder das Betriebssystem gemeint. Die Programmiersprache dient der Definition von Paketen und Konfigurationen. Sie können sich das bildlich so vorstellen: NixOS wird um den Nix-Paketmanager herum gebaut und stellt diesem seine Fähigkeiten zur Systemkonfiguration zur Verfügung.
Nix unterscheidet sich bei der Paketverwaltung grundlegend von anderen Linux-Distributionen, denn die Umgebung baut Pakete isoliert voneinander. Auf diesem Weg ist sichergestellt, dass diese reproduzierbar sind und sich keine nicht-deklarierten Abhängigkeiten ergeben. Ursprünglich wurde Nix für die Softwareverteilung konzipiert. Die Umgebung erlaubt das Erstellen und Verteilen von Softwarepaketen sowie die Installation und Administration dieser Pakete auf den Zielsystemen. Hierfür kommt der sogenannte Home Manager zum Einsatz, um Pakete und Konfigurationsdateien auf Benutzerebene im Home-Verzeichnis des Benutzers zu verwalten. Der Home Manager greift dabei auf den Nix-Paketmanager zurück und erlaubt die deklarative Installation.
Abhängigkeiten gelöst
Nix adressiert verschiedene Probleme, die in den meisten aktuellen Systemen zur Bereitstellung und Paketverwaltung existieren. Dazu gehört das Bestimmen von Abhängigkeiten: Die korrekte Installation einer Softwarekomponente setzt voraus, dass alle Abhängigkeiten ebenfalls eingespielt werden. Die meisten Systeme besitzen keine Möglichkeit zur Überprüfung, ob die angegebenen Dependencies einer Komponente tatsächlich ausreichen.
Ein weiteres bedeutendes Problem liegt in der fehlenden Unterstützung der gleichzeitigen Verfügbarkeit mehrerer Varianten einer Komponente. Es muss möglich sein, verschiedene Versionen gleichzeitig zu installieren oder mehrere Instanzen der gleichen Version mit unterschiedlichen Parametern zu erstellen. Allerdings sind die Komponenten im Allgemeinen nicht vollständig voneinander isoliert. So kann beispielsweise die Aktualisierung einer Komponente, die von einer anderen abhängig ist, letztere beeinträchtigen.
Nix löst diese Probleme, indem es Pakete in Pfaden erstellt und speichert. Zum Beispiel werden die Artefakte eines Pakets "X" in "/nix/store/Hashwert-X" statt in "/usr/ lib" gespeichert. Die Pfadkomponente "Hashwert" ist ein kryptographischer Hash aller Eingaben wie beispielsweise Quellen et cetera. Damit bricht Nix mit etablierten Hierarchien, wie sie üblicherweise von Linux-Systemen bekannt sind. Der allgemeine Speicherpfad lautet dementsprechend "/nix/store/<Hash>-<Name>-<Version>". Diese scheinbar einfache Struktur hat ihren Charme, da einem Pfad immer nur ein Inhalt zugewiesen wird, was die Reproduzierbarkeit sicherstellt. Gleichzeitig schützt die Verwendung des Hashwerts vor Manipulationen. Durch die Abkehr von globalen Speicherorten löst Nix die Probleme verdeckter Abhängigkeiten.
Doch es gibt noch eine weitere Begrifflichkeit beim Umgang mit Nix: die sogenannten Flakes. Obwohl es sich bislang um ein experimentelles Feature handelt, wird es bereits rege von der Nix-Gemeinde genutzt. Von Programmier- beziehungsweise Skriptsprachen wie JavaScript, Go und Python sind die Dateien "package.json", "go.mod" und "pyproject.toml" bekannt, die die Abhängigkeiten zwischen Softwarepaketen und die Erstellung von Projekten beschreiben. Außerdem verwenden diese Dateien wie "package-lock.json", "go.sum" und "poetry.lock", um die Versionen von Abhängigkeiten zu sperren. Nix bedient sich dieses Konzepts und führt hierfür das Flake-Feature ein. Flakes verwalten die Abhängigkeiten zwischen den Nix-Ausdrücken. Dabei bezieht sich ein Flake auf einen Dateisystembaum, dessen Wurzelverzeichnis die Nix-Dateispezifikation "flake.nix" enthält. Ein Flake repräsentiert einen Dateisystembaum, der meist aus einem Git-Repository oder einem Tarball stammt.
Bild 1: Die Deklarationstrinität verdetlich, was Nix ist – und was nicht.
Nix installieren und einrichten
Bevor Sie mit Nix arbeiten können, steht die Installation der Umgebung auf dem Plan. Sie können Nix als Paketmanager unter macOS, Linux oder dem Windows-Subsystem für Linux (WSL) aufspielen. Alternativ greifen Sie zum Systemumgebungsmanager auf NixOS. Für den Einstieg empfiehlt sich die Verwendung des Paketmanagers auf einem bestehenden Linux-System. Die Entwickler empfehlen die Multiuser-Installation, wenn auf dem System "systemd" ausgeführt wird, SELinux deaktiviert ist und Sie sich mit "sudo" authentifizieren. Für die Installation verwenden Sie den Befehl
sh <(curl -L https://nixos.org/nix/install) --daemon
Für alle weitere Umgebungen stehen über die Downloadseite [4] entsprechende Installationspakete zur Verfügung.
Für DevOps-Aufgaben greifen Unternehmen häufig auf eine Kombination aus ökosystemspezifischen Tools und benutzerdefinierten Skripten zurück. Für derlei Aufgaben ist Nix geradezu prädestiniert. So ist es nicht weiter erstaunlich, dass die Distribution häufig als die ultimative Lösung für DevOps bezeichnet wird. In der Tat: Die Konsolidierung entsprechender Prozesse mit Nix leistet einen bedeutenden Beitrag bei der Standardisierung in Sachen Entwicklung und Bereitstellung.
Hierfür stellt Nix das Tool "NixOps" zur Verfügung, mit dem Sie NixOS-Systeme über das Netzwerk oder in der Cloud deployen. Dazu nimmt das Tool als Eingabe eine deklarative Spezifikation eines Satzes logischer Maschinen entgegen und führt anschließend alle notwendigen Schritte zur Umsetzung dieser Spezifikation aus, sprich das Instanziieren der Cloudmaschinen, das Erstellen und Herunterladen von Abhängigkeiten, das Starten und Stoppen der Dienste et cetera.
Dabei kommen Ihnen verschiedene Besonderheiten von NixOps zugute. Das Tool arbeitet, wie schon erwähnt, deklarativ, wodurch die NixOps-Spezifikationen die gewünschte Konfiguration der Maschinen angeben und NixOps die Aktionen ermittelt, die zur Umsetzung dieser Konfiguration erforderlich sind. Es ergeben sich somit zwischen einem Deployment und einem Re-Deployment keine Unterschiede; die resultierenden Maschinenkonfigurationen sind identisch. NixOps führt eine vollständig automatisierte Bereitstellung durch. Sollte dazu das Starten von VMs oder das Erstellen von Laufwerken notwendig sein, kümmert sich das Tool auch darum.
Ein weiterer Pluspunkt: NixOps ist Multicloud-fähig. Die generierten Konfigurationen lassen sich so in verschiedenen Zielumgebungen einsetzen. Konkret können Sie beispielsweise eine logische Maschine auf einer lokalen Umgebung bereitstellen, eine andere auf einer automatisch erzeugten Amazon EC2-Instanz in der Region "eu-west-1", eine weitere in der Region "us-east-1" und so weiter. Dabei ist auch eine Trennung von logischen und physischen Aspekten eines Deployments möglich.
Da NixOps-Spezifikationen modular aufgebaut sind, lassen sich die Teile trennen, die festlegen, was logische Maschinen wo erledigen sollen. Sie können die identischen Spezifikationen problemlos in verschiedenen Umgebungen einsetzen. Dazu benötigen Sie lediglich einen Formalismus für die Paketverwaltung und das Management der Systemkonfiguration. Dieser Umstand vereinfacht das Hinzufügen von Ad-hoc-Paketen zu einer Bereitstellung.
Ausführen können Sie NixOps unter Linux, macOS und WSL2. Um mit NixOS zu arbeiten, ist zunächst die Installation des Nix-Paketmanagers erforderlich. Für die Installation der neusten NixOps-Version führen Sie den Befehl nix-env -i nixops aus. Von Vorteil ist außerdem, dass NixOps beispielsweise Amazon Webservices und die Google Cloud Engine unterstützt. Für Unternehmen, die auf Hetzner Online setzen, steht ein eigenes Plug-in zur Verfügung [5].
Bild 2: Mithilfe der VirtualBox-Appliance gestaltet sich der Einstieg in Nix einfach.
Deployments mit NixOps
Um ein Image auf dem lokalen NixOS-Rechner bereitzustellen, auf dem das OS bereits ausgeführt wird, legen Sie eine einfache NIX-Datei an. Darin weisen Sie der Option "deployment.targetHost" die IP-Adresse beziehungsweise den Hostnamen des Rechners zu. Die Option "deployment.targetEnv" bleibt in diesem Fall undefiniert. Mit der Datei "einfachesBeispiel-nixos.nix" bestimmen Sie das physische NixOS-Zielnetzwerk:
 
{
    webserver =
        { config, pkgs, ... }:
        { deployment.targetHost = "192.168.1.1";
        };
}
Der Zugriff auf NixOps-basierte Systeme erfolgt mit dem Befehl nixops ssh <Systemname>. Sie sind allerdings nicht auf die Ausführung eines Befehls auf einem System beschränkt, sondern können diesen auf mehrere Systeme anwenden:
$ nixops ssh-for-each -d load-balancer-ec2 -- df /tmp
backend1...> /dev/xvdb 153899044 192084 145889336 1% /tmp
proxy......> /dev/xvdb 153899044 192084 145889336 1% /tmp
backend2...> /dev/xvdb 153899044 192084 145889336 1% /tmp
 
Die Ausführung erfolgt standardmäßig sequenziell, lässt sich mittels der Option "-p" aber auch parallelisieren.
Um den Rechnerstatus in einem Deployment zu prüfen, verwenden Sie nixops check. Dieser Prüfmechanismus ermittelt zunächst, ob das oder die angegebenen Systeme existieren, ob sie ausgeführt werden und ob sie über eine SSH-Verbindung erreichbar sind. Außerdem ermittelt die Prüffunktion angeschlossene Festplatten sowie ob diese verfügbar oder womöglich ausgefallen sind. Außerdem gibt die Check-Funktion die Namen aller "systemd"-Einheiten aus, die sich in einem ausgefallenen Zustand befinden. Die Ausgabe könnte für das oben gezeigte EC2-Netzwerk mit drei Maschinen wie in Listing 1 gezeigt lauten.
Listing 1: Ausfall des Rechners "Backend"
$ nixops check -d load-balancer-ec2
+----------------+--------------+-------------+----------------+------------------+---------------------+--------------------+----------+
| Name         | Exists  |  Up    | Reachable  |   Disks OK    |  Load avg.         |  Failed units     |   Notes |
+----------------+--------------+-------------+----------------+------------------+---------------------+--------------------+----------+
| backend1   | Yes      | Yes     |   Yes          |   Yes            |   0.030.030.05 |   httpd.service  |             |
| backend2   | Yes      | No      |   N/A          |   N/A          |                           |                       |             |
| proxy         | Yes      |  Yes   |   Yes           |   Yes            |    0.000.010.05  |                         |             |
+----------------+--------------+-------------+----------------+------------------+---------------------+--------------------+----------+
Listing 1 zeigt, dass der Apache-Webserver auf dem System "Backend1" ausgefallen ist und der Rechner "Backend2" überhaupt nicht reagiert. Kommt es zu einem Totalausfall, reparieren Sie das Deployment mit dem Befehl nixops deploy --check. NixOps erlaubt Ihnen auch die Definition von speziellen Netzwerkattributen für das gesamte Netzwerk, zum Beispiel:
 
{
     network = {
         description =
           "Netzwerkbeispiel";
         enableRollback = true;
     };
     defaults = {
         imports = [ ./einfach.nix ];
     };
 
     machine = { ... }: {};
}
 
Die Einstellung "defaults" wendet das gegebene NixOS-Modul auf alle im Netzwerk definierten Maschinen an. Mit "network.description" hinterlegen Sie eine Beschreibung zu Sinn und Zweck des Netzwerks. Die Option "network.enableRollback" setzen Sie auf "true", damit bei jedem Deployment ein neues Profil generiert wird und bei Bedarf ein Rollback mit dem Befehllnixops rollbackkausgeführt werden kann. Eine weitere praktische Möglichkeit stellt die Verwendung von Netzwerkargumenten dar. Dabei ist die Übernahme von Argumenten außerhalb des Nix-Ausdrucks möglich. Nachfolgendes Beispiel verwendet das Wartungsargument "maintenance" und dient im konkreten Fall der Aktivierung des HTTP-Servers:
 
{ maintenance ? false
}:
{
   machine ==
          { config, pkgs, ... }:
          { services.httpd.enable = maintenance
            ....
          };
}
 
Zur Übergabe netzwerkspezifischer Argumente nutzen Sie den Befehl set-args nixops, zum Beispiel:
$ nixops set-args --arg maintenance true -d test
Um die eingesetzten Argumente auszugeben, greifen Sie auf den Befehl nixops info -d test zurück, der Ihnen eine Ausgabe wie in Listing 2 dargestellt liefert. Haben Sie Änderungen an den Argumenten vorgenommen, stellen Sie mittels nixops deploy die neue Konfiguration bereit.
Listing 2: Netzwerktest
Network name: test
Network UUID: 634d6273-f9f6-11e2-a004-15393537e5ff
Network description: Unnamed NixOps network
Nix expressions: .../network-arguments.nix
Nix arguments: maintenance = true
+-----------------------+-----------------------+------------+----------------------+---------------------+
|   Name           |   Status               | Type     |   Resource Id     |   IP address     |
+-----------------------+-----------------------+------------+----------------------+---------------------+
|   machine       | Missing / New    |   none   |                           |                         | 
+-----------------------+-----------------------+------------+----------------------+---------------------+
Erweiterte NixOps-Funktionenn
Alle Dateien, die sich im Verzeichnis "/nix/store" befinden, sind für alle User des betreffenden Systems lesbar. Das Speichern geheimer Schlüsseln ist somit keine gute Idee. Dieses Problem lösen Sie mithilfe der Konfigurationsoption "deployment.keys", die NixOps getrennt vom Hauptkonfigurationsderivat für jede Maschine verwaltet. Um eine Konfiguration zu schützen, fügen Sie zu jeder Maschine folgenden Schlüssel hinzu:
 
{
machine ==
        { config, pkgs, ... }:
         {
          deployment.keys.my-secret.text = "mein_gemeiner_schluessel";
          deployment.keys.mein-geheimnis.user = "Benutzername";
          deployment.keys.mein-geheimnis.gruppe = "Gruppe";
          deployment.keys.mein-geheimnis.permissions = "0640";
     };
}
 
Dieses Setting generiert die Datei "/run/ keys/my-secret" samt spezifischen Inhalten, Besitzern und Berechtigungen. Bei Deployments mit mehreren Rechnern erweist es sich derweil als praktisch, dass die Konfiguration auf die eines anderen Knotens im selben Netzwerk zugreifen kann. Das ist beispielsweise dann sinnvoll, wenn Sie eine Portnummer nur einmal speichern möchten. Dazu nutzen Sie "nodes".
Zusammenspiel mit Docker
Ein weiteres Highlight: NixOS arbeitet hervorragend mit Docker zusammen. Da viele Cloudplattformen Docker-basierte Container-Hosting-Dienste offerieren, ist das Erstellen von Containern für einen bestimmten Dienst eine gängige Aufgabe beim Erzeugen reproduzierbarer Software. Dabei kommt Ihnen zugute, dass Sie Docker-Container mit Nix generieren können. Dazu müssen sowohl Nix als auch Docker installiert sein. Docker ist im Softwarearchiv "Nixpkgs" verfügbar, alternativ können Sie auch eine native Docker-Installation verwenden. Um einen ersten Container zu erzeugen, wählen Sie aus "Nixpkgs" die "dockerTools" aus:
 
{ pkgs ? import <nixpkgs> { }}, pkgsLinux ? import <nixpkgs> { system = "x86_64-linux"; }
  }:
pkgs.dockerTools.buildImage {
      name = "Mein_Docker_Container";
      config = {
         Cmd = [ "${pkgsLinux.hello}/bin/hello" ];
     };
}
 
Als Nächstes rufen Sie "dockerTools.buildImage" auf und spezifizieren verschiedene Parameter. Mit "name" weisen Sie dem Container eine Bezeichnung zu. Mit "config" geben Sie in Verbindung mit Cmd den Befehl an, der innerhalb des Containers ausgeführt werden soll, sobald das Image startet. Dann speichern Sie die Konfiguration in einer Nix-Datei, beispielsweise in "MeinDocker.nix". Um den Container zu erstellen, verwenden Sie den "Build"-Befehl nix-build MeinDocker.nix. Auf der Konsole können Sie den Build-Vorgang verfolgen. Zum Abschluss wird etwa folgende Ausgabe generiert:
 
Cooking the image...
Finished.
/nix/store/Hashwert-docker-image-MeinDocker.tar.gz
Um mit dem Container zu arbeiten, laden Sie diesen mit
docker load < result
Loaded image: MeinDocker:Hashwert
 
Zur Ausführung verwenden Sie den Befehl docker run -t MeinDocker:<Hashwert>. Alternativ können Sie auch zu Arion [6] greifen, einem Docker-Compose-Wrapper mit Nix-Unterstützung.
Fazit
Nix eröffnet ein völlig neues Feld beim Deployment – die Probleme mit Abhängigkeiten und beim vollständigen Rollback gehören der Vergangenheit an. Bei den ersten Gehversuchen mit NixOS sollten Sie jedoch dazu bereit sein, sich auf neue Konzepte einzulassen. Wer tiefer einsteigen möchte, kann sich verschiedener Quellen bedienen. Die Entwickler stellen unter [7] Dokumentationen zum Paketmanager und zum OS zur Verfügung. Auch lohnt sich ein Blick in das Community-Wiki zu NixOS [8]. (dr)Nix eröffnet ein völlig neues Feld beim Deployment – die Probleme mit Abhängigkeiten und beim vollständigen Rollback gehören der Vergangenheit an. Bei den ersten Gehversuchen mit NixOS sollten Sie jedoch dazu bereit sein, sich auf neue Konzepte einzulassen. Wer tiefer einsteigen möchte, kann sich verschiedener Quellen bedienen. Die Entwickler stellen unter [7] Dokumentationen zum Paketmanager und zum OS zur Verfügung. Auch lohnt sich ein Blick in das Community-Wiki zu NixOS [8].
(dr)
Link-Codes
[2] Anzahl und Aktualität installierbarer Pakete: https://repology.org/repositories/graphs/
[4] Nix-Downloadseite: https://nixos.org/download.html/
[7] Nix-Dokumentation: https://nixos.org/learn/
[8] Community-Wiki zu NixOS: https://nixos.wiki/