System

systemd

systemd ist der Standard-Init-Prozess und System-Manager in fast allen modernen Linux-Distributionen (z. B. Ubuntu, Debian, RHEL, Fedora, Arch Linux). Es wurde 2010 von Lennart Poettering und Kay Sievers entwickelt, um das alte, sequenzielle SysVinit-System abzulösen.

1. Was ist systemd und welche Aufgaben hat es?

Der Prozess systemd startet direkt nach dem Linux-Kernel und erhält immer die Process ID (PID) 1. Als Mutter aller Prozesse verfolgt und verwaltet es den gesamten Zustand des Betriebssystems.

Haupteigenschaften

2. Das Konzept der "Units"

In systemd wird alles über sogenannte Units verwaltet. Jede Unit repräsentiert eine Systemressource oder eine Aufgabe. Es gibt verschiedene Unit-Typen, erkennbar an ihrer Dateiendung:

Unit-Typ Endung Beschreibung
Service .service Verwaltet Hintergrundprozesse / Daemons (z. B. nginx.service, ssh.service).
Target .target Gruppiert andere Units (entspricht den alten Runleveln, z. B. multi-user.target).
Socket .socket Hört auf Netzwerksockets oder IPC-Pipes zur Socket-Aktivierung.
Timer .timer Zeitgesteuerter Ausführungstrigger (Ersatz für klassische Cronjobs).
Mount / Automount .mount / .automount Steuert das Einhängen von Dateisystemen (/etc/fstab-Ersatz).
Path .path Überwacht Dateien/Ordner auf Änderungen und startet daraufhin Services.
Slice / Scope .slice / .scope Verwaltet Ressourcenbeschränkungen (CPU, RAM) via cgroups.

3. Dateipfade & Speicherorte für Konfigurationen

Konfigurationsdateien sind in einer strikten Hierarchie organisiert:

  1. /lib/systemd/system/ (oder /usr/lib/systemd/system/)

    • Hier liegen die vom Paketmanager (APT, DNF, Pacman) installierten Standard-Units.

    • Wichtig: Diese Dateien niemals manuell bearbeiten, da sie bei Updates überschrieben werden!

  2. /run/systemd/system/

    • Zur Laufzeit generierte Units (z. B. temporäre Einstellungen).

  3. /etc/systemd/system/

    • Der Ort für eigene Konfigurationen.

    • Hier abgelegte Dateien haben Vorrang vor den Dateien in /lib/systemd/system/.

4. Aufbau einer .service-Unit-Datei

Eine Service-Datei besteht typischerweise aus drei Hauptabschnitten: [Unit], [Service] und [Install].

Ini, TOML
[Unit]
Description=Mein eigener Python-Webservice
After=network.target remote-fs.target
Wants=postgresql.service

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/var/www/myextension
ExecStart=/usr/bin/python3 /var/www/myextension/app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s

# Sicherheits-Hardening (Optional)
ProtectSystem=full
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Die Abschnitte im Detail:

5. Wichtige Befehle zur Verwaltung

Die Steuerung von systemd erfolgt über das Befehlszeilenwerkzeug systemctl.

Steuerung von Diensten

Bash
# Status eines Dienstes prüfen
systemctl status mein-dienst.service

# Dienst sofort starten / stoppen / neustarten
systemctl start mein-dienst
systemctl stop mein-dienst
systemctl restart mein-dienst

# Konfiguration neu laden ohne den Dienst zu stoppen
systemctl reload mein-dienst

# Autostart beim Booten aktivieren / deaktivieren
systemctl enable mein-dienst
systemctl disable mein-dienst

# Autostart aktivieren UND sofort starten
systemctl enable --now mein-dienst

Konfiguration bearbeiten und laden

Bash
# Erstellt/Editiert einen Override in /etc/systemd/system/
systemctl edit mein-dienst.service

