Serie: Podman-Bausteine – Netzwerk: Kommunikation zwischen Containern (Teil 9)

  indibart   Lesezeit: 20 Minuten Auf Mastodon ansehen

Der Artikel erklärt Netzwerk-Namespaces bei Podman und zeigt, warum Container ohne eigenes Netzwerk isoliert bleiben (rootful: Default-Bridge ohne DNS; rootless: Pasta ohne gemeinsamen Pfad) – und wie podman network create dies löst.

serie: podman-bausteine – netzwerk: kommunikation zwischen containern (teil 9)

Eine Tutorialreihe zum Einsatz von Podman: Anwendung, Vorteile und Funktionsweise.

Inhaltsverzeichnis

  1. Teil: Einleitung und Installation
  2. Teil: Podman CLI
  3. Teil: Wie funktioniert die Restart-Policy? Oder: Podman hat keinen Daemon
  4. Teil: Quadlets
  5. Teil: Updates
  6. Teil: Healthchecks
  7. Teil: Ports publishing – rootful & rootless
  8. Teil: Volumes & Mounts
  9. Teil: Netzwerk: Kommunikation zwischen Containern (Das ist dieser Artikel)
  10. Teil: Capabilities

Einleitung

Seit zwei Artikeln arbeiten wir uns schrittweise vor, Caddy als rootless Container zum Laufen zu bringen:

  • Wir haben die nötigen Container-Ports auf Host-Ports gemappt.
  • Wir haben dafür gesorgt, dass Daten erhalten bleiben, auch wenn der Container gelöscht wird.
  • Und wir haben Caddy so konfiguriert, dass es auf IT Tools umleiten kann und eine 404-Seite anzeigt, wenn keine passende Seite gefunden wird.

In diesem Artikel beschäftigen wir uns mit dem letzten nötigen Baustein, damit Caddy andere Container erreichen kann: Dem Podman-Netzwerk. Netzwerke bei Podman können ziemlich komplex werden. Wenn du Podman rootful laufen lässt, hast du die gleichen Möglichkeiten, die Docker dir bietet. Wir beschränken uns hier auf das Nötigste, um Caddy im rootless Modus zu betreiben.

Eine Warnung vorab: Ich gebe mir sehr Mühe, in dieser Serie möglichst kein Vorwissen zu erwarten, ausser dass du einer Anleitung folgen kannst, um eine simple Web-Applikation über das Terminal zu installieren. Doch in diesem Artikel werden wir über Netzwerke sprechen. Auch ohne Container ist das kein leichtes Thema und hat eine gewisse Komplexität. Wenn du nicht mit den wichtigsten Elementen eines Netzwerks vertraut bist oder dir die OSI- und TCP/IP-Modelle nichts sagen, wird es wohl schwierig werden, alles hier zu verstehen.

Lass dich davon aber nicht unterkriegen! Versuche einfach grob nachzuvollziehen, wie der Informationsfluss zwischen Containern über ein Podman-Netzwerk funktioniert. Wo kann der Informationsfluss durch und wo nicht? Wo fehlen Verbindungen für den Informationsfluss? Wie schaffen wir eine?

Bevor wir uns mit Netzwerken beschäftigen können, müssen wir uns mit Namespaces auseinandersetzen. Denn wenn ein Container gestartet wird, weisst Podman dem Container automatisch einem Network-Namespace zu.

Network Namespaces

Namespaces sind Kernkomponenten von Linux und die Grundlagen der Isolation von Containern. Es gibt verschiedene Arten von Namespaces. Unter anderem sind das der PID-Namespace, der für Prozess-Isolation, der User-Namespace, der für Benutzer-Isolation und der Network-Namespace, der für die Netzwerk-Isolation zuständig ist. Wir beschäftigen uns hier nur mit dem Network-Namespace. Der Network-Namespace ist der Grund dafür, dass standardmässig kein Informationsfluss zu anderen Namespaces möglich ist, weil zwischen ihnen keine Verbindungen existieren. Unsere Aufgabe ist es nun, Wege für den Informationsfluss zu schaffen und dabei die grundsätzliche Isolation zu behalten.

