Suche
NetBeans Blog

NetBeans Teilchenbeschleuniger: Docker und ROOT

Thomas Kruse, Karsten Sitterberg

(c) Shutterstock / Serz_72

Docker innerhalb der NetBeans-Entwicklung nutzen – mit diesem spannenden Thema tragen sich Thomas Kruse und Karsten Sitterberg in den NetBeans Blog auf JAXenter ein. Dabei dient das CERN-ROOT-Framework als Beispiel dafür, wie verschiedene Umgebungen eines Frameworks genutzt werden können, ohne dabei in Versionskonflikte zu geraten.

Es geht um Geschwindigkeit

Das CERN-ROOT-Framework soll hier als Beispiel dafür dienen, wie verschiedene Versionen bzw. Umgebungen eines C++-Frameworks genutzt werden können, ohne dabei in die Hölle von Konflikten paralleler Installationen von Bibliotheken, Header-Daten und Sourcen zu blicken. Insbesondere wenn es darum geht, nicht nur Versionen sondern auch Umgebungen wie beispielsweise Ubuntu Linux, Scientific Linux und Fedora einfach testen und supporten zu können, naht jedem Sysadmin angesichts der aufzubauenden Matrix an Systemen schnell der Angstschweiß.

Neben der Option, physisch unterschiedliche Hardware zu verwenden, ist in den letzten Jahren der Trend in Richtung Virtualisierung gegangen. Vor allem VirtualBox von Oracle zusammen mit Vagrant von Hashicorp sind echte Produktivitätsbooster.

Dank Virtualisierung ist es möglich, dass selbst auf einem Entwicklerlaptop unterschiedliche Umgebungen in Isolation und parallel betrieben werden können. Diese können, z.B. mittels Snapshots oder Konfigurationsmanagement wie Puppet, in exakt definierten Zuständen genutzt werden.

Wenn die verwendete Entwicklungsumgebung, z.B. NetBeans, dann auch noch gute Unterstützung für Remote-Builds hat, ist man schon nahe an einem perfekten Entwicklungsprozess. Geschwindigkeit und Effizienz der Nutzung von Betriebsressourcen (wie RAM oder CPU) sind in einer virtuellen Maschine jedoch eingeschränkt. Etwas Leichtgewichtigeres wäre da wünschenswert, und das gibt es auch in Form von Containern.

W-JAX 2018 Java-Dossier für Software-Architekten

Kostenlos: 40+ Seiten Java-Wissen von Experten

Sie finden Artikel zu Enterprise Java, Software-Architektur, Java 9, Java 11, Deep Learning und Docker von Experten wie Kai Tödter (Siemens), Arne Limburg (Open Knowledge), Manfred Steyer (SOFTWAREarchitekt.at) und vielen weiteren.

Docker Container

Anders als bei einer vollen Virtualisierung wird ein Container als leichtgewichtiger Betriebssystemprozess gestartet und mit Betriebsmitteln vom Rest des Systems isoliert. Man kann sich das wie ein erweitertes chroot vorstellen. Der Preis für eine höhere Flexibilität als chroot und eine bessere Performanz als eine virtuelle Maschine schlägt sich in der etwas geringeren Isolation und der Notwendigkeit, ein Linux-System als Umgebung verwenden zu müssen, nieder.

Mac- und Windows-Nutzer können Docker dennoch verwenden, jedoch muss innerhalb des Containers eine Linux-Umgebung genutzt werden. Bei Windows oder Mac wird als Docker-Laufzeitumgebung — für den Anwender unsichtbar — VirtualBox und Vagrant genutzt.

ROOT

ROOT ist ein Software-Framework basierend auf der Programmiersprache C++ und wurde am CERN für die Analyse von Daten aus Hochenergiephysik-Experimenten entwickelt. Mit Möglichkeiten zur Speicherung, Analyse und Darstellung ist es hervorragend für den Einsatz im Big-Data-Business geeignet. Der Nutzer kann die Möglichkeiten von ROOT entweder in Form von Skripten, die im Cling C++ Interpreter ausgeführt werden, oder in Form von kompilierten Programmen ausschöpfen. Weiterhin ist mit ROOT eine Integration mit anderen Sprachen wie Python und R möglich.

Einrichtung

