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
- Autostart der Container
- Pods
- Rootless Container
- Volumes
- Unshare
- Probleme
- Ausblick
- Nützliche Links
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.