Jedes Mal, wenn wir einen Container kreieren, weisst Podman dem Container einen eigenen Network-Namespace zu. Somit hat der Host einen eigenen Network-Namespace, der sich von denjenigen der Container unterscheidet, die auf dem Host laufen. Das echte, physische Netzwerkinterface, das mit dem Internet verbunden ist, ist Teil des Network-Namespaces des Hosts. Das macht den Host-Namespace speziell.

Um einen Namespace besser fassen zu können, hilft es, sich ihn als einen eigenständigen Computer vorzustellen. Wie ein Computer kann ein Network-Namespace ein Interface haben. Also ein Gerät, von dem aus die Kommunikation zum Netzwerk stattfindet. Ähnlich, wie eine Netzwerkkarte an einem physischen Computer, wo ein Ethernet-Kabel eingesteckt wird, um es mit einem anderen Gerät zu verbinden.

In meiner Vorstellung ist der Host also ein eigenständiger Computer namens Host. Er steht in einem Raum voller anderer Computer. Der Computer Host ist insofern speziell, als dass er der einzige ist, dessen Netzwerk-Interface mit dem Internet verbunden ist. Da alle Container eigene Namespaces bekommen, stelle ich sie mir auch als eigenständige Computer in diesem Raum vor. In unserem Fall gibt es also auch noch einen Computer namens it-tools und einen Computer namens caddy im Raum. Sie sind mittels Kabel mit dem Computer Host verbunden und haben so Zugriff auf das Internet.

Betrachten wir diese Situation noch etwas genauer: Dazu nehmen wir nochmals die Befehle vom letzten Mal und vereinfachen sie soweit möglich. Dann ergänzen wir noch die rootful Variante, denn diese funktioniert etwas anders und es lohnt sich, einen Blick darauf zu werfen.

# Rootless
podman run --name it-tools -d docker.io/sharevb/it-tools:latest
podman run --name caddy -d docker.io/caddy

# Rootful
sudo podman run --name it-tools -d docker.io/sharevb/it-tools:latest
sudo podman run --name caddy -d docker.io/caddy

Wir erhalten in jeder Variante drei Network-Namespaces: Den Host-Namespace, den Namespace des it-tools-Containers und den Namespace des caddy-Containers. Der Host-Namespace ist mit dem Internet verbunden. Die beiden Container-Namespaces sind mit dem Host-Namespace verbunden. Alles, wie erwartet. Dies siehst du im folgenden Diagramm.

Wie du siehst, hat Caddy in beiden Fällen keine Möglichkeit den it-tools-Container zu erreichen, wenn auch aus unterschiedlichen Gründen (gestrichelte Linie). Betrachten wir also eine Variante nach der anderen. Wir wollen dabei das Augenmerk darauf legen, weshalb Caddy den it-tools-Container nicht erreichen kann. Beginnen wir mit der Rootful-Variante.

Rootful ohne –network (Default Bridge)

Wie gesagt haben wir drei Network-Namespaces. Beide Namespaces der Container sind mit der Host-Namespace verbunden und zwar über eine Bridge, die Teil des Host-Namespace ist. Eine Bridge ist ein virtuelles Interface, um Host und Container miteinander zu verbinden. (Die Linux-Implementierung einer Bridge ist im Kern eine Software-Implementierung eines Ethernet-Switches. Es arbeitet auf Schicht 2 nach OSI-Modell bzw. dem Data-Link-Layer nach TCP/IP-Modell. Es leitet Frames anhand von MAC-Adressen weiter, lernt dynamisch, welche MAC-Adresse hinter welchem “Port” erreichbar ist, und flutet unbekannte Ziele an alle Ports.)

Diese Bridge ist die Default-Bridge von Podman und wurde automatisch von Podmans Netzwerk-Backend Netavark erstellt. Neben der Switch-Funktionalität übernimmt sie auch andere Aufgaben. Für unser Szenario ist die Funktion als DNS-Server für die Container wichtig. Bei der Default-Bridge ist die DNS-Funktionalität standardmässig deaktiviert.

