GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Was passiert hinter den Kulissen eines wurzellosen Podman-Containers?

Es lohnt sich immer zu wissen, was hinter den Kulissen passiert. Werfen wir einen Blick darauf, was unter der Haube von wurzellosen Podman-Containern passiert. Wir erklären jede Komponente und schlüsseln dann alle beteiligten Schritte auf.

Das Beispiel

In unserem Beispiel versuchen wir, einen Container auszuführen, der bereits Buildah ausführt, um ein Container-Image zu erstellen. Zuerst erstellen wir ein einfaches Dockerfile namens Containerfile das ein ubi8-Image zieht und einen Befehl ausführt, der Ihnen mitteilt, dass Sie in einem Container laufen:

$ mkdir containers
$ cat > ~/Containerfile << _EOF
FROM ubi8
RUN echo “in buildah container”
_EOF

Führen Sie als Nächstes den Container mit dem folgenden Podman-Befehl aus:

$ podman run --device /dev/fuse -v ~/Containerfile:/Containerfile:Z \
     -v ~/containers:/var/lib/containers:Z buildah buildah bud /

Dieser Befehl fügt das zusätzliche Gerät /dev/fuse hinzu , die erforderlich ist, um Buildah innerhalb des Containers auszuführen. Wir binden Volumes in Containerfile ein damit Buildah es finden kann, und verwenden Sie das SELinux-Flag :Z um Podman zu sagen, dass er es umbenennen soll. Um den Containerspeicher von Buildah außerhalb des Containers zu handhaben, mounten wir auch die lokalen containers Verzeichnis, das ich oben erstellt habe. Und schließlich führen wir den Buildah-Befehl aus.

Hier ist die tatsächliche Ausgabe, die ich sehe, wenn ich diesen Befehl ausführe:

$ podman run -ti --device /dev/fuse -v ~/Containerfile:/Containerfile:Z -v ~/containers:/var/lib/containers:Z buildah/stable buildah bud /
Trying to pull docker.io/buildah/stable...
   denied: requested access to the resource is denied
Trying to pull registry.fedoraproject.org/buildah/stable...
   manifest unknown: manifest unknown
Trying to pull quay.io/buildah/stable...
Getting image source signatures
Copying blob 907e338ec93d done
Copying blob a3ed95caeb02 done
Copying blob a3ed95caeCob02 done
Copying blob a3ed95caeb02 skipped: already exists
Copying blob d318c91bf2a8 done
Copying blob e721a8015139 done
Copying blob a3ed95caeb02 done
Copying blob 8dd367492bc7 done
Writing manifest to image destination
Storing signatures
STEP 1: FROM ubi8
Getting image source signatures
Copying blob c65691897a4d done
Copying blob 641d7cc5cbc4 done
Copying config 11f9dba4d1 done
Writing manifest to image destination
Storing signatures
STEP 2: RUN echo "in buildah container"
in buildah container
STEP 3: COMMIT
Getting image source signatures
Copying blob 6866631b657e skipped: already exists
Copying blob 48905dae4010 skipped: already exists
Copying blob 5f70bf18a086 skipped: already exists
Copying config 9c54016647 done
Writing manifest to image destination
Storing signatures
9c5401664748e032b43b8674dba90e9b853d6b47b679d056cb2a1e3118f9dab7

Sehen wir uns nun genauer an, was tatsächlich im Podman-Befehl vor sich geht.

Einrichten der Benutzer- und Mount-Namespaces

Beim Einrichten von Benutzer- und Mount-Namespaces prüft Podman zunächst, ob bereits ein Benutzer-Namespace konfiguriert ist. Dies geschieht, indem geprüft wird, ob für den Benutzer ein Pausenprozess ausgeführt wird. Die Rolle des Pause-Prozesses besteht darin, den Benutzernamensraum am Leben zu erhalten, da alle Rootless-Container im selben Benutzernamensraum ausgeführt werden müssen. Wenn dies nicht der Fall ist, wären einige Dinge (wie die gemeinsame Nutzung des Netzwerk-Namensraums von einem anderen Container) unmöglich.