Im Folgenden wird die Einrichtung von Docker, dem als Beispiel genutzten CERN ROOT Docker Container und der NetBeans IDE für die Remote C++-Entwicklung erklärt. Dazu sollte ein eigener Ordner im Dateisystem erstellt werden, in dem das Beispielprojekt aus der Archivdatei im Anhang dieses Artikel ausgepackt wird. Aus diesem Ordner heraus werden die Kommandobeispiele ausgeführt. (Alternativ – und bei Nutzung von Windows – kann ein fester Pfad an den Stellen, in denen das pwd Kommando verwendet wird, eingesetzt werden.)

Docker

Die Installation von Docker ist denkbar einfach: Hat man bereits ein Linux-System, installiert man über den Paketmanager seiner Wahl Docker. Mac- und Windows-Anwender installieren sich die Docker Toolbox von https://www.docker.com/toolbox

Unter Ubuntu installiert man das Paket docker.io und gibt sich die Rechte, um mit Docker interagieren zu können. Achtung: Wer in der Docker-Gruppe ist, kann sich mittels Docker am System Administratorrechte holen. (So viel zum Thema reduzierte Isolation, aber es geht ja um Entwicklerarbeitsplätze, an denen der Nutzer sowieso volle Rechte haben sollte.)

sudo apt-get install docker.io 
sudo usermod -a -G docker <username>

Am einfachsten startet man danach das System neu. Dadurch wird der Docker-Dienst gestartet, und die Berechtigungen werden korrekt angewendet.

ROOT Docker Image

Docker verwendet Images als Grundlage für die Container genannten Laufzeitinstanzen. Ein solches Image wird einmal gebaut und wird danach nicht mehr geändert. Damit eignet es sich hervorragend, um reproduzierbare und sogar identische Umgebungen für Tests und Entwicklung zu produzieren.

Um ein Image zu erzeugen, wird das Rezept für das Image in ein Dockerfile geschrieben. Docker verwendet ein geschichtetes Dateisystem, worüber sich eine Art Vererbung auf Image-Ebene realisieren lässt. Davon wird Gebrauch gemacht, um Vererbung zu realisieren. Jedes Docker-Image kann daher ein Basis-Image besitzen. Im Dockerfile wird das Basis-Image mittels FROM deklariert:

FROM gcc

Damit die Generierung von Docker-Images wartbar bleibt, bietet es sich an, mit Platzhaltern z.B. für konkrete Versionen von zu installierenden Artefakten zu arbeiten. Mit Docker können Parameter exportiert werden, die dann bei der Imageerstellung als Umgebungsvariablen für die ausgeführten Kommandos verfügbar sind. Im Beispiel geschieht dies für die zur Anwendung kommende Version des ROOT-Frameworks und die Download URL:

ENV USER_DIR      /home/user
ENV ROOT_VERSION  v6.04.02 
ENV DOWNLOAD_URL  https://root.cern.ch/download/root_${ROOT_VERSION}.source.tar.gz

Diese werden dann bei der tatsächlichen Installation genutzt, wie im folgenden Auszug zu sehen ist. Auch ist gut zu erkennen, dass innerhalb des Containers auf die Umgebung eingegangen werden kann: So ist die Anzahl der Prozessorkerne wie üblich über das Kommando nproc zu ermitteln und kann genutzt werden, um bei der Kompilierung entsprechend viele Threads einzusetzen.

RUN mkdir root-cern \ 
  && echo "download ${DOWNLOAD_URL}" \ 
  && curl -L --silent "${DOWNLOAD_URL}" | tar -xz --strip=1 -C "${USER_DIR}/root-cern" \ 
  && cd "${USER_DIR}/root-cern/build" \ 
  && cmake ../ \ 
  && cmake --build . -- -j$(($(nproc) + 1)) \ 
  && cmake --build . --target install \ 
  && ldconfig
Abb. 1: Nutzung mehrerer Threads beim Build in der CPU-Übersicht

Abb. 1: Nutzung mehrerer Threads beim Build in der CPU-Übersicht

 

Damit der Zugriff per SSH gelingt – dies wird von NetBeans für den Remote-Zugriff genutzt -, muss noch der Container konfiguriert werden, um den SSH Daemon beim Containerstart mitlaufen zu lassen. Außerdem muss der TCP Port 22 nach außen deklariert werden.

EXPOSE 22

Das Bauen des Docker-Images wird mittels docker build angestoßen. Es kann noch ein aussagekräftiger Name für das Image und das Verzeichnis für die Ausführung mitgegeben werden. Im Beispiel sieht das so aus:
docker build -t cern-root .

Der Container nutzt das offizielle gcc-Docker-Basisimage. Ist dies noch nicht lokal vorhanden, z.B. weil es das erste Mal verwendet wird, so wird dies vom zentralen Repository – genannt Docker Hub – heruntergeladen. Anschließend werden die Sourcen für ROOT, rund 100 MB, vom CERN heruntergeladen und kompiliert.

