Podman Archives - credativ®

Nach unseren Beiträgen zu Podman und Buildah widmen wir uns heute dem dritten Tool aus der Sammlung von RedHat-Container Tools: Skopeo.

Bei Skopeo handelt es sich um ein vergleichsweise kleines Tool, das hauptsächlich Anwendung in der Container-Erstellung und -Nutzung findet und damit prädestiniert für folgende Aufgaben und Bereiche ist:

Hierbei ist noch zu erwähnen, dass Skopeo auch einen lokalen Docker-Daemon wie eine Registry behandelt und dies als Quelle genutzt werden kann. Auch wird, wenn nicht anders angegeben, immer auch für die Images von der aktuell für Skopeo genutzten Architektur ausgegangen. Dies lässt sich jedoch durch Parameter ändern bzw. erweitern.

Entwicklung

Die Skopeo Version 1.0 wurde am 18.05.2020 veröffentlicht und wird aktuell, wie auch die anderen RHEL-Container-Tools, unter der Apache 2.0 Lizenz angeboten. Es wird ebenfalls in Golang entwickelt, hat keinen festen Release-Zyklus und wird von der containers-group betreut. Der Code kann auf Github eingesehen werden.

Installation

Die Installation von Skopeo ist mittlerweile auf fast allen Linux-Distributionen recht trivial über eine einfache Paketinstallation erledigt, da alle gängigen Distributionen das Paket mittlerweile in den Repositories hinterlegt haben. Sollte eine bestimmte Version benötigt werden, oder es nicht möglich sein Pakete zu installieren, so wird Skopeo auch als dedizierter Container zur Verfügung gestellt.

Auch ist hier wie üblich zu beachten, dass nicht jede Distribution auch die aktuellste Version bereit stellt und damit ggf. Dritt-Repositories hinzugefügt werden müssen.

Eine detaillierte Dokumentation dazu findet sich auf Github.

Konfiguration

Skopeo nutzt hier, ebenso wie podman und buildah, folgende Dateien als Standard:

Arbeiten mit Skopeo

In den hier verwendeten Beispielen nutzen wir die aktuell unter CentOS 8 Streams verfügbare Version 1.5.0.

Inspizieren eines Images

Mittels skopeo inspect können die Details zu einem Image bzw. dem Repository eingesehen werden ohne sich die Daten vorher herunterladen zu müssen. Es werden unter anderem alle verfügbaren Tags des Repositorys sowie verschiedene Details zum angefragten Image beziehungsweise Image-Tag.

Es werden hierbei noch verschiedene Parameter angeboten um bestimmte Details anzufragen oder die Ausgabe anzupassen.
Folgend ein paar Beispiele:

Alle Optionen für inspect sowie diverse Beispiele finden sich in der Dokumentation.

Hierzu ein Beispiel für ein generisches skopeo inspect:

$ skopeo inspect docker://docker.io/library/postgres:latest
{
    "Name": "docker.io/library/postgres",
    "Digest": "sha256:f91f537eb66b6f80217bb6921cd3dd4035b81a5bd1291e02cfa17ed55b7b9d28",
    "RepoTags": [
        "10",
        ...
        "bullseye",
        "buster",
        "latest"
    ],
    "Created": "2022-01-04T01:19:59.244463885Z",
    "DockerVersion": "20.10.7",
    "Labels": null,
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
        "sha256:a2abf6c4d29d43a4bf9fbb769f524d0fb36a2edab49819c1bf3e76f409f953ea",
        ...
    ],
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/14/bin",
        "GOSU_VERSION=1.14",
        "LANG=en_US.utf8",
        "PG_MAJOR=14",
        "PG_VERSION=14.1-1.pgdg110+1",
        "PGDATA=/var/lib/postgresql/data"
    ]
}

Und ein Bespiel für ein skopeo inspect --config:

$ skopeo inspect --config docker://docker.io/library/postgres:latest

{
    "created": "2022-01-04T01:19:59.244463885Z",
    "architecture": "amd64",
    "os": "linux",
    "config": {
        "ExposedPorts": {
            "5432/tcp": {}
        },
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/14/bin",
            "GOSU_VERSION=1.14",
            "LANG=en_US.utf8",
            "PG_MAJOR=14",
            "PG_VERSION=14.1-1.pgdg110+1",
            "PGDATA=/var/lib/postgresql/data"
        ],
        "Entrypoint": [
            "docker-entrypoint.sh"
        ],
        "Cmd": [
            "postgres"
        ],
        "Volumes": {
            "/var/lib/postgresql/data": {}
        },
        "StopSignal": "SIGINT"
    },
    "rootfs": {
        "type": "layers",
        "diff_ids": [
            "sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f",
        ...
        ]
    },
    "history": [
        {
            "created": "2021-12-21T01:22:43.418913408Z",
            "created_by": "/bin/sh -c #(nop) ADD file:09675d11695f65c55efdc393ff0cd32f30194cd7d0fbef4631eebfed4414ac97 in / "
        },
        ...
        {
            "created": "2022-01-04T01:19:59.244463885Z",
            "created_by": "/bin/sh -c #(nop)  CMD [\"postgres\"]",
            "empty_layer": true
        }
    ]
}

