Fullmoon System

Linux-Container verstehen: Namespaces, cgroups v2 und Rootless Podman

EdwardMoon

Linux-Container sind Prozesse, die den Hostkernel teilen. Prozess-, Mount-, Netzwerk- und User-Namespaces begrenzen ihre Sicht auf das System; cgroups beschränken Ressourcen wie CPU, Arbeitsspeicher und PIDs. Dieses Modell unterscheidet sich von kleinen virtuellen Maschinen. Wer es versteht, kann Rechte, Images, Volumes, Netzwerke und Ausfallbereiche passend entwerfen.

Die Übung verwendet cgroup v2 und Rootless Podman auf Rocky Linux 9. SELinux und firewalld bleiben aktiv. Entfernte Installationsskripte werden nicht direkt an eine Shell weitergereicht; neben Image-Tags werden Digest und Herkunft dokumentiert. Ausgaben können von Podman- und Kernelversion abhängen. Deshalb in jedem Schritt den tatsächlichen Zustand prüfen.

Linux-Container-Aufbau: Rootless-Benutzer, OCI-Image-Layer, Namespaces, gemeinsamer Kernel und cgroups v2
Mehrere isolierte Umgebungen aus OCI-Image-Layern teilen einen Linux-Kernel und begrenzen Ressourcen über cgroups

Container und virtuelle Maschinen vergleichen

Merkmal Container Virtuelle Maschine
Kernel Gemeinsamer Hostkernel Eigener Kernel pro Gastbetriebssystem
Isolation namespaces·LSM·seccomp·capabilities Hypervisor und virtuelle Hardware
Image OCI-Layer und Metadaten Vollständiges Datenträgerabbild
Start Isolierten Prozess erzeugen Gastbetriebssystem booten
Ressourcen cgroups und Laufzeitlimits Zuweisung von vCPUs, RAM und virtuellen Geräten
Risikogrenze Gemeinsame Betroffenheit durch Kernel-Schwachstellen Getrennte Kernelgrenze zwischen Gast und Host
Rootless begrenzt mögliche Schäden, indem Container-root auf unprivilegierte Host-UIDs abgebildet wird. Es ist jedoch keine vollständige Sicherheitsgrenze. Hostkernel, Container-Engine, OCI-Laufzeit und Images fortlaufend patchen und keine unnötigen Capabilities, Geräte, Sockets oder Host-Namespaces freigeben.

Bestandteile von Linux-Containern

Komponente Aufgabe Typische Prüfung
OCI image Root-Dateisystem-Layer und Ausführungsmetadaten podman image inspect·history
Container engine pull·build·network·storage·lifecycle podman info
OCI runtime Namespaces und cgroups erstellen und Prozess ausführen crun –version oder runc –version
conmon Containerprozess überwachen, Standardein-/ausgabe und Beendigung behandeln podman info –debug
namespaces Sichtbare IDs, Mounts und Netzwerke eines Prozesses isolieren lsns·/proc/PID/ns
cgroups v2 CPU-, Speicher-, I/O- und PID-Nutzung erfassen und begrenzen podman stats·/sys/fs/cgroup
SELinux/seccomp Zulässige Dateien und Systemaufrufe begrenzen getenforce·podman inspect

Übungsumgebung prüfen

Podman- und Kernelfunktionen prüfen

uname -r
cat /etc/os-release
podman --version
podman info --debug

podman info --format '{{.Host.CgroupsVersion}}'
stat -fc %T /sys/fs/cgroup
getenforce
systemctl is-active firewalld

Prüfen, dass cgroup2fs und Podmans Meldung zu cgroup v2 übereinstimmen. Quadlet benötigt cgroup v2. Bei SELinux Enforcing müssen Volume-Labels stimmen. Nicht sämtliche Sicherheitsfunktionen abschalten, wenn ein Zugriff zunächst scheitert.

Rootless-UID- und GID-Bereiche prüfen

id
grep -E "^${USER}:" /etc/subuid /etc/subgid
command -v newuidmap newgidmap
command -v pasta

podman unshare cat /proc/self/uid_map
podman unshare cat /proc/self/gid_map

Rootless Podman verwendet zusätzliche ID-Bereiche aus /etc/subuid und /etc/subgid, um Container-UIDs auf unprivilegierte Host-UIDs abzubilden. Fehlen Bereiche, muss die Administration mit usermod –add-subuids und –add-subgids nicht überlappende Bereiche zuweisen. Gemeinsam genutzte NFS-Home-Verzeichnisse verstehen User-Namespaces nicht. Deshalb Rootless-graphroot auf einem lokalen Dateisystem vorsehen.

Namespaces praktisch untersuchen

