Container mit Podman

Posted on Sa 30 April 2022 in Blog

Der Talk „Containerverwaltung mit Podman“ bei den Grazer Linuxtagen 2022 hat mich dazu inspiriert, meine Erfahrungen mit Podman aufzuschreiben. Im Red Hat Umfeld hat Podman schon länger Docker als Standardcontainer Engine ersetzt, daher haben wir es schon einige Zeit im Einsatz. Meiner Ansicht nach ist ein großer Pluspunkt von Podman, die Möglichkeit Container rootless zu betreiben, d.h. mit einem unpriviligierten Benutzer, der am Host-System keinerlei Root-Rechte hat. Großteils sind die Erfahrungen in diesem Beitrag auch meist aus der Perspektive der rootless Container.

Inhalt:

Podman

Der Name Podman steht für Pod-Manager und ist eine Container-Engine, die von Red Hat entwickelt wird. Der große Unterschied zwischen Podman und Docker ist der Verzicht auf einen Daemon. Daher ist es auch möglich ohne Probleme die Container mit Podman als rootless zu betreiben. Auch wenn ich es bisher nicht gemacht habe, sollte es möglich sein, mittels alias docker=podman die typischen Docker-Befehle out-of-the-box zu verwenden. Einen Überblick über Befehle und ein Vergleich dieser zwischen Docker und Podman bietet Podman Usage Transfer.

Autostart der Container

Da es keinen Daemon gibt, verwendet Podman zum Starten der Container systemd. Zum Starten von rootless Container wird systemd mit der --user Option ausgeführt. Damit Container von unpriviligierten Benutzern automatisch zu Systemstart starten können, muss das für den entsprechenden User zuerst ermöglicht werden

loginctl enable-linger USERNAME

Unter Umständen muss dann beim entsprechenden User in der .bashrc-Datei noch folgendes eingetragen werden:

export XDG_RUNTIME_DIR=/run/user/$(id -u)

Eventuell müssen für den Benutzer auch noch die entsprechenden Verzeichnisse angelegt werden

mkdir -p ~/.config/systemd/user/

Zum erstellen der systemd-Datei, kann man den Podman-Befehl podman generate systemd nutzen:

podman generate systemd CONTAINE_NAME --name --files --time 5 --restart-policy=always

Durch diesen Befehl wird ein systemd-File für den Container mit dem Namen CONTAINER_NAME erstellt:

-name es wird der Name des Containers anstatt der ID verwendet

-files es wird eine Datei erstellt und nicht per stdout auf der Konsole ausgegeben

--restart-policy definiert die Restart-Policy von sfystemd

Nach dem Erstellen der Datei muss dieses Service dann noch aktiviert werden. Hierfür werden die Änderungen zuerst in systemd neu geladen:

systemctl --user daemon-reload

Für rootless Container lautet der Befehl:

systemctl enable --user container-CONTAINER_NAME.service

Pods

Neben dem nicht vorhanden Daemon, sind, meines Wissens nach, Pods ein weiterer Unterschied zu Docker. Das Konzept der Pods stammt aus dem Kubernetes Umfeld und es handelt sich hierbei um Zusammenschlüsse mehrerer Container, die sich Ressourcen teilen. Wichtig bei der Erstellung von Pods ist, dass schon bei der Erstellung eines Pods bekannt gegeben werden muss, welche Ports der Pod nach außen verfügbar machen soll. Ein nachträgliches Ändern ist nicht möglich. Pods haben den großen Vorteil, dass alle Container innerhalb eines Pods per Localhost miteinander kommunizieren können.

Bei der erstmaligen Verwendung eins Pods wird einem der sogenannte Infrastruktur-Container ins Auge stechen. Dieser Infrastruktur-Container verwaltet alles Ressourcen des Pods. Der Befehl zum Erstellen eines (simplen) Pods ist:

podman pod create --infra-name=pod1-infra --name=pod1

Hier wird ein Pod mit dem Namen pod1 und einem Infrastruktur-Container mit dem Namen pod1-infra erstellt. Um den eben erstellten Pod zu starten, muss folgender Befehl ausgeführt werden:

podman pod start pod1

