Neuigkeiten von trion.
Immer gut informiert.

Springboot Java Docker Images mit jib

Jib - Containerize your Java application

Mit Docker haben sich Container als Auslieferungsformat (nicht nur) für Microservices zum Status Quo für Cloudanwendungen etabliert. Zur Erstellung der Container-Images war lange Zeit Zugriff auf einen Docker-Daemon erforderlich. Besonders in Umgebungen wie Kubernetes-Clustern oder Public-Clouds mit gemeinsam genutzter Infrastruktur soll auf privilegierte Container verzichtet werden. Mit dem typischen Ansatz von Docker-in-Docker oder Docker-Outside-Docker erhält ein so ausgeführter Build nämlich relativ viele Rechte. Dank der OCI Spezifikation für Image-Formate ist Docker mit einem Dockerfile jedoch nur noch eine Möglichkeit, zum auslieferbaren Image zu gelangen.

Ein weiterer Aspekt sind die oft schwer umzusetzenden Optimierungsmöglichkeiten für Docker Images: Um vom Layer-Caching optimal zu profitieren und damit sowohl I/O-Operationen als auch Speicherplatz zu sparen, sollten die Daten, die sich weniger häufig ändern, in einem separaten Layer positioniert werden, als Daten, die sich regelmäßig ändern. Bei einer Spring Boot Anwendung könnte man zum Beispiel zwischen allen Abhängigkeiten und den eigentlichen, zu der Anwendung selbst gehörenden Class-Dateien differenzieren: Die Abhängigkeiten werden sich seltener ändern, als das Programm, das bei jedem Bugfix und jeder Erweiterung Änderungen erfährt. Nur dieser geänderte Image-Layer muss dann verteilt werden.

Für beide Probleme soll das Google Projekt "Jib" eine Lösung liefert. Am Beispiel einer Spring Boot Anwendung und Maven wird der Einsatz von Google Jib demonstriert.

Die Beispielanwendung kann z.B. von hier bezogen werden: https://github.com/trion-development/spring-boot-rest-sample

Spring Boot Demo Anwendung

Die Spring Boot Demoanwendung wird zunächst in ein lokales Verzeichnis per git clone ausgecheckt. Anschließend kann der Maven basierte Build um das Jib-Plugin erweitert werden, um Docker-Images aus der Spring Boot Anwendung zu erzeugen.

git-clone der Spring Boot Anwendung
$ git clone https://github.com/trion-development/spring-boot-rest-sample
Cloning into 'spring-boot-rest-sample'...
remote: Enumerating objects: 143, done.
remote: Counting objects: 100% (143/143), done.
remote: Compressing objects: 100% (59/59), done.
remote: Total 207 (delta 89), reused 133 (delta 83), pack-reused 64
Receiving objects: 100% (207/207), 70.24 KiB | 0 bytes/s, done.
Resolving deltas: 100% (106/106), done.
Checking connectivity... done.

Die Spring Boot Anwendung kann ganz normal mit Maven gebaut werden. Damit auch ein Docker Image erzeugt werden kann, wird im nächsten Schritt das jib-maven-plugin in der pom.xml ergänzt.

Jib Maven Build

Maven lässt sich durch Plugins erweitern und damit um zahlreiche Features erweitern. Es gibt bereits andere Maven-Plugins, um die Buildergebnisse zu modifizieren, Jib wird im Prinzip genauso in den Build aufgenommen, wie andere Plugins.

Die Konfiguration erfolgt ebenfalls an der Position, an der das Plugin definiert wird. Dort lassen sich Startparameter für die JVM, das zu verwendende Docker-Basisimage und die Zielregistry angeben.

Konfiguration von Jib in der Maven pom.xml
...
<build>
  <plugins>
...
    <plugin>
      <groupId>com.google.cloud.tools</groupId>
      <artifactId>jib-maven-plugin</artifactId>
      <version>0.9.11</version>
      <configuration>
        <from>
          <image>openjdk:8-alpine</image>
<!--          <credHelper></credHelper> -->
        </from>
        <to>
          <image>trion/${project.artifactId}:${project.version}</image>
<!--          <credHelper></credHelper> -->
        </to>
        <container>
          <jvmFlags>
            <jvmFlag>-Xms256m</jvmFlag>
            <jvmFlag>-Xmx256m</jvmFlag>
          </jvmFlags>
        </container>
      </configuration>
  </plugin>
...

Um sicher zu gehen, dass der Maven-Build erfolgreich läuft, kann das Goal mvn package aufgerufen werden. Damit übersetzt Maven die Spring Boot Anwendung und erstellt ein ausführbares JAR.