Um eine dedizierte Liste aller im Repository verfügbaren Image-Tags zu bekommen eignet sich der Befehl skopeo list-tags. Skopeo braucht nur die URL zum Repository, zum Beispiel docker://docker.io/library/postgres, und kann so die ganze Liste aller Image-Tags auslesen und verfügbar machen.

Die Dokumentation ist selbst-erklärend und fällt entsprechend kompakt aus.

Kopieren von Images

Mittels skopeo copy können Images entweder zwischen verschiedenen Registries kopiert werden, oder auch zum Beispiel von einer Registry in ein lokales Verzeichnis.

Hierbei stehen vielfältige Parameter zur Verfügung, wobei hier wieder nur ein paar Beispiele genannt werden sollen:

Hierbei kann es natürlich nötig sein Login-Informationen anzugeben. Details dazu werden weiter unten beschrieben.

Generelle Dokumentation zu den Copy-Möglichkeiten findet sich in der Dokumentation.

Löschen von Images

Mit Skopeo ist es ebenfalls möglich Images einer Registry remote als “gelöscht” zu markieren. Hierbei ist zu beachten, dass das Image erst dann tatsächlich im Dateisystem entfernt wird, wenn die Löschroutine / Garbage-Collection des Registry ihren Lauf hatte. Neben der Authentifizierung sind hier keine weiteren Optionen möglich.

Alle Informationen finden sich inkl. Beispielen in der offiziellen Dokumentation.

Synchronisieren von Images

Skopeo bietet die Möglichkeit alle Images eines Repositories entweder mit einem anderen Repository oder einem lokalen Ordner zu synchronisieren. Hierbei werden alle Images des Quellpfades in den Zielpfad übernommen.

Anwendung findet diese Synchronisierungsmöglichkeit zum Beispiel bei lokalen Entwicklungsumgebung deren Images in einem Repository liegen und dort automatisch synchronisiert werden können. Auch der umgekehrte Weg, die Synchronisierung aller Images aus einem internen Repository auf eine lokale Umgebung ist hiermit einfach umzusetzen.

Es ist sogar möglich als Quelle eine yaml-Datei anzugeben in welcher als Liste verschiedene Images aus verschiedenen Repositories hinterlegt sind und diese dann einmal auf einen bestimmten Host oder eine andere Registry zu kopieren. Hierbei ist zu beachten, dass in der YAML-Version keine lokalen Pfade als Quelle erlaubt sind. Natürlich können in der yaml-Datei auch verschiedene zusätzliche Parameter wie zum Beispiel Logindaten hinterlegt werden.

Eine volle Übersicht der Parameter und verschiedene Beispiele finden sich wie gewohnt in der Dokumentation.

Authentifizierung

In fast allen Fällen ist es möglich die Parameter für die Authentifizierung direkt im skopeo-Aufruf mit anzugeben.
Bei Befehlen welche Quelle und Ziel beinhalten gibt es die Möglichkeit für --src-creds sowie --dest-creds. Beim Arbeiten an einer einzelnen Registry wird meist nur --creds genutzt. Gleichermaßen gibt es auch diverse Einzelparameter für username und password.

Alternativ kann ein Login auch mittels skopeo login manuell gestartet werden. Dieser Aufruf erfragt die Logindaten interaktiv und erstellt ein Auth-file (Im Standard ${XDG_RUNTIME_DIR}/containers/auth.json), welches dann ebenfalls als Parameter an die einzelnen Skopeo-Aufrufe weitergereicht werden kann. So müssen Authentifizierungsdaten nicht zwangsläufig im Code selbst hinterlegt werden sondern können, angepasst an die Vorliebe des Nutzers, interaktiv erfragt werden. Diese Auth-Datei teilt sich Skopeo auch mit Podman und Buildah und reiht sich damit nahtlos in die Sammlung von RedHat-Container Tools ein.

Um Datensätze wieder zu entfernen kann skopeo logout genutzt werden.

Fazit

Skopeo ist eine sinnvolle Ergänzung zu den beiden anderen Container-Tools und bietet einige Features bei denen sich der Autor schon länger fragt warum diese nicht auch an andere Stelle zur Verfügung stehen.

Besonders die inspect und copy Funktionen von Skopeo können viele verschiedene andere Schritte und auch eine Menge Zeit einsparen wenn man viel mit verschiedenen Repositories arbeitet. Allein sich die verschiedenen Tags eines Images und deren Details anzuschauen ist durchaus eine große Hilfe in der alltäglichen Arbeit und erspart diverse Suchanfragen und Recherchen über UIs.