Namespaces trennen die Sicht eines Prozesses auf globale Ressourcen. Im PID-Namespace erscheinen andere Prozessnummern. Mount-Namespaces trennen Mount-Tabellen, Netzwerk-Namespaces Schnittstellen, Routen und Ports; User-Namespaces trennen UID-/GID-Zuordnung und den Geltungsbereich von Capabilities.

Aktuelle Namespaces auflisten

lsns
readlink /proc/self/ns/user
readlink /proc/self/ns/pid
readlink /proc/self/ns/mnt
readlink /proc/self/ns/net

Unprivilegierten User- und PID-Namespace erstellen

unshare --user --map-root-user --pid --fork sh -c '
  id
  echo "namespace PID: $$"
  readlink /proc/self/ns/user
  readlink /proc/self/ns/pid
'

uid=0 in der Ausgabe bedeutet root innerhalb des neuen User-Namespace, nicht Host-root. Host-UID-Zuordnung und Capabilities bleiben eingeschränkt, sodass nicht sämtliche Hostdateien oder Geräte zugänglich sind. Wegen des gemeinsamen Kernels bleiben Kernel-Schwachstellen sowie falsch eingebundene Geräte oder Sockets jedoch Risiken.

OCI-Images und Digests

Images bestehen aus unveränderlichen Layern und Metadaten wie Konfiguration, Entrypoint und Umgebungsvariablen. Ein Registry-Tag kann später auf einen anderen Digest zeigen. Für produktive Bereitstellungen daher den geprüften Manifest-Digest dokumentieren und Richtlinien für Signaturen, SBOM und Schwachstellen anwenden.

Image mit vollständig qualifiziertem Namen laden

IMAGE='docker.io/library/busybox:1.36.1'
podman pull "$IMAGE"

podman image inspect "$IMAGE"   --format 'ID={{.Id}} Digest={{.Digest}} Created={{.Created}}'
podman history --no-trunc "$IMAGE"
podman images --digests

Kurznamen können je nach registries.conf auf unterschiedliche Registries verweisen. Deshalb den vollständigen Namen mit Registry und Namespace verwenden. Wurde während der Entwicklung ein Tag genutzt, den von inspect gelieferten Digest im Freigabeprotokoll festhalten und in Quadlet oder Deployment-Manifest auf image@sha256 festlegen.

OCI-Manifest prüfen

skopeo inspect   docker://docker.io/library/busybox:1.36.1   | jq '{Name,Digest,Created,Architecture,Os}'

skopeo inspect --raw   docker://docker.io/library/busybox:1.36.1   | jq .

Rootless-Container mit Podman starten

BusyBox httpd nur über Loopback bereitstellen, das Root-Dateisystem schreibgeschützt halten und Standard-Capabilities, Rechteausweitung sowie übermäßige PID-, Speicher- und CPU-Nutzung begrenzen. Nur tatsächlich benötigte Schreibpfade als tmpfs oder ausdrückliche Volumes anbieten.

IMAGE='docker.io/library/busybox:1.36.1'
install -d -m 0750 "$HOME/container-data"
printf 'hello from rootless Podman\n' > "$HOME/container-data/index.html"

podman run --detach --rm --name web-demo \
  --read-only --cap-drop=all --security-opt=no-new-privileges \
  --pids-limit=128 --memory=256m --cpus=0.50 \
  --publish 127.0.0.1:8080:8080 \
  --volume "$HOME/container-data:/www:ro,Z" \
  --tmpfs /tmp:rw,noexec,nosuid,nodev,size=32m \
  "$IMAGE" httpd -f -p 8080 -h /www

Laufzustand und Limits prüfen

podman ps
podman port web-demo
curl --fail --silent --show-error http://127.0.0.1:8080/

podman stats --no-stream web-demo
podman top web-demo user hpid pid args
podman inspect web-demo > web-demo.inspect.json
jq '.[0].HostConfig | {ReadonlyRootfs,Memory,NanoCpus,PidsLimit}'   web-demo.inspect.json
Fehlt bei –publish die Host-IP, kann der Port auf allen Schnittstellen erreichbar werden. Dienste hinter einem lokalen Reverse-Proxy an 127.0.0.1 binden. Bei erforderlicher externer Freigabe firewalld-Zone, Quellnetze, TLS und Authentifizierung gesondert prüfen.

Prozesse und Namespaces verfolgen

HOST_PID=$(podman inspect --format '{{.State.Pid}}' web-demo)
printf 'host PID=%s
' "$HOST_PID"

ps -o user,pid,ppid,cmd -p "$HOST_PID"
sudo lsns -p "$HOST_PID"
sudo readlink "/proc/${HOST_PID}/ns/user"
sudo readlink "/proc/${HOST_PID}/ns/net"
sudo cat "/proc/${HOST_PID}/cgroup"