Ein Benutzernamensraum ist erforderlich, um rootless zu erlauben, bestimmte Arten von Dateisystemen einzuhängen und auf mehr als eine UID und GID zuzugreifen.

Wenn der Pausenprozess existiert, wird sein Benutzernamensraum hinzugefügt. Diese Aktion wird sehr früh in ihrer Ausführung ausgeführt, bevor die Go-Laufzeit gestartet wird, da ein Multithread-Programm seinen Benutzer-Namespace nicht ändern kann. Wenn der Pausenvorgang jedoch nicht existieren, dann liest Podman die /etc/subuid und /etc/subgid Dateien und suchen Sie nach dem Benutzernamen oder der UID des Benutzers, der den Podman-Befehl ausführt. Sobald Podman den Eintrag findet, verwendet es den Inhalt sowie die aktuelle UID/GID des Benutzers, um einen Benutzernamensraum für ihn zu generieren.

Zum Beispiel, wenn der Benutzer als UID 1000 ausgeführt wird und einen Eintrag von USER:100000:65536 hat , führt Podman die setuid- und setgid-Apps aus, /usr/bin/newuidmap und /usr/bin/newgidmap , um den Benutzernamensraum zu konfigurieren. Der Benutzernamensraum bekommt dann folgendes Mapping:

0     3267      1
1     100000    65536

Beachten Sie, dass Sie den Benutzernamensraum sehen können, indem Sie Folgendes ausführen:

$ podman unshare cat /proc/self/uid_map

Als Nächstes erstellt Podman einen Pausenprozess, um den Namespace am Leben zu erhalten, sodass alle Container im selben Kontext ausgeführt werden und dieselben Mounts sehen können. Der nächste Podman-Prozess tritt dem Namespace direkt bei, ohne dass er zuerst erstellt werden muss. Wenn der Benutzerbereich jedoch nicht erstellt werden konnte, prüft Podman, ob der Befehl noch ohne Benutzernamensraum ausgeführt werden kann. Einige Befehle wie podman version brauche keine. Andernfalls schlägt ein Befehl ohne Benutzernamensraum fehl.

Anschließend verarbeitet Podman die Befehlszeilenoptionen und überprüft, ob sie korrekt sind. Sie können podman-help verwenden und podman run --help , um verfügbare Optionen aufzulisten und die Manpages für weitere Beschreibungen zu verwenden.

Schließlich erstellt Podman einen Mount-Namespace zum Mounten des Containerspeichers.

Bild ziehen

Beim Pullen des Images prüft Podman, ob das Container-Image buildah/stable ist ist im lokalen Containerspeicher vorhanden. Ist dies der Fall, richtet Podman das Netzwerk ein (siehe nächster Abschnitt). Wenn das Container-Image jedoch nicht vorhanden ist, erstellt Podman eine Liste mit Kandidaten-Images, die mithilfe der in /etc/containers/registries.conf definierten Suchregistrierungen abgerufen werden können .

Die containers/image Bibliothek wird verwendet, um diese Kandidaten-Images einzeln in einer durch registries.conf definierten Reihenfolge abzurufen . Das erste erfolgreich abgerufene Bild wird verwendet.

  1. Die containers/image -Skript verwendet DNS, um die IP-Adresse für die Registrierung zu finden.
  2. Dieses Skript TCP verbindet sich mit der IP-Adresse über httpd Anschluss (80).
  3. Der containers/image sendet eine HTTP-Anforderung für das Manifest von /buildah/stable:latest Container-Image.
  4. Wenn das Skript das Bild nicht finden kann, verwendet es die nächste Registrierung als Ersatz und kehrt zu Schritt 1 zurück. Wenn das Bild jedoch ist gefunden, beginnt es mit dem Pullen jeder Ebene des Bildes unter Verwendung von containers/image Bibliothek.