# systemd-Daemon anweisen, Geänderte Unit-Dateien neu einzulesen (SEHR WICHTIG!)
systemctl daemon-reload

Targets und Systemzustände

Bash
# Aktuelles Standard-Target anzeigen
systemctl get-default

# Auf grafische Oberfläche (Runlevel 5) umstellen
systemctl set-default graphical.target

# Auf Konsolenmodus (Runlevel 3) umstellen
systemctl set-default multi-user.target

6. Logging mit journald

systemd-journald sammelt zentral alle Systemnachrichten (Kernel-Logs, stdout/stderr von Services, Syslog). Der Zugriff erfolgt über journalctl.

Bash
# Gesamtes Protokoll seit dem letzten Bootvorgang anzeigen
journalctl -b

# Live-Logs eines spezifischen Dienstes verfolgen (wie tail -f)
journalctl -u mein-dienst.service -f

# Logs der letzten 2 Stunden anzeigen
journalctl --since "2 hours ago"

# Nur Fehler-Nachrichten anzeigen
journalctl -u mein-dienst.service -p err

7. Weitere wichtige Komponenten des systemd-Ökosystems

systemd ist längst mehr als nur ein Init-System, sondern eine Suite von Werkzeugen:

cgroups

Control Groups (cgroups) spielen eine zentrale Schlüsselrolle in systemd. Sie bilden das fundamentale Kernel-Feature, mit dem systemd Prozesse gruppiert, verfolgt, isoliert und bezüglich ihrer Systemressourcen beschränkt.

Vor systemd nutzten klassische Init-Systeme (wie SysVinit) Prozess-IDs (PIDs) und PID-Dateien (.pid), um zu verfolgen, welche Prozesse zu welchem Dienst gehörten. Wenn ein Dienst Tochterprozesse erzeugte (z. B. ein Apache-Webserver mit vielen Worker-Prozessen) und abstürzte oder unsauber beendet wurde, blieben verwaiste Prozesse („Zombie-Prozesse“) im System zurück.

Mit cgroups gelingt systemd die vollständige und lückenlose Prozesskontrolle.

1. Die Kernaufgaben von cgroups in systemd

A. Lückenloses Prozess-Tracking (Keine "Verwaisten Prozesse")

Jeder Dienst, den systemd startet, wird in seine eigene cgroup platziert.

B. Ressourcenbeschränkung und -kontrolle (Resource Limiting)

Über cgroups kann systemd genau festlegen, wie viel Hardware-Ressourcen eine Unit oder eine Gruppe von Units verbrauchen darf. Sie können das direkt in der Unit-Datei konfigurieren:

2. Die cgroup-Hierarchie in systemd

systemd baut beim Systemstart eine baumartige Struktur unter dem virtuellen Dateisystem /sys/fs/cgroup/ auf. Diese Struktur unterteilt die Last des Systems in drei Hauptzweige (Slices):

Plaintext
/sys/fs/cgroup/
├── system.slice      --> Alle Systemdienste (z. B. nginx, sshd, docker)
├── user.slice        --> Angemeldete Benutzer & deren Sitzungen/Prozesse
└── machine.slice     --> Virtuelle Maschinen (KVM/QEMU) & LXC/Systemd-Container

3. Befehle zum Anzeigen und Steuern von cgroups

Der Befehl systemd-cgls (cgroup list)

Zeigt den gesamten cgroup-Baum des Systems inklusive aller laufenden PIDs an:

Bash
systemd-cgls

Der Befehl systemd-cgtop (cgroup top)

Ähnlich wie der normale top-Befehl, zeigt aber den Live-Ressourcenverbrauch (CPU, RAM, I/O) aufgeteilt nach cgroups/Services an:

Bash
systemd-cgtop

Ressourcen zur Laufzeit anpassen

Sie können Limits eines laufenden Dienstes dynamisch anpassen, ohne die Unit-Datei manuell zu bearbeiten:

Bash
# Setzt das Arbeitsspeicher-Limit für Nginx sofort auf 1 GB
systemctl set-property nginx.service MemoryMax=1G

4. Die Bedeutung von cgroups für Container (Docker, Podman)

Cgroups sind zusammen mit Linux Namespaces die fundamentale Grundlage moderner Container-Technologien:

Da systemd cgroups nativ verwaltet, integrieren sich Container-Runtimes wie Docker, Podman oder containerd meist direkt mit dem systemd-cgroup-Treiber.

namespaces

Linux Namespaces sind ein fundamentales Feature des Linux-Kernels, das die Grundlage für moderne Container-Technologien (wie Docker, Podman oder Kubernetes) bildet.

Einfach ausgedrückt: Namespaces isolieren Systemressourcen für bestimmte Prozesse.

Ein Prozess (oder eine Gruppe von Prozessen), der sich in einem eigenen Namespace befindet, sieht nur die Ressourcen, die diesem Namespace zugewiesen sind. Aus der Perspektive des Prozesses sieht es so aus, als hätte er ein eigenes, völlig isoliertes Linux-System für sich allein.

1. Die Analogie: Das Bürogebäude

Stell dir das Linux-Betriebssystem wie ein großes Bürogebäude vor:

2. Die 8 Typen von Linux Namespaces

Linux stellt verschiedene Arten von Namespaces zur Verfügung. Jeder Typ isoliert eine ganz bestimmte Systemressource:

Namespace-Typ Isolierte Ressource Was der Prozess sieht
PID (Process ID) Prozess-IDs Einen eigenen Prozess-Baum. Prozess #1 im Namespace ist die "Initiator-PID", obwohl er auf dem Host eine ganz andere PID hat.
NET (Network) Netzwerk-Stack Eigene Netzwerk-Interfaces (eth0, lo), eigene Routing-Tabellen, Firewall-Regeln (iptables) und Port-Belegungen.
MNT (Mount) Dateisystem-Mountpoints Einen eigenen Mount-Baum. Änderungen an Dateisystem-Mounts betreffen nur diesen Namespace.
UTS (UNIX Timesharing) Hostname & Domain Einen eigenen Hostnamen (z. B. my-container) unabhängig vom tatsächlichen Host-Namen.
IPC (Inter-Process Comm.) Shared Memory & Queues Eigene System V IPC-Objekte und POSIX Message Queues (Prozesse aus verschiedenen IPC-Namespaces können nicht direkt über Shared Memory kommunizieren).
USER (User IDs) Benutzer & Gruppen (UID/GID) Ein normaler Benutzer auf dem Hostsystem kann Root (UID 0) innerhalb seines User-Namespaces sein (wichtig für die Sicherheit!).
CGROUP Control Groups Sichtbarkeit der eigenen Cgroup-Hierarchie (Ressourcen-Limits wie CPU/RAM).
TIME Systemzeit Abweichende Systemzeiten/Uhrzeiten (z. B. für Testumgebungen mit simulierten Datumswerten).

3. Wie Namespaces technisch funktionieren

Im Kernel ist jeder Prozess durch eine Struktur namens task_struct repräsentiert. Diese Struktur enthält Zeiger auf die jeweiligen Namespaces, zu denen der Prozess gehört.

System Calls zur Steuerung:

Es gibt drei primäre Systemaufrufe (System Calls) in C, um mit Namespaces zu interagieren:

  1. clone(): Erstellt einen neuen Kind-Prozess (ähnlich wie fork()), kann aber über Flags (z. B. CLONE_NEWNET, CLONE_NEWPID) direkt neue Namespaces für das Kind anlegen.

  2. unshare(): Erlaubt einem bereits laufenden Prozess, sich von seinen bisherigen Namespaces zu lösen und neue zu erstellen.

  3. setns(): Klinkt einen Prozess in einen bereits existierenden Namespace ein (das nutzt z. B. docker exec, um sich in den Namespace eines laufenden Containers einzuklinken).