Für diesen Pod kann dann mittels podman generate systemd eine systemd-Datei erstellt werden:

podman generate systemd pod1 --name --files --time 5 --restart-policy=always

Es wird für jeden Container im Pod eine separate Datei erstellt und dadurch wird dann der Pod auch bei einem Neustart des Systems in der korrekten Reihenfolge gestartet.

Wie schon erwähnt, müssen beim Erstellen des Pods schon die Ports definiert werden, wenn die Container von außerhalb des Pod erreichbar sein sollen. Wenn beispielsweise ein Nginx-Image erreichbar sein soll, dann kann beim Start den entsprechenden Port angeben:

podman pod create --infra-name=pod2-infra --name=pod2 -p 8080:80

Danach kann man den Nginx-Container herunterladen und dem entsprechenden Pod zuweisen:

podman run --pod pod2 --name pod2-nginx -d nginx

Mittels curl kann man dann das Resultat überprüfen:

curl localhost:8080

Rootless Container

Mittels Podman ist es auch für Benutzer ohne Root-Rechte möglich, Container zu starten. Sie müssen hierfür auch keiner speziellen Gruppe (beispielsweise Gruppe docker) zugeordnet werden. Um das zu ermöglichen, greift man User-Namespaces des Linux-Kernels zurück. Hierfür müssen auch die Dateien /etc/subuid und /etc/subgid exisitieren bzw. Einträge für den Benutzer, der den Container ausführen will, vorhanden sein. Die zwei Dateien geben die Bereiche der User- und Gruppen-IDs an, als die sich der Benutzer am Host-System ausgeben darf. Dadurch bekommen können die User- und Gruppen-IDs des Containers am Host-System in einen für den Benutzer zugreifbaren Bereich gemappt werden.

Die größte Einschränkung bei der Ausführung als unpriviligierte Benutzer ist, die Einschränkung welche Ports verwendet werden können. Die Ports unter 1024 können standardmäßig nur von Diensten genutzt werden, die mittels root-Rechten gestartet wurden.

Ein Fehler, der mittlerweile behoben sein sollte, hat dazu geführt, dass man sich vor dem erstmaligen Starten eines Containers per SSH einloggen musste. Wenn man von einem anderen Benutzeraccount (su - username) aus in den entsprechenden rootless-Account wechselt und danach den Container ausgeführt hat, hat es in früheren Podman-Versionen dazu geführt, dass die Verzeichnisse nicht richtig gesetzt wurden und die Container nach einer gewissen Zeit in den Status created versetzt wurden, obwohl sie normal gelaufen sind.

Mittels des Befehls podman info sollte man ohne Probleme erkenne, ob die Pfade richtig gesetzt wurden. Und zwar sollte hier beim Punkt runRoot etwas wie folgt stehen:

runRoot: /run/user/USERID/containers

Und nicht:

runRoot: /tmp/run-USERID

Der Grund ist, dass Daten in /tmp nach einer gewissen Zeit gelöscht werden und dadurch Podman in einen inkonsistenten Status kommt.

Volumes

Volumes bieten die Möglichkeit, Verzeichnisse vom Host-System in den Container einzubinden, um beispielsweise die Daten eines Datenbank-Containers auch wirklich dauerhaft zu speichern.

Bei der Entwicklung von Podman sehr aktiv ist auch Dan Walsh, der sich vor allem durch SELinux einen Namen in der Linuxwelt gemacht hat. Im Vortrag bei den Grazer Linuxtagen 2022, wurde SELinux in den permissive Mode versetzt, um mit Volumes zu arbeiten. Das ist ja meiner Ansicht nach nicht der richtige Weg. SO zeigt der Artikel Latest container exploit (runc) can be blocked by SELinux durchaus aus schön, warum es eine gute Idee ist, SELinux aktiviert zu lassen.

Grundsätzlich habe ich auch bisher keinerlei Probleme mit Volumes und SELinux gehabt. Zum Einbinden eines Volumes sollte man am Ende des Befehls :Z verwenden. Als Beispiel starten wir als Benutzer pgrootless eine Postgres-Datenbank:

