JIB – Docker Simple, Fast, Reproducible

It’s 8am and you are ready to put your Spring Boot app into a Docker container. First, you need to install Docker. Depending on your OS, it might be easier or harder. Then, you can try to write your first Docker script for your Docker Java container. But wait! There is already an OpenJDK8 Docker container. Actually, there is this even smaller alpine container. You could also add a .dockerignore file so you only include the jar into your container. Eventually, you find the docker-maven-plugin but it is already 6pm, time to go home.

JIB

So, in order to put your Spring Boot app into Docker, you have to learn a lot. JIB will make things easier for you. You only need the JIB Maven plugin and it will use an optimised Google Distroless Docker base image by default. You do not need to install Docker at all. Another advantage is that JIB builds the Docker image by separating dependencies, resources, and classes. This improves the rebuild speed. Further, JIB ensures reproducibility. It wipes out timestamps, users, and groups and always generates the same image for the same content.

Docker Distroless Java Base Image

Compared to alpine Docker images, the Distroless base images are stripped down to the bare basics. They do not contain any unnecessary components and therefore have reduced attack surface. In production, you would never need to use a shell in your container or install additional tools. Docker’s multi stage makes it possible to use those images more efficiently. Surprisingly, the Distroless Java 8 base image is around 15MB bigger then the OpenJDK8 alpine image. Considering the security aspects, Distroless is definitely the better choice.

Optimisations

When running a Docker container that you created with JIB 0.9.11, you will realise that JIB still has a little flaw. Your Java 8 app will ignore the memory cgroup limits of your Docker container. In order to fix this issue, you add a few parameters, including experimental Java options, to JIB in your pom.xml. The first two options will make Java aware of the cgroup memory limits. The third option tells Java how much of a fraction the maximum heap can be. With the Distroless Java 8 base image, there are always some MB left. But you might need to test it out yourself. The fourth flag enables faster pseudo random number generation.

<jvmFlag>-XX:+UnlockExperimentalVMOptions</jvmFlag>
<jvmFlag>-XX:+UseCGroupMemoryLimitForHeap</jvmFlag>
<jvmFlag>-XX:MaxRAMFraction=1</jvmFlag>
<jvmFlag>-Djava.security.egd=file:/dev/./urandom</jvmFlag>

JIB Hands-on Tutorial

In order to perform this tutorial, you need to have Minikube installed and a Docker account. You are going to put a Spring Boot app into a Docker container and start it on Minikube Kubernetes. This tutorial uses the content of the jib-k8s-limits-example folder of the demo project jib-k8s-examples.

Now, let’s try it out. Make sure you are logged in into Docker Hub on your computer or set the credentials of your JIB configuration. First, you need to create a Docker Repository with the name jib-k8s-limits-example. Then, you are going to build your app as a Docker image and push it to your Docker Repository.

When you created the Docker repository, change also the image path in your JIB configuration of your pom.xml.

mvn clean compile jib:build

Start Minikube and reset it.

minikube delete
minikube start

Minikube needs to pull your container from Docker Hub, so you need to set your Docker Hub credentials in Kubernetes.

kubectl create secret docker-registry regcred \
  --docker-server=https://registry.hub.docker.com/ \
  --docker-username=<YOUR_DOCKER_USERNAME> \
  --docker-password=<YOUR_DOCKER_API_KEY> \
  --docker-email=<YOUR_DOCKER_EMAIL>

Now, you can create your deployment and services in Kubernetes. The Kubernetes yaml configuration will expose your app and its Spring Boot Actuator. The Kubernetes configuration requests 200MB of memory for the container and has a limit of 300MB. It contains a readiness probe and a liveness probe. It also uses the secret defined above so it can pull the image from Docker Hub.

kubectl create -f jib-k8s-limits-example.yaml

In case something goes wrong, you can destroy stack with the following command.

kubectl delete -f jib-k8s-limits-example.yaml

Your app is exposed via NodePorts, so you will have a random port assigned to it. You can get the url and its port with the following command. Use the url to access the endpoint of the app.

minikube service jib-k8s-limits-example-http --url

To get the url and its port for the app’s Actuator, you can execute the following command. Then, you just need to add /actuator to the url.

minikube service jib-k8s-limits-example-actuator --url

The app also contains a custom Actuator endpoint at /actuator/memory. This will show you the Heap memory values in MB. As you can see, your Docker container sets 279MB as as the Heap limit out of the available 300MB.

JIB provides fast and reproducible builds. By default, it uses the more secure Distroless Java base image. At 8:30am, a mere 30 minutes later, you have set up JIB and put your Spring Boot app into a Docker image. With the experimental Java options, you can make sure that your app uses the Docker memory limit efficiently. Now, you have the rest of the day to enjoy your coffee!

Resources