Maven Build der Spring Boot Anwendung
$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-rest-sample 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ spring-boot-rest-sample ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ spring-boot-rest-sample ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ spring-boot-rest-sample ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory spring-boot-rest-sample/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ spring-boot-rest-sample ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.21.0:test (default-test) @ spring-boot-rest-sample ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running hello.ApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ spring-boot-rest-sample ---
[INFO] Building jar: spring-boot-rest-sample/target/spring-boot-rest-sample-1.0.0.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.4.RELEASE:repackage (default) @ spring-boot-rest-sample ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.166 s
[INFO] Final Memory: 32M/756M
[INFO] ------------------------------------------------------------------------

Funktioniert der Build, kann nun der nächste Schritt in Richtung Docker-Image mit dem Jib-Plugin getätigt werden. Jib verwendet einen Buildcontext, genauso wie ein reguläres Docker-Image mit Buildcontext und Dockerfile erstellt wird. Der verwendete Kontext und das zugehörige Dockerfile, das Jib verwenden würde, kann zu Diagnosezwecken inspiziert werden. Auch zum besseren Verständnis, wie Jib funktioniert, lohnt sich ein Blick auf den exportierten Docker-Buildcontext.

Export des Docker Build Context von Jib
$ mvn jib:exportDockerContext
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-rest-sample 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- jib-maven-plugin:0.9.11:exportDockerContext (default-cli) @ spring-boot-rest-sample ---
[INFO] Created Docker context at spring-boot-rest-sample/target/jib-docker-context
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.319 s
[INFO] Final Memory: 27M/778M
[INFO] ------------------------------------------------------------------------
$ ls target/jib-docker-context/
classes  Dockerfile  libs  resources

Jib verwendet unter der Haube ebenfalls ein Dockerfile. Ein Blick in das Dockerfile zeigt, wie die Java-Anwendung in unterschiedliche Layer aufgeteilt wird und wie die Anwendung gestartet wird.

Werden separate Layer für Libraries, Ressourcen und die Klassen der Anwendung verwendet, unterstützt dies die optimale Nutzung des Layer-Caching bei Docker. Unter der Annahme, dass die Abhängigkeiten in Form von Libraries sich am seltensten ändern und die Programmklassen am häufigsten, werden die Abhängigkeiten entsprechend in den obersten Layer und die Programmklassen im letzten Layern des Image positioniert.

Analyse des von Jib verwendeten Dockerfile
$ cat target/jib-docker-context/Dockerfile
FROM openjdk:8-alpine

COPY libs /app/libs/
COPY resources /app/resources/
COPY classes /app/classes/

ENTRYPOINT ["java","-Xms256m","-Xmx256m","-cp","/app/resources:/app/classes:/app/libs/*","hello.Application"]
CMD []

Jib übernimmt den eigentlichen Build des Docker-Images, dazu wird kein Docker-Daemon benötigt. Damit eignet sich Jib für die Erstellung von Docker-Images in einer CI-Umgebung oder auch Kubernetes, wenn kein Zugriff auf einen Docker-Daemon erwünscht ist.

Da ein erstelltes Image typischwerweise über eine Docker Image-Registry verteilt wird, ist dies auch in Jib als automatischer Schritt konfiguriert. Wird jib:build als Maven Goal aufgerufen, so wird zunächst ein Container-Image gebaut und anschließend in die konfigurierte Registry gepusht.
Alternativ ist es möglich das Image lediglich an einen Docker-Daemon statt einer zentralen Registry zur Verfügung zu stellen.

Spring Boot Container Image im Docker Daemon

Um das mit Jib gebaute Image nicht in eine Container-Registry zu pushen, sondern nur einem Docker-Daemon bereitzustellen, wird ein alternatives Maven Goal verwendet: jib:dockerBuild.

Build von Image mit Jib für Docker Daemon
$ mvn package jib:dockerBuild
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-rest-sample 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- jib-maven-plugin:0.9.11:dockerBuild (default-cli) @ spring-boot-rest-sample ---
[INFO]
[INFO] Containerizing application to Docker daemon as trion/spring-boot-rest-sample:1.0.0...
[INFO]
[INFO] Getting base image openjdk:8-alpine...
[INFO] Building dependencies layer...
[INFO] Building resources layer...
[INFO] Building classes layer...
[INFO] The base image requires auth. Trying again for openjdk:8-alpine...
[INFO] Retrieving registry credentials for registry.hub.docker.com...
[INFO] Finalizing...
[INFO] Loading to Docker daemon...
[INFO]
[INFO] Container entrypoint set to [java, -Xms256m, -Xmx256m, -cp, /app/resources:/app/classes:/app/libs/*, hello.Application]
[INFO]
[INFO] Built image to Docker daemon as trion/spring-boot-rest-sample:1.0.0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.284 s
[INFO] Final Memory: 35M/783M
[INFO] ------------------------------------------------------------------------

Ein evtl. gesetztes Environment für einen remote Docker-Daemon wird genutzt, sodass mit Jib das Image auch in einen anderen Daemon gepusht werden kann, z.B. an einen entfernten Docker-Daemon oder Minikube.

Mit docker images läßt sich das erstellte Image anzeigen. Jib setzt das Erzeugungsdatum sowohl bei dem Image, als auch bei allen im Image befindlichen Dateien auf den Unix-Timestamp "0", so dass der 1.1.1970 als Erstellzeitpunkt angezeigt wird.

Erstelltes Docker-Image im Docker-Daemon
$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
trion/spring-boot-rest-sample   1.0.0               6fb8cd569ba4        48 years ago        135MB

Das so erzeugte Image ist lokal mit Docker direkt ausführbar.

Start der Spring Boot-Anwendung mit Docker
$ docker run --rm -p 8080:8080 trion/spring-boot-rest-sample:1.0.0
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

2018-10-03 07:54:33.291  INFO 1 --- [           main] hello.Application                        : Starting Application on cdd282da0d8a with PID 1 (/app/classes started by root in /)
...

Docker-Registry Credentials

Falls Credentials zur Authentifizierung an einer Registry benötigt werden, um ein Basis-Image zu beziehen, oder das Ergebnis-Image zu pushen, können diese auf zwei Wegen bereitgestellt werden. Zum einen können die Credentials in der Maven settings.xml hinterlegt werden.

Alternativ können Docker-Credentials-Helper oder Umgebungsvariablen genutzt werden, um die Zugangsdaten zur Docker-Registry bereitzustellen.

Zugangsdaten für Container-Registry in Maven settings.xml
<settings>
...
  <servers>
...
    <server>
      <id>registry.hub.docker.com</id>
      <username>xxxx</username>
      <password>xxxx</password>
    </server>
...
  </servers>
...
</settings>

Automatischer Image push

Sind alle benötigten Zugangsdaten vorhanden, so kann ein Jib Build mit automatischem Push des erzeugten Container-Image verwendet werden.

Jib Build mit automatischem Push des erstellen Container-Image in eine Docker-Registry
$ mvn jib:build
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-rest-sample 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- jib-maven-plugin:0.9.11:build (default-cli) @ spring-boot-rest-sample ---
[INFO]
[INFO] Containerizing application to localhost:5000/trion/spring-boot-rest-sample:1.0.0...
[INFO]
[INFO] Retrieving registry credentials for localhost:5000...
[INFO] Getting base image openjdk:8-alpine...
[INFO] Building dependencies layer...
[INFO] Building resources layer...
[INFO] Building classes layer...
[INFO] The base image requires auth. Trying again for openjdk:8-alpine...
[INFO] Retrieving registry credentials for registry.hub.docker.com...
[INFO] Finalizing...
[INFO]
[INFO] Container entrypoint set to [java, -Xms256m, -Xmx256m, -cp, /app/resources:/app/classes:/app/libs/*, hello.Application]
[INFO]
[INFO] Built and pushed image as trion/spring-boot-rest-sample:1.0.0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.207 s
[INFO] Final Memory: 33M/565M
[INFO] ------------------------------------------------------------------------

Fazit

Jib ist aktuell noch ein recht junges, aber sehr vielversprechendes Werkzeug. Für den Build wird kein Docker-Daemon benötigt, sodass dem Einsatz in einer CI-Umgebung nichts im Wege steht.

Jib macht Annahmen darüber, wie Java Anwendungen aufgebaut sind, und kann damit optimierte Container-Images erzeugen. Wie in einem so frühen Projektstadium - die Versionsnummer ist nicht ohne Grund noch unter 1.0 - nicht anders zu erwarten, werden viele Randfälle gerade noch entdeckt und entsprechende Fehlertickets und Verbesserungsvorschläge bei GitHub eingestellt. Das sollte jedoch nicht davon abhalten, sich mit dem Projekt und den sich damit eröffnenden Möglichkeiten zu beschäftigen.




Zu den Themen Kubernetes, Docker und Cloud Architektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.