You might have got the point about Docker after reading Chapter 1, A Solution Approach.
A Docker container provides a lightweight runtime environment, consisting of the core features of a virtual machine and the isolated services of operating systems, known as a docker image. Docker makes the packaging and execution of µServices easier and smoother. Each operating system can have multiple Dockers, and each Docker can run multiple applications.
Docker needs a virtualized server if you are not using a Linux OS. You can install VirtualBox or similar tools such as Docker Toolbox to make it work for you. The Docker installation page gives more details about it and lets you know how to do it. So, leave it to the Docker installation guide available on Docker's website.
You can install Docker, based on your platform, by following the instructions given at https://docs.docker.com/engine/installation/.
DockerToolbox-1.9.1f was the latest version available at the time of writing. This is the version we used.
Default machines are created with 2 GB of memory. We'll recreate a Docker Machine with 4 GB of memory:
docker-machine rm default docker-machine create -d virtualbox --virtualbox-memory 4096 default
There are various Docker maven plugins that can be used:
You can use any of these, based on your choice. I found the Docker Maven plugin by @rhuss
to be best suited for us. It is updated regularly and has many extra features when compared to the others.
We need to introduce the Docker Spring Profile in application.yml
before we start discussing the configuration of docker-maven-plugin
. It will make our job easier when building services for various platforms. We need to configure the following four properties:
8080
.preferIpAddress
will be set to true
.serviceUrl:defaultZone
.To add a Spring profile in your project, add the following lines in application.yml
after the existing content:
--- # For deployment in Docker containers spring: profiles: docker server: port: 8080 eureka: instance: preferIpAddress: true client: serviceUrl: defaultZone: http://eureka:8761/eureka/
We will also add the following code in pom.xml
to activate the Spring profile Docker, while building a Docker container JAR. (This will create the JAR using the previously defined properties, for example port:8080
.)
<profiles> <profile> <id>docker</id> <properties> <spring.profiles.active>docker</spring.profiles.active> </properties> </profile> </profiles>
We just need to use Maven docker
profile while building the service, shown as follows:
mvn -P docker clean package
The preceding command will generate the service
JAR with Tomcat's 8080 port and will get registered on Eureka Server with the hostname eureka
.
Now, let's configure
docker-maven-plugin
to build the image with our restaurant microservice. This plugin has to create a Dockerfile first. The Dockerfile is configured in two places – in pom.xml
and docker-assembly.xml
. We'll use the following plugin configuration in pom.xml
:
<properties> <!-- For Docker hub leave empty; use "localhost:5000/" for a local Docker Registry --> <docker.registry.name>localhost:5000/</docker.registry.name> <docker.repository.name>${docker.registry.name}sourabhh /${project.artifactId}</docker.repository.name> </properties> ... <plugin> <groupId>org.jolokia</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.13.7</version> <configuration> <images> <image> <name>${docker.repository.name}:${project.version}</name> <alias>${project.artifactId}</alias> <build> <from>java:8-jre</from> <maintainer>sourabhh</maintainer> <assembly> <descriptor>docker-assembly.xml</descriptor> </assembly> <ports> <port>8080</port> </ports> <cmd> <shell>java -jar /maven/${project.build.finalName}.jar server /maven/docker-config.yml</shell> </cmd> </build> <run> <!-- To Do --> </run> </image> </images> </configuration> </plugin>
Above the Docker Maven plugin configuration, create a Dockerfile that creates the JRE 8 (java:8-jre
) -based image. This exposes ports 8080 and 8081.
Next, we'll configure docker-assembly.xml
, which tells the plugin which files should be put into the container. It will be placed under src/main/docker
:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> <id>${project.artifactId}</id> <files> <file> <source>{basedir}/target/${project.build.finalName}.jar</source> <outputDirectory>/</outputDirectory> </file> <file> <source>src/main/resources/docker-config.yml</source> <outputDirectory>/</outputDirectory> </file> </files> </assembly>
Above assembly, add the service
JAR and docker-config.yml
in the generated Dockerfile. This Dockerfile is located under target/docker/
. On opening this file, you will find the content to be similar to this:
FROM java:8-jre MAINTAINER sourabhh EXPOSE 8080 COPY maven /maven/ CMD java -jar /maven/restaurant-service.jar server /maven/docker-config.yml
The preceding file can be found at restaurant-service argetdockersousharm
estaurant-servicePACKT-SNAPSHOTuild
. The build
directory also contains the maven
directory, which contains everything mentioned in docker-assembly.xml
.
Lets' build the Docker Image:
mvn docker:build
Once this command completes, we can validate the image in the local repository using Docker Images, or by running the following command:
docker run -it -p 8080:8080 sourabhh/restaurant-service:PACKT-SNAPSHOT
Use -it
to execute this command in the foreground, in place of –d
.
To execute a Docker Image with Maven, we need to add the following configuration in the pom.xml
. <run>
block, to be put where we marked the To Do under the image block of docker-maven-plugin
section in the pom.xml
file:
<properties> <docker.host.address>localhost</docker.host.address> <docker.port>8080</docker.port> </properties> ... <run> <namingStrategy>alias</namingStrategy> <ports> <port>${docker.port}:8080</port> </ports> <volumes> <bind> <volume>${user.home}/logs:/logs</volume> </bind> </volumes> <wait> <url>http://${docker.host.address}:${docker.port}/v1/restaurants/1</url> <time>100000</time> </wait> <log> <prefix>${project.artifactId}</prefix> <color>cyan</color> </log> </run>
Here, we have defined the parameters for running our Restaurant service container. We have mapped Docker container ports 8080
and 8081
to the host system's ports, which allows us to access the service. Similarly, we have also bound the containers' logs directory to the host systems' <home>/logs
directory.
The Docker Maven plugin can detect if the container has finished starting up by polling the ping URL of the admin backend until it receives an answer.
Please note that Docker host is not localhost if you are using DockerToolbox or boot2docker on Windows or Mac OS X. You can check the Docker Image IP by executing docker-machine ip default
. It is also shown while starting up.
The Docker container is ready to start. Use the following command to start it using Maven:
mvn docker:start .
Starting and stopping a Docker container can be done by binding the following executions to the docker-maven-plugin
life cycle phase in pom.xml
:
<execution> <id>start</id> <phase>pre-integration-test</phase> <goals> <goal>build</goal> <goal>start</goal> </goals> </execution> <execution> <id>stop</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution>
We will now configure the failsafe plugin to perform integration testing with Docker. This allows us to execute the integration tests. We are passing the service URL in the service.url
tag, so that our integration test can use it to perform integration testing.
We'll use the DockerIntegrationTest
marker to mark our Docker integration tests. It is defined as follows:
package com.packtpub.mmj.restaurant.resources.docker; public interface DockerIntegrationTest { // Marker for Docker integratino Tests }
Look at the following integration plugin code. You can see that DockerIntegrationTest
is configured for the inclusion of integration tests (failsafe plugin), whereas it is used for excluding in unit tests (Surefire plugin):
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.18.1</version> <configuration> <phase>integration-test</phase> <includes> <include>**/*.java</include> </includes> <groups>com.packtpub.mmj.restaurant.resources.docker.DockerIntegrationTest</groups> <systemPropertyVariables> <service.url>http://${docker.host.address}:${docker.port}/</service.url> </systemPropertyVariables> </configuration> <executions> <execution> <goals> <goal>integration-test</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <configuration> <excludedGroups>com.packtpub.mmj.restaurant.resources.docker.DockerIntegrationTest</excludedGroups> </configuration> </plugin>
A simple integration test looks like this:
@Category(DockerIntegrationTest.class) public class RestaurantAppDockerIT { @Test public void testConnection() throws IOException { String baseUrl = System.getProperty("service.url"); URL serviceUrl = new URL(baseUrl + "v1/restaurants/1"); HttpURLConnection connection = (HttpURLConnection) serviceUrl.openConnection(); int responseCode = connection.getResponseCode(); assertEquals(200, responseCode); } }
You can use the following command to perform integration testing using Maven:
mvn integration-test
Add the following tags under docker-maven-plugin
to publish the Docker Image to Docker Hub:
<execution> <id>push-to-docker-registry</id> <phase>deploy</phase> <goals> <goal>push</goal> </goals> </execution>
You can skip JAR publishing by using the following configuration for maven-deploy-plugin
:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.7</version> <configuration> <skip>true</skip> </configuration> </plugin>
Publishing a Docker image in Docker Hub also requires a username and password:
mvn -Ddocker.username=<username> -Ddocker.password=<password> deploy
You can also push a Docker image to your own Docker registry. To do this, add the docker.registry.name
tag as shown in the following code. For example, if your Docker registry is available at xyz.domain.com
on port 4994
, then define it by adding the following line of code:
<docker.registry.name>xyz.domain.com: 4994</docker.registry.name>
This does the job and we can not only deploy, but also test our Dockerized service.
Each microservice will have its own Docker container. Therefore, we'll use the Docker Compose Docker container manager to manage our containers.
Docker Compose will help us to specify the number of containers and how these will be executed. We can specify the Docker Image, ports, and each container's links to other Docker containers.
We'll create a file called docker-compose.yml
in our root project directory and add all the microservice containers to it. We'll first specify the Eureka Server as follows:
eureka: image: localhost:5000/sourabhh/eureka-server ports: - "8761:8761"
Here, image represents the published Docker image for Eureka Server and ports represents the mapping between the host being used for executing the Docker Image and the Docker host.
This will start Eureka Server and publish the specified ports for external access.
Now, our services can use these containers (dependent containers such as Eureka). Let's see how restaurant-service
can be linked to dependent containers. It is simple; just use the links
directive:
restaurant-service: image: localhost:5000/sourabhh/restaurant-service ports: - "8080:8080" links: - eureka
The preceding links declaration will update the /etc/hosts
file in the restaurant-service
container with one line per service that the restaurant-service
depends on (let's assume the security
container is also linked), for example:
192.168.0.22 security 192.168.0.31 eureka
If you don't have a docker
local registry set up, then please do this first for issue-less or smoother execution.
Build the docker
local registry by:
docker run -d -p 5000:5000 --restart=always --name registry registry:2
Then, perform push
and pull
commands for the local images:
docker push localhost:5000/sourabhh/restaurant-service:PACKT-SNAPSHOT docker-compose pull
Finally, execute docker-compose
:
docker-compose up -d
Once all the microservice containers (service and server) are configured, we can start all Docker containers with a single command:
docker-compose up –d
This will start up all Docker containers configured in Docker Composer. The following command will list them:
docker-compose ps Name Command State Ports ------------------------------------------------------------- onlinetablereservation5_eureka_1 /bin/sh -c java -jar ... Up 0.0.0.0:8761->8761/tcp onlinetablereservation5_restaurant-service_1 /bin/sh -c java -jar ... Up 0.0.0.0:8080->8080/tcp
You can also check docker image logs using the following command:
docker-compose logs [36mrestaurant-service_1 | ←[0m2015-12-23 08:20:46.819 INFO 7 --- [pool-3-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_RESTAURANT-SERVICE/172.17 0.4:restaurant-service:93d93a7bd1768dcb3d86c858e520d3ce - Re-registering apps/RESTAURANT-SERVICE [36mrestaurant-service_1 | ←[0m2015-12-23 08:20:46.820 INFO 7 --- [pool-3-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_RESTAURANT-SERVICE/172.17 0.4:restaurant-service:93d93a7bd1768dcb3d86c858e520d3ce: registering service... [36mrestaurant-service_1 | ←[0m2015-12-23 08:20:46.917 INFO 7 --- [pool-3-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_RESTAURANT-SERVICE/172.17