Auch das einfache Kopieren eines Images von einer Registry (z.B. Docker-Hub) in eine interne Registry ist gerade nach den Limitierung von Dockerhub eine willkommene Funktion, auch wenn die nach und nach Einzug haltende Funktion des Cache-Proxies bei einigen Registries wie zum Beispiel Harbor dies auch ablösen kann.

Wir unterstützen Sie gerne

Mit über 22+ Jahren an Entwicklungs- und Dienstleistungserfahrung im Open Source Bereich, kann die credativ GmbH Sie mit einem beispiellosen und individuell konfigurierbaren Support professionell Begleiten und Sie in allen Fragen bei Ihrer Open Source Infrastruktur voll und ganz unterstützen.

Die credativ GmbH ist ein herstellerunabhängiges Beratungs- und Dienstleistungs- unternehmen mit Standort in Mönchengladbach. Seit dem erfolgreichen Merger mit Instaclustr 2021 ist die credativ GmbH das europäische Hauptquartier der Instaclustr Gruppe.

Die Instaclustr Gruppe hilft Unternehmen bei der Realisierung eigener Applikationen im großen Umfang dank Managed-Plattform-Solutions für Open Source Technologien wie zum Beispiel Apache Cassandra®, Apache Kafka®, Apache Spark™, Redis™, OpenSearch™, Apache ZooKeeper™, und PostgreSQL®.
Instaclustr kombiniert eine komplette Dateninfrastruktur-Umgebung mit praktischer Expertise, Support und Consulting um eine kontinuierliche Leistung und Optimierung zu gewährleisten. Durch Beseitigung der Konplexität der Infrastruktur, wird es Unternehmen ermöglicht, ihre internen Entwicklungs- und Betriebsressourcen auf die Entwicklung innovativer kundenorientierter Anwendungen zu geringeren Kosten zu konzentrieren. Zu den Kunden von Instaclustr gehören einige der größten und innovativsten Fortune-500-Unternehmen.

In unserem vorhergehenden Artikel über Buildah haben wir erläutert wie man Container auch ohne Docker und root-Berechtigungen erstellt.

In diesem Artikel soll es darum gehen wie man eben jene Container auch ohne erweiterte Rechte nutzen kann.

Podman gehört neben dem bereits erwähnten Buildah und Skopeo zu den RedHat Container Tools und ist, kurz Zusammengefasst, eine daemonlose Laufzeitumgebung für Container. Es ist dabei, wie der Docker-Daemon, für den Betrieb eines einzelnen Hosts ausgelegt und bietet auch keine Clusterfunktionalität.

Entwicklung

Podman wurde in der Version 1.0 am 11.01.2019 released und steht ebenfalls unter der Apache 2.0 Lizenz.
Die Implementierung erfolgt in Golang und wird von der „containers organisation“ in erster Instanz vorangetrieben. Diese beinhaltet sowohl RedHat-Mitarbeiter als auch externe Entwickler.
Der Code kann auf Github eingesehen werden. Die Entwicklung erfolgt dabei in keinem festen Releasezyklus. So können sowohl Monate wie auch Wochen zwischen den Versionen liegen, je nachdem wenn Entschieden wird, dass genügend neue Features für einen Release implementiert wurden.

Podman selbst baut komplett auf die libpod auf, bzw. könnte man auch sagen, dass es das Tool zur libpod ist. Daher ist der Name des Repositorys auch libpod und nicht podman.

Container ohne root-Berechtigungen

Eine zentrale Komponente von Buildah und auch Podman ist hier die libpod welche es ermöglicht Container nur mit Nutzerberechtigungen zu starten und Images zu erstellen.
Diese setzt auf slirp4netns, fuse-overlayfs und die /etc/sub(u|g)id.

Dieser Bereich wurde bereits ausführlich im Artikel zu Buildah behandelt, weswegen hier nur auf diesen verwiesen wird, um Wiederholungen zu vermeiden.

Installation

Podman ist bei den gängigen RedHat-Distributionen direkt in den Repositories verfügbar.
Diese können dort je nach Version via dnf install podman oder yum install podman installiert werden.
Zu beachten ist, dass die Pakete in den CentOS-Distributionen nicht unbedingt aktuell sind. Daher bietet es sich hier an ebenfalls auf Kubic auszuweichen.

Für Debian und Derivate sowie Suse stehen analog zu Buildah Pakete in Kubic bereit.

Weitere Informationen dazu finden sich in der Dokumentation

[podmanager@buildah ~]$ podman -v
podman version 1.8.2

Konfiguration