Durch diese Bridge haben die Container via physischem Interface des Hosts Zugriff auf das Internet. Die Container könnten nun theoretisch über die Bridge miteinander kommunizieren. Weil aber die DNS-Funktion deaktiviert ist, geht das nur, wenn sie die IP-Adressen voneinander kennen.

Wir haben zwar den rootful Modus bei Caddy und IT Tools nicht getestet, aber da keine DNS-Funktion aktiviert ist, können wir sicher sein, dass Caddy den it-tools-Container erreichen kann. Der Grund dafür ist, dass wir im Caddyfile die IP-Adresse des it-tools-Containers nicht angegeben haben, sondern den Container-Namen.

podman network

Wir können uns das nun direkt anschauen. Podman kennt den Subbefehl network, den wir für alle Arbeiten nutzen können, die Podman-Netzwerk betreffen. Wir können damit Podman-Netzwerke betrachten, aber auch neue erstellen und wieder abräumen. Im Hintergrund erledigt diese Arbeit übrigens nicht Podman selbst, sondern ein eigenständiges Werkzeug namens Netavark, das Podman für jede Netzwerk-Operation aufruft.

Lassen wir uns erstmal alle Netzwerke anzeigen. Dies tun wir mit dem Befehl podman network ls bzw. mit vorangestelltem sudo, um die rootful Netzwerke zu sehen:

sudo podman network ls

Jetzt sollte dir das Standard-Netzwerk podman aufgelistet werden:

NETWORK ID    NAME                         DRIVER
2f259bab93aa  podman                       bridge

Mittels podman network inspect kannst du ein bestimmtes Netzwerk genauer anschauen:

sudo podman network inspect podman

Ich erhalte folgenden Output:

[
     {
          "name": "podman",
          "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
          "driver": "bridge",
          "network_interface": "podman0",
          "created": "2026-10-11T12:07:08.148090335+02:00",
          "subnets": [
               {
                    "subnet": "10.88.0.0/16",
                    "gateway": "10.88.0.1"
               }
          ],
          "ipv6_enabled": false,
          "internal": false,
          "dns_enabled": false,
          "ipam_options": {
               "driver": "host-local"
          },
          "containers": {
               "cfc68096a936f479de8b2b089683a359512c6f0fd4de784c0725dfdb494d222e": {
                    "name": "it-tools",
                    "interfaces": {
                         "eth0": {
                              "subnets": [
                                   {
                                        "ipnet": "10.88.0.2/16",
                                        "gateway": "10.88.0.1"
                                   }
                              ],
                              "mac_address": "3e:8a:82:f3:b0:2a"
                         }
                    }
               },
               "e0791926c59199a8b5801a6396a9a8ecac8142467f3066f76fdbcaa769625202": {
                    "name": "caddy",
                    "interfaces": {
                         "eth0": {
                              "subnets": [
                                   {
                                        "ipnet": "10.88.0.3/16",
                                        "gateway": "10.88.0.1"
                                   }
                              ],
                              "mac_address": "9a:ea:35:75:2c:29"
                         }
                    }
               }
          }
     }
]

Durch diese Ausgabe sehen wir, dass der Name des Netzwerkes podman lautet und dass eine Bridge als Verbindungsstück genutzt wird ("driver": "bridge"). Wir sehen, dass es Zugriff auf das Internet gewährt ("internal": false), aber keine DNS-Funktionaltität zur Verfügung stellt ("dns_enabled": false). Und wir sehen, dass die beiden Container it-tools und caddy mit dem Netzwerk, eben über eine Bridge verbunden sind. Wir sehen das Subnet und das Gateway des Netzwerkes sowie die IP-Adressen der Container. Wir können also alles vom Diagramm weiter oben bestätigen.

Betrachten wir als nächstes die Netzwerk-Situation von Caddy und IT Tools im rootless Modus.

Rootless ohne –network (pasta)

