Microservice deployment using containers

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.

Installation and configuration

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.

Docker Machine with 4 GB

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

Building Docker images with Maven

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:

  • We'll use the Spring profile identified as Docker.
  • There won't be any conflict of ports among embedded Tomcat, since services will be executed in their own respective containers. We can now use port 8080.
  • We will prefer to use an IP address to register our services in Eureka. Therefore, the Eureka instance property preferIpAddress will be set to true.
  • Finally, we'll use the Eureka Server host name in 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.

Running Docker using Maven

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 .

Integration testing with Docker

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

Pushing the image to a registry

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.

Managing Docker containers

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

Tip

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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset