Java 8/11 and Docker (Part 1)
Java 8/11 and Docker can get along and get along well with a little care even when cgroups put their hand in it: let's see together how to tame the old dear JVM 8.
The backend development in recent years has changed radically for those who let themselves be fascinated and involved by Docker and everything that revolves around him. For many, first of all, it has greatly simplified not only the method of access and interaction with the systems around the application that we are developing, such as the database or the queue broker, but above all it has made it possible to make it simpler and finally within reach writing and maintaining integration tests (as we had already seen). This wave has come to invest also the deployment unit : if before you deployed EAR, WAR or JAR executables, now you deploy Docker images ! If much has changed around our code, however, the latter has remained fairly firm, almost behind. In fact, the jump from Java 8 to later versions (which are now running!) Is not painless at all, especially if you are dealing with projects even for just a couple of years or third-party products, updated in the features but not in the supported JVM version. The desire to "containerize" them too is not lacking: it is now difficult to renounce the convenience that the whole environment "built" by continuous integration goes as it is in UAT and perhaps in production!!
The scripting of the environments and their "replicability" made all our work, in my opinion, much more industrial and less artisanal. If it is extremely easy then to create a Docker image that contains our Java 8-based application, it is not so easy to find that something is wrong when you impose resource limits on the container and the JVM does not respect them !
Docker and limited resources: why do it?
As long as we use Docker for development support, we certainly don't need to limit resources to containers. But when we intend to use it in production, we have to start thinking seriously about how many cores and how much memory our application needs to work well, and no more. So how does Docker do this? Let's take a short step back. Since its inception, the difference between a container and a virtual machine has always been highlighted, with graphics of this type:
The Docker Engine (stack on the right) often runs inside a Guest OS (on the left): the two technologies therefore coexist very well. In fact Docker gives you the illusion of having a file system, a network, CPU and RAM just for you, but in reality it does nothing but isolate processes by exploiting two features in the linux kernel which are cgroups and namespaces (and many spells on iptables ). In short, the first regulates the allocation of resources such as CPU and RAM to a process, the second isolates the process, the file system and the network. And here comes the fun part: if up to now using Docker with Java 8 had not aroused any suspicion, as soon as the limits imposed by cgroups begin to apply , surprises begin, which unfortunately are very subtle. As we will see shortly, it is in fact known that the first JVM 8 (from update 121 onwards) totally ignore the constraints imposed by cgroups regarding Memory and CPU , creating unexpected problems.
The first official support was introduced from update 131 onwards , but you still have to be careful and understand what parameters to pass to the JVM . To rest assured, you should switch to Java 10+, but if we are here to talk about it, it is precisely because we cannot do it! But why impose limits on the Docker container? It is a scenario that cannot be ignored if we are going to deploy our image within a cluster (managed by Swarm or Kubernetes for example) where dozens (if not more) of containers will reside competing for the resources of the node in which they turn: therefore limiting their visibility is fundamental for everyone's survival.
Java 8, Docker, CPU and Memory
Let's start with a simple Java 8 project based on Spring Boot that creates a Docker image thanks to Fabric8's docker-maven-plugin plugin . The project in question is called jvm-info and is available on GitHub .
Prerequisites At the time of writing the post, I am using:
-- Docker for Mac version 18.06.1-ce, in Swarm Mode . -- Docker's VM has 4 CPUs and 2 Gb of RAM, fundamental limits for understanding what will come next.
Once the project has been cloned from GitHub, you can build the images with a simple:
1. mvn clean install
Why "images in the plural"? This project actually creates two images:
-- cnj / openjdk-jvm-info based on the standard image openjdk: 8u181-jdk-slim-stretch , jdk 1.8u 181. -- cnj / fabric8-jvm-info based on the image fabric8 / java-centos-openjdk8-jdk (jdk 1.8u181) who is already aware of these problems and implements solutions. We can use it as a yardstick to understand what parameters to pass to the JVM
These i will use in my tests for the Memory & cpu in my next blog posts.