Der gesamte Vorgang dauert auf einem Notebook mit eher schwacher CPU und vier Kernen insgesamt rund eine Stunde, auf einem Desktop-Rechner mit 16 Threads ca. 15 Minuten.

Das CERN ROOT-Framework wird dabei nach /usr/local installiert – innerhalb des Images wohlgemerkt.

NetBeans

Auch wenn es bereits NetBeans-Docker-Images gibt, ist der einfachste Weg für ein stabiles NetBeans, die passende Version von der offiziellen Webseite https://www.netbeans.org/ zu beziehen. Für die C++ Version wird kein separates Java benötigt, dies bringt die NetBeans IDE ab Version 8.1 mit. Möchte man nicht nur C++ entwickeln, sondern zum Beispiel C++ gemischt mit Java, dann sollte ein aktuelles JDK und die entsprechende Edition der NetBeans IDE installiert werden.

Testlauf

Dieser Schritt ist optional, trägt aber zum Verständnis dessen bei, was unter der Haube passiert. Das Docker-Image ist erstellt und dient als Vorlage für eine oder mehrere Instanzen, die daraus abgeleitet werden. Im regulären Betrieb erstellt man einen Container (Instanz) aus dem Image, diesen kann man dann starten und stoppen oder auch entfernen, wenn die Instanz nicht mehr benötigt wird. Für den Probelauf werden diese Schritte zusammengefasst und zusätzlich eine interaktive Shell gestartet, um damit innerhalb des Containers agieren zu können.
docker run --name run_root -ti --rm cern-root /bin/bash

Nun befindet man sich innerhalb einer Instanz und kann dort wie gewohnt arbeiten. Testweise kann man sich vergewissern, dass GCC installiert ist, und sich die Version ausgeben lassen. Beenden kann man die Sitzung wie jede normale Shell-Sitzung auch mittels exit – hier wird danach der Container gestoppt. Der Container muss nicht gelöscht werden, da er durch den Parameter –rm nach dem Lauf automatisch durch Docker gelöscht wird. Ohne diesen Parameter oder manuelles Löschen gäbe es nach jeder Ausführung eine weitere Instanz. Darin gespeicherte Dateien und Änderungen wären von anderen Instanzen isoliert. Gespeicherte Änderungen werden zusammen mit der Instanz gelöscht, auch das ist eine Konsequenz der Isolation. Es gibt Dateien, bei denen das gerade nicht der Fall sein soll. Dafür hat Docker das Konzept von Volumes. Dabei handelt es sich um einen Speicherbereich, der unabhängig vom Lebenszyklus eines Containers existiert und sogar von mehreren Containern gleichzeitig genutzt werden kann. Im Dockerfile wurde der Pfad /home/user/data/sample als so ein persistenter Bereich deklariert:

VOLUME /home/user/data/sample

So ein Volume erlaubt es, vom Container mit dem umgebenden System verbunden zu werden. So werden im Container erstellte Dateien transparent auch nach außen sichtbar. Dies wird durch den Parameter -v konfiguriert, der lokales Verzeichnis und Pfad im Container verbindet. (Bei Mac- und Windows-Systemen ist das nicht ganz so trivial, funktioniert aber auch. An dieser Stelle sei auf die Docker-Dokumentation für diese Plattformen verwiesen.)
Wenn das Beispielprojekt in einem Ordner vorbereitet wurde, sollte auch aus diesem Ordner heraus der Container gestartet werden. Das erlaubt es, ohne absolute Pfadangabe auszukommen und das sample -Verzeichnis in Kombination mit einer Ermittlung des aktuellen Verzeichnisses relativ zu referenzieren, um es mit dem Container zu verbinden.

Genauso wie Dateisystempfade können Netzwerkports verbunden werden. Hierfür ist der Parameter -p anzugeben, der wieder lokalen Port und Port im Container erwartet.

Damit ergibt sich folgende Kommandozeile, um eine entsprechend konfigurierte Containerinstanz zu erzeugen, die über dem Namen run_root referenziert wird:

docker create --name run_root -v `pwd`/sample:/home/user/data/sample -p 1022:22 cern-root

Die so erzeugte Instanz kann nun auch gestartet werden:

docker start run_root

