Wie ich Anwendungen in Containern mit Atomic ausführe: Teil 2

Wie ich Anwendungen in Containern mit Atomic ausführe: Teil 2

Das silberblaue Meer durchsegeln

Die Container-Welt ist schnelllebig und seit dem letzten Artikel wurde die Atomic-Workstation in SilverBlue [1] umbenannt. Das passt ja spitze zu meiner Haarfarbe [2]! Wenn Sie über Container-Systeme diskutieren möchten, dann treten Sie doch der Community hier bei [3].

In Teil 1 dieser Blog-Reihe [4] bin ich auf Project Atomic und seinen Alltagsnutzen eingegangen. Wie versprochen legen wir diesmal so richtig los und führen eine komplexe Anwendung mit Hilfe von Containern aus. So gewinnen wir einen genaueren Einblick in die Arbeit mit einem containerbasierten Betriebssystem. Sie sind bestimmt Entwickler und deshalb habe ich beschlossen, dass wir die fantastische Anwendung IntelliJ GoLand [5] containerisieren. GoLand an sich ist eine spitzenmässige integrierte Entwicklungsumgebung (IDE), aber den von uns erstellten Container kann man als Grundlage zur Containerisierung aller weiteren Intellij-Tools nehmen: Alle Abhängigkeiten stimmen überein, also kann man einfach die Download-URL zum gewünschten Intellij-Produkt übertragen.

Den fertigen Code für diesen Container finden Sie auf https://github.com/ninech/atomic-blog-p2, wo es ein voll funktionsfähiges Beispiel gibt. Sie können den Container direkt von https://hub.docker.com/r/ninech/goland/ ziehen.

Alles Leben hat seinen Anfang im Meer

Alles Leben hat seinen Anfang im Meer, auch unsere Container-Entwicklung beginnt mit einem Wal – oder vielmehr mit einem grossen, dicken Docker-Daemon. Dockerfiles sind in der Praxis nach wie vor das Beschreibungsformat für Container, egal mit welchen Tools man sie erstellt und dann betreibt. Deshalb beginnen wir damit bei der Erstellung unserer Anwendung.

Dockerfiles-anwenden

Das ist eigentlich ganz einfach, aber gehen wir es kurz durch:

  1. Wir arbeiten mit Fedora. Machen wir uns das Leben leicht und nutzen wir dasselbe Basis-Betriebssystem innerhalb unseres Image. Ich habe hier den Tag offen gelassen. Sie können sich je nach verwendetem System aber auch für 27 oder 28 entscheiden.

  2. Setzen Sie Umgebungsvariablen für die Versionsnummern. Um in Zukunft Go oder das GoLand-Image upzugraden, sollte es ausreichen, diese abzuändern und neu zu erstellen.

  3. Installieren Sie alle Abhängigkeiten. Obwohl Docker kein Muss ist, installieren wir es, da wir Atomic nutzen und ich davon ausgehe, dass Sie Ihre GoLang-Anwendungen als Container packen. Daher ergibt es Sinn, dies von innerhalb der IDE aus zu tun. Zu berücksichtigen ist auch, dass wir hier einen bestimmten Benutzer hinzufügen und dessen Gruppen-Sudo Zugriff zum Container gewähren. Normalerweise würde ich das nicht vorschlagen, aber im Entwicklungs-Image haben wir es sehr oft mit Tools zu tun, welche die Installation irgendeiner Root erfordern, um sich eine Menge Stress zu ersparen (sagen Ihnen globale NPM-Installationen was?). Das wirkt sich natürlich auf die Sicherheit aus.

  4. Wir hätten GoLang aus den Fedora-Paketen heraus installieren können, aber diese enthalten meist nicht die aktuellste Version. Deshalb laden wir stattdessen das Archiv herunter. IDE-Pakete von Intellij sind als Archive auf dem Server des Anbieters verfügbar. Also müssen wir die Anwendung hier herunterladen und vorbereiten.

  5. Das WorkDir wird auf das Stammverzeichnis des Containerbenutzers gesetzt und ist als Volume zu verlinken. Das ist nötig, weil die Intellij-IDE ihre Konfiguration im Stammverzeichnis speichert und das soll definitiv auch zwischen Container-Neustarts so bleiben. Das passt auch gut mit GOPATH zusammen, das in aktuellen Versionen von Go standardmässig auf $HOME/go gesetzt ist.

  6. Der Standardbefehl ist einfach darauf gesetzt, die IDE vom bereitgestellten Skript aus zu starten.