Schauen wir uns als erstes den Host-Namespace an. In diesem Szenario hat es keine Bridge, dafür aber zwei Pasta-Prozesse, einer pro Container-Namespace. Wie wir aus dem Rootful Szenario gelernt haben, ist eine Bridge ein Interface, das den Host und die Container untereinander verbinden kann. Eine Bridge mit aktivierter DNS-Funktion würde uns folglich die Möglichkeit geben, dass der caddy-Container den it-tools-Container erreichen könnte. Wieso wird hier also keine Bridge verwendet?

Die Erstellung eines Interfaces erfordert Root-Privilegien, die Podman im rootless Modus nicht hat. Daher braucht es eine andere Strategie. Podman nutzt hierfür Pasta, eine Übersetzungsschicht zwischen einem Container und dem Host. Statt eines Interfaces nutzt Pasta Netzwerk-Sockets. Die können ohne Root-Privilegien erstellt werden.

Pro Container-Namespace wird also ein Pasta-Prozess erstellt. Dieser erstellt im Container-Namespace ein Interface und verbindet sich damit (Benötigen wir jetzt Root-Privilegien oder nicht?). Der Container kommuniziert jetzt über das Interface als würde er über ein normales Netzwerk kommunizieren, mit Ziel-IP und Port. Pasta fängt die Information ab, erstellt einen Netzwerk-Socket für die Ziel-IP und den Ziel-Port und schreibt die Nachricht in den Netzwerk-Socket. Der Kernel prüft die eigene Routing-Tabelle und gibt sie entsprechend weiter. Wenn Antworten vom Ziel eintreffen, ordnet der Kernel sie dem passenden Socket zu, bereit zum Auslesen. Pasta liest diese Daten aus dem Socket, übersetzt sie zurück und schreibt sie ins Interface. Pasta sitzt jetzt also zwischen dem Interface im Container-Namespace und dem Socket auf dem Host und übersetzt die Informationen hin und her.

Was also ist jetzt genau der Unterschied zwischen einem Interface und einem Netzwerk-Socket? Ein Interface lässt sich vergleichen mit der Ausfahrt zur Strasse. Es wird nicht bestimmt, wohin deine Reise geht oder mit welchem Fortbewegungsmittel du das tust, es gibt dir einfach Zugang. Ein Netzwerk-Socket andererseits ist eher wie ein Ticket für den öffentlichen Verkehr (ÖPNV-Ticket). Es legt fest wohin die Reise geht (Ziel-IP:Port) und mit welchem Fortbewegungsmittel (TCP vs. UDP), für wen (besitzenden Prozess) und bis wann das Ticket gültig ist (Schliessung des Sockets). Was das Ticket nicht festlegt, ist, welche konkrete Strasse der Bus nimmt — das entscheidet das ÖPNV-Unternehmen (= die Routing-Tabelle des Kernels).

Diese Analogie zeigt auch, warum die Socket-Operationen unprivilegiert sind: Ein Prozess bekommt nie direkten Zugriff auf die Netzwerkhardware oder den rohen Datenverkehr. Er spricht ausschliesslich über den Netzwerk-Socket mit dem Kernel, und der Kernel entscheidet bei jeder Operation, ob sie erlaubt ist. Weil der Prozess den Kernel nicht umgehen kann, kann der Kernel die “sicheren” Operationen für alle freigeben, ohne das System zu gefährden (ausgehend verbinden, auf einen hohen Port binden, Daten senden/empfangen). Genauso wie das ÖPNV-Unternehmen über alle ÖPNV-Verbindungen bestimmt und du lediglich ein Ticket innerhalb dieser Verbindungen kaufen kannst.

Letzten Endes haben wir eine sehr ähnliche Situation wie im rootful Szenario: Die Container haben Zugriff aufs Internet, können sich aber gegenseitig nicht erreichen. Zur Lösung dieses Problems benötigen wir ein separates Podman Netzwerk. Dieses erstellen wir im nächsten Schritt.

Container-Netzwerk