Da der SSH-Dienst, wie im Dockerfile spezifiziert, gestartet wird und der SSH Port 22 auf den lokalen Port 1022 gemappt ist, kann nun auch ein Login mittels SSH erfolgen. Zum Testen kann dies von der Kommandozeile erfolgen – das Passwort ist netbeans.

ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 1022 user@localhost

Soll der Container gestoppt werden, wird das Kommando stop verwendet:

docker stop run_root

Ein gestoppter Container kann zusammen mit dem persistenten Volume mittels rm -v gelöscht werden:

docker rm -v run_root

Soll auch das Image gelöscht werden, kann dies anschließend erfolgen:

docker rmi cern-root

Nach diesen Vorarbeiten kann nun die NetBeans IDE konfiguriert werden, um per SSH die laufende Containerinstanz zu nutzen.

Integration

Der Container wird – falls nicht bereits durch die vorherigen Schritte geschehen – erzeugt und gestartet. Er steht anschließend zur Nutzung per SSH bereit.

docker create --name run_root -v `pwd`/sample:/home/user/data/sample -p 1022:22 cern-root docker start run_root

Verwendung mit NetBeans

Um die Docker-Umgebung mit NetBeans zu nutzen, muss zunächst ein neuer C++ Build Host konfiguriert werden, der es NetBeans erlaubt, auf die ROOT-Umgebung in Docker zuzugreifen. Die Build Hosts befinden sich bei NetBeans unter dem Reiter „Services“. Hier kann durch Rechtsklick auf den Eintrag „C/C++ Build Hosts“ ein neuer hinzugefügt werden:

Abb 2. Anlegen eines neuen C/C++ Build Hosts in den Netbeans-"Services"

Abb 2. Anlegen eines neuen C/C++ Build Hosts in den Netbeans-„Services“

Die Einrichtung dieses neuen Hosts benötigt einen Hostnamen und den Port, unter dem der SSH-Dienst erreichbar ist. NetBeans sucht automatisch nach erreichbaren Hosts, berücksichtigt jedoch keine vom Standardport 22 abweichenden Ports. Daher muss der Docker Host als localhost mit dem weiter oben beschriebenen Port 1022 konfiguriert werden.

Abb 3: Einstellen von Name und Port des Hosts

Abb 3: Einstellen von Name und Port des Hosts

Anschließend müssen die Zugangsdaten konfiguriert werden, hier kann am einfachsten Username und Passwort verwendet werden. In diesem Fall wird als Login, wie vorher im Docker Container konfiguriert, „user“ benutzt.

Abb. 4: Konfiguration des Remotezugriffs

Abb. 4: Konfiguration des Remotezugriffs

Nachdem die Konfiguration abgeschlossen ist, versucht NetBeans, sich mit dem neuen Remote Host zu verbinden und fragt das zugehörige Passwort ab: Wie auch der Username ist dies im Dockerfile konfiguriert worden – im Beispielprojekt ’netbeans‘.

 

Abb. 5: Eingabe des Passworts

Abb. 5: Eingabe des Passworts

Nach erfolgreichem Verbindungsaufbau kann die Entwicklung von neuen ROOT/C++-Projekten beginnen.

ROOT-Projekt anlegen

Zum Anlegen eines C++-Projektes wird der für NetBeans gewohnte Dialog verwendet. In diesem Falle wird ein Projekt angelegt, für das bereits Sourcecode existiert.

Abb. 6: Anlegen eines C++-Projektes

Abb. 6: Anlegen eines C++-Projektes

Im nächsten Schritt wird das Verzeichnis ausgewählt, in dem sich der bereits vorhandene Quellcode befindet, und der zu nutzende Build Host konfiguriert. Natürlich kann das Projekt auch später noch zur Nutzung anderer Build Hosts konfiguriert werden. Das ist wichtig, wenn mehrere Docker-Instanzen verwendet werden, um es unterschiedlichen Umgebungen bereitzustellen.

Abb. 7: Auswahl von Build Host und Quellcode-Verzeichnis

Abb. 7: Auswahl von Build Host und Quellcode-Verzeichnis

Um sicherzustellen, dass NetBeans auf die richtigen Dateien auf dem Remote Host zugreift, muss beim ersten Ausführen des Programms die lokale Verzeichnisstruktur mit derjenigen auf dem Remote Host abgeglichen werden. Dies entspricht in diesem Fall dem Volume Mapping von Docker.

Abb. 8: Lokale und entferne Verzeichnisse zuordnen

Abb. 8: Lokale und entferne Verzeichnisse zuordnen

Als lokaler Pfad wird daher das Quellverzeichnis des Projektes gewählt – hier ist es sample. Der Remote-Pfad lautet: /home/user/data/sample.