Auf dem Wal reiten

Nach Erstellung dieser Dockerfile haben wir alles, was wir zur Ausführung benötigen. Vielleicht haben Sie schon einen Ausführungsbefehl im Hinterkopf, der in etwa so aussieht:

docker run --rm -d --name goland -v
/home/${USER}/nine-goland:/home/developer ninech/goland

Wenn wir dies ausführen, bekommen wir folgendes Ergebnis:

Startfehler: Die grafische Umgebung konnte nicht gefunden werden.

Oh nein! Leider waren wir ein bisschen zu schnell mit unserem einfachen Ausführungsbefehl. Also müssen wir diesen zuerst reparieren. Die gute Nachricht: Das geht ziemlich einfach [6]. Über ein Socket wird auf das Display zugegriffen. Deshalb müssen wir es in den Container einbinden und auch das Host-Display auf das Display des lokalen Geräts setzen, indem wir die DISPLAY-Variable übergeben [7]:

-v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=${DISPLAY}

Hier führen wir noch eine weitere sinnvolle kleine Änderung durch. Da wir Fedora benutzen, müssen wir uns auch mit SELinux auseinandersetzen, was allerdings nicht so problematisch ist, da man nur eine Sicherheitsbezeichnung hinzufügen muss. Diese Vorgehensweise und ihr genaues Ergebnis können Sie im SELinux Profil-Repository von Project Atomic sehen [8]:

--security-opt label=type:container_runtime_t

Ziehen Sie all dies zusammen und Sie erhalten einen Befehl wie folgt:

$ docker run --rm -d --name goland -v
/home/${USER}/nine-goland:/home/developer -v
/tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=${DISPLAY} --security-opt label=type:container_runtime_t ninech/goland

Wenn wir dies ausprobieren, stellen wir fest, dass die Anwendung jetzt läuft und wir entsprechend mit der Entwicklung von GoLang-Tools beginnen können.

Wäre dies nur ein Docker-Tutorial, wären wir jetzt fertig. Weil es aber um Atomic und das Neueste vom Neuen geht, legen wir jetzt erst richtig los!

Es mit dem Wal aufnehmen

Der Atomic-Befehl ist ein grossartiges Tool, mit dem wir den Lebenszyklus unserer Anwendungen kontrollieren und diese ausführen können, ohne dass wir uns Docker-Befehle wie die bereits angesprochenen merken oder diesen Aliasse zuweisen müssen. Der Atomic-Befehl nutzt eine Reihe spezifischer Bezeichnungen, die Sie Ihrer Dockerfile hinzufügen. So kann diese wichtige Phasen des Lebenszyklus Ihrer Anwendung kontrollieren. Das ist sehr geschickt, denn so können wir alles, was wir für unsere Anwendung benötigen, im Image zusammenfassen und dessen Benutzern wird seine Verwendung erleichtert.

Fügen wir der Dockerfile eine neue Bezeichnung hinzu, die RUN (Ausführen) heisst. Wir fügen den oben erstellten «docker run»-Befehl in sie ein (ohne die Option --rm, da wir an diesem Punkt den Container zwischen den Neustarts beibehalten wollen).

LABEL RUN='docker run -d --name goland -v
/home/${USER}/nine-goland:/home/developer -v
/tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=${DISPLAY} --security-opt
label=type:container_runtime_t ninech/goland'