Beginnen wir nochmals von vorne und konzentrieren uns wieder nur auf rootless. Wir beenden und löschen die vorher erstellten Container:

# Rootless
podman stop caddy it-tools
podman rm caddy it-tools

# Rootful
sudo podman stop caddy it-tools
sudo podman rm caddy it-tools

Wie zuvor gesehen, benötigen die Container ein Netzwerk mit einer Bridge, um miteinander zu kommunizieren. Für die rootless Container müssen wir dieses Netzwerk erst erstellen. Dies tun wir mit dem Befehl podman network create. Zudem müssen wir dem Netzwerk noch einen Namen geben, um es später zu identifizieren. Ich verwende hier proxy als Namen, da es den Reverse Proxy mit den anderen Containern (in unserem Fall nur it-tools) verbindet.

podman network create proxy

Mit dem Befehl podman network ls können wir die vorhandenen Netzwerke anzeigen lassen:

podman network ls

Jetzt erstellen wir die Container und verbinden sie mit dem Netzwerk, indem wir den --network-Parameter mit dem Namen des gerade erstellten Netzwerkes verwenden:

podman run --name it-tools -d --network proxy docker.io/sharevb/it-tools:latest
podman run --name caddy -d --network proxy docker.io/caddy/caddy:latest

Jetzt haben wir folgende Situation:

Es fällt auf, dass wir im Host-Namespace nur einen, statt zwei Pasta-Prozesse haben. Dieser ist nicht mit den Container-Namespaces, sondern mit einem neuen Namespace namens rootless-netns verbunden. In diesem Namespace hat es eine Bridge namens proxy, inklusive DNS-Funktionalität. Die beiden Container-Namespaces sind mit dieser neuen Bridge verbunden.

Wie wir aus dem Rootful Szenario wissen, können sich die Container jetzt über die Bridge auch gegenseitig erreichen und zwar sogar via Hostname, dank der aktivierten DNS-Funktionalität. Der Pasta-Prozess wiederum übersetzt zwischen dem Namespace rootless-netns und dem Host, was den Containern den Zugriff aufs Internet gewährt.

Benötigen wir jetzt Root-Privilegien oder nicht?

Vielleicht ist es dir schon aufgefallen. Zu Beginn habe ich gesagt, dass das Erschaffen eines Interfaces Root-Privilegien benötigt, die Podman rootless nicht hat. Nur um danach direkt zu sagen, dass Pasta ein Interface im Container-Namespace erschafft. Und jetzt erschaffen wir mit dem Befehl podman network create proxy – also ohne Root-Rechte – einen Namespace mit einer Bridge. Kann Pasta jetzt doch Interfaces erschaffen? Und wenn ja, weshalb macht Pasta nicht einfach eine Bridge auf dem Host, wie im Rootful Szenario?

Podman erbt die Rechte seines Users. Im Host-Namespace hat nur der Root-User Root-Rechte, alle anderen nicht. Da wir aber Podman mit unserem User ausführen, erhält Podman (und somit Pasta) nur unsere Rechte. Somit haben weder Pasta noch Podman Root-Privilegien auf dem Host. Pasta kann also kein Interface auf dem Host erstellen, deshalb nutzt es da Netzwerk-Sockets.

Anders sieht es innerhalb der Container-Namespaces aus, denn Privilegien sind immer auf den Namespace bezogen. Innerhalb seiner eigenen Namespaces hat ein User sozusagen Root-Rechte. Und da wir mit unserem User die Container erstellt haben, gehören die Namespaces dieser Container unserem User. Dort hat unser User Root-Rechte, also auch die Rechte, um ein Interface zu erstellen.

Genau das nutzt Podman aus, wenn wir mittels podman network create ein Netzwerk erschaffen. Es erstellt einen zusätzlichen Namespace und lässt Netavark darin eine Bridge aufbauen, die von allen Containern genutzt werden kann. Denn auch dieser Namespace gehört unserem User und eine Bridge zu erstellen ist somit möglich. Pasta macht genau das gleiche wie schon ohne Podman-Netzwerk: Es erstellt ein Interface in diesem neuen Namespace, öffnet Netzwerk-Sockets auf dem Host und leistet die Übersetzungsarbeit dazwischen.

