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 :
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
- Hystrix tutorial
- Eureka, Ribbon, Feign tutorial
- Microservice ecosystem with Docker, Spring Cloud and Spring Boot
- Zuul tutorial
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.
Pingback: Spring Cloud Zuul example | Example Driven (Self) Development
very Nice you example
Pingback: Spring Cloud hystrix example | Example Driven (Self) Development
Pingback: Spring Cloud, Eureka, Ribbon, Feign example | Example Driven (Self) Development
$ 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
I tried it on a brand new machine and it works. Just mvn install then docker-compose up
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.
very nice article and it works without any issues.. thank you sir..