In diesem Beispiel buildah/stable wurde unter quay.io/buildah/stable gefunden . Die containers/image Das Skript findet, dass es sieben Ebenen in quay.io/buildah/stable gibt und beginnt damit, alle gleichzeitig aus der Containerregistrierung auf den Host zu kopieren. Sie gleichzeitig zu kopieren ist effizient.

Während jede Schicht auf den Host kopiert wird, ruft Podman containers/storage auf Bücherei. Die containers/storage Das Skript setzt die Ebenen der Reihe nach und für jede Ebene wieder zusammen. Es erstellt einen Overlay-Einhängepunkt in ~/.local/share/containers/storage auf der vorherigen Schicht. Wenn es keine vorherige Ebene gibt, wird die erste Ebene erstellt.

Hinweis: In rootless Podman verwenden wir tatsächlich ein fuse-overlayfs ausführbar, um die Ebene zu erstellen. Rootfull verwendet die overlayfs des Kernels Treiber. Derzeit erlaubt der Kernel rootlosen Benutzern nicht, Overlay-Dateisysteme zu mounten, aber sie können FUSE-Dateisysteme mounten.

Als nächstes containers/storage entpackt den Inhalt der Ebene in die neue Speicherebene. Da die Schichten nicht tariert sind, containers/storage chowns die UID/GIDs von Dateien im Tarball in das Home-Verzeichnis. Beachten Sie, dass dieser Vorgang fehlschlagen kann, wenn die in der TAR-Datei angegebene UID oder GID nicht dem Benutzernamensraum zugeordnet wurde. Siehe Warum kann Podman mein Image nicht ohne Root ziehen?

Container erstellen

Jetzt ist es für Podman an der Zeit, einen neuen Container basierend auf dem Image zu erstellen. Um dies zu erreichen, fügt Podman den Container zur Datenbank hinzu und fragt dann containers/storage ab Bibliothek zum Erstellen und Mounten eines neuen Containers in c/storage . Die neue Containerebene fungiert als letzte Lese-/Schreibebene und wird über dem Image bereitgestellt.

Netzwerk einrichten

Als nächstes müssen wir das Netzwerk einrichten. Um dies zu erreichen, findet und führt Podman /usr/bin/slirp4netns aus Containernetzwerke einzurichten. In rootless Podman können wir kein vollständiges, separates Netzwerk für Container erstellen, da diese Funktion für Nicht-Root-Benutzer nicht zulässig ist. In rootless Podman verwenden wir slirp4netns um das Hostnetzwerk zu konfigurieren und ein VPN für den Container zu simulieren.

Hinweis: In Rootful-Containern verwendet Podman die CNI-Plug-ins, um eine Bridge zu konfigurieren.

Wenn der Benutzer eine Portzuordnung wie -p 8080:80 angegeben hat , slirpnetns würde auf dem Host-Netzwerk an Port 8080 lauschen und dem Container-Prozess erlauben, sich an Port 80 zu binden. Der slirp4netns Der Befehl erstellt ein Tap-Gerät, das in den neuen Netzwerk-Namespace eingefügt wird, in dem sich der Container befindet. Jedes Paket wird von slirp4netns zurückgelesen und emuliert einen TCP/IP-Stack im Userspace. Jede Verbindung außerhalb des Namespace des Container-Netzwerks wird in eine Socket-Operation konvertiert, die der nicht privilegierte Benutzer im Namespace des Host-Netzwerks ausführen kann.

Umgang mit Volumen

Um Volumes handhaben zu können, liest Podman den gesamten Containerspeicher. Es sammelt die verwendeten SELinux-Labels und erstellt ein neues, unbenutztes Label, um den Container mit opencontainers/selinux auszuführen Bibliothek.