4. Praxistest: Namespaces auf der Kommandozeile ausprobieren

Du kannst Namespaces direkt mit dem Utility-Befehl unshare in deiner Linux-Shell testen.

Beispiel 1: Einen eigenen Hostnamen-Namespace (UTS) erstellen

Bash
# Erstelle ein neues UTS-Namespace und starte darin eine Bash
sudo unshare --uts bash

# Ändere den Hostnamen innerhalb des Namespaces
hostname isolierter-host

# Überprüfe den Namen:
hostname
# Output: isolierter-host
Wenn du nun ein zweites Terminal auf deinem Rechner öffnest und hostname eingibst, siehst du weiterhin deinen ursprünglichen Hostnamen.

Beispiel 2: Einen eigenen Prozess-Namespace (PID) erstellen

Bash
# Erstelle einen neuen PID- und Mount-Namespace
sudo unshare --pid --mount-proc --fork bash

# Welches ist die PID dieses Prozesses im Namespace?
ps aux
Du wirst sehen, dass deine bash plötzlich die PID 1 hat und du keine anderen Prozesse deines echten Rechners mehr sehen kannst.

5. Namespaces vs. Cgroups vs. Chroot: Wo liegt der Unterschied?

Oft werden diese Begriffe im Zusammenhang mit Containern zusammengeworfen. Sie erfüllen jedoch unterschiedliche Aufgaben:

Ein Docker-Container ist im Grunde nichts anderes als ein ganz normaler Linux-Prozess, für den der Kernel mehrere Namespaces und Cgroups kombiniert hat.

Linux Namespaces sind die Zauberei hinter der Container-Revolution. Sie sorgen dafür, dass Prozesse gegeneinander abgeschottet werden, ohne dass dafür ein schwerfälliger Hypervisor oder ein zweites Betriebssystem (wie bei einer virtuellen Maschine) geladen werden muss. Das macht Container extrem leichtgewichtig und schnell.
User Namespaces gehören zu den mächtigsten Sicherheitsfeatures des Linux-Kernels. Sie ermöglichen die Trennung und Zuordnung (Mapping) von Benutzer- und Gruppen-IDs (UIDs/GIDs) zwischen dem Host-System und dem isolierten Namespace.

Kurz gesagt: Ein Prozess kann innerhalb seines User Namespaces volle Root-Rechte (UID 0) besitzen, ist aber auf dem eigentlichen Host-System ein völlig gewöhnlicher, privilegienloser Benutzer (z. B. UID 1000).

1. Das Kernprinzip: UID/GID Mapping

Das Herzstück der User Namespaces ist das sogenannte UID Mapping. Der Linux-Kernel übersetzt dabei die Benutzer-IDs dynamisch an den Grenzen des Namespaces.

Ein vereinfachtes Mapping-Schema sieht so aus:

Plaintext
[ Innerhalb des User Namespaces ]    --->    [ Auf dem Host-System ]
  UID 0   (Root im Container)       --->      UID 1000  (Dein normaler User)
  UID 1   (Sub-User 1)              --->      UID 100001
  UID 2   (Sub-User 2)              --->      UID 100002
Wenn ein Prozess im Container versucht, eine Datei zu erstellen, sieht das für den Prozess im Container so aus, als gehöre sie root (UID 0). Schaust du dir dieselbe Datei jedoch vom Host-System aus an, gehört sie in Wahrheit dem Benutzer 1000.

Wo wird das konfiguriert?

Der Kernel verwaltet dieses Mapping über zwei Dateien im /proc-Dateisystem des jeweiligen Prozesses:

  • /proc/<PID>/uid_map

  • /proc/<PID>/gid_map

Dort ist nach folgendem Muster hinterlegt, wie die UIDs übersetzt werden:
ID-inside-NS ID-outside-NS Length