Der Tatsache, dass Privilegien immer auf den Namespace bezogen sind, werden wir im nächsten Artikel zu Capabilities wieder begegnen.

IT Tools und Caddy aufsetzen

Wir haben das letzte Puzzleteil erarbeitet und setzen jetzt alles um, was wir bisher gelernt haben. Und dieses Mal sollte es auch wirklich klappen.

Zuerst sollten wir aber aufräumen, damit wir nochmals sauber starten können:

podman stop caddy it-tools
podman rm caddy it-tools
podman network rm proxy

Um unser Ziel in Erinnerung zu rufen: Wir wollen erreichen, dass alle Anfragen an https://it-tools.example.com über Caddy gehen und die IT Tools anzeigen. Alle anderen Anfragen sollen die 404-Seite anzeigen, da im Caddyfile keine entsprechende Konfiguration vorhanden ist.

Der ganze Ablauf ist wie folgt:

  1. DNS-Einträge für it-tools.example.com und test.example.com in die /etc/hosts-Datei schreiben.
  2. Das Podman-Netzwerk proxy erstellen.
  3. Container it-tools starten.
  4. Erreichbarkeit von IT Tools testen.
  5. Privilegierte Ports neu definieren.
  6. Verzeichnisse für Caddy anlegen.
  7. Caddyfile einrichten.
  8. 404.html Seite schreiben.
  9. Container caddy mit Podman starten.
  10. Erreichbarkeit der 404-Seite testen.
  11. Erreichbarkeit der IT Tools via Caddy testen.

DNS-Einträge

  1. Sichere die originale hosts-Datei.

    sudo cp /etc/hosts /etc/hosts.backup
  2. Schreibe einen Eintrag für it-tools.example.com und test.example.com in /etc/hosts. (Denke daran, durch deine eigene Domain zu ersetzen, falls du eine hast.)

    myip=$(ip route get 1.1.1.1 | awk '{print $7; exit}')
    echo "$myip it-tools.example.com" | sudo tee -a /etc/hosts
    echo "$myip test.example.com" | sudo tee -a /etc/hosts
  3. Wenn du mit den Tests fertig bist, solltest du unbedingt die ursprüngliche hosts-Datei wiederherstellen.

    sudo mv /etc/hosts.backup /etc/hosts

Den zusätzlichen test.example.com-Eintrag nutzen wir, um die 404-Seite zu testen. Die URL zeigt auf unsere IP-Adresse, ist jedoch keinem Container im Caddyfile zugeordnet.

Netzwerk proxy erstellen

Der neue Schritt als Ergebnis dieses Artikels:

podman network create proxy

IT Tools starten

Wir nutzen für das Starten der IT Tools den Befehl vom letzten Artikel und ergänzen ihn um --network proxy:

podman run --pull always \
    --restart unless-stopped \
    -p 8080:8080 \
    --network proxy \
    -d \
    --name it-tools \
    docker.io/sharevb/it-tools:latest

Erreichbarkeit von IT Tools testen

Wir machen zwei Erreichbarkeits-Tests, bevor wir Caddy gestartet haben:

  1. Versuche die IT Tools wie zuvor über http://localhost:8080 zu erreichen. Das sollte funktionieren.
  2. Versuche die IT Tools über https://it-tools.example.com zu erreichen. Das sollte hingegen nicht funktionieren. Schliesslich würde das über Caddy führen, den wir aber noch nicht gestartet haben. Stattdessen erhalten wir die Fehlermeldung. Bei Firefox lautet die Fehlermeldung: Verbindung fehlgeschlagen. Firefox kann sich nicht mit dem Server auf it-tools.example.com verbinden

Privilegierte Ports neu definieren

# Temporär setzen (bis zum nächsten Reboot)
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80

Verzeichnisse für Caddy-Dateien anlegen

Wenn du nach dem letzten Mal schön aufgeräumt hast:

