Podman¶
Alle Anwendungen auf unseren Hosts laufen als Container, betrieben von Podman. Diese Seite erklärt die Begriffe, die man zum Lesen einer Stack-Datei braucht, wie Podman und systemd die Container bei uns betreiben und wie Secrets in die Container kommen. Wie die Secrets verschlüsselt im Repo liegen, steht in Secrets im Repo.
Viele Apps auf einem Host¶
Auf einem Host laufen bei uns viele Apps, und jede bringt ihre eigenen Anforderungen mit:
- Jede braucht bestimmte Bibliotheken, Laufzeitumgebungen und Dienste in bestimmten Versionen; direkt auf dem Host kommen sie sich in die Quere.
- Updates und das Entfernen einer App hinterlassen Reste auf dem Host.
- Eine App soll sich auf einem anderen Host genauso wieder aufsetzen lassen.
Container lösen das: Jede App läuft mit allem, was sie braucht, abgeschottet in ihrem eigenen Container. Damit die Container laufen, braucht es ein Programm, das sie startet und verwaltet.
Alle Apps laufen als Container, nicht direkt auf dem Host.
- Jede App bringt im Container ihre eigenen Bibliotheken und Programme in der Version mit, die sie braucht. Zwei Apps, die verschiedene Versionen von PHP oder Postgres brauchen, kommen sich nicht in die Quere, und eine App sieht die Dateien der anderen nicht.
- Ein Update tauscht nur das Image aus: Der alte Container wird entfernt, ein neuer aus dem neuen Image erzeugt. Auf dem Host bleibt nichts von der alten Version zurück, und ein Schritt zurück heißt, wieder das alte Image zu nehmen (solange die Daten das zulassen, siehe Image-Version anheben).
- Ein Container aus demselben Image mit derselben Konfiguration verhält sich auf jedem Host gleich. Zusammen mit den Stack-Dateien im Repo lässt sich eine App deshalb jederzeit neu aufsetzen; nur die Daten in den Volumes kommen aus dem Backup.
- Wird eine App nicht mehr gebraucht, verschwinden mit ihren Containern auch alle ihre Programme vom Host.
Die Container betreiben wir mit Podman. Es gibt andere Programme dafür; das bekannteste ist Docker, und viele Hersteller beschreiben ihre Apps für Docker. Podman ist weitgehend kompatibel mit Docker, gehört bei Rocky Linux zum Betriebssystem (siehe Server-Technologien), lässt die Container von systemd betreiben (siehe Podman und systemd) und setzt die SELinux-Labels für die Container selbst (siehe SELinux).
Image, Container, Volume, Netzwerk¶
Ein Image ist eine Vorlage: die Anwendung mit allem, was sie zum Laufen braucht, vom Hersteller fertig zusammengestellt.
Images liegen in einer Registry, etwa Docker Hub oder Quay, und tragen einen Tag, der die Version angibt.
quay.io/hedgedoc/hedgedoc:1.12.0 ist das Image von HedgeDoc mit dem Tag 1.12.0.
Ein Container ist eine laufende Anwendung, erzeugt aus einem Image. Er hat ein eigenes Dateisystem, eigene Prozesse und eigene Netzwerkadressen und sieht vom Host und von anderen Containern nur, was man ihm ausdrücklich gibt. Wird der Container neu erzeugt, etwa bei einem Update, beginnt er wieder mit dem Inhalt des Images; alles, was er vorher in sein eigenes Dateisystem geschrieben hat, ist weg.
Daten, die bleiben sollen, liegen deshalb in einem Volume: einem Speicherort auf dem Host, den Podman verwaltet und in den Container einblendet. Ein Volume überdauert jedes Neuerzeugen des Containers. Seltener blenden wir statt eines Volumes einen Ordner des Hosts direkt ein, einen Bind-Mount.
Über ein Netzwerk erreichen sich Container untereinander, und zwar über ihren Namen: HedgeDoc erreicht seine Datenbank unter hedgedoc-postgres.
Ein Container kann an mehreren Netzwerken hängen.
flowchart LR
registry[("Registry<br>z. B. quay.io")]
image["Image<br>hedgedoc:1.12.0"]
container["Container<br>hedgedoc-server"]
volume[("Volume<br>hedgedoc-server-uploads")]
db["Container<br>hedgedoc-postgres"]
registry -->|herunterladen| image
image -->|erzeugen| container
container <-->|liest und schreibt| volume
container <-->|"Netzwerk hedgedoc"| db
All das beschreiben wir in den Stack-Dateien unter syslet/<host>/, zum Beispiel syslet/werner/stack-hedgedoc.cue.
Wie man so eine Datei liest, steht in Eine Stack-Datei lesen.
Podman und systemd¶
Die Container betreibt Podman, und zwar über systemd, das auf jedem Linux-Host die Dienste startet und überwacht.
syslet schreibt für jeden Container, jedes Volume und jedes Netzwerk eine Quadlet-Datei auf den Host.
Daraus erzeugt systemd je eine systemd-Unit, für einen Container mit seinem Namen, etwa hedgedoc-server.service.
Startet systemd die Unit, erzeugt Podman den Container; systemd überwacht ihn dann wie jeden anderen Dienst:
Stürzt er ab, startet systemd ihn neu, und nach einem Neustart des Hosts startet es alle Container wieder.
Deshalb steuert man einen Container auf dem Host mit systemctl über seine Unit und nicht mit podman start oder podman stop.
Was man direkt mit Podman tut, bekommt systemd nicht mit: Einen so gestoppten Container startet systemd unter Umständen gleich wieder, und ein so gestarteter läuft außerhalb seiner Unit, ohne Überwachung.
Was man auf dem Host überhaupt von Hand tun darf, steht in Was man von Hand darf.
Die Container betreibt systemd, kein eigener Dienst. Podman braucht keinen Dienst, der dauerhaft im Hintergrund läuft und die Container verwaltet; das übernimmt systemd, das ohnehin läuft. Status, Logs und Neustarts funktionieren für Container deshalb genauso wie für jeden anderen Dienst des Hosts.
Interne Netzwerke¶
Auf werner bekommt jede App mit mehreren Containern ein eigenes Netzwerk, das als intern markiert ist (Internal: "true", in der Stack-Datei ganz oben).
An einem internen Netzwerk hängen nur die Container dieses einen Stacks; sie erreichen sich gegenseitig, aber nicht das Internet, und von außen sind sie nicht erreichbar.
Datenbanken, Caches und Suchdienste hängen nur an diesem internen Netzwerk.
Der Container mit der eigentlichen App hängt zusätzlich am Netzwerk caddy, über das ihn der Reverse Proxy erreicht.
Auf containerhost hängen dagegen alle Container an einem gemeinsamen Netzwerk; es heißt zwar internal, ist aber nicht als intern markiert (siehe syslet/containerhost/host.cue).
Weil die internen Dienste auf werner nur aus dem eigenen Stack erreichbar sind, stehen ihre Passwörter im Klartext in der Stack-Datei; die Begründung steht in Secrets im Repo.
Secrets¶
Secrets wie Passwörter und Tokens bekommen die Container als Podman Secrets; dieser Abschnitt erklärt, wie sie vom Repo dorthin kommen, was das schützt und was nicht.
Vom Repo in den Container¶
Im Repo liegen die Secrets verschlüsselt in creds-*.enc.yaml.
syslet schickt sie beim Ausrollen so, wie sie sind, an den Host und entschlüsselt sie erst dort, mit dem Schlüssel, den es aus dem SSH-Schlüssel des Hosts ableitet.
Die entschlüsselten Werte legt syslet als Podman Secrets ab: Podman verwaltet sie unter einem Namen, und ein Container bekommt nur die Secrets, die in seiner Stack-Datei stehen.
flowchart LR
subgraph repo ["Repo"]
creds["creds-hedgedoc-server.enc.yaml<br>(verschlüsselt)"]
end
subgraph host ["Host, z. B. werner"]
syslet["syslet<br>entschlüsselt beim Ausrollen"]
store[("Podman Secret<br>hedgedoc-server-oidcclientsecret")]
container["Container hedgedoc-server<br>CMD_OAUTH2_CLIENT_SECRET"]
end
creds -->|"cue cmd apply<br>per SSH, verschlüsselt"| syslet
syslet -->|Klartext| store
store -->|beim Start| container
In der Stack-Datei steht dafür nur der Name des Secrets und wie es in den Container kommt, zum Beispiel in syslet/werner/stack-hedgedoc.cue:
| Text Only | |
|---|---|
Wie die Namen gebildet werden, steht in syslet in diesem Repo.
Secrets kommen als Podman Secrets in die Container. Das hält Passwörter aus allem heraus, was man sich zur Fehlersuche ansieht oder weitergibt:
- Die Werte stehen nicht in den Quadlet- und systemd-Unit-Dateien, die syslet auf den Host schreibt, sondern nur deren Namen.
podman inspectzeigt statt des Wertes nur*******.- Secrets landen weder in einem Image noch in dem, was
podman commitoderpodman exportaus einem Container machen.
Außerdem bekommt eine Secret-Datei (type=mount) das private SELinux-Label ihres Containers.
Andere Container kommen nicht daran, auch wenn sie unter derselben Benutzer-ID laufen (siehe SELinux).
Als Umgebungsvariable oder als Datei¶
Podman kann ein Secret auf zwei Arten in einen Container geben:
type=env- als Umgebungsvariable, im Beispiel oben
CMD_OAUTH2_CLIENT_SECRET. So machen wir es bei den meisten Apps, weil sie ihre Einstellungen nur aus Umgebungsvariablen lesen können. Das ist eine Notlösung, keine Wahl. type=mount- als Datei unter
/run/secrets/<name>im Container; das ist der Standard, wenntype=fehlt. Die App bekommt dann in einer Umgebungsvariable nur den Pfad zur Datei, oft in einer Variable, die auf_FILEendet. So machen wir es überall, wo die App das kann, zum Beispiel beim Membertool (MEMBERTOOL_SMTP_PASSWORD_FILEinsyslet/werner/stack-membertool.cue), beim Redirect-Dienst und beim rest-server. Mituid=,gid=undmode=legen wir fest, welchem Benutzer im Container die Datei gehört und wer sie lesen darf.
Wo die App es kann, kommt ein Secret als Datei, nicht als Umgebungsvariable. Eine Umgebungsvariable sieht nicht nur die App selbst:
- Jeder Prozess, den die App startet, erbt ihre Umgebungsvariablen und damit das Secret.
- Viele Apps geben bei Fehlern oder im Debug-Modus ihre Umgebung aus, und Fehlerberichte (Crash-Reports) enthalten sie oft. Das Secret steht dann in Logs oder wird an Dritte geschickt.
Eine Datei liest die App gezielt, wenn sie den Wert braucht; sie wird weder vererbt noch nebenbei ausgegeben.
Klartext auf dem Server¶
Auf dem Host liegen die Secrets zwangsläufig im Klartext: Die Apps brauchen sie, um sich etwa bei der Datenbank oder bei Mailjet anzumelden.
Podman speichert sie unverschlüsselt in einer Datei, die nur root lesen darf.
Den Klartext auf dem Server nehmen wir hin.
Wer root auf dem Host ist, kann ohnehin alles: in jeden Container schauen, jede Datei lesen, jeden Prozess beobachten.
Was Podman Secrets nicht leisten¶
- Sie liegen auf der Platte: Auch als Datei (
type=mount) liegt ein Secret nicht in einem tmpfs, auch wenn der Pfad/run/secrets/danach aussieht. - Mit
type=envist das Secret in der Umgebung des Prozesses lesbar (/proc/<pid>/environ) und steht in der Startkonfiguration, die Podman für den Container auf der Platte ablegt. Beides kann nurrootlesen, oder der Container selbst.
Weiterlesen¶
- syslet in diesem Repo: wie unsere Stack-Dateien aufgebaut sind
- syslet in diesem Repo: wie die Secrets heißen
- SELinux: wie Container und ihre Volumes voneinander getrennt sind
- Secrets im Repo: wie die Secrets verschlüsselt im Repo liegen und wer sie lesen kann
- Container-Status und Logs ansehen
- Secret anlegen oder ändern
- Spec types in der syslet-Doku: welche Quadlets syslet erzeugt
- Secret encryption in der syslet-Doku: wo syslet entschlüsselt
- Pass a secret to a container in der syslet-Doku
- Podman-Doku zu Quadlets
- Podman-Doku zu
podman secret createund zur Option--secret - Podman-Doku