71 Punkte von GN⁺ 2026-01-21 | 9 Kommentare | Auf WhatsApp teilen
  • Selbst wenn man docker run ubuntu ausführt, wird der Linux-Kernel des Hosts gemeinsam genutzt, und Ubuntu liefert nur User-Space-Werkzeuge
  • Das Ergebnis von uname -r zeigt die Kernel-Version des Hosts, während nur /etc/os-release Ubuntu-Informationen anzeigt
  • VMs besitzen jeweils einen eigenen Kernel und benötigen mehrere Minuten zum Booten, während Container innerhalb von Millisekunden starten und sich den Host-Kernel über Isolierung auf OS-Ebene ohne Hardware-Virtualisierung teilen, was den Overhead gering hält
  • Dank der Stabilität der Linux-System-Call-ABI können Container verschiedener Distributionen auf demselben Kernel laufen
  • In einer Umgebung mit 16 GB RAM liegt die praktische Obergrenze bei etwa 50–100 leichten Containern, 10–30 mittelgroßen und 5–10 großen Containern
  • Dieses Architekturverständnis ist wichtig, weil Kernel-Schwachstellen alle Container betreffen und die Wahl des Basis-Images direkte Auswirkungen auf Kompatibilität und Sicherheit hat

Was es bedeutet, „Ubuntu auszuführen“

  • Führt man docker run ubuntu:22.04 aus, erhält man einen Bash-Prompt, der wie Ubuntu aussieht, und kann apt update sowie Paketinstallationen ausführen
  • Führt man jedoch innerhalb des Containers uname -r aus, wird die Kernel-Version des Hosts angezeigt (z. B. 6.5.0-44-generic)
  • Die Datei /etc/os-release zeigt zwar Ubuntu 22.04 an, aber der Kernel gehört zum Host-System, und der „Ubuntu“-Teil ist lediglich das Dateisystem, das den User Space bildet

Container vs. virtuelle Maschinen: Architekturvergleich

  • VMs virtualisieren die Hardware, Container virtualisieren das Betriebssystem
  • Wichtige Unterschiede:
    • Kernel: VMs besitzen jeweils einen eigenen Kernel, Container teilen sich den Host-Kernel
    • Boot-Zeit: VMs mehrere Minuten, Container Millisekunden
    • Speicher-Overhead: VMs 512 MB–4 GB, Container 1–10 MB
    • Festplattennutzung: VMs 10–100 GB, Container-Images 10–500 MB
    • Isolierungsgrad: VMs auf Hardware-Ebene, Container auf Prozess-Ebene
    • Leistung: VMs mit etwa 5–10 % Overhead, Container mit nahezu nativer Performance

Woraus ein Basis-Image tatsächlich besteht

  • Inhalt des Tarballs, der beim Pullen von ubuntu:22.04 heruntergeladen wird:
  • 1. Unverzichtbare Binärdateien (/bin, /usr/bin)

    • /bin/bash (Shell), /bin/ls (Dateiliste), /bin/cat (Dateianzeige)
    • /usr/bin/apt (Paketmanager), /usr/bin/dpkg (Debian-Paketwerkzeug)
  • 2. Gemeinsame Bibliotheken (/lib, /usr/lib)

    • glibc und weitere Shared Libraries, gegen die Programme gelinkt sind
    • /lib/x86_64-linux-gnu/libc.so.6 (C-Bibliothek – Grundlage aller C-Programme)
    • Wichtige Bibliotheken wie libpthread.so.0, libm.so.6 usw.
  • 3. Konfigurationsdateien (/etc)

    • /etc/apt/sources.list (Paket-Repositories)
    • /etc/passwd (Benutzerdatenbank)
    • /etc/resolv.conf (DNS-Konfiguration, meist vom Host gemountet)
  • 4. Paketdatenbank

    • /var/lib/dpkg/status (installierte Pakete)
    • /var/lib/apt/lists/ (Cache verfügbarer Pakete)
  • Kernel, Bootloader und Treiber sind nicht enthalten