mkdir -p $HOME/{caddy,site}

Caddyfile einrichten

Lege die Caddyfile-Datei mit deinem Lieblings-Editor an (wenn du sie letztes Mal wieder gelöscht hast):

nano $HOME/caddy/Caddyfile

Füge folgenden Inhalt ein:

# it-tools über HTTPS (self-signed Zertifikat)
https://it-tools.example.com {
    tls internal
    reverse_proxy it-tools:8080
}

# Catch-all für alle anderen/nicht vorhandenen Domains -> eigene 404-Seite
:443 {
    tls internal {
        on_demand
    }
    root * /srv
    file_server

    handle_errors {
        @404 expression {http.error.status_code} == 404
        rewrite @404 /404.html
        file_server
    }
}

404.html Seite schreiben

Lege die 404.html-Datei mit deinem Lieblings-Editor an (ausser du hast sie noch vom letzten Artikel):

nano $HOME/site/404.html

Füge folgenden Inhalt ein:

 html>
<html>
<head>
    <title>404 - Seite nicht gefundentitle>
head>
<body>
    <h1>404 - Seite nicht gefundenh1>
    <p>Die angeforderte Seite existiert nicht.p>
body>
html>

Caddy starten

Wir starten Caddy als Container mit dem Befehl vom letzten Mal plus dem Netzwerkparameter --network proxy:

podman run -p 80:80 -p 443:443 -p 443:443/udp \
    --mount type=bind,source=$HOME/site,target=/srv,readonly,relabel=shared \
    --mount type=bind,source=$HOME/caddy,target=/etc/caddy,readonly,relabel=shared \
    --mount type=volume,source=caddy_data,target=/data \
    --mount type=volume,source=caddy_config,target=/config \
    --network proxy \
    -d \
    --name caddy \
    docker.io/caddy

Erreichbarkeit der 404-Seite testen

Wir rufen https://test.example.com auf. Eventuell müssen wir, wie schon beim letzten Mal, den Browser anweisen selbst signierte SSL-Zertifikate zu akzeptieren. Danach sollte dir die 404-Seite angezeigt werden. Super!

Erreichbarkeit der IT Tools-Seite via Caddy testen

Versuche die IT Tools über https://it-tools.example.com zu erreichen.

Wie bei der 404-Seite wirst du den Browser anweisen müssen, selbst signierte SSL-Zertifikate zu akzeptieren. Danach sollte es funktionieren.

Hurra! Alles hat geklappt.

Zusatz: IT Tools mit oder ohne published Ports?

Einen Makel haben wir noch drin: Die IT Tools sind sowohl über http://localhost:8080 als auch über https://it-tools.example.com zu erreichen. Normalerweise will man aber, dass eine App nur über die schöne URL und somit nur via Proxy erreichbar ist.

Wir haben das so gemacht, damit wir testen konnten, ob der it-tools-Container grundsätzlich läuft. Da jetzt alles geklappt hat, benötigen wir dies nicht mehr. Um das zu erreichen, musst du den Container nochmals löschen und ohne -p 8080:8080 neu erstellen:

podman stop it-tools
podman rm it-tools
podman run --pull always \
    --restart unless-stopped \
    --network proxy \
    -d \
    --name it-tools \
    docker.io/sharevb/it-tools:latest

Danach solltest du die IT Tools nur noch via https://it-tools.example.com erreichen.

Zusätzliche Informationen

Mehrere Netzwerke

Natürlich kannst du mehrere Netzwerke erstellen. Beispielsweise, wenn du App A und App B über Caddy erreibar machen möchtest, gleichzeitig aber verhindern willst, dass sich App A und App B gegenseitig erreichen können. Dann kannst du zwei Netzwerke errichten: ProxyA und ProxyB. Caddy ist dann Teil von beiden Netzwerken, während die beiden Apps nur mit einem Netzwerk verbunden werden. Sagen wir zusätzlich nutzt App B die Datenbank und du willst verhindern, dass irgendjemand von aussen auf die Datenbank zugreifen kann. Dann richtest du nochmals ein Netzwerk DBNet ein und verbindest die Datenbank und die App B mit diesem Netzwerk.

