NixOS als Server-Betriebssystem: Deklarative Infrastruktur
Das Fundament unter den Containern
Wer über Kubernetes spricht, spricht über Container, Pods, Deployments und Services. Worüber erstaunlich selten gesprochen wird, ist das Betriebssystem, auf dem das alles läuft. Die meisten Kubernetes-Setups basieren auf Ubuntu Server oder Debian -- solide Distributionen, die ihren Dienst zuverlässig tun. Aber nach einigen Jahren Betrieb auf genau diesen Distributionen haben wir gemerkt, dass das Host-OS der Teil unserer Infrastruktur ist, der uns die meisten Kopfschmerzen bereitet.
Nicht wegen Instabilität. Sondern wegen mangelnder Reproduzierbarkeit. Wenn ein Server nach einem halben Jahr im Betrieb anders aussieht als am Tag der Einrichtung -- weil jemand manuell ein Paket installiert hat, weil ein apt upgrade eine Konfigurationsdatei überschrieben hat, weil ein Dienst aktiviert wurde, den niemand mehr zuordnen kann -- dann hat man ein Problem. Nicht heute, aber beim nächsten Debugging-Einsatz um drei Uhr morgens.
Seit gut einem Jahr setzen wir NixOS als Host-Betriebssystem für unsere Kubernetes-Nodes ein. In diesem Beitrag erklären wir, warum wir diesen Schritt gemacht haben und was sich dadurch verändert hat.
Was NixOS anders macht
NixOS ist eine Linux-Distribution, die auf dem funktionalen Paketmanager Nix aufbaut. Das klingt zunächst nach einer Nische für Enthusiasten, ist aber bei näherer Betrachtung ein fundamental anderer Ansatz für Systemkonfiguration.
Der zentrale Unterschied zu Debian, Ubuntu oder CentOS: Der gesamte Zustand eines NixOS-Systems wird in einer einzigen Konfigurationsdatei beschrieben -- configuration.nix. Installierte Pakete, aktivierte Dienste, Netzwerk-Konfiguration, Firewall-Regeln, Benutzer, SSH-Schlüssel -- alles steht deklarativ in dieser Datei. Es gibt kein imperatives apt install oder systemctl enable. Wenn etwas nicht in der Konfiguration steht, existiert es nicht auf dem System.
Das hat weitreichende Konsequenzen. Wenn wir die configuration.nix von Server A auf einen frischen Server B kopieren und nixos-rebuild switch ausführen, erhalten wir einen identischen Server. Nicht ähnlich, nicht fast gleich -- identisch. Dieselben Pakete in denselben Versionen, dieselben Dienste mit denselben Einstellungen.
Wer mit Infrastructure as Code arbeitet, erkennt das Prinzip sofort: Die Konfigurationsdatei ist die Single Source of Truth. Der Unterschied zu Ansible oder Puppet ist, dass NixOS keine Konvergenz anstrebt, sondern einen exakten Zustand garantiert. Es gibt keinen Drift, weil das System gar keinen anderen Zustand einnehmen kann als den konfigurierten.
Unsere K3s-Node-Konfiguration
Wir nutzen NixOS ausschließlich als Host-Betriebssystem. Unsere Anwendungen laufen nicht als Nix-Pakete, sondern als Container auf Kubernetes. NixOS ist das Fundament, K3s läuft darauf, und in K3s laufen unsere Workloads als Docker-Container. Diese Trennung ist uns wichtig: Nix ist hervorragend für deklarative Systemkonfiguration, aber unser Anwendungs-Deployment bleibt bei Kubernetes und Helm -- den Werkzeugen, die wir in früheren Beiträgen ausführlich beschrieben haben.
Eine typische configuration.nix für einen unserer K3s-Nodes sieht vereinfacht so aus:
{ config, pkgs, ... }:
{
# Netzwerk-Konfiguration
networking.hostName = "k3s-node-01";
networking.interfaces.ens3.ipv4.addresses = [{
address = "10.0.1.10";
prefixLength = 24;
}];
networking.defaultGateway = "10.0.1.1";
networking.nameservers = [ "10.0.1.1" ];
# Firewall: K3s-relevante Ports freigeben
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 6443 10250 ];
allowedUDPPorts = [ 8472 ];
};
# SSH-Zugang
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
};
# K3s als Service aktivieren
services.k3s = {
enable = true;
role = "agent";
serverAddr = "https://10.0.1.5:6443";
tokenFile = "/etc/k3s/token";
};
# Grundlegende Pakete auf dem Host
environment.systemPackages = with pkgs; [
vim
htop
curl
kubectl
];
# Benutzer
users.users.deploy = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAA... deploy@encircle360"
];
};
# NixOS-Version pinnen
system.stateVersion = "21.11";
}
Das ist die vollständige Konfiguration eines K3s-Worker-Nodes. Netzwerk, Firewall, SSH, K3s-Service, installierte Pakete, Benutzer -- alles in einer Datei. Wer NixOS noch nie gesehen hat, kann diese Konfiguration trotzdem lesen und verstehen, was auf dem Server läuft. Das ist ein erheblicher Vorteil gegenüber der Kombination aus Bash-Skripten, Ansible-Playbooks und manuellen Anpassungen, die wir vorher hatten.
Die aktuelle NixOS-Version 21.11 ("Porcupine"), die wir einsetzen, bringt K3s als Modul direkt mit. Das war nicht immer so -- in früheren Versionen musste man K3s manuell als systemd-Service konfigurieren. Dass die NixOS-Community K3s als First-Class-Modul aufgenommen hat, zeigt, wie ernst die Distribution im Server-Bereich genommen wird.
Atomare Upgrades und Generationen
Der zweite große Vorteil von NixOS neben der deklarativen Konfiguration sind atomare Upgrades. Wenn wir eine Änderung an der configuration.nix vornehmen und nixos-rebuild switch ausführen, passiert Folgendes: Nix berechnet den neuen Systemzustand, lädt die benötigten Pakete herunter, baut die Konfiguration zusammen -- und aktiviert den neuen Zustand atomar. Es gibt keinen Zustand dazwischen, in dem das System halb alt und halb neu ist.
# Konfiguration anpassen
sudo vim /etc/nixos/configuration.nix
# Neuen Systemzustand anwenden
sudo nixos-rebuild switch
Jedes nixos-rebuild switch erzeugt eine neue sogenannte Generation. Das System behält die vorherigen Generationen, und jede Generation ist ein vollständiger, bootfähiger Systemzustand. Im GRUB-Bootmenü sieht man die letzten Generationen als separate Einträge.
Das bedeutet: Wenn ein Upgrade schiefgeht -- ein Kernel-Update verursacht Probleme, ein Service startet nicht mehr, was auch immer -- booten wir einfach die vorherige Generation. Kein manuelles Downgrade, kein Suchen nach der alten Paketversion, kein Hoffen, dass apt den alten Zustand wiederherstellen kann. Ein Neustart mit der vorherigen Generation, und der Server läuft wie vorher.
# Zur vorherigen Generation zurückkehren
sudo nixos-rebuild switch --rollback
# Alle verfügbaren Generationen auflisten
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
In der Praxis haben wir dieses Rollback bisher dreimal genutzt. Jedes Mal war es ein Vorgang von unter zwei Minuten. Auf unseren Ubuntu-Servern hätte die Diagnose allein länger gedauert.
Vergleich mit Ubuntu und Debian
Wir haben NixOS nicht gewählt, weil Ubuntu oder Debian schlecht sind. Beide sind bewährte Server-Distributionen mit exzellenter Dokumentation und riesiger Community. Aber sie verfolgen einen grundlegend anderen Ansatz.
Zustandsverwaltung. Ubuntu und Debian verwalten ihren Zustand imperativ. Man installiert Pakete, aktiviert Services, editiert Konfigurationsdateien -- und der aktuelle Zustand ist das Ergebnis aller bisherigen Aktionen. Wer nach einem Jahr wissen will, warum ein bestimmtes Paket installiert ist, muss in Logs suchen oder raten. Bei NixOS ist die Antwort immer in der configuration.nix.
Reproduzierbarkeit. Einen Ubuntu-Server exakt zu reproduzieren, erfordert externe Werkzeuge wie Ansible, Puppet oder Terraform. Bei NixOS ist die Reproduzierbarkeit eingebaut. Die Konfigurationsdatei ist gleichzeitig die Dokumentation und das Deployment-Skript.
Upgrades. apt upgrade auf einem Ubuntu-Server kann funktionieren -- oder auch nicht. Teilweise aktualisierte Systeme, Konflikte zwischen Abhängigkeiten, überschriebene Konfigurationsdateien -- jeder, der länger als ein Jahr Ubuntu-Server betrieben hat, kennt diese Probleme. NixOS-Upgrades sind atomar und reversibel.
Allerdings hat NixOS auch seinen Preis: Die Lernkurve ist steil. Die Nix-Sprache ist gewöhnungsbedürftig, die Dokumentation ist zwar besser geworden, aber immer noch nicht auf dem Niveau von Debians oder Ubuntus Wiki. Und wenn man ein Problem hat, das über die Standardkonfiguration hinausgeht, sind Erfahrungsberichte im Netz dünner gesät. Wir haben anfangs durchaus Zeit investiert, um das Nix-Ökosystem zu verstehen. Diese Investition hat sich gelohnt, aber sie war real.
Warum nicht Nix für alles?
Eine Frage, die immer wieder aufkommt: Wenn NixOS so gut ist, warum laufen unsere Anwendungen dann nicht auch als Nix-Pakete? Warum der Umweg über Kubernetes und Container?
Die Antwort ist pragmatisch. NixOS ist hervorragend als Host-Betriebssystem: stabil, reproduzierbar, wartungsarm. Aber unser Anwendungs-Deployment-Workflow basiert auf Docker-Images, Helm Charts und Kubernetes-Manifesten. Unsere Entwickler bauen Container, unsere CI-Pipeline erzeugt Container, unsere Kunden verstehen Container. Das gesamte Tooling -- Monitoring, Logging, Secrets-Management, Networking -- ist auf Kubernetes ausgelegt.
NixOS als Host-OS und Kubernetes als Anwendungsplattform -- das ist keine Kompromisslösung, sondern eine bewusste Schichtentrennung. Jede Schicht macht das, was sie am besten kann. NixOS sorgt dafür, dass der Host reproduzierbar, aktuell und stabil ist. Kubernetes sorgt dafür, dass die Anwendungen skalieren, aktualisiert werden und ausfallsicher laufen.
Fazit
NixOS hat unsere Server-Infrastruktur spürbar verbessert. Die deklarative Konfiguration eliminiert Konfigurationsdrift. Atomare Upgrades und Generationen machen Updates sicher und reversibel. Die Reproduzierbarkeit ermöglicht es uns, neue Nodes in Minuten statt Stunden aufzusetzen.
Der Weg dorthin war nicht ohne Aufwand. Nix zu lernen braucht Zeit, und nicht jedes Problem lässt sich per Stackoverflow-Suche lösen. Aber für den spezifischen Anwendungsfall -- Server-Betriebssystem für Kubernetes-Nodes -- überwiegen die Vorteile deutlich. Unsere Nodes sind vorhersagbar, unsere Upgrades sind schmerzfrei, und wenn doch etwas schiefgeht, sind wir einen Reboot von der vorherigen, funktionierenden Version entfernt.
Wer Kubernetes auf eigener Hardware betreibt und mit der Pflege des Host-Betriebssystems hadert, sollte NixOS ernsthaft in Betracht ziehen. Die Lernkurve ist real, aber das Ergebnis ist eine Infrastruktur, die sich so verhält, wie man es von Infrastructure as Code erwartet -- nicht nur auf der Kubernetes-Ebene, sondern bis hinunter zum Betriebssystem.
Geschrieben von
Patrick HütterGründer & Software-Architekt
Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.
Das könnte dich auch interessieren
CI-Runner in der microVM: Docker-Builds mit Kata Containers auf Kubernetes
31.08.2026 · 10 Min. Lesezeit
GitOps mit Helmfile und Kyverno: Unser Deployment-Workflow
10.03.2025 · 7 Min. Lesezeit
K3s und KubeVirt: Konvergente Infrastruktur auf Bare Metal
20.01.2025 · 6 Min. Lesezeit