Der Kernel bleibt gleich, alles andere ändert sich

  • Funktionen, die der Linux-Kernel bereitstellt: Prozess-Scheduling, Speicherverwaltung, Dateisystem-Operationen, Netzwerk-Stack, Gerätetreiber, System Calls
  • Wenn ein Container-Prozess open(), read(), fork() aufruft, wird dies direkt an den Host-Kernel weitergereicht
  • Dem Kernel ist weder bekannt noch wichtig, ob der betreffende Prozess in einem „Ubuntu-Container“ oder „Alpine-Container“ läuft
  • Stabilität der System-Call-Schnittstelle

    • Die Linux-Syscall-ABI ist sehr stabil
    • Warum ein mit glibc 2.31 (Ubuntu 20.04) kompiliertes Binary auch auf einem Ubuntu-24.04-Kernel läuft:
      • Der Kernel wahrt Abwärtskompatibilität
      • Keine Änderung der System-Call-Nummern
      • Neue Funktionen kommen hinzu, bestehende werden aber kaum entfernt
    • Deshalb kann ein Ubuntu-18.04-Container auf einem Host mit Kernel 6.5 laufen

Praktischer Test: gleicher Kernel, anderer User Space

  • Führt man dieselbe Kernel-Abfrage in mehreren Basis-Images aus, sieht man, dass alle Images den Host-Kernel gemeinsam nutzen
  • ubuntu:22.04, debian:12, alpine:3.19, fedora:39, archlinux:latest zeigen alle dieselbe Kernel-Version an (6.5.0-44-generic)
  • Unterschiede zwischen den Containern liegen bei Komponenten wie dem uname-Binary oder libc, also in der Userland-Zusammensetzung

Warum Container so effizient sind

  • 1. Keine Kernel-Duplizierung

    • VMs laden jeweils einen vollständigen Kernel in den Speicher (ca. 100–500 MB)
    • 10 VMs verbrauchen Speicher für 10 Kernel, 10 Container nutzen nur einen Kernel
  • 2. Sofortiger Start

    • Boot-Reihenfolge einer VM: BIOS → Bootloader → Kernel → Init-System → Services
    • Ein Container existiert mit nur fork()- und exec()-Aufrufen innerhalb von Millisekunden als Prozess
    • Typischer VM-Start: 30–60 Sekunden / Container-Start: etwa 0,347 Sekunden
  • 3. Gemeinsame Image-Layer

    • Werden 100 Container aus ubuntu:22.04 gestartet, existieren die Basis-Image-Layer nur einmal auf der Festplatte
    • Jeder Container erhält lediglich einen dünnen Copy-on-Write-Layer für Änderungen
  • 4. Speicherteilung über den Kernel

    • Der Page Cache des Kernels wird gemeinsam genutzt
    • Wenn 50 Container dieselbe Datei lesen, cached der Kernel sie nur einmal
    • Bei identischen Shared Libraries können Speicherseiten per Copy-on-Write gemeinsam genutzt werden

Berechnung der Container-Grenzen

  • Speicheranalyse (auf Basis einer VM mit 16 GB RAM)

    • Gesamter RAM: 16.384 MB
    • Overhead des Host-OS: -1.024 MB
    • Docker-Daemon: -256 MB
    • Overhead der Container-Runtime: -512 MB
    • Für Container verfügbar: 14.592 MB
  • Speicherverbrauch nach Container-Typ

    • Minimal (sleep): ca. 1 MB
    • Alpine + kleine App: ca. 25 MB
    • Ubuntu + Python-App: ca. 120 MB
    • Ubuntu + Java-App: ca. 500 MB
    • Node.js-Service: ca. 200 MB
  • Theoretisches Maximum

    • Minimal-Container (1 MB): 14.592
    • Alpine + kleine App (25 MB): 583
    • Ubuntu + Python (120 MB): 121
    • Java-Microservice (500 MB): 29
  • Praktische Grenzen

    • Zusätzlich zum Speicher zu beachten:
      • CPU-Scheduling: Zu viele konkurrierende Container verursachen Latenzspitzen
      • Dateideskriptoren: Standard-ulimit 1024
      • Netzwerk-Ports: Für Port-Mapping stehen nur 65.535 zur Verfügung
      • PIDs: Begrenzung durch /proc/sys/kernel/pid_max (Standard: 32.768)
      • Festplatten-I/O: OverlayFS-Overhead, viele Layer müssen durchsucht werden
    • Bei realen Workloads auf einer VM mit 16 GB liegt die praktische Obergrenze bei:
      • Leichten Containern (API, Worker): 50–100
      • Mittleren Containern (DB, Cache): 10–30
      • Großen Containern (ML-Modelle, JVM-Apps): 5–10