Rootful

Wir haben uns auf das Rootless-Szenario konzentriert. Letzten Endes ist das ja einer der zentralen Punkte bei Podman. Doch auch wenn wir Podman rootful nutzen, sind die Befehle die gleichen wie bei rootless, nur eben mit Root-Rechten (sprich mit vorangestelltem sudo) ausgeführt:

sudo podman network create proxy
sudo podman run --name it-tools -d --network proxy docker.io/sharevb/it-tools:latest
sudo podman run --name caddy -d --network proxy docker.io/caddy/caddy:latest

Was dann passiert, haben wir schon gesehen: Es wird eine Bridge im Host-Namespace erstellt, genau wie die Default-Bridge. Der einzige Unterschied ist die eingeschaltete DNS-Funktion.

Andere Netzwerk-Arten

Wir haben uns heute nur mit Bridge-Netzwerken beschäftigt. Es gibt bei Podman aber noch andere Arten von Netzwerken: macvlan und ipvlan. Wenn du eine andere Art nutzen willst, kannst du das über den Parameter -d bzw. --driver tun. Wir hätten auch selber den Parameter --driver bridge oder -d bridge verwenden können, um sicherzustellen, dass ein Bridge-Netzwerk erstellt wird, doch da dies schon der Standard ist, konnten wir auch einfach podman network create verwenden. Für die anderen Netzwerkarten benötigst du zwingend den --driver Parameter.

Noch ein Hinweis: Wenn ich die Dokumentation von Podman richtig verstanden habe, sind macvlan und ipvlan im rootless Modus eigentlich nicht brauchbar. Wenn du eines von beiden einsetzen musst oder willst, wirst du rootful gehen müssen. Getestet habe ich es aber nie. Lass es mich wissen, falls du mehr dazu weisst.

Fazit und Ausblick

Netzwerke bei Podman drehen sich im Kern um eine Frage: Haben zwei Namespaces eine Verbindung oder nicht? Ohne eigenes Netzwerk sind Container isoliert – rootful über die DNS-lose Default-Bridge, rootless sogar komplett via Pasta, da jeder Container nur eine private 1:1-Verbindung zum Host hat. podman network create löst das: Es legt eine eigene Namespace mit einer Bridge (gebaut von Netavark) und aktiviertem aardvark-dns an, wodurch sich Container gegenseitig auch per Name erreichen. Damit läuft unser Caddy-Setup jetzt endlich vollständig.

Dass das alles ohne Root-Rechte möglich ist, liegt daran, dass Privilegien immer relativ zur eigenen Namespace sind – ein Thema, dem wir im nächsten Artikel über Capabilities nochmals vertieft begegnen.

TL;DR

Ohne eigenes Netzwerk: rootful nutzt die DNS-lose Default-Bridge podman, rootless (Pasta) isoliert Container komplett voneinander.

Eigenes Netzwerk mit Namensauflösung erstellen:

podman network create proxy

Container daran anschliessen:

podman run --pull always --restart unless-stopped --network proxy -d --name it-tools docker.io/sharevb/it-tools:latest

podman run -p 80:80 -p 443:443 -p 443:443/udp \
    --mount type=bind,source=$HOME/site,target=/srv,readonly,relabel=shared \
    --mount type=bind,source=$HOME/caddy,target=/etc/caddy,readonly,relabel=shared \
    --mount type=volume,source=caddy_data,target=/data \
    --mount type=volume,source=caddy_config,target=/config \
    --network proxy \
    -d \
    --name caddy \
    docker.io/caddy

Faustregel: Kein --network = kein gemeinsamer Pfad zwischen Containern. Ein selbst erstelltes Netzwerk gibt dir Bridge und DNS gratis dazu.

Quellen und Dokumentation

Tags

Container, Podman, Podman-Netzwerk, Network-Namespace, Pasta, Rootless Container

Es wurden noch keine Kommentare verfasst, sei der erste!