Beispiel: 0 100000 65536 bedeutet:

  • UID 0 im Namespace entspricht UID 100000 auf dem Host.

  • Es werden insgesamt 65536 fortlaufende UIDs gemappt (also von UID 0–65535 im Container auf UID 100000–165535 auf dem Host).

2. Warum das herkömmliche Docker ein Sicherheitsrisiko war

In der klassischen Docker-Architektur läuft der Docker-Daemon (dockerd) als echter Root-User auf dem Host-System.

Wenn du einen Standard-Container startest, ist der Prozess im Container standardmäßig ebenfalls als Root (UID 0) unterwegs – und zwar ohne User-Namespace-Mapping:

  • Container Root (UID 0) == Host Root (UID 0)

Das Gefahrenszenario (Container Escape):

Gelingt es einem Angreifer durch eine Sicherheitslücke im Anwendungscode oder im Kernel, aus dem Container auszubrechen (ein sogenannter Container Escape), landet er direkt auf dem Host-System. Da seine UID auf dem Host ebenfalls 0 ist, hat er sofort die vollständige Kontrolle über den gesamten Server.

3. Wie Rootless Docker mit User Namespaces maximale Sicherheit schafft

Bei Rootless Docker (sowie nativ bei Podman) läuft schon der Docker-Daemon selbst als unprivilegierter Benutzer (z. B. UID 1000) auf dem Host. Wenn nun ein Container gestartet wird, kommt zwingend ein neuer User Namespace zum Einsatz.

Das bietet drei entscheidende Sicherheitsvorteile:

A. Keine Host-Root-Rechte bei einem Container-Escape

Bricht ein Angreifer aus einem Rootless-Container aus, sieht der Host-Kernel seine Aktionen nicht als root, sondern als gewöhnlicher Benutzer (z. B. UID 1000 oder UID 100000). Der Angreifer kann:

  • Keine Systemdateien wie /etc/shadow lesen oder verändern.

  • Keine neuen Kernel-Module laden.

  • Keine Festplatten neu partitionieren oder das System herunterfahren.

  • Keine Prozesse anderer Host-Benutzer manipulieren.

B. Unprivilegierte Erstellung von Namespaces

Normalerweise erfordert das Erstellen von Netzwerk- oder Mount-Namespaces Root-Rechte. User Namespaces sind die einzige Ausnahme: Ein normaler Benutzer darf ein neues User Namespace erstellen und erlangt innerhalb dieses Namespaces die Rechte (Capabilities) zum Erstellen aller anderen Namespace-Typen (NET, MNT, PID etc.).

Dadurch können normale Benutzer vollwertige Container starten, ohne dass dafür jemals ein Root-Prozess beteiligt sein muss.

C. Schutz vor Privilege Escalation über Suid-Binaries oder Sockets

Sollte im Container eine SUID-Datei (wie passwd oder sudo) missbraucht werden, um im Container Root-Rechte zu erlangen, bleibt dieser Rechtegewinn strikt auf die Grenzen des User Namespaces beschränkt.

4. Einschränkungen von User Namespaces & Rootless Docker

Obwohl User Namespaces die Sicherheit drastisch erhöhen, bringen sie bauartbedingte Einschränkungen mit sich:

  1. Privilegierte Ports (< 1024): Ein Rootless-Container kann auf dem Host standardmäßig keine Ports unterhalb von 1024 (z. B. Port 80 oder 443) direkt anbinden, da dies auf dem Host weiterhin dem echten Root vorbehalten ist (lässt sich via Kernel-Parameter net.ipv4.ip_unprivileged_port_start anpassen).

  2. Dateirechte auf Mounts: Wenn Host-Verzeichnisse in den Container gemountet werden (Bind Mounts), muss das UID-Mapping berücksichtigt werden. Dateien, die auf dem Host dem normalen User gehören, werden im Container eventuell als fremde UID angezeigt, wenn das Mapping nicht exakt passt.

  3. Cgroup v2 erforderlich: Für eine vollständige Ressourcenbegrenzung (CPU, RAM) in Rootless-Umgebungen wird zwingend ein modernes Linux-System mit Cgroup v2 vorausgesetzt.

