Microservice ecosystem with Docker, Spring Cloud and Spring Boot


Goal of this example

This example shows how to create a “microservice ecosystem” from dockerized spring boot applications. The services are accessible internally inside docker, but external access is only in a controlled way through an API Gateway. Inside the “ecosystem” the number or running instances or the locations can change any time, without affecting the “interface” that is provided to the extnal world. Such a dockerized ecosystem can be used for

  • Setting up a local test environment of a microservice project
  • Testing different cluster configurations
  • Testing cluster fail-over
  • Can be the basis of a production deployment (if added support for multi node clusters, security, etc)

We will build the following :

boot-docker
We will use a Zuul API Gateway and an Eureka service registry. Details of how they work are explained in other blog post of the series (links below), but what matters for this exercise is that they are regular Spring Boot applications. A very similar ecosystem could be build from any java application.

Technology Used

  • Spring boot 1.3.5.RELEASE
  • Docker
  • Docker Compose
  • Docker maven plugin
  • Zuul
  • Eureka

This article is part of a Spring Cloud / Netflix OSS series

How to create a docker image from a spring boot application

Our first goal is to generate a Docker image from a Spring boot application using maven. This can be achieved by the Spotify docker maven plugin. It eliminates the need to create a Dockerfile, instead all important properties like the base image and entrypoint can be set in the pom.xml like this :


            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>0.4.10</version>
                <configuration>
                    <imageName>${project.artifactId}</imageName>
                    <baseImage>java:8</baseImage>
                    <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
                    <!-- copy the service's jar file from target into the root directory of the image -->
                    <resources>
                        <resource>
                            <targetPath>/</targetPath>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                </configuration>
                <executions>
                    <execution>
                        <id>build-image</id>
                        <phase>package</phase>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

This does the following :

  • Sets the <baseImage> (equivalent to FROM in dockerfile) to java:8
  • Sets the <entryPoint> (equivalent to CMD in dockerfile) to the generated jar file.
  • Binds the plugin to the build goal, so it will be automatically executed when <pre>mvn install</pre> is executed

If the command was successfully executed then we should see the image listed when

 docker images 

is executed.

Link docker containers to see each other over the network

Once we have our docker images we want to start them up in a way they connect to each other over the network. The legacy way to do is to use the -link command, e.g.: like this :

 
## Starts up an eureka image, names it eureka-server and exposes port 8761 
docker run -p 8761:8761 --name eureka-server eureka-server 

## Starts up an echo-service image and makes the container named eureka-server accessible over the network by the eureka-server name 
docker run --name echo-service --link eureka-server echo-service 

Use docker-compose the start up multiple containers and set up a container network

Docker compose is a command line tool to start up multiple predefined containers. Container definitions are stored in a file named docker-compose.yml . The above example starts up three containers. The depends_on defines the startup order and how containers see the other. The file also defines the exposed ports of the services.

version: '2'
services:
  echo-service:
    image: echo-service
    depends_on:
     - eureka-server
  eureka-server:
    image: eureka-server
    ports:
     - "8761:8761"
  zuul-server:
    image: zuul-server
    depends_on:
     - eureka-server
    ports:
     - "9090:9090"

The above file defines that

  • Eureka will expose it’s 8761 port to the external network
  • Zuul server will expose it’s 9090 port to the external network
  • Echo-service will be only internally accessible

To start up the containers build all images then type

##use the -d option to start up in deamon mode

docker-compose up

docker compose could also build the images, but it would require a Dockerfile that we replaced with the Docker maven plugin. Once the services are up you can execute a few commands to validate the setup

##show running docker containers
docker ps

##List docker netwrks
docker network ls

Scaling

One fo the many benefits of docker-compose is that it allows easy scaling of the services. For example if we want two echo-service containers then simply call

<span class="pl-c">##starts a 2nd instance of echo-service</span> 
docker-compose scale echo-service=2

Resource utilization

Starting up three or more VMs would require a lot of time and resource. Docker containers share their resources with the host, therefore the overhead is very minimal. The startup time and memory footprint of a dockerized java application is almost identical to a regulat java application.

8 comments

  1. Pingback: Spring Cloud Zuul example | Example Driven (Self) Development

  2. very Nice you example

  3. Pingback: Spring Cloud hystrix example | Example Driven (Self) Development

  4. Pingback: Spring Cloud, Eureka, Ribbon, Feign example | Example Driven (Self) Development

  5. Mikhai;

    $ docker run -p 8761:8761 eureka-server

    Caused by: java.lang.IllegalStateException: Failed to introspect annotated methods on class org.springframework.cloud.netflix.eureka.server.EurekaServerInitializerConfiguration
    at org.springframework.core.type.StandardAnnotationMetadata.getAnnotatedMethods(StandardAnnotationMetadata.java:163) ~[spring-core-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.retrieveBeanMethodMetadata(ConfigurationClassParser.java:360) ~[spring-context-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:294) ~[spring-context-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:230) ~[spring-context-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:570) ~[spring-context-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    … 27 common frames omitted
    Caused by: java.lang.NoClassDefFoundError: javax/servlet/ServletContext
    at java.lang.Class.getDeclaredMethods0(Native Method) ~[na:1.8.0_111]
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) ~[na:1.8.0_111]
    at java.lang.Class.getDeclaredMethods(Class.java:1975) ~[na:1.8.0_111]
    at org.springframework.core.type.StandardAnnotationMetadata.getAnnotatedMethods(StandardAnnotationMetadata.java:152) ~[spring-core-4.3.6.RELEASE.jar!/:4.3.6.RELEASE]
    … 31 common frames omitted
    Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletContext
    at java.net.URLClassLoader$1.run(URLClassLoader.java:370) ~[na:1.8.0_111]
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[na:1.8.0_111]
    at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_111]
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[na:1.8.0_111]

    Shouldn’t javax.servlet-api be added as a dependency? I wonder why eureka doesn’t include it itself already

    • Peter Szanto

      I tried it on a brand new machine and it works. Just mvn install then docker-compose up

    • Mikhai;

      After a few painful hours I’ve realized that the problem was caused by
      spring-boot-starter-parent
      1.5.0.RELEASE

      Switching to 1.4.3.RELEASE at least let it start.

  6. very nice article and it works without any issues.. thank you sir..

Leave a reply to Peter Szanto Cancel reply