71 Punkte von GN⁺ 2026-01-21 | Noch keine 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

Noch keine Kommentare.

Noch keine Kommentare.