Linux-Distributionskompatibilität

  • ABI-Zusicherung des Kernels

    • Linux hält eine stabile Syscall-Schnittstelle aufrecht
    • Für alte Kernel kompilierte Binaries laufen auf neuen Kerneln
    • Ein Ubuntu-18.04-Binary läuft problemlos auf Kernel 6.5
  • Wann die Kompatibilität bricht

    • Anforderungen an Kernel-Funktionen: wenn der Container Features braucht, die der Kernel nicht hat (z. B. io_uring erfordert Kernel 5.1+)
    • Abhängigkeit von Kernel-Modulen: WireGuard benötigt das WireGuard-Kernelmodul, NVIDIA-Container benötigen den NVIDIA-Kerneltreiber
    • Seccomp-/Capability-Beschränkungen: wenn der Host benötigte Syscalls blockiert (z. B. erfordert die Nutzung von ptrace --cap-add SYS_PTRACE)

Leitfaden zur Wahl des Basis-Images

Basis-Image Größe Paketmanager Verwendungszweck
scratch 0 MB keiner statisch kompilierte Go-/Rust-Binaries
alpine 7 MB apk minimale Container, musl libc
distroless 20 MB keiner sicherheitsorientiert, ohne Shell und Paketmanager
debian-slim 80 MB apt ausgewogen zwischen Größe und Kompatibilität
ubuntu 78 MB apt entwicklerfreundlich
fedora 180 MB dnf aktuelle Pakete, SELinux
  • Wann welches Image sinnvoll ist

    • scratch: für statisch kompilierte Binaries, enthält nur das Binary und sonst kein OS
    • alpine: minimales Image mit Shell-Zugriff; nutzt musl libc statt glibc, was zu Kompatibilitätsproblemen führen kann
    • distroless: sicherheitsorientiertes Produktions-Image; Debugging ist schwieriger, weil Shell und Paketmanager fehlen, dafür ist es sicherer

Grenze zwischen User Space und Kernel

  • Was aus dem Basis-Image kommt (User Space)

    • Shell (/bin/bash, /bin/sh)
    • C-Bibliotheken (glibc, musl)
    • Paketmanager (apt, apk, yum)
    • Zentrale Utilities (ls, cat, grep)
    • Konfiguration des Init-Systems (meist nicht systemd selbst)
    • Standardbenutzer und -gruppen (/etc/passwd)
    • Bibliothekspfade und Konfiguration
  • Was vom Host kommt (Kernel)

    • Prozess-Scheduling und Speicherverwaltung
    • Netzwerk-Stack (TCP/IP, Routing)
    • Dateisystem-Operationen (Lesen, Schreiben, Mounten)
    • Sicherheitsfunktionen (Namespaces, cgroups, seccomp)
    • Gerätetreiber (GPU, Netzwerk, Storage)
    • Zeit- und Taktverwaltung
    • Kryptografie und Zufallszahlengenerierung
  • Die durch Namespaces erzeugte Illusion

    • Der Kernel stellt Namespaces bereit, sodass sich Container isoliert anfühlen
    • Ein Prozess, der im Container als PID 1 erscheint, existiert auf dem Host unter einer höheren PID (z. B. 45678)
    • Der Kernel hält die Zuordnung aufrecht: Container-PID 1 → Host-PID 45678
    • So funktioniert Isolierung ohne Virtualisierung

Bedeutung für Produktionsumgebungen

  • 1. Kernel-Schwachstellen betreffen alle Container

    • Hat der Host-Kernel eine Schwachstelle, sind alle Container exponiert
    • Host-Patches aktuell zu halten ist Pflicht
  • 2. Der Host-Kernel begrenzt die Container-Funktionen

    • Für die Nutzung von io_uring ist auf dem Host Kernel 5.1+ nötig
    • eBPF-Funktionen erfordern Kernel 4.15+ mit bestimmten aktivierten Optionen
  • 3. Bedeutung von glibc vs. musl

    • Alpine verwendet musl libc
    • Manche für glibc kompilierten Binaries funktionieren dort nicht
    • Beispiel: Beim Ausführen eines glibc-Binarys auf Alpine kann ein Fehler auftreten, weil /lib/x86_64-linux-gnu/libc.so.6 nicht vorhanden ist
  • 4. Das Container-„OS“ ist rein ein Organisationskonzept

    • Aus Sicht des Kernels gibt es keinen Unterschied zwischen einem „Ubuntu-Container“ und einem „Debian-Container“
    • Beides sind lediglich Prozesse, die Syscalls ausführen