Die Konfigurationsdatei für Podman ist analog zu Builder unter /etc/containers/libpod.conf für die globale und unter ~/.config/containers/libpod.conf für die benutzerspezifische Konfiguration zu finden.
Die Vorlage mit den Default-Werten findet sich unter /usr/share/containers/libpod.conf. Diese sollte jedoch nicht direkt, sondern durch die beiden Alternativen angepasst werden.
In der Datei lassen sich verschiedene Parameter für Podman konfigurieren; welches CNI-Plugin genutzt werden soll, welche Container-Runtime oder auch wo die Volumes der Container zu finden sind.

Ein Online-Beispiel findet sich auf Github

Für einen ersten Testbetrieb sind hier jedoch keine Änderungen erforderlich, es dient lediglich dazu ggf. podman an die eigenen Wünsche anzupassen.

Arbeiten mit Podman

Podman wurde als Drop-In-Replacement für Docker entworfen und daher funktionieren die meisten Befehle wie ps, rm, inspect, logs oder exec analog zu docker und sollen hier wenn dann nur kurz erwähnt werden. Die Funktionalität ist dabei nicht nur auf den Betrieb von Container beschränkt, es ist im begrenzten Maße auch möglich Container zu erstellen. Im Hintergrund setzt Podman auf die Funktionalität von Buildah, kann jedoch Container nur aus einem Containerfile heraus erstellen.
Details dazu finden sich in der Dokumentation

Auch ein podman top $ContainerID funktioniert ebenso wie das Erstellen, Migrieren und Restore eines Checkpoints.

[user@system1 ~]$ podman container checkpoint <container_id> -e /tmp/checkpoint.tar.gz
[user@system1 ~]$ scp /tmp/checkpoint.tar.gz <destination_system>:/tmp

[user@system2 ~]$ podman container restore -i /tmp/checkpoint.tar.gz

In den folgenden Bereichen soll daher in erster Linie auf die Unterschiede in der Containerhandhabe zwischen Docker und Podman eingegangen werden.

Einen Container starten

Um einen Container der Wahl (hier postgres) zu starten, pullen wir das Image und starten dies anschließend.

[podmanager@buildah ~]$ podman pull postgres
...
Copying config 73119b8892 done  
Writing manifest to image destination
Storing signatures
73119b8892f9cda38bb0f125b1638c7c0e71f4abe9a5cded9129c3f28a6d35c3

[podmanager@buildah ~]$ podman inspect postgres | grep "PG_VERSION="
    "PG_VERSION=12.2-2.pgdg100+1",
    "created_by": "/bin/sh -c #(nop)  ENV PG_VERSION=12.2-2.pgdg100+1",

[podmanager@buildah ~]$ podman run -d -e POSTGRES_PASSWORD=SuperDB --name=postgres_dev postgres
c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
[podmanager@buildah ~]$ podman ps
CONTAINER ID  IMAGE                              COMMAND   CREATED        STATUS            PORTS  NAMES
c8b9732b6ad2  docker.io/library/postgres:latest  postgres  5 seconds ago  Up 4 seconds ago         postgres_dev

Es läuft nun ein PostgreSQL®-Container mit dem Namen „postgres_dev“. Dies unterscheidet sich soweit nicht von Docker.

Die Besonderheit von podman ist erst in der Prozessliste ersichtlich:

podmana+  2209     1  0 13:11 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -u c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -r /usr/bin/runc -b

232070    2219  2209  0 13:11 ?        Ss     0:00  \_ postgres

Der PostgreSQL®-Prozess läuft nicht als Kind eines Daemon-Prozesses, sondern der Komponente „conmon“.
Diese überwacht den Zustand des Containers nach dem Start. Außerdem stellt es den Socket für die Kommunikation bereit und den Stream für den Output, welche
in das von podman konfigurierte Log geschrieben werden.
Weiterführende Informationen zu conmon finden sich auf Github

Starten wir jetzt einen zweiten Container (postgres_prod) via podman wird ein weiterer conmon-Prozess gestartet:

[podmanager@buildah ~]$ podman run -d -e POSTGRES_PASSWORD=SuperDB --name=postgres_prod postgres
6581a25c82620c725fe1cfb6546479edac856228ecb3c11ad63ab95a453c1b64

[podmanager@buildah ~]$ podman ps
CONTAINER ID  IMAGE                              COMMAND   CREATED         STATUS             PORTS  NAMES
6581a25c8262  docker.io/library/postgres:latest  postgres  15 seconds ago  Up 15 seconds ago         postgres_prod
c8b9732b6ad2  docker.io/library/postgres:latest  postgres  7 minutes ago   Up 7 minutes ago          postgres_dev
podmana+  2209     1  0 13:11 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -u c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -r /usr/bin/runc -b
232070    2219  2209  0 13:11 ?        Ss     0:00  \_ postgres
...
podmana+  2337     1  0 13:19 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c 6581a25c82620c725fe1cfb6546479edac856228ecb3c11ad63ab95a453c1b64 -u 6581a25c82620c725fe1cfb6546479edac856228ecb3c11ad63ab95a453c1b64 -r /usr/bin/runc -b
232070    2348  2337  0 13:19 ?        Ss     0:00  \_ postgres
...