Wenn wir jetzt das Image wieder erstellen, geschieht etwas Wunderbares:

$ docker build -t ninech/goland .
$ atomic run ninech/goland

Jetzt startet unsere Anwendung mit dem Befehl, den wir der RUN-Bezeichnung hinzugefügt haben. Im Hintergrund verwaltet das Atomic-Tool nun diesen Container und erhält diesen mitsamt seinem Inhalt für uns.

Mit Atomic können wir auch die Bezeichnungen INSTALL (Installieren) und UNINSTALL (Deinstallieren) setzen, die (normalerweise superprivilegierte) Schritte zur Erstellung des Containers durchführen können. Diese werden üblicherweise dazu verwendet, um Systemd-Dateien zu installieren oder andere Elemente zu erstellen, die benötigt werden, damit der Container auf dem Host-System laufen kann. Atomic unterstützt auch den Befehl «atomic upgrade ninech/goland», der ein neues Image für uns zieht und den Container aktualisiert [9].

So kann in Ihrem Anwendungs-Container alles zusammengefasst werden, was für den gesamten Lebenszyklus Ihrer Anwendung benötigt wird. Und das ist richtig genial! Aber wie bereits gesagt: Wir legen gerade erst los.

Kapitän Ahab setzt die Segel

Der Container läuft jetzt also; er hat Zugriff auf das Grafiksystem und kann mit einem einfachen Atomic-Befehl ausgeführt werden. Allerdings nutzt er an diesem Punkt immer noch Docker und das wollen wir definitiv nicht.

Es gibt recht viele Tools, mit denen man Docker ersetzen kann, und auch wenn keines von ihnen dies komplett tut, können sie zusammen doch ein sehr ähnliches Leistungsspektrum bieten. Im Zuge dieses Posts konzentrieren wir uns auf podman/libpod [10], da dieses Tool zurzeit am ehesten einem kompletten Ersatz entspricht. Project Atomic verfügt über einen ausgezeichneten Blog [11], in dem dieses Tool vorgestellt wird. Den sollten Sie lesen, bevor es weitergeht [12].

Podman-Installation

Podman ist eine tolle Alternative zu Docker. Sie können es in Atomic bekommen, indem Sie Folgendes ausführen:

rpm-ostree install podman

Podman deckt im Prinzip fast alle Funktionen von Docker ab [13], indem es viele verfügbare Image-Tools bequem in einem Paket zusammenfasst, aber ohne Daemon [14]. Viele Leute, die mit Atomic-Systemen arbeiten, weisen Docker den Alias Podman zu.

Podman-Ausführung

Jetzt haben wir Podman und damit ist der Rest ein Kinderspiel: Wir müssen einfach in unserem Befehl «docker» durch «podman» ersetzen.

podman run -d --name goland -v
/home/${USER}/nine-goland:/home/developer -v
/tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=${DISPLAY} --security-opt
label=type:container_runtime_t ninech/goland

Das war's! Jetzt sollte alles funktionieren, und zwar ohne Daemon. Also können Sie nun sicher Ihren Docker-Prozess neu starten und Ihre IDE am Laufen halten. Probieren Sie's doch aus! Es macht überraschend viel Spass.

Vorsicht: Wenn Sie den obigen Befehl als Ihre Atomic-Run-Bezeichnung nutzen, zieht er das Image zweimal; einmal über die Docker-Engine für Atomic zur Erkennung und noch einmal beim Start mit Podman. Das ändert sich hoffentlich, sobald der Atomic-Befehl Podman als standardmässige Container-Engine nutzt.

Debugging-Tools

Wir haben in diesem Artikel noch nicht besprochen, wie man die GoLand-Debugging-Tools zum Laufen bekommt. Intern nutzen sie Delve und damit dies funktioniert, müssen wir im Container fork/exec zulassen. Das geht definitiv am einfachsten, indem man schlicht den Container wie folgt uneingeschränkt laufen lässt:

podman run -v /tmp/.X11-unix:/tmp/.X11-unix -v
${HOME}:/home/developer-e DISPLAY=${DISPLAY} -v
/var/run/docker.sock:/var/run/docker.sock --security-opt $
label=type:container_runtime_t --security-opt seccomp=unconfined
ninech/goland:latest

Offensichtlich gibt man damit im Sinne der Benutzerfreundlichkeit sämtliche Sicherheit auf, was nur in einer Umgebung akzeptabel ist, die man komplett unter Kontrolle hat. In meinen Versuchen konnte ich dies aber kaum vermeiden. Wenn Sie eine bessere Lösung haben, freue ich mich, davon zu erfahren [15].

Fazit

In diesem Artikel wurden nur die Grundlagen besprochen, wie man einen Container in einer IDE zum Laufen bekommt. Wahrscheinlich möchten Sie weitere Abhängigkeiten und Convenience-Installationen für Tools hinzufügen, die Sie regelmässig nutzen. Das Gute ist, dass Sie jetzt einen flexiblen Container haben, der mit allen Intellij-Produkten funktioniert – und zwar ohne Daemon – und in den Sie Ihre gängigen Tools einbinden können, ohne das Basis-System zu verändern.

Wenn man eine Software so erstellt, offenbart dies eindeutig ihre Abhängigkeiten; so bekommt man ein klareres Bild, was tatsächlich benötigt wird, um eine Anwendung auszuführen. Zudem stellt man fest, dass man Container zur Ausführung aller Schritte nutzen kann, nicht nur für Server-Anwendungen.

Im nächsten und letzten Teil dieser Reihe werfen wir einen Blick auf Buildah zur Erstellung von Images ohne Daemon sowie auf das Upgraden Ihres Betriebssystems durch Umbasierung und fassen einige der Probleme und Erfahrungen zusammen, die sich aus der Nutzung von Atomic als Haupt-Betriebssystem ergeben haben.



Hier gelangen Sie zum ersten Teil der Serie

«Wie ich Anwendungen in Containern mit Atomic ausführe: Teil 1»


[1]    https://www.teamsilverblue.org/  
[2]    https://www.nine.ch/en/nine-internet-solutions-ag/team 
[3]    https://community.teamsilverblue.org/ 
[4]    https://www.nine.ch/en/engineering-logbook/dockerising-my-workstation-with-atomic-part-1 
[5]    https://www.jetbrains.com/idea/ 
[6]    Ich nutze das standardmässige Wayland-Gnom-Desktop, ansonsten ymmv.
[7]    Eine vollständige Erklärung der Display-Variable finden Sie auf https://docstore.mik.ua/orelly/unix3/upt/ch35_08.htm 
[8]    https://github.com/projectatomic/container-selinux 
[9]    Möglicherweise benötigen Sie die Option «--force», um Ihre Container zu aktualisieren.
[10] https://github.com/projectatomic/libpod 
[11] https://www.projectatomic.io/blog/2018/02/reintroduction-podman/ 
[12] Wenn Sie Podman auf dem Gerät eines anderen Benutzers testen wollen – und damit auf dessen Risiko ;) –, versuchen Sie Ihr Glück auf https://www.katacoda.com/courses/containers-without-docker/running-containers-with-podman 
[13] Multi-Stage-Dockerfiles und ONBUILD scheinen derzeit nicht zu funktionieren. Informationen hierzu finden Sie im Buildah-Projekt, da Podman auf Buildah zurückgreift, um Images zu erstellen.
[14]  Folgen Sie #nobigfatdaemons auf Twitter.
[15] Kontaktieren Sie mich auf Twitter (@t_whiston) oder über die SilverBlue-Foren.

Tom Whiston

Strategic & Agile Consultant @ Nine
Find me on Github