Auch PID 1 im Container erscheint auf dem Host als gewöhnliche PID. nsenter erlaubt mächtige Eingriffe zur Diagnose innerhalb der Isolationsgrenze. Den Zugriff darauf im Betrieb beschränken und zunächst Engine-Schnittstellen wie podman exec, logs und inspect verwenden.

podman exec web-demo sh -c '
  echo "container PID=$$"
  id
  cat /proc/self/status | grep -E "^(Name|Pid|NSpid|CapEff|NoNewPrivs):"
'

podman logs web-demo
podman events --since 10m --filter container=web-demo

cgroups v2 praktisch prüfen

cgroups v2 erfassen und begrenzen Ressourcen. Speicherlimits verringern Risiken unkontrollierter Speichernutzung, PID-Limits die Ausbreitung von Fork-Bombs und CPU-Quoten die Auswirkungen störender Nachbarlasten. Im Rootless-Betrieb können manche Controller-Limits durch systemd-Benutzerdelegation oder Hostrichtlinien eingeschränkt sein.

podman stats --no-stream web-demo
podman inspect web-demo --format '{{.State.CgroupPath}}'

systemd-cgls "/user.slice/user-${UID}.slice"
systemctl --user status

podman update   --memory=192m   --pids-limit=96   web-demo
podman stats --no-stream web-demo

Zu niedrige Limits können bereits bei normaler Last OOM-Kills oder fehlgeschlagene Anfragen auslösen. Speichermodell der Anwendung, JVM- und Worker-Anzahl, Healthchecks und Neustartstrategie gemeinsam abstimmen. Auch freie Hostressourcen und Pressure Stall Information überwachen.

Volumes und SELinux

Eigenes Hostverzeichnis und privates Label

install -d -m 0750 "$HOME/volume-label-demo"
printf 'hello from a labeled volume\n' > "$HOME/volume-label-demo/index.html"

podman run --rm --read-only --cap-drop=all \
  --security-opt=no-new-privileges \
  --volume "$HOME/volume-label-demo:/data:ro,Z" \
  docker.io/library/busybox:1.36.1 cat /data/index.html

ls -Zd "$HOME/volume-label-demo"

:Z versieht einen Pfad mit einem privaten SELinux-Label für einen einzelnen Container. Für gemeinsam genutzte Pfade kann :z geeignet sein. Großflächiges Relabeling von Systemverzeichnissen oder sensiblen Home-Verzeichnissen kann jedoch Hostdienste beschädigen. Nur eigene Verzeichnisse einbinden und Sicherung, Eigentümer sowie UID-Zuordnung vorab planen.

Unsichere Vorgehensweisen bei Containern

Vorgehensweise Risiko Empfohlene Alternative
Vollständig privilegierter Modus Hebt zahlreiche Geräte-, Capability- und LSM-Grenzen auf Nur benötigte Capabilities und Geräte einzeln erlauben
host network/PID Gemeinsame Host-Namespaces und weitreichende Sicht auf den Host Eigener Netzwerk-Namespace und ausdrücklich veröffentlichte Ports
Podman/Docker socket mount Beliebige Container und Mounts auf dem Host ausführbar Eingeschränkter API-Proxy oder separates Automatisierungskonto
latest tag Ergebnis erneuter Bereitstellung verändert sich Geprüfter Digest und Signaturrichtlinie
Sämtliche Sicherheitsfunktionen deaktivieren Verlust der SELinux- und Firewall-Schutzschichten Labels, Ports und Richtlinien nur im erforderlichen Umfang anpassen
curl-Ausgabe direkt an die Shell übergeben Entfernten Code ohne Prüfung und Integritätskontrolle ausführen Offizielle Pakete sowie Signatur- und Prüfsummenprüfung

Rechte und Mounts prüfen

podman inspect web-demo   --format '{{json .HostConfig.SecurityOpt}} {{json .HostConfig.CapDrop}}'

podman inspect web-demo   --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} rw={{.RW}}{{println}}{{end}}'

podman diff web-demo
podman top web-demo capeff label

Container mit Quadlet betreiben

Eine Quadlet-.container-Datei im Suchpfad der Rootless-Benutzer-Units ermöglicht systemd die Verwaltung von Dienstlebenszyklus und Logs. Das ist für den Betrieb geeigneter als ein einmaliger podman-run-Aufruf in einem Shellskript. Den folgenden Image-Wert produktiv durch einen geprüften SHA-256-Digest ersetzen.

Einmaligen Übungscontainer beenden

podman stop web-demo
podman ps --all --filter name=web-demo