Hier finden sich im Prozess jeweils die UUIDs der Container wieder.
Der cmdline des Prozesses ist natürlich sehr viel länger als hier dargestellt. Folgend noch ein komplettes Beispiel manuell formatiert:

[podmanager@buildah ~]$ ps -ef f  | grep conmon
...
podmana+  2209     1  0 13:11 ?        Ssl    0:00 /usr/bin/conmon
--api-version 1
-c c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
-u c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
-r /usr/bin/runc
-b /home/podmanager/.local/share/containers/storage/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata
-p /var/tmp/run-1002/containers/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata/pidfile
-l k8s-file:/home/podmanager/.local/share/containers/storage/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata/ctr.log
--exit-dir /var/tmp/run-1002/libpod/tmp/exits
--socket-dir-path /var/tmp/run-1002/libpod/tmp/socket
--log-level error
--runtime-arg --log-format=json
--runtime-arg --log
--runtime-arg=/var/tmp/run-1002/containers/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata/oci-log
--conmon-pidfile /var/tmp/run-1002/containers/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata/conmon.pid
--exit-command /usr/bin/podman
--exit-command-arg --root 
--exit-command-arg /home/podmanager/.local/share/containers/storage 
--exit-command-arg --runroot 
--exit-command-arg /var/tmp/run-1002/containers 
--exit-command-arg --log-level 
--exit-command-arg error 
--exit-command-arg --cgroup-manager 
--exit-command-arg cgroupfs 
--exit-command-arg --tmpdir 
--exit-command-arg /var/tmp/run-1002/libpod/tmp 
--exit-command-arg --runtime 
--exit-command-arg runc 
--exit-command-arg --storage-driver 
--exit-command-arg overlay 
--exit-command-arg --storage-opt 
--exit-command-arg overlay.mount_program=/usr/bin/fuse-overlayfs 
--exit-command-arg --storage-opt 
--exit-command-arg overlay.mount_program=/usr/bin/fuse-overlayfs 
--exit-command-arg --events-backend 
--exit-command-arg file 
--exit-command-arg container 
--exit-command-arg cleanup 
--exit-command-arg c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
...

Durch die Formatierung ist sehr schön zu sehen, wie die Übergabe von Parametern zwischen podman und conmon über die *args funktioniert.

Zusätzlich zu conmon wird natürlich auch jeweils eine Instanz von slirp4netns und fuse-overlayfs pro container gestartet um Netzwerk und Storage ohne root-Berechtigungen bereitzustellen.

podmana+  2201     1  0 13:11 ?        Ss     0:00 /usr/bin/fuse-overlayfs -o lowerdir=/home/podmanager/.local/share/containers/storage/overlay/l/FX4RZGGJ5HSNVMGVFG6K3I7PIL:/home/podmanager/.local/share/containers/storage/overlay/l/AIHUOS

podmana+  2206     1  0 13:11 pts/0    S      0:00 /usr/bin/slirp4netns --disable-host-loopback --mtu 65520 --enable-sandbox -c -e 3 -r 4 --netns-type=path /tmp/run-1002/netns/cni-18902a12-5b1b-15d3-0c31-138efe1d66ba tap0

podmana+  2209     1  0 13:11 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -u c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -r /usr/bin/runc -b

232070    2219  2209  0 13:11 ?        Ss     0:00  \_ postgres

Erstellen eines Systemd-service-files

Da die Container ohne Daemon laufen und einzeln angestartet werden können, bietet es sich natürlich auch an diese nicht via Docker, sondern via Systemd zu steuern.
Das Schreiben von Service-files ist jedoch im allgemeinen mühsam, weswegen podman dafür direkt eine Funktion eingebaut hat.

Folgend ein Beispiel für unsere postgres_dev

[podmanager@buildah ~]$ podman generate systemd postgres_dev
# container-c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7.service
# autogenerated by Podman 1.8.2
# Tue Mar 24 13:47:11 CET 2020

[Unit]
Description=Podman container-c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7.service
Documentation=man:podman-generate-systemd(1)
Wants=network.target
After=network-online.target

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
ExecStart=/usr/bin/podman start c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
ExecStop=/usr/bin/podman stop -t 10 c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7
PIDFile=/var/tmp/run-1002/containers/overlay-containers/c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7/userdata/conmon.pid
KillMode=none
Type=forking

[Install]
WantedBy=multi-user.target default.target