podman run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -e PGDATA=/var/lib/postgresql/data/pgdata -v /home/pgrootless/postgres:/var/lib/postgresql/data:Z -d postgres

Startet man den Postgres-Container ohne :Z, dann kommt folgende Fehlermeldung:

mkdir: cannot create directory /var/lib/postgresql/data/pgdata: Permission denied

Grund hierfür ist, dass SELinux den Zugriff verweigert. Ein sealert -a /var/log/audit/audit.log fördert folgenden (bzw. einen ähnlichen) Eintrag zutage:

Additional Information:
Source Context                system_u:system_r:container_t:s0:c55,c727
Target Context                unconfined_u:object_r:user_home_t:s0
Target Objects                postgres2 [ dir ]
Source                        mkdir
Source Path                   /bin/mkdir
Port                          <Unknown>
Host                          <Unknown>
Source RPM Packages           coreutils-8.30-12.el8.x86_64
Target RPM Packages
SELinux Policy RPM            selinux-policy-targeted-3.14.3-80.el8_5.2.noarch
Local Policy RPM              selinux-policy-targeted-3.14.3-80.el8_5.2.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Enforcing
Host Name                     rockylinux-s-1vcpu-1gb-amd-fra1-01
Local ID                      940d6dfd-30fc-4ecf-a8d8-2606010aaa6e

Raw Audit Messages
type=AVC msg=audit(1651060484.250:1494): avc:  denied  { write } for  pid=40655 comm="mkdir" name="postgres2" dev="vda1" ino=8606953 scontext=system_u:system_r:container_t:s0:c55,c727 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=dir permissive=0

Unshare

Wenn man mit Rootless-Container und Volumes arbeitet, wird man feststellen, dass diese andere Benutzer- und Gruppen-IDs haben als der Benutzer selbst. Das hat mit den User-Namespaces des Linux-Kernels zu tun und tritt dann auf, wenn die Applikation im Container ebenfalls als non-root-User läuft. Dies sollte bei jedem guten Container-Image der Fall sein.

Wenn man auf das Verzeichnis des Postgres-Containers im vorherigen Punkt zugreifen und sich beispielsweise die Inhalte der Datei pg_hba.conf ausgeben will, dann bekommt man einen Fehler

cat ~/postgres/pgdata/pg_hba.conf
cat: /home/pgrootless/postgres/pgdata/pg_hba.conf: Permission denied

Um diese Verzeichnisse trotzdem zugänglich zu machen, gibt es den Befehl podman unshare:

podman unshare cat pgdata/pg_hba.conf

Probleme

Es sind bei der Verwendung von Podman immer wieder kleinere Probleme aufgetreten. Bei einem Update von Podman gab es danach einmal folgende Fehlermeldung

ERRO[0000] Error waiting for container to exit: failed to get journal cursor: failed to get cursor: cannot assign requested address

Dies hat sich beheben lassen, indem man in der Datei /usr/share/containers/containers.conf folgenden Wert eingetragen hat

events_logger = "file"

Auch folgendes Warning kam nach einem Update:

Failed to decode the keys ["secret" "secret.opts"] from "/usr/share/containers/containers.conf" 

Hierbei handelt es sich um das Problem, dass sich die Bezeichnung von secret auf secrets geändert hat, d.h. hierfür musste ebenfalls die Konfigurationsdatei (/usr/share/containers/containers.conf) angepasst werden.

Ausblick

Während die ersten Versionen von Podman vielleicht eher für etwas experimentierfreudige Anwender waren und es meist darum ging bestehende Features von Docker in Podman zu implementieren, geht Podman mit Version 4 auch durchaus eigene Wege. So gibt es mit Version 4 die Möglichkeit, mittels des podman image scp Befehls Container Images auch ohne Registry einfach zu transferieren.

Und Podman findet auch abseits des Red Hat Universums immer größere Verbreitung. In der openSuse-Welt (siehe openSuse MicroOS) ist es schon länger verbreitet und auch in Debian 11 und Ubuntu ist es mittlerweile in den default-Repos vorhanden.

Als spannendes Beispiel sollte man definitiv auch Postgres Container Apps von Crunchy Data erwähnen.

Nützliche Links