Häufige Missverständnisse

  • ❌ „Container sind leichte VMs“: Container sind Prozesse mit fortgeschrittener Isolierung; VMs virtualisieren Hardware und führen einen separaten Kernel aus
  • ❌ „Jeder Container hat seinen eigenen Kernel“: Alle Container teilen sich den Host-Kernel; das „OS“ des Containers besteht nur aus User-Space-Dateien
  • ❌ „Einen Ubuntu-Container starten = Ubuntu ausführen“: Man nutzt den Host-Kernel und Ubuntu-Werkzeuge; wenn der Host Debian ist, läuft tatsächlich ein Debian-Kernel
  • ❌ „Ein Basis-Image enthält ein vollständiges Betriebssystem“: Ein Basis-Image enthält nur minimale User-Space-Werkzeuge, keinen Kernel, Bootloader oder Treiber
  • ❌ „Mehr Container = mehr Speicher“: Durch gemeinsame Layer und Kernel-Page-Caching können Container Speicher oft effizient gemeinsam nutzen

Kernaussagen

  • Ein Docker-Basis-Image ist ein Dateisystem-Snapshot der User-Space-Komponenten einer Linux-Distribution
    • Also der Binärdateien, Bibliotheken und Konfigurationen, durch die sich Ubuntu wie Ubuntu anfühlt
  • Das eigentliche Betriebssystem, also der Kernel, wird mit dem Host geteilt
  • Diese Architektur ermöglicht:
    • Startzeiten im Millisekundenbereich (kein Kernel-Boot)
    • Minimalen Speicher-Overhead (ein Kernel, gemeinsam genutzte Seiten)
    • Hohe Dichte im großen Maßstab (Hunderte Container pro Host)
    • Nahezu native Performance (direkte Syscalls an den Kernel)
  • Der Trade-off ist eine schwächere Isolierung als bei VMs – da Container den Kernel teilen, betreffen Kernel-Exploits alle Container
  • Für die meisten Workloads ist dieser Trade-off lohnend

9 Kommentare

 
bbulbum 2026-01-22

Kernel + Werkzeuge = Distribution
Dann wäre auch das Ubuntu, oder nicht..

 
sacredshine 2026-01-21

Es gibt also auch Tutorials dazu, Docker direkt unter Linux selbst nachzubauen, dabei Verzeichnisse zu isolieren und sich mit Benutzern, Gruppen und allerlei anderem zu beschäftigen.

 
dongho42 2026-01-21

Sehr informativ.

 
seunggi 2026-01-21

https://de.news.hada.io/topic?id=9531

Deshalb kann man, etwas überspitzt gesagt, chroot + cgroup = Docker so verstehen.

 
euphcat 2026-01-21

Acktually, das kommt eher systemd-nspawn ein bisschen näher ☝️🤓

 
hohemian 2026-01-22

Eigentlich

 
euphcat 2026-01-22

Sarkasmus / selbstironisch

 
crawler 2026-01-21

Wirklich faszinierend.

Der Haupttext scheint das anhand von Linux zu erklären, aber
wenn man es unter Windows ausführt, würde dann der von WSL2 erzeugte virtuelle Kernel wie im Artikel beschrieben gemeinsam genutzt werden?

Falls Docker eine Schwachstelle hätte, über die man den Kernel angreifen könnte, frage ich mich auch, ob Windows mit dieser zusätzlichen Virtualisierungsschicht womöglich sicherer wäre als Linux.

 
minsuchae 2026-01-22

Die Reaktion im obigen Kommentar hat mich etwas überrascht.
Ich dachte, das wäre selbstverständlich und allen beim Einsatz klar.
Der Linux-Kernel kommt vom Host – alles andere wird in Form der Tools eingebracht, die in der jeweiligen Linux-Distribution verwendet werden.

WSL2 läuft meines Wissens virtualisiert unter Hyper-V.
Windows – Linux in der virtuellen Maschine – und darin dann noch einmal ein Container ...

Grundsätzlich ist root innerhalb eines Containers nicht wirklich root des gesamten Systems, deshalb kann man den Kernel normalerweise nicht einfach beliebig manipulieren.
Wenn allerdings eine Sicherheitslücke auftaucht, wird es natürlich kritisch.

Aus Performance-Sicht ist Windows deshalb tendenziell langsamer, weil dort noch eine zusätzliche Virtualisierungsschicht dazwischenliegt.