Abb. 9: Auswahl des Remote-Verzeichnisses

Abb. 9: Auswahl des Remote-Verzeichnisses

Damit ist die Einrichtung des Projektes abgeschlossen und die Entwicklung kann beginnen.

ROOT-Projekt ausführen lassen

In unserem Beispiel wird ein einfaches ROOT-Programm geschrieben, das bei jedem Aufruf den Graphen sin(x)/x in einem zufälligen Intervall zeichnet und dann in der Datei ex.pdf ablegt. Beim ersten Ausführen des ROOT-Programmes fragt NetBeans, welches Executable ausgeführt werden soll und wo dieses liegt, macht dabei aber gleich auch einen eigenen Vorschlag, der dann in der Regel verwendet werden kann.

Abb. 10: Auswahl des Executables

Abb. 10: Auswahl des Executables

Wird die generierte pdf-Datei nun zum Beispiel von Evince angezeigt, bekommt Evince nach jedem Programmaufruf mit, dass sich der Inhalt ändert, und aktualisiert die Anzeige. Dies liegt daran, dass die Ergebnisse durch das von Docker eingebundene Volume direkt verfügbar sind und auch die Benachrichtigung bei Änderungen funktioniert, so dass Evince darauf reagieren kann.

Abb. 11: Quellcode mit Ergebnisgraph und Kommandozeilenoutput von NetBeans

Abb. 11: Quellcode mit Ergebnisgraph und Kommandozeilenoutput von NetBeans

Auch Debugging ist mit NetBeans auf diese Weise möglich. Dies beinhaltet unter anderem das Setzen von Breakpoints, die schrittweise Ausführung sowie die Anzeige von Variableninhalten. Es kann also genauso komfortabel gearbeitet werden, wie ohne eine Docker-Zwischenschicht.

Abb. 12: Remote-Debugging in NetBeans mit ROOT und C++

Abb. 12: Remote-Debugging in NetBeans mit ROOT und C++

Ausblick

Mit verschiedenen Versionen zu arbeiten ist mit Docker einfach – die Version wird im Dockerfile angepasst und für jede ROOT-Version ein Image erstellt. Damit lassen sich dann analog die Containerinstanzen erzeugen. Für andere Linux-Distributionen kann genauso verfahren werden.

Was NetBeans theoretisch auch unterstützt, jedoch zumindest mit der 8.1rc2-Version nicht funktioniert hat, ist die Nutzung von X11 Forwarding. Damit wäre auch die Entwicklung von grafischen Anwendungen in einem Docker Container möglich, falls Linux als System verwendet wird.

Dieser Artikel hat die Möglichkeiten von Docker zusammen mit NetBeans für die Entwicklung von C++-Anwendungen aufgezeigt und soll neugierig auf eigene Experimente machen. Docker hat neben Virtualisierung einen festen Platz im Werkzeugkasten eines jeden Entwicklers verdient. Wer gerne mehr zu Docker erfahren möchte, sollte sich unbedingt die Zeit nehmen und die Docker-Dokumentation studieren oder eine spezielle Docker-Schulung in Betracht ziehen.

Und auch bei NetBeans ist abzusehen, dass sich hier noch etwas tut – zumindest gibt es im NetBeans Wiki schon erste Ideen für direkten Docker Support in der IDE: http://wiki.netbeans.org/Docker

Schön wäre hier, wenn für das hier beschriebene Szenario statt SSH ein docker exec bzw. ein beliebiges Kommando genutzt werden könnte. Damit würde das Setup weniger komplex und es bestünde keine Abhängigkeit mehr auf das eingebaute SSH in NetBeans.

Den Quellcode für das Beispielprojekt im Artikel finden Sie auf GitHub unter: https://github.com/trion-development/netbeans-docker-root-sample

Aufmacherbild: Silhouette cargo container ship von Shutterstock / Urheberrecht: Serz_72

Geschrieben von
Thomas Kruse
Thomas Kruse
Thomas Kruse hat Informatik studiert und ist als Architekt, Berater und Coach für die trion development GmbH tätig. In seiner Freizeit engagiert er sich im OpenSource Umfeld und leitet die Java Usergroup in Münster.
Karsten Sitterberg
Karsten Sitterberg
Karsten Sitterberg ist als freiberuflicher Entwickler, Trainer und Berater für Java und Webtechnologien tätig. Karsten ist Physiker (MSc) und Oracle-zertifizierter Java Developer. Seit 2012 arbeitet er mit trion zusammen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: