Containerization has become one the most talked evolution in web application infrastructure that drive cloud native software architectures. Cloud native software usually relies on microservice architectural style where a software system is developed as a suite of small self containing services each running its own process and an isolated database. Services communicate with lightweight mechanisms, usually via HTTP resource APIs. Adopting microservice architectural style requires heavy automation of infrastructure and easily deployable applications. The new architectural style also puts the development process under heavy stress test.
Containers have existed in Linux for a long time, but not until Docker they have been a common build block of web application infrastructure. Docker is a toolset around Linux containers that allowed the mainstream software industry to adopt container technology. Differing from virtual machines, containers are a process isolation technique a lot more lightweight than a traditional virtual machine. Containers require smaller memory footprint and allow bootstrapping of the system just in few seconds. This makes them perfect solution for running cloud native applications built with microservices architecture.
Spring Boot is one of the most popular technologies to bundle and bootstrap Java or Groovy based web applications. Spring Boot provides an embedded application server, convention over configuration based autoconfiguration for Spring Framework and lot of production ready features such as metrics, health check and externalized configuration. Spring Boot serves as a main building block for cloud native web applications.
Packaging the Spring Boot demo application
The demo application used in this exercise is fairly simple URL shortening service, available at Gofore GitHub account. The application itself is not that interesting, but the way it is packaged and run on Docker makes it really interesting. Spring Boot 1.3 introduced two groundbreaking new features: fully executable packaging and hot restarting.
Fully executable packaging works by embedding a small script at the front of the jar or war file. Spring Boot applies repackaging of the application package to do its magic. This can be achieved with Maven or Gradle plugins. Repackaging with embedded script might break some tools so use the new feature with caution. The script adds automatic detection of init.d service (start|stop|restart|status), which allows the packaged application just be symlinked under init.d to make it an operating system level service. The embedded script also works with systemd.
Configuring the Maven plugin for fully executable packaging requires the following configuration:
And after packaging the application creating init.d service goes like a breeze:
Live deploy with hot restart
Hot restarting of the application is a bit different approach than hot swapping of classes that for example JRebel does. Spring Boot’s hot restarting uses the same idea that Play Framework has been successfully using. The application does some tricks to internally restart the application as fast as possible. With a such small project like gofurl the cold start time goes from 6 seconds to under 1 second with hot restart. And everything happens automatically, the changed files just need to be recompiled and Spring Boot detects the changes from classpath. Hot restart approach provides a really fast development cycle from code change to live testing and it works more reliably than hot swapping tools like JRebel or Spring Loaded.
Spring Boot’s hot restart feature can be enabled by just adding the developer tools dependency to your project manifest. With maven use the following:
Optional property prevents the developer tools to float as a transitive dependency to projects depending from the main project. Hot restart is enabled by default and must be explicitly disabled by setting spring.devtools.restart.enabled property to false. Developer tools is also automatically disabled if the application is started from a fully packaged bundle.
Hot restart works straight from your IDE after the changed files are compiled. In IntelliJ Idea files must be manually compiled and in Eclipse automatic compiling triggers the hot restart. Hot restarting is also supported when running Spring Boot application with the Maven or Gradle plugins.
Provisioning immutable Docker containers
Productions environments should be immutable by default and Docker provides a foundation for immutability by layered read only file systems. Multiple file systems are layered on top of each other providing operating system, libs, application runtime and finally the application itself. Normal development process with Docker requires few steps: 1) Change application code. 2) Build docker image. 3) Run docker image.
The demo application uses Spotify’s Docker Maven plugin to create a docker image from the project:
The plugin configuration defines the Docker image name to be created, where the Dockerfile can be loaded and what resources should be bundled to the image. The project can be build into a docker image with the following Maven command:
The application requires MongoDB database, and as Docker was a process isolation technique, we should run one command per container. This is where Docker Compose comes to help. Docker Compose is a tool that provides a way to orchestrate multiple docker containers and their dependencies. If we define that gofurl container depends a MongoDB container, Docker Compose can automatically start all the dependencies. For this project three containers are used: one for the application itself, one for the MongoDB binary and one to preserve the MongoDB data files. This allows updating the MongoDB container without losing the stored data.
After building the project with Maven, Docker Compose can start the application with:
When automated this process requires maybe tens of seconds or at most a minute or so. But for development, it is still too slow. A code change should reflect to the running application immediately, or at least in few seconds.
Enter the voodoo
Can we combine hot restarting and running application in a container? Oh yes we can! But does it make sense? At least for large microservice systems requiring tens of services Docker Compose plays a well role orchestrating and bootstrapping all the services.
Docker can run one process, which can be the Maven command for running Spring Boot application. And Spring Boot can be configured to do hot restart on classpath changes. The only problem is that by default Docker containers should be immutable. But we can change this by adding a volume from the host inside the container. The volume contains the application classpath. Now the only requirement for the Docker image is that it contains JDK and Maven binary to run the Spring Boot Maven plugin.
There’s also another problem with Maven, also known as “downloading the whole internet”, which means that when you don’t have local dependency cache, a lot of dependencies will be downloaded from the internet. This can be avoided by mapping the local .m2 repository inside the container so it can use your cache instead of downloading all the dependencies every time the application is started.
Now let’s change the container definition for gofurl to a development version that uses the described technique:
If you try this with default settings of the Spring Boot Maven plugin, you will realize that nothing happens even if files are changed in your host’s classpath. This is because the application runs inside Maven’s classloader which prevents Spring Boot to do its hot restart magic. The solution is to fork the application in its own JVM process. Now we can define a Dockerfile that starts the application with Maven plugin inside the container.
The Docker image is based on the official Java 8 image and only downloads and adds Maven to PATH. The image does not know anything about our application, because we are relying on the volume mapping to provide the application classpath. The command starts Maven with spring-boot:run goal that fires up the application. Additional JVM arguments are provided to bind the MongoDB URI to the linked MongoDB container. When JVM arguments are defined, the plugin will fork automatically, but fork option is required if no arguments are needed.
Note that Docker runs as root and you should have the application classpath compiled before starting the container. Otherwise your local user won’t be able to overwrite the files in the classpath. When everything is in place and the development version of the application can be started with Docker Compose:
The application should be started now and if you make changes in your IDE and compile the changed sources, magic happens! The application inside the running container is updated and automatically restarted. Was all this worth the trouble? You could just run your Spring Boot application from the IDE and expose the MongoDB container port for the application. And you still need JDK and all the tools to compile the sources on host machine to provide a working classpath. I haven’t tested but the same approach should also work with Play Framework. Play has one advantage over Spring Boot. It also bundles a compiler so if you are brave enough to change your Scala code with just a text editor, you don’t even need JDK or IDE on your host machine to make live changes to an application running inside a container.