User Namespaces entkoppeln die Macht der Root-Rechte innerhalb einer isolierten Umgebung von den tatsächlichen Rechten auf dem zugrundeliegenden Betriebssystem. Sie verwandeln das Konzept von Docker vom "Alles-oder-Nichts-Prinzip" (Root-Daemon) hin zu einem echten Least-Privilege-Modell.

System Load

Die System Load (oder Load Average) eines Linux-Servers gibt an, wie stark die Systemressourcen – primär die CPU und I/O-Wartezeiten – über einen bestimmten Zeitraum ausgelastet sind. Sie misst die durchschnittliche Anzahl von Prozessen, die sich im Zustand "ausführbar" (runnable) oder "nicht unterbrechbar" (uninterruptible) befinden.

Die 3 Werte der Load Average

Wenn man Befehle wie uptime, top oder w ausführt, sieht man drei Zahlenwerte für die Load Average:

Plaintext
load average: 0.75, 1.20, 2.05
Diese drei Werte repräsentieren den geglätteten Durchschnitt der Load über drei verschiedene Zeitintervalle:

  1. 1 Minute (0.75)

  2. 5 Minuten (1.20)

  3. 15 Minuten (2.05)

Anhand der Entwicklung lässt sich erkennen, wohin die Tendenz geht:

Was bedeuten die Prozess-Zustände genau?

Linux zählt für die Berechnung der Load zwei Prozess-Zustände im Kernel:

  1. Running / Runnable (R): Prozesse, die entweder gerade auf einer CPU ausgeführt werden oder darauf warten, von der CPU Zeit zugewiesen zu bekommen (Prozessorschlange).

  2. Uninterruptible Sleep (D): Prozesse, die auf eine I/O-Ressource warten (z. B. Lesen/Schreiben auf der Festplatte/SSD, Dateizugriffe über NFS, Netzwerk-I/O). Während sie warten, können sie nicht unterbrochen werden.

Wichtig: Eine hohe Load bedeutet nicht zwangsläufig, dass die CPU zu 100 % ausgelastet ist. Eine langsame oder blockierte Festplatte kann viele Prozesse in den D-Zustand versetzen und die Load extrem in die Höhe treiben, obwohl die CPU fast nichts zu tun hat (I/O Wait).

Wie interpretiert man die Zahl? (CPU-Kerne berücksichtigen)

Eine nackte Load-Zahl lässt sich nur im Kontext der Anzahl der verfügbaren CPU-Kerne (bzw. Threads) bewerten.

Zustand 1 CPU-Kern 4 CPU-Kerne Interpretation
Unterauslastet < 0.70 < 2.80 Das System hat reichlich Reserven.
Optimal 0.70 – 1.00 2.80 – 4.00 Hohe Effizienz ohne Wartezeiten/Staus.
Überlastet > 1.00 > 4.00 Prozesse müssen warten; Reaktionszeiten steigen.
Die Anzahl der verfügbaren logischen Kerne lässt sich auf dem Server wie folgt abfragen:

Bash
nproc

Der Unterschied zwischen CPU-Auslastung (%) und Load

Diagnose-Befehle zur Überprüfung

Um herauszufinden, warum die Load hoch ist, bieten sich folgende Terminal-Werkzeuge an:

  1. uptime / w: Schneller Überblick über die 1-, 5- und 15-Minuten-Load.

  2. top / htop: Zeigt Echtzeit-Prozesse, CPU-Auslastung sowie den Zustand %wa (I/O-Wait).

  3. dstat / iostat: Hilft festzustellen, ob eine Festplatte oder SSD der Flaschenhals ist.