Multicontainer-Apps mit Podman

Docker war für viele der erste Kontaktpunkt mit Containern. Die Möglichkeit einen Stack reproduzierbar auf beliebige Systeme auszurollen, war dabei genauso verlockend, wie unterschiedliche Applikationen zu bauen, ohne das eigene System mit verschiedenen Abhängigkeiten vollzuladen.

Warum eine andere Container-Engine nutzen?

Der erste Anlaufpunkt für den Erhalt von Docker ist die Installationsdokumentation, deren Abarbeitung darin mündet den lokalen Benutzer in eine eigens erstellte Docker-Gruppe hinzuzufügen.
Der Docker-Daemon läuft dann mit der Berechtigung als root-Nutzer, weshalb jeder Nutzer, der durch diesen Schritt in diese Gruppe hinzugefügt wird, die gleiche Berechtigung erhält.
Ein Nutzer kann also

docker run -it -v /:/ alpine bash

ausführen und erhält im Container-Kontext das gesamte System als gemountetes Volume unter seine Kontrolle.
Inzwischen ist es allerdings möglich, Docker auch ohne diese Berechtigungen zu betreiben (https://docs.docker.com/engine/security/rootless/). Jedoch kamen andere Hürden für die Nutzung dazu:
Seit 2020 können Images nicht mehr unbegrenzt von der Docker-Registry geladen werden (Docker-Blog). Dies fällt vor allem in Unternehmensnetzwerken auf, bei welchen das Limit von 100 Images pro IP-Adresse am Tag relativ schnell erreicht ist.
Eine frische Docker-Installation verwendet den Docker-Hub als erste Quelle für die Beschaffung neuer Images, aber selbstverständlich ist es möglich, diese Images lokal vorzuhalten und den Docker-Dienst die Quelle auswählen zu lassen.
Gerade die Einfachheit die Anwendung „Docker-Desktop“ zu verwenden, stellt ein niederschwelliges Angebot dar, Container direkt zu konsumieren, ohne sich über Netzwerk oder persistenten Storage zu sorgen. Eine Lizenzänderung im letzten Jahr führte dazu, dass dieser Dienst für Unternehmen mit über 250 Mitarbeitenden und über 10 Millionen Dollar Jahresumsatz kostenpflichtig wurde.
Ein Grund mehr, sich nach Alternativen umzusehen. Die Software Podman ist dafür ein vielversprechender Kandidat. Schon jetzt führt ein

yum install docker

auf einem Red Had Enterprise Linux- (RHEL) bzw. einem RHEL Derivat dazu, dass eigentlich Podman installiert wird und verschiedene Aliase für den Docker-Befehl hinterlegt werden.
Bei diesen docker-Aufrufen bekommt man nun dies angezeigt:

Emulate Docker CLI using podman

Bei Nutzung fühlt es sich jedoch wie ein Docker-Client an. Zudem fällt auf, dass kein Daemon angestartet werden muss und keine Nutzer-Modifikationen notwendig sind: Podman läuft ohne root-Berechtigung und ohne Anstarten von Docker. Vermisst wird beim Umstieg dann lediglich Docker-Compose – die Möglichkeit mehrere Container via YAML-Datei zusammen in einem virtuellen Container-Netzwerk zu betreiben und diese mittels DNS untereinander anzusprechen.
Podman bietet jedoch die Möglichkeit, mehrere Container zu einer Multi-Container-App zusammenzuschnüren und anzubieten. Anders als im Ansatz von Docker-Compose wird dafür allerdings kein eigenes Docker-Netzwerk mit mehreren Containern erstellt.
Podmans Multi-Container-Ansatz folgt dem Ansatz von Kubernetes, indem sich Container einen logischen Host und Laufwerksfreigaben teilen können. Neben dem Vorteil der schnelleren Portierbarkeit in Kubernetes-Umgebungen, finden Container auch ihre verknüpften Dienste auf ihrem Host. Außerdem müssen Laufwerke nicht mehrfach gemountet werden, wie es beispielsweise bei einem LAMP-Konstrukt mit Reverse-Proxy der Fall ist.

Pod erstellen

Nachfolgend wird beschrieben, wie ein Software-Stack mit WordPress und einer Datenbank in Podman abgebildet werden kann.
In Docker würden beide Container einem dedizierten Netzwerk zugewiesen werden, und sich über einen Aufruf ihrer direkten Namen gegenseitig finden. Fernab davon bringt podman eine Funktionalität mit, um mehrere Container, die miteinander kommunizieren, und für einen Anwender konsumierbar sind, lauffähig zu machen.
Um solch eine Multi-Container-App mit Podman zu kreieren, wird zuerst ein Pod erstellt. Nur dieser Pod kommuniziert nach außen. Deshalb wird neben dem Namen, auch das Portmapping nach Außen angegeben.

podman pod create --label "io.containers.autoupdate=registry
--name wordpress -p 80:80

Applikations-Container erstellen

Danach wird der Pod mit verschiedenen Containern beladen. Dies geschieht durch den Schalter „–pod“.
In dem untenstehenden Beispiel ist so zu erkennen, dass der WordPress-Container sein Backend, genauer die MariaDB-Datenbank, auf demselben Host vorfindet.

Datenbank starten

Hier wird eine MariaDB-Datenbank innerhalb des Pods gestartet. Dadurch das wir den Pod lediglich mit Port 80, nach außen, ausgestattet haben, ist die Datenbank nur für Container innerhalb des Container-Pods erreichbar.

podman run -d --name database \
       --pod wordpress \
       -e=MYSQL_USER=wordpress \
       -e=MYSQL_PASSWORD=wordpress \
       -e=MYSQL_DATABASE=wordpress \
       -e=MYSQL_ROOT_PASSWORD=geheim \
       docker.io/library/mariadb:latest

WordPress starten

Als nächstes starten wir WordPress. Diese Anwendung erhält dieselben Nutzer und Passwörter wie die Datenbank zugewiesen. Zu beachten ist die Variable „WORDPRESS_DB_HOST“: Sie verweist auf den localhost, also auf den gleichen Host wie der WordPress-Container.

podman run -d --name frontend \
       --pod wordpress \
       -e=WORDPRESS_DB_HOST=127.0.0.1 \
       -e=WORDPRESS_DB_PASSWORD=wordpress \
       -e=WORDPRESS_DB_NAME=wordpress \
       -e=WORDPRESS_DB_USER=wordpress \
       docker.io/library/wordpress:latest

Bei der Überprüfung der laufenden Container werden die gestarteten Container, aber auch der eigentliche Pod, als Container angezeigt:

podman ps

Podman bietet weiterhin die Möglichkeit aus einer Container-Instanz systemd-Datei zu generieren, um den erstellten Container-Service wie einen System-Service zu behandeln. Wir können dadurch Container zentral verwalten, ihr Logging überwachen und sie beim Booten mit in den Prozess des Startens übernehmen. Dies ist auch im Nutzer-Kontext, also mit eingeschränkten Rechten möglich.

podman generate systemd \
       --files \
       --name wordpress \
       --restart-policy=always \
       -t 10

mv *.service /etc/systemd/system

Dieser Codeschnipsel generiert dabei die Konfigurationsdateien. Anschließend wird der Container-Service bootstabil durch die Einbindung in Systemd gebildet:

systemctl enable container-database.service
systemctl start container-database.service
systemctl enable container-frontend.service
systemctl start container-frontend.service
systemctl enable pod-wordpress.service
systemctl start pod-wordpress.service

Die Verknüpfung mit systemd und dem gesetzten Label bietet einen weiteren Vorteil: Container können automatisch aktuell gehalten werden. Dies geschieht entweder durch diesen Befehl:

podman auto-update

Dieser kann in eine wiederkehrende Aufgabenverwaltung aufgenommen werden. Oder die Verknüpfung wird durch

systemctl enable --now podman-auto-update.timer

festgelegt. Hier wird überprüft, ob das Image mit dem hinterlegten Label noch aktuell ist, falls nicht, wird es ausgetauscht.
Das erstellte Container-Konstrukt lässt sich als Kubernetes-YAML (v1-Spezifikation), auf Wunsch auch mit einem Service-Objekt, ausgeben.
Hier wird die YAML-Ausgabe in eine Datei umgeleitet:

podman generate kube wordpress >> wordpress.yaml

Nun können die Container wahlweise mit systemd oder dem Podman-Client gesteuert werden:

podman stop frontend 
podman rm frontend
systemctl stop container-wordpress

Portierbarkeit

Um die Multicontainer-Applikation erneut auszurollen, kann, ähnlich wie bei docker-compose, auf die gespeicherte YAML-Datei zurückgegriffen werden:

podman play kube wordpress.yaml

Es fällt auf, dass die Container in der YAML-Datei auf ein provisioniertes Laufwerk verweisen. Dadurch kann die YAML-Datei nicht auf beliebigen Hosts ausgeführt werden. Sobald die Volume-Definiton auf HostPath geändert wurde, ist die YAML-Datei allerdings portabel.

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: wordpress
  name: wordpress
spec:
  containers:
  - args:
    - mariadbd
    image: docker.io/library/mariadb:latest
    name: database
    ports:
    - containerPort: 80
      hostPort: 80
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /var/lib/mysql
      name: database
  - args:
    - apache2-foreground
    image: docker.io/library/wordpress:latest
    name: frontend
    securityContext:
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
    volumeMounts:
    - mountPath: /var/www/html
      name: wordpress
  restartPolicy: Never
  volumes:
  - name: database
    hostPath:
       path: /database
  - name: wordpress
    hostPath:
      path: /wordpress

Fazit

„Podman-Pod“ bietet die Möglichkeit Multicontainer-Apps bereitzustellen. Dies kann von Benutzer:innen sogar ohne administrative Berechtigungen geschehen und die Verknüpfung mit Systemd ermöglicht eine zentrale Stelle für Verwaltung sowie die Updates der Container-Images.
Durch die Kubernetes-Nähe der erzeugten YAML-Datei ist eine spätere Portierbarkeit in eine Container-Orchestrierungsplattform mühelos möglich. Daher bietet Podman einen exzellenten neuen Startpunkt in die Container-Welt.