Da der Benutzer zwei Volumes angegeben hat, die in den Container gemountet werden sollen, und Podman gebeten hat, den Inhalt umzubenennen, verwendet Podman opencontainers/selinux um das SELinux-Label rekursiv auf die Quelldateien/-verzeichnisse der Volumes anzuwenden. Podman verwendet dann die opencontainers/runtime-tools Bibliothek zum Zusammenstellen einer Laufzeitspezifikation der Open Containers Initiative (OCI):

  1. Podman teilt runtime-tools mit seine fest codierten Standardwerte für Dinge wie Fähigkeiten, Umgebung und Namespaces zur Spezifikation hinzuzufügen.
  2. Podman verwendet die OCI-Image-Spezifikation, die aus buildah/stable gezogen wurde image, um Inhalte in der Spezifikation festzulegen, wie das Arbeitsverzeichnis, den Einstiegspunkt und zusätzliche Umgebungsvariablen.
  3. Podman nimmt die Eingaben des Benutzers und verwendet die runtime-tools Bibliothek, um Felder in der Spezifikation für jedes der Volumes hinzuzufügen, und es setzt den Befehl für den Container auf buildah bud / .

In unserem Beispiel hat der Benutzer Podman mitgeteilt, dass er das Gerät /dev/fuse verwenden möchte Innenseite des Behälters. Auf einem Root-Container würde Podman die OCI-Laufzeit anweisen, ein /dev/fuse zu erstellen Gerät innerhalb des Containers, aber mit wurzellosem Podman dürfen Benutzer keine Geräte erstellen, also weist Podman stattdessen die OCI-Spezifikation an, mount /dev/fuse zu binden vom Host in den Container.

Starten des Containermonitors conmon

Sobald die Volumes bearbeitet sind, findet Podman den Standard conmon und führt ihn aus für den Container /usr/bin/conmon . Diese Informationen werden aus /usr/share/containers/libpod.conf gelesen . Podman teilt es dann dem conmon mit ausführbar, um die OCI-Laufzeit zu verwenden, die auch in libpod.conf aufgeführt ist; normalerweise /usr/bin/runc oder /usr/bin/crun . Podman teilt dies auch conmon mit um podman container cleanup $CTRID auszuführen für den Container, wenn der Container beendet wird.

Conmon geht beim Überwachen des Containers wie folgt vor:

  1. Conmon führt die OCI-Laufzeit aus, übergibt ihr den Pfad zur OCI-Spezifikationsdatei und zeigt auf den Einhängepunkt der Containerschicht in containers/storage . Dieser Einhängepunkt heißt rootfs.
  2. Conmon überwacht den Container, bis er beendet wird, und meldet seinen Beendigungscode zurück.
  3. Conmon handhabt, wenn der Benutzer eine Verbindung zum Container herstellt, und stellt einen Socket bereit, um STDOUT und STDERR des Containers zu streamen.
  4. STDOUT und STDERR werden auch in einer Datei für podman logs protokolliert .

Nach dem Ausführen von conmon , aber bevor die OCI-Laufzeit gestartet wird, hängt Podman an den „attach“-Socket an, da der Container nicht mit -d ausgeführt wurde . Wir müssen dies tun, bevor wir den Container ausführen, da wir sonst riskieren, alles zu verlieren, was der Container vor dem Anhängen in seine Standard-Streams geschrieben hat. Dies zu tun, bevor der Container startet, gibt uns alles.

Starten der OCI-Laufzeit