# Da zuvor --rm verwendet wurde, darf nach regulärem Ende kein Container zurückbleiben.
podman container exists web-demo; printf 'exit=%s
' "$?"
mkdir -p "$HOME/.config/containers/systemd"
${EDITOR:-vi} "$HOME/.config/containers/systemd/web-demo.container"

~/.config/containers/systemd/web-demo.container

[Unit]
Description=Rootless read-only web demo

[Container]
Image=docker.io/library/busybox:1.36.1
ContainerName=web-demo
Exec=httpd -f -p 8080 -h /www
Volume=%h/container-data:/www:ro,Z
PublishPort=127.0.0.1:8080:8080
ReadOnly=true
NoNewPrivileges=true
DropCapability=all
PidsLimit=128

[Service]
MemoryMax=256M
Restart=on-failure
TimeoutStartSec=120

[Install]
WantedBy=default.target

Erzeugte Quadlet-Unit und Dienst prüfen

mkdir -p "$HOME/.config/containers/systemd"
chmod 0700 "$HOME/.config/containers/systemd"
chmod 0644 "$HOME/.config/containers/systemd/web-demo.container"

systemctl --user daemon-reload
systemctl --user start web-demo.service
systemctl --user status web-demo.service
journalctl --user -u web-demo.service --since '-10 min'

podman info --format '{{.Host.CgroupsVersion}}'
curl --fail --silent --show-error http://127.0.0.1:8080/

Die generierte Quadlet-Service-Unit nicht direkt aktivieren. Stattdessen WantedBy im [Install]-Abschnitt der .container-Datei durch den Generator berücksichtigen lassen. Soll der Rootless-Dienst nach Abmeldung weiterlaufen, kann die Administration loginctl enable-linger verwenden. Vorher die betrieblichen und sicherheitsbezogenen Auswirkungen dauerhaft ohne Anmeldung laufender Benutzerprozesse freigeben lassen.

Diagnosereihenfolge

  1. Mit podman ps –all und Dienststatus Exitcodes sowie Neustartschleifen prüfen.
  2. Zeitpunkte von Anwendungs- und Engine-Ereignissen mit podman logs und events abgleichen.
  3. Image-ID, Digest, Befehl, Umgebung, Mounts und Sicherheitsoptionen mit inspect prüfen.
  4. Portbindungen, Rootless-Netzwerk, DNS und Host-Firewall prüfen.
  5. SELinux-AVCs, Volume-Labels, UID-Zuordnung und Dateirechte prüfen.
  6. cgroup-Limits für Speicher, PIDs und CPU sowie OOM- und Druckmetriken prüfen.
  7. Mit demselben Digest und minimalen Eingaben auf einem isolierten Host reproduzieren.
podman ps --all --size
podman inspect web-demo
podman logs --timestamps web-demo
podman events --since 30m
podman stats --no-stream web-demo

journalctl --user -u web-demo.service --since '-30 min'
sudo ausearch -m AVC,USER_AVC -ts recent
ss -lntp | grep ':8080'

Checkliste für den Betrieb

  • Gemeinsamer Kernel von Containern gegenüber VMs sowie Aufgaben von Namespaces und cgroups sind verstanden.
  • Rootless-subuid und -subgid, cgroup v2, Laufzeit, lokaler Speicher und Netzwerkwerkzeuge sind geprüft.
  • Vollständiger Registry-Name, geprüfter Image-Digest sowie Signatur-, SBOM- und Schwachstellenergebnisse sind dokumentiert.
  • Schreibgeschütztes Root-Dateisystem, no-new-privileges, Capability-Entzug sowie PID-, Speicher- und CPU-Limits sind angewendet.
  • Ports sind nur an benötigte Host-IPs gebunden; privilegierter Modus, Host-Namespaces und Engine-Socket werden vermieden.
  • Eigene Volumes und passende SELinux-Labels werden bei aktiven Sicherheitsfunktionen verwendet.
  • Quadlet-Dienst, Logs, Healthchecks, Sicherung, Wiederherstellung und Update-Rollback sind geprüft.

Offizielle Dokumentation und weiterführende Artikel

Zusammenfassung

Sicherer Containerbetrieb berücksichtigt die Risiken des gemeinsamen Kernels und verwaltet Namespaces, cgroups v2, Benutzerzuordnung, SELinux und die OCI-Lieferkette gemeinsam. Rootless als Ausgangspunkt wählen, Digests festlegen, Capabilities, Mounts und Ports begrenzen sowie schreibgeschützte Dateisysteme und Ressourcenlimits verwenden. Quadlet, systemd-Logs, Healthchecks, Sicherung und Rollback vervollständigen einen reproduzierbaren Dienst.