Tuesday, 5 July 2016

Docker images creation in a development environment

Summary


Sorry, It´s been a while since my last post, a mixture of not having anything worth writing, holidays and some online training that drains most of my time.
Here is something I would like to share that might reduce the pain for those who want to dockerize their Spring-Boot applications (or any Java application) and then test and tune them while running inside a Docker container.

Former setup

  • A commit was pushed to Jenkins, who compiled and unit-tested it.
  • After some code analysis, a Docker image was created and pushed to a repository
    With set-up, testing was highly inefficient.
  • To test it in a server, I had to pull it and execute it and, while I was focused in the performance and memory consumption of my application, tuning the JVM parameters was essential... Unfortunately, they were given at compilation time (a Maven plugin) and could not be changed (or I did not know how to).
The drawbacks I see of this approach:
  • It was slow to test new changes, as the whole CI flow needed to be executed.
  • Unnecessary docker-related commits to the branch generating the Docker images.
The solution I found? Building the image myself before testing it.

Building your images with Docker Compose


This setup makes the try-an-error cycle much faster while allowing higher degree of customization, depending on the target environment:


In this leaner flow, you build the image in situ.


What do you need for this? Just three simple text files


Create a docker-compose file with your environment-specific requirements
version: '2'
services:
service_1:
# With this block we specify that we are building the image from a local context
build:
context: .
dockerfile: Dockerfile
ports:
- #Omitted for brevity
environment:
# Example of an URL to a CI build (can be set up in the .env file)
- jenkins_jar_path=${path_to_ci_build}/service.jar
# JAVA_OPTS for our application (in this case enabling JMX monitoring)
- java_jmx_opts=-Djava.security.egd=/dev/urandom -Dserver.port=15301 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9199 -Dcom.sun.management.jmxremote.rmi.port=9199 -Djava.rmi.server.hostname=172.29.45.226 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
# Memory settings
- java_mem_opts=-Xmx2G

Here, we have replaced the reference to the image built by our CI system and instead we will build it ourselves. The Java application will take the parameters we give it in this file, allowing fast try-and-test cycles.

Add a Dockerfile
FROM java:openjdk-8u66-jre
# We need to copy the script to the image
COPY ./entrypoint.sh /
# Set it as executable
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
view raw Dockerfile hosted with ❤ by GitHub

WARNING: Pay attention to the Unix/Windows end-line characters unless you want to spend a couple of days chasing ghosts!


Add an entry-point script
#!/bin/bash
echo ##################################################
echo JAVA JMX OPTS: ${java_jmx_opts}
echo JAVA MEM OPTS: ${java_mem_opts}
echo JAR PATH: ${jenkins_jar_path}
echo ###################################################
# Download the JAR file from a CI server if a path is given
if [ -z ${jenkins_jar_path+x} ]; then wget -O ./app.jar ${jenkins_jar_path}
java ${java_jmx_opts} ${java_mem_opts} -jar ./app.jar
view raw entry-point.sh hosted with ❤ by GitHub

As you can see, there are several options for retrieving the JAR file that will be executed:
  • From a local compilation: Very convenient if you are performing changes in the code and you want to see the effect right away.
  • From a CI agent/repository: If, on the other hand, you are more focused in tuning a previously generated Jar file (memory settings, monitoring and so on.
You can easily configure this using an external environment file (.env) holding the path the binary file:

# This path should point to either a specific
# release or to a SNAPSHOT version
jenkins_jar_path=http://myjenkinsserver/myproject/releases/0.0.1/myService.jar
view raw .env hosted with ❤ by GitHub

What are the advantages of doing this?

  • You can modify in which way you want to invoke your application without having to modify anything but the docker-compose file.
  • The Dockerfile and entry-point.sh script are totally generic and can be reused in other modules.


How to build the image and run the container:

Just place the three files you wrote together (or even better, if you are reusing the Dockerfile and entrypoint.sh just use their paths) and execute these commands:

docker-compose build --no-cache

Once your image has been built, launch your services as usual:

docker-compose up -d

Your service will be up and running and you can test your configuration changes right away!

Anyway, I am still struggling to get this right, specially that I intend to use Docker Swarm to launch my services in a cluster.

How do you tackle this sort of problems with the images generated by your CI system? Do you use other approaches? Feel free to share your thoughts and hopefully we all can learn from them!

No comments:

Post a Comment