Einen Fehler gibt es hier jedoch noch. Es muss im Servicefile der User ergänzt werden unter dem der Container gestartet werden soll, insofern dies nicht als root passieren soll. Dazu muss in der Sektion [Service] nur User=podmanager ergänzt werden (bzw. der Username auf ihrem System).

Um den Container als Service zu hinterlegen wären unter CentOS 8 folgende Schritte durchzuführen:

[podmanager@buildah ~]$ podman generate systemd --files --name postgres_dev
/home/podmanager/container-postgres_dev.service
# User= im servicefile ergänzen
[podmanager@buildah ~]$ sudo cp /home/podmanager/container-postgres_dev.service /etc/systemd/system/
[podmanager@buildah ~]$ sudo systemctl daemon-reload
[podmanager@buildah ~]$ sudo systemctl start container-postgres_dev.service
[podmanager@buildah ~]$ systemctl status container-postgres_dev.service 
● container-postgres_dev.service - Podman container-postgres_dev.service
   Loaded: loaded (/etc/systemd/system/container-postgres_dev.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-03-24 14:04:14 CET; 1s ago
     Docs: man:podman-generate-systemd(1)
  Process: 7691 ExecStart=/usr/bin/podman start postgres_dev (code=exited, status=0/SUCCESS)
 Main PID: 7717 (conmon)
    Tasks: 11 (limit: 25028)
   Memory: 46.7M
   CGroup: /system.slice/container-postgres_dev.service
           ├─7710 /usr/bin/fuse-overlayfs -o lowerdir=/home/podmanager/.local/share/containers/storage/overlay/l/FX4RZGGJ5HSNVMGVFG6K3I7PIL:/home/podmanager/.local/share/containers/storage/overlay/l/AIHUOSIVGT5DN5GCUR7PRELVKK:/home/podma>
           ├─7714 /usr/bin/slirp4netns --disable-host-loopback --mtu 65520 --enable-sandbox -c -e 3 -r 4 --netns-type=path /tmp/run-1002/netns/cni-a0ee9d78-2f8c-a563-1947-92d0766a43b7 tap0
           ├─7717 /usr/bin/conmon --api-version 1 -c c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -u c8b9732b6ad253710ae6e75f934a74e8469e61bc5b5d88c2fa92c7257d00d2e7 -r /usr/bin/runc -b /home/podmanager/.local/share/c>
           ├─7727 postgres
           ├─7758 postgres: checkpointer
           ├─7759 postgres: background writer
           ├─7760 postgres: walwriter
           ├─7761 postgres: autovacuum launcher
           ├─7762 postgres: stats collector
           └─7763 postgres: logical replication launcher

Mär 24 14:04:13 buildah.localdomain systemd[1]: Starting Podman container-postgres_dev.service...
Mär 24 14:04:14 buildah.localdomain podman[7691]: postgres_dev
Mär 24 14:04:14 buildah.localdomain systemd[1]: Started Podman container-postgres_dev.service.

Wichtig zu erwähnen ist noch, dass man den über Systemd gestarteten Container natürlich auch via podman administrieren / erreichen kann, zumindest das Starten und Stoppen aber dem Service überlassen sollte.

Erstellen eines Pods

Wie der Name vermuten lässt ist es mit Podman nicht nur möglich Container zu erstellen, sondern diese auch in Pods zu organisieren.
Ein Pod stellt dabei analog zu Kubernetes einen organisatorischen Verbund von Containern dar die sich auch bestimmte Namespaces wie pids, network o.ä. teilen können.
Diese werden dann via podman pod $cmd administriert.

Im Folgenden laden wir das Image des postgres-exporters für prometheus und erstellen daraus einen Pod mit der Bezeichnung postgres-prod-pod.

[podmanager@buildah ~]$ podman pull docker.io/wrouesnel/postgres_exporter

[podmanager@buildah ~]$ podman pod create --name postgres-prod-pod
727e7544515e0e683525e555934e02a341a42009a9c49fb2fd53094187a1e97c

[podmanager@buildah ~]$ podman run -d --pod postgres-prod-pod -e POSTGRES_PASSWORD=SuperDB postgres:latest
8f313260973ef6eb6fa84d2893875213cee89b48c93d08de7642b0a8b03c4a88

[podmanager@buildah ~]$ podman run -d --pod postgres-prod-pod -e DATA_SOURCE_NAME="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable" postgres_exporter
fee22f24ff9b2ace599831fa022fb1261ef836846e0ba938c7b469d8dfb8a48a

[podmanager@buildah ~]$ podman pod ps
POD ID         NAME                STATUS    CREATED          # OF CONTAINERS   INFRA ID
727e7544515e   postgres-prod-pod   Running   48 seconds ago   3                 6edc862441f1

[podmanager@buildah ~]$ podman pod inspect postgres-prod-pod
{
     "Config": {
          "id": "727e7544515e0e683525e555934e02a341a42009a9c49fb2fd53094187a1e97c",
          "name": "postgres-prod-pod",
          "hostname": "postgres-prod-pod",
          "labels": {

          },
          "cgroupParent": "/libpod_parent",
          "sharesCgroup": true,
          "sharesIpc": true,
          "sharesNet": true,
          "sharesUts": true,
          "infraConfig": {
               "makeInfraContainer": true,
               "infraPortBindings": null
          },
          "created": "2020-03-24T14:44:27.01249721+01:00",
          "lockID": 0
     },
     "State": {
          "cgroupPath": "/libpod_parent/727e7544515e0e683525e555934e02a341a42009a9c49fb2fd53094187a1e97c",
          "infraContainerID": "6edc862441f18234f0c61693f11d946f601973a71b85fa9d777273feed68ed3c",
          "status": "Running"
     },
     "Containers": [
          {
               "id": "6edc862441f18234f0c61693f11d946f601973a71b85fa9d777273feed68ed3c",
               "state": "running"
          },
          {
               "id": "8f313260973ef6eb6fa84d2893875213cee89b48c93d08de7642b0a8b03c4a88",
               "state": "running"
          },
          {
               "id": "fee22f24ff9b2ace599831fa022fb1261ef836846e0ba938c7b469d8dfb8a48a",
               "state": "running"
          }
     ]
}

Hier ist nun zu sehen, dass der Pod die beiden Container postgres und postgres-exporter beinhaltet (Vergleich UUIDs).
Der dritte Container ist der Infra-Container für das Bootstrapping und soll hier nicht weiter Thema sein.

In der Prozessliste zeigt sich das Ganze dann wie folgt:

podmana+  6279     1  0 14:44 ?        Ss     0:00 /usr/bin/fuse-overlayfs -o lowerdir=/home/podmanager/.local/share/containers/storage/overlay/l/
podmana+  6281     1  0 14:44 pts/2    S      0:00 /usr/bin/slirp4netns --disable-host-loopback --mtu 65520 --enable-sandbox -c -e 3 -r 4 --netns-type=path /tmp/run-1002/netns/cni-5fe4356e-77c0-8127-f72b-7335c2ed05c4 tap0
podmana+  6284     1  0 14:44 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c 6edc862441f18234f0c61693f11d946f601973a71b85fa9d777273feed68ed3c -u 6edc862441f18234f0c61693f11d946f601973a71b85fa9d777273feed68ed3c -r /usr/bin/runc -b
podmana+  6296  6284  0 14:44 ?        Ss     0:00  \_ /pause
podmana+  6308     1  0 14:44 ?        Ss     0:00 /usr/bin/fuse-overlayfs -o lowerdir=/home/podmanager/.local/share/containers/storage/overlay/l/FX4RZGGJ5HSNVMGVFG6K3I7PIL:/home/podmanager/.local/share/containers/storage/overlay/l/AIHUOS
podmana+  6312     1  0 14:44 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c 8f313260973ef6eb6fa84d2893875213cee89b48c93d08de7642b0a8b03c4a88 -u 8f313260973ef6eb6fa84d2893875213cee89b48c93d08de7642b0a8b03c4a88 -r /usr/bin/runc -b
232070    6322  6312  0 14:44 ?        Ss     0:00  \_ postgres
...
232070    6549  6322  0 14:44 ?        Ss     0:00      \_ postgres: postgres postgres ::1(51290) idle
podmana+  6520     1  0 14:44 ?        Ss     0:00 /usr/bin/fuse-overlayfs -o lowerdir=/home/podmanager/.local/share/containers/storage/overlay/l/P5NJW4TB6JUFOBBIN2MOHW7272:/home/podmanager/.local/share/containers/storage/overlay/l/KVC54Z
podmana+  6523     1  0 14:44 ?        Ssl    0:00 /usr/bin/conmon --api-version 1 -c fee22f24ff9b2ace599831fa022fb1261ef836846e0ba938c7b469d8dfb8a48a -u fee22f24ff9b2ace599831fa022fb1261ef836846e0ba938c7b469d8dfb8a48a -r /usr/bin/runc -b
251072    6534  6523  0 14:44 ?        Ssl    0:00  \_ /postgres_exporter

Hier ist zu sehen, dass es zwar mehrere conmon und overlayfs-Prozesse für die Container gibt, jedoch nur einen slirp4netns, da die Container sich diesen teilen und darüber auch via localhost kommunizieren können.
Auch ist zu sehen dass die PostgreSQL®-Datenbank eine Verbindung von localhost hat (PID 6549) wobei es sich um den Exporter handelt.

Im Normalfall werden bei der Erstellung eines Pods via podman pod create folgende Namespaces für die Container zusammengefasst: net,ipc,uts und user.
Somit hat auch jeder Container trotz der Zusammenfassung im Pod noch seinen eigenen PID-namespace.
Sollte dies jedoch gewünscht sein, so kann mittels dem Parameter --share bei der Erstellung angegeben werden was alles geteilt werden soll.

So sieht z.B. die Prozessliste des Pods ohne gemeinsamen pid-namespace aus. Jeder Container hat seine eigene Prozesstruktur.

[podmanager@buildah ~]$ podman pod top postgres-prod-pod
USER                PID   PPID   %CPU    ELAPSED            TTY   TIME   COMMAND
0                   1     0      0.000   11m45.845291911s   ?     0s     /pause 
postgres            1     0      0.000   11m45.854330176s   ?     0s     postgres 
postgres            25    1      0.000   11m45.854387876s   ?     0s     postgres: checkpointer    
postgres            26    1      0.000   11m45.854441615s   ?     0s     postgres: background writer    
postgres            27    1      0.000   11m45.854483844s   ?     0s     postgres: walwriter    
postgres            28    1      0.000   11m45.854525645s   ?     0s     postgres: autovacuum launcher    
postgres            29    1      0.000   11m45.854567082s   ?     0s     postgres: stats collector    
postgres            30    1      0.000   11m45.854613262s   ?     0s     postgres: logical replication launcher    
postgres            31    1      0.000   11m45.854653703s   ?     0s     postgres: postgres postgres ::1(51292) idle 
postgres_exporter   1     0      0.000   11m45.859505449s   ?     0s     /postgres_exporter

Als Alternative die Ausgabe bei der Erstellung des Pods mittels podman pod create --name postgres-prod-pod --share=pid,net,ipc,uts,user

[podmanager@buildah ~]$ podman pod top postgres-prod-pod
USER       PID   PPID   %CPU    ELAPSED         TTY     TIME   COMMAND
root       1     0      0.000   20.396867487s   ?       0s     /pause 
postgres   6     0      0.000   20.396936905s   pts/0   0s     postgres 
postgres   60    6      0.000   18.396997034s   ?       0s     postgres: checkpointer    
postgres   61    6      0.000   18.397086198s   ?       0s     postgres: background writer    
postgres   62    6      0.000   18.39713465s    ?       0s     postgres: walwriter    
postgres   63    6      0.000   18.39718056s    ?       0s     postgres: autovacuum launcher    
postgres   64    6      0.000   18.397229737s   ?       0s     postgres: stats collector    
postgres   65    6      0.000   18.397279102s   ?       0s     postgres: logical replication launcher    
20001      66    0      0.000   16.397325377s   pts/0   0s     /postgres_exporter

Zur Auswahl stehen bei --share ipc, net, pid, user und uts.

Der gesamte pod kann nun auch via podman pod stop/start gestartet und gestoppt werden.
Ebenso können für Pods wie Container systemd-service-files generiert werden.

Podman und Kubernetes

Podman bietet einige Unterstützung im Bereich Kubernetes-YAML.

So ist es z.B. möglich aus den Pods, welche man mit podman erstellt hat, kubernes-Pod YAML zu generieren.

[podmanager@buildah ~]$ podman generate kube postgres-prod-pod -f postgres-prod-pod.yaml
[podmanager@buildah ~]$ cat postgres-prod-pod.yaml
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.8.2
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-03-24T14:08:14Z"
  labels:
    app: postgres-prod-pod
  name: postgres-prod-pod
spec:
  containers:
  - command:
    - postgres
    env:
    ...
    image: docker.io/library/postgres:latest
    name: reverentptolemy
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    workingDir: /
  - env:
    ...
    image: docker.io/wrouesnel/postgres_exporter:latest
    name: friendlyshirley
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
      runAsGroup: 20001
      runAsUser: 20001
      seLinuxOptions: {}
    workingDir: /
status: {}

Mit der Option -s wird sogar noch ein Service mit ggf. published-Ports generiert:

---
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2020-03-24T14:10:42Z"
  labels:
    app: postgres-prod-pod
  name: postgres-prod-pod
spec:
  selector:
    app: postgres-prod-pod
  type: NodePort
status:
  loadBalancer: {}

Die generate-yaml Funktionalität greift dabei für Pods wie auch für Services und auch in beide Richtungen.
Mit podman play ist es möglich Pod und Container-Definitionen in podman zu testen.

Fazit

Dies war ein grober Einblick in die Möglichkeiten Container ohne Daemon und root-Berechtigungen auf einem Host zu betreiben.
Natürlich ist mit podman noch viel mehr möglich, jedoch würde die Erläuterung jeder Option hier den Rahmen sprengen.

Zu erwähnen ist vielleicht noch, dass Podman mit aktiviertem cgroupsV2 auch z.B. Resourcennutzung in Pods auswerten kann.
Dies ist jedoch aktuell nur unter Fedora31 standardmäßig aktiviert.