Java Anwendungen im Docker Container

Jede Java Anwendungen bauen, ausliefern und überall ausführen - klingt super und geht mit Docker! Ich zeige euch, wie ihr möglichst einfach und schnell in die Docker Welt einsteigt. Wir bauen ein Docker Image mit einer Java Anwendung, starten den eigenen Container lokal und testen die Anwendung im Container.

Installationen und Vorbereitung

Um die Voraussetzungen für den Einstieg in die Docker Welt zu schaffen, müssen wir folgendes tun:
  1. Docker für Windows installieren, siehe: 
    https://docs.docker.com/docker-for-windows/install/
    Wenn ihr Linux oder MacOS nutzt, findet ihr auf der Docker Webseite auch passende Anleitungen.
  2. Eine Java-Anwendung schreiben, die dann in den Container kommt 😄
    Falls ihr gerade keine zur Hand habt, könnt ihr diese verwenden:
    https://github.com/elmar-brauch/thymeleaf 
    Im Prinzip funktioniert es mit jeder beliebigen Java-Anwendung.
    Hier in der Demo nutze ich Vorteile von Spring Boot, um eine möglichst einfache Demonstration machen zu können.


Docker Image bauen

Nach der Installation und Vorbereitung, ist das Bauen eines Docker Images der nächste Schritt.
Unser Docker Image setzt auf ein bestehendes OpenJDK Docker Image auf und fügt unsere Java-Anwendung in Form einer jar-Datei hinzu.

jar-Datei mit Maven bauen

