15.
April 2020
JUG Switzerland
Java
&
Spring Boot
im
Container
MICHAEL VITZ
Senior Consultant
INNOQ Deutschland GmbH
@michaelvitz
Example Application
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplicatio
<parent> import org.springframework.web.bind.annotation.GetMapping;
<groupId>org.springframework.boot</groupId> import org.springframework.web.bind.annotation.RestController;
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.M4</version> @SpringBootApplication
<relativePath/> @RestController
</parent> public class Application {
<groupId>de.mvitz</groupId> public static void main(String[] args) {
<artifactId>spring-container</artifactId> SpringApplication.run(Application.class, args);
<version>1.0.0-SNAPSHOT</version> }
<properties> @GetMapping
<java.version>11</java.version> public String index() {
</properties> return "Hello JUG Switzerland!";
}
<dependencies> }
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
“Fat”-JAR Container
FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim
COPY ./target/spring-container-*.jar /spring-container.jar
CMD ["java", "-jar", "/spring-container.jar"]
EXPOSE 8080
FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim
RUN mkdir -p /app
WORKDIR /app
COPY ./target/spring-container-*.jar /app/spring-container.jar
CMD ["java", "-jar", “/app/spring-container.jar"]
EXPOSE 8080
FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim
RUN mkdir -p /app && \
chown -R daemon /app
USER daemon
WORKDIR /app
COPY ./target/spring-container-*.jar /app/spring-container.jar
CMD ["java", "-jar", “/app/spring-container.jar"]
EXPOSE 8080
Docker
docker build -t spring-container .
Docker
• + No changes in POM required
• + Straightforward Dockerfile
• + No additional abstraction
• - Separate step in build process
• - “Fat”-JAR
Fabric8 Maven-Docker-Plugin
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.33.0</version>
</plugin>
./mvnw verify docker:build
https://github.com/fabric8io/docker-maven-plugin
Fabric8 Maven-Docker-Plugin
• + Straightforward Dockerfile
• + No separate step in build process
• +- Only small abstraction
• +- No plugin configuration in POM required
• - “Fat”-JAR
Fabric8 Maven-Docker-Plugin
<configuration>
<images>
<image>
<name>spring-container-fabric8</name>
<build>
<from>adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim</from>
<runCmds>
<run>mkdir -p /app && chown -R daemon /app</run>
</runCmds>
<user>daemon</user>
<workdir>/app</workdir>
<assembly>
<targetDir>/app</targetDir>
<descriptorRef>artifact</descriptorRef>
</assembly>
<cmd>
<exec>
<arg>java</arg>
<arg>-jar</arg>
<arg>/app/${project.artifactId}-${project.version}.jar</arg>
</exec>
</cmd>
<ports>
<port>8080</port>
</ports>
</build>
Fabric8 Maven-Docker-Plugin
• + Additional capabilities (start/stop/watch/…)
• +- Some more abstraction
• - Dockerfile in XML
• - “Fat”-JAR
“Fat”-JAR?
$ du -h target/spring-container-1.0.0-SNAPSHOT.jar
16M target/spring-container-1.0.0-SNAPSHOT.jar
Sending build context to Docker daemon 19.96MB
Step 1/7 : FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim
---> 6e24b2c53f87
Step 2/7 : RUN mkdir -p /app && chown -R daemon /app
---> Using cache
---> 04631ac529dd
Step 3/7 : USER daemon
---> Using cache
---> fe0fe11bb555
Step 4/7 : WORKDIR /app
---> Using cache
---> 4a95f3163d2d
Step 5/7 : COPY ./target/spring-container-*.jar /app/spring-container.jar
---> 7763afdd5b50
Step 6/7 : CMD ["java", "-jar", "/app/spring-container.jar"]
---> Running in a327c50e7a72
Removing intermediate container a327c50e7a72
---> 311762838046
Step 7/7 : EXPOSE 8080
---> Running in 834b132542c6
Removing intermediate container 834b132542c6
---> 1622208fcb32
Docker Layers
• Only contain diff to previous layer Read/Write
• Read only (except Read/Write layer at runtime) EXPOSE 8080
• Rule of thumb: Every instruction -> Layer
• Can be cached and reused by builds CMD [“java”, …
• Size does matter during transfer COPY …
FROM …
maven-dependency-plugin
maven-dependency-plugin
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>copy-dependencies</goal> FROM adoptopenjdk/openjdk11:jdk-11.0.6_10-alpine-slim
</goals>
<configuration> RUN mkdir -p /app/lib && \
<includeScope>runtime</includeScope> chown -R daemon /app
</configuration>
</execution> USER daemon
</executions> WORKDIR /app
</plugin>
COPY ./target/dependency/ /app/lib
COPY ./target/spring-container-*.jar /app/spring-container.ja
CMD [ "java", \
"-classpath", \
"/app/spring-container.jar:/app/lib/*", \
"de.mvitz.spring.container.Application" ]
EXPOSE 8080
https://maven.apache.org/plugins/maven-dependency-plugin/
maven-dependency-plugin
• + Works with every Java application
• + Only downloads dependencies
• + Dockerfile stays clean
• - Not obvious that plugin is required for Image building
jib
jib
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.1.0</version>
<configuration>
<to>
<image>spring-container-jib</image>
</to>
</configuration>
</plugin>
./mvnw verify jib:dockerBuild
https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin
jib
• + Works with every Java application
• + Distroless Image
• +- No own Dockerfile
• +- Can be used without Docker daemon
• - Level of abstraction
Spring Boot
Spring Boot Extract
jar xf target/spring-container-1.0.0-SNAPSHOT.jar
Spring Boot Extract
• + Works with current stable Spring Boot version
• + Straightforward script
• +- Separate build step
• - Spring Boot dependent
• - Spring Loader is included by default
Spring Boot Layered JAR
java \
-Djarmode=layertools \
-jar target/spring-container.jar \
extract
Spring Boot Layered JAR
• + Layers are customisable (e.g. layer for company wide dependencies)
• + Straightforward script
• +- Separate build step
• +- Not yet released (Spring Boot 2.3 Feature)
• - Spring Boot dependent
Spring Boot Build Packs
./mvnw spring-boot:build-image
Spring Boot Build Packs
• + No need for configuration
• +- Not yet released (Spring Boot 2.3 Feature)
• - Multiple abstraction layers
• - Loss of control
Zombies
#!/usr/bin/env sh
set -euo pipefail
IFS=$'\n\t'
java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseJVMCICompiler \
-jar /app/spring-container.jar
CMD ["/app/run.sh"]
EXPOSE 8080
https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
#!/usr/bin/env sh
set -euo pipefail
IFS=$'\n\t'
exec java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseJVMCICompiler \
-jar /app/spring-container.jar
CMD ["/app/run.sh"]
EXPOSE 8080
https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
Additional thoughts
Additional thoughts
• Consider Docker Multi-Stage Builds
• Check and configure JVM Memory Management
• Look at other Solutions
• Containers are not a silver bullet
• https://docs.docker.com/develop/develop-images/dockerfile_best-
practices/
Thanks! Questions? www.innoq.com
Michael Vitz
michael.vitz@innoq.com
+49 151 19116015
@michaelvitz
https://github.com/mvitz/javaspektrum-spring-container
innoQ Deutschland GmbH innoQ Schweiz GmbH
Krischerstr. 100 Ohlauer Str. 43 Ludwigstr. 180E Kreuzstr. 16 Hermannstrasse 13 Gewerbestr. 11
40789 Monheim am Rhein 10999 Berlin 63067 Offenbach 80331 München 20095 Hamburg CH-6330 Cham
Germany Germany Germany Germany Germany Switzerland
+49 2173 3366-0 +49 2173 3366-0 +49 2173 3366-0 +49 2173 3366-0 +49 2173 3366-0 +41 41 743 0116