Die OCI-Laufzeit liest die OCI-Spezifikationsdatei und konfiguriert den Kernel zum Ausführen des Containers. Es:

  1. Stellt die zusätzlichen Namespaces für den Container ein.
  2. Konfiguriert die Cgroups wenn der Container auf Cgroups V2 ausgeführt wird (Cgroups V1 unterstützt keine Rootless-Cgroups).
  3. Stellt das SELinux-Label zum Ausführen des Containers ein.
  4. Liest die seccomp.json Datei (standardmäßig /usr/share/containers/seccomp.json ) und richtet seccomp-Regeln ein.
  5. Setzt die Umgebungsvariablen.
  6. Bind hängt die beiden angegebenen Volumes auf die Pfade im rootfs. Wenn der Zielpfad nicht im rootfs existiert, erstellt die OCI-Laufzeit das Zielverzeichnis.
  7. Schaltet root auf das rootfs um (macht das rootfs / im Behälter).
  8. Verzweigt den Containerprozess.
  9. Führt alle OCI-Hook-Programme aus und übergibt ihnen die rootfs sowie die PID 1 des Containers.
  10. Führt den vom Benutzer buildah bud / angegebenen Befehl aus mit der PID 1 des Containers.
  11. Beendet die OCI-Laufzeit und verlässt conmon um den Container zu überwachen.

Und schließlich conmon meldet den Erfolg an Podman zurück.

Ausführen von buildah Primärprozess des Containers

Nun zur letzten Gruppe von Schritten. Es beginnt, wenn der Container den anfänglichen Buildah-Prozess startet. (Weil wir in unserem Beispiel Buildah verwendet haben.) Buildah teilt die zugrunde liegenden containers/image und containers/storage Bibliotheken mit Podman, so dass es tatsächlich den meisten der oben definierten Schritte folgt, die Podman zum Abrufen seiner Bilder und zum Generieren seiner Container verwendet hat.

Podman hängt an conmon Socket und liest/schreibt weiterhin STDOUT in conmon . Beachten Sie, dass, wenn der Benutzer Podmans -d angegeben hätte Flag, würde Podman beenden, aber der conmon würde den Container weiterhin überwachen.

Wenn der Containerprozess beendet wird, sendet der Kernel ein SIGCHLD an conmon Prozess. Im Gegenzug conmon :

  1. Zeichnet den Exit-Code des Containers auf.
  2. Schließt die Logdatei des Containers.
  3. Schließt STDOUT/STDERR des Podman-Befehls.
  4. Führt die podman container cleanup $CTRID aus Befehl.

Podman-Container-Bereinigung beendet dann slirp4netns network und teilt containers/storage mit um alle Mount-Punkte des Containers zu unmounten. Wenn der Benutzer --rm angegeben hat dann wird stattdessen der Behälter vollständig entfernt. Die Containerschicht wird aus containers/storage entfernt , und die Containerdefinition wird aus der DB entfernt.

Da der ursprüngliche Podman-Befehl im Vordergrund ausgeführt wurde, wartet Podman auf conmon zu beenden, ruft den Exit-Code aus dem Container ab und wird dann mit dem Exit-Code des Containers beendet.

Abschluss

Hoffentlich hilft Ihnen diese Erklärung, die ganze Magie zu verstehen das passiert unter der Decke, wenn der Rootless-Podman-Befehl ausgeführt wird.

Neu bei Containern? Laden Sie den Containers Primer herunter und lernen Sie die Grundlagen von Linux-Containern kennen.


Linux
  1. Warum kann Podman mein Image nicht ziehen?

  2. Rootless Podman als Nicht-Root-Benutzer ausführen

  3. Verwenden von Dateien und Geräten in Podman-Containern ohne Root

  4. So debuggen Sie Probleme mit Volumes, die in Rootless-Containern gemountet sind

  5. Steuerung des Zugriffs auf rootless Podman für Benutzer

Wie Cirrus CLI Podman verwendet, um rootless Builds zu erreichen

Useradd vs. Adduser:Was ist der Unterschied?

Werfen Sie einen Blick hinter die Kulissen mit einer Postman-Installations- und Anleitung

Was ist der Zweck des Benutzers „mysql.sys@localhost“.

Was ist der Zweck des „Systembenutzers“ in der MySQL-Replikation?

Was ist der MySQL-Benutzer debian-sys-maint (und mehr)?