Die jar-Datei können wir z.B. mit Maven bauen. Wenn ihr mein Demo-Code verwendet (https://github.com/elmar-brauch/thymeleaf), der auf einem normalen Spring Boot Projekt mit Maven basiert, erstellen wir wie folgt die jar-Datei:
Projekt in Eclipse markieren => im Eclipse Menu "Run" auswählen => Run As => Maven build

Alternativ kann man auch in der Shell direkt das Maven-Kommando "mvn package" ausführen.

In beiden Alternativen finden wir anschließend im target Verzeichnis eine jar-Datei. Mit meinem Demo-Code heißt sie "thymeleaf-0.0.1-SNAPSHOT.jar".

Es gibt noch diverse andere Wege, um jar-Dateien zu bauen, diese stelle ich hier aber nicht vor. 

Dockerfile schreiben

Zum Erstellen eines Docker Images benötigen wir ein Dockerfile, welches das Image genau beschreibt. Vereinfacht gesagt befinden sich im Dockerfile diverse Angaben zum Anlegen von Dateien, Installieren und Starten von Software usw. Damit definiert das Dockerfile wie genau ein Container aufgebaut ist, der anhand dieses Images erstellt wird.
Eine ausführlichere Definition des Docker Images und weiterer Docker Konzepte findet ihr z.B. hier:

Das Dockerfile erstellen wir direkt im Basis-Verzeichnisses unseres Projektes, so dass es diesen relativen Pfad zur jar-Datei hat: ./target/thymeleaf-0.0.1-SNAPSHOT.jar
Wenn sich das Dockerfile in einem anderen Verzeichnis befindet, kann es komplizierter werden.

Verzeichnisstruktur im Demo-Projekt Dockerfile und jar-Datei gelb markiert

Die Datei "Dockerfile" hat keine Endung und auch im Standard genau diesen Namen. Ihr Inhalt für mein Demo-Projekt sieht so aus:
    
    FROM openjdk:11-jre-slim
    ADD ./target/thymeleaf-0.0.1-SNAPSHOT.jar /app.jar

    EXPOSE 8080
    CMD java -jar app.jar

  • Mit der Installation von Java bzw. dem Java Runtime Environment möchte ich mich nicht befassen, daher setze ich auf ein im Docker-Hub bereits existierendes Image auf:
    "FROM openjdk:11-jre-slim"
    Unser Docker Image baut sich also in Form von weiteren Schichten (Layers) auf dem openjdk Image auf.
    Beim Bauen des Docker Images schaut Docker in den bekannten Docker Registries nach, ob es das Image "openjdk:11-jre-slim" findet. In der Standard-Installation nutzen wir den Docker Hub als Registry und findet dort auch das offizielle OpenJDK Image:
    https://hub.docker.com/_/openjdk
    "11-jre-slim" ist das Tag, das die genaue Version des Images festlegt, welches wir als Basis für unser Image verwenden.
    Weitere Details zu Docker Images, Registry, Container etc. stelle ich hier nicht vor, schaut euch dazu die Docker Dokumentation an:
    https://docs.docker.com/
  • ADD ./target/thymeleaf-0.0.1-SNAPSHOT.jar /app.jar
    kopiert unsere jar-Datei (thymeleaf-0.0.1-SNAPSHOT.jar) in das Root-Verzeichnis des Docker Images und ändert den Namen dort in app.jar
  • EXPOSE 8080 sorgt dafür, dass unser Docker Container auf Port 8080 offen ist. Unsere Spring Boot Anwendung lauscht im Standard auf Port 8080. Damit sie noch erreichbar ist, wenn sie im Docker Container läuft, muss der entsprechende Port im Container bzw. im Image exponiert werden.
  • CMD java -jar app.jar ist die Shell-Form der Docker CMD Anweisung. Sie legt den Befehl fest, der beim Start des Docker Containers ausgeführt wird. java -jar app.jar ist der Befehl zum Starten einer jar-Datei. Somit wird dann unsere Spring Boot Anwendung im Container gestartet.
Wie ich schon am Anfang sagte, können wir auf diese Weise jede beliebige Java Anwendung im Container starten. Je nachdem wie unsere Java-Anwendung genau aussieht, muss das Dockerfile entsprechend angepasst werden.

Docker Image bauen

Das Bauen des Docker Images ist in der Shell ein einzelner Befehl und sieht so aus:

PS C:\git\Java\thymeleaf> docker build -t demo .
Sending build context to Docker daemon  18.27MB
Step 1/4 : FROM openjdk:11-jre-slim
 ---> e93b583389ea
Step 2/4 : ADD ./target/thymeleaf-0.0.1-SNAPSHOT.jar /app.jar
 ---> 833c4db2e93f
Step 3/4 : EXPOSE 8080
 ---> Running in f5dd78878eab
Removing intermediate container f5dd78878eab
 ---> da22706dc55b
Step 4/4 : CMD java -jar app.jar
 ---> Running in 9c05ab388bae
Removing intermediate container 9c05ab388bae
 ---> 7e3f0993eba7
Successfully built 7e3f0993eba7
Successfully tagged demo:latest

In der ersten Zeile steht das Kommando zum Bauen:
docker build -t demo .
Dabei ist zu beachten, dass ich den Befehl im selben Verzeichnis ausgeführt habe, in dem sich auch mein Dockerfile befindet - daher endet mein Befehl mit ".", welcher auf das aktuelle Verzeichnis verweist.
"docker build" ist der Befehl zum Bauen des Images mit "-t demo" habe ich dem Image noch den Namen "demo" gegeben.

Danach seht ihr in der Konsole die Log-Ausgaben von Docker, welche die einzelnen Schritte beim Bauen des Images zeigen und am Ende die Erfolgsmeldung liefern:
Successfully tagged demo:latest
Da wir kein Tag definiert haben, wird automatisch das Tag latest gesetzt. 

Docker Container starten & testen

Der letzte Schritt ist das Starten eines Docker Containers basierend auf dem zuvor erstellten Image. Dazu den folgenden Befehl in der Shell ausführen:

PS C:\git\Java\thymeleaf> docker run -p 80:8080 demo:latest
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.4.RELEASE)
...

  • "docker run" ist der Docker Befehl zum Starten eines Containers. Die danach folgenden Parameter beschreiben wie und was genau gestartet wird.
  • "demo:latest" definiert welches Image genau in welcher Version bzw. mit welchem Tag als Container ausgeführt wird. Die Image und Tag Angabe im docker run Befehl muss dem zuvor gebauten Image entsprechen. Das Tag latest könnten wir hier auch weglassen, da es der Standard ist, wenn kein Tag angegeben wird.
  • "-p 80:8080" publiziert den Docker Container Port 8080 an den Host Port 80. Dadurch werden Request an Port 80 des Hosts (hier localhost) an Port 8080 des Containers weitergeleitet. Port 8080 ist der Standard Port vom embedded Tomcat in Spring Boot. Mein Demo-Projekt hat den Port nicht verändert.
  • Wenn man den Docker Container so startet, werden Logs der Java Anwendung direkt in die Shell geschrieben. Daher sehen wir das Spring Logo in ASCII-Kunst und weitere Log-Meldungen, die den Start unserer Java-Anwendung im Docker Container protokollieren.
    Da ihr euch nun sicher brennend für ASCII-Kunst interessiert, möchte ich euch diesen Link nicht vorenthalten: http://www.asciikunst.com/ 😂
    Es ist noch wichtig zu wissen, dass die Java-Anwendung in die Console loggen muss, damit die Log-Meldungen im STDOUT vom Docker Container und dann in der Shell ankommen. Hier gibt es für andere Szenarien sinnvollere Konfigurationen - zum Kennenlernen von Docker finde ich diese Variante aber gut, weil ihr direkt seht, ob in eurer Java-Anwendung im Container alles ok ist.
Zum Testen meiner Demo-Anwendung im Docker Container öffnet ihr in eurem Browser nun einfach folgenden Link: http://localhost

Ausblick

Ich habe euch hier ein Schnelleinstieg in Docker gezeigt.
Um Zeit zu sparen, habe ich auf eine vorhandene Demo-Anwendung aufgesetzt, die ihr zusammen mit dem gezeigten Dockerfile hier in GitHub findet:

In einem künftigen Blog-Post werde ich euch das Eclipse Plugin "Eclipse Docker Tooling" vorstellen. Falls die Shell-Kommandos für euch ungewohnt sind, ist das eine tolle Eclipse-Erweiterung, die uns das Schreiben komplexer Shell-Kommandos abnimmt und uns einen schönen Überblick über Docker Images und Container gibt. 

Ich hatte dieses Buch zu Docker gelesen.

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection