Building a multi-container Spring Boot and MongoDB webapp with Docker 1.12.x

I’ve spent a bunch of time playing with apps in single containers up until now and reached the point where I started to think about questions like:

“ok, so how is this done if you have an application with multiple services, where you may need to scale individual services but not others?”

and

“how does an app in one container talk to another container?”

Composing applications from multiple containers and handling the scaling I think this is the sweetspot for Kubernetes, but I don’t feel ready to ramp up quite that much just yet. Besides, with Docker 1.12 adding ‘swarm mode’ and with docker-compose, it’s looking like Docker has most of the tools you need to build and scale a multi-container app out of the box without adding additional tools to the stack.

So here’s where I started:

  • Container 1: Spring Boot app with JAX-RS RESTful endpoints
  • Container 2: MongoDB database
  • Container 3: Data volume container for MongoDB

As it turns out, this is doesn’t involve too much additional work than just building Dockerfiles for the individual containers and then wiring them together with a docker-compose.yml file.

Starting with each container, here’s the pretty simple Dockerfiles for each:

Spring Boot container:

FROM java:openjdk-8-alpine
ADD SpringBootAddressBook-0.0.1-SNAPSHOT.jar /opt/SpringBootAddressBook-0.0.1-SNAPSHOT.jar
EXPOSE 8080
ENV MONGODB_DB_NAME addressbook
ENV MONGODB_DB_HOST mongo
ENV MONGODB_DB_PORT 27017
ENTRYPOINT ["java", "-jar", "/opt/SpringBootAddressBook-0.0.1-SNAPSHOT.jar"]

 

MongoDB container:
MongoDB can be run straight from the official dockerfiles on Docker Hub, using one container for the server and one for a data container – see the complete docker-compose file below.

Bringing it all together, here’s the Docker Compose file to orchestrate the containers together:

version: '2'
services:
    mongodata:
        image: mongo:3.2
        volumes:
        - /data/db
        entrypoint: /bin/bash
    mongo:
        image: mongo:3.2
        depends_on: 
            - mongodata
        volumes_from:
            - mongodata
        ports:
        #kh: only specify internal port, not external, so we can scale with docker-compose scale
            - "27017"
    addressbook:
        image: addressbook
        depends_on: 
            - mongo
        environment:
            - MONGODB_DB_NAME=addressbook
        ports:
            - 8080:8080
        links:
            - mongo

At this point, the group of containers can be brought up as a whole with:

docker-compose up

… and brought down with:

docker-compose down

You can individually scale any container with:

docker-compose scale containername=count

…. where count is the number of container instances to spin up.

 

So what if you want to add in a web frontend as a container too? Easy enough. Here’s an AngularJS frontend, served by nginx:

    web:
        image: docker-web-angularjs
        ports:
            - "80"

Now, if we spin up multiple containers for the REST backend and the nginx frontend as well, we need a load balancer as well, right? Also easy, just add in haproxy:

    lb:
        image: dockercloud/haproxy
        depends_on: 
            - addressbook
        environment:
            - STATS_PORT=1936
            - STATS_AUTH="admin:password"
        links:
            - addressbook
            - web
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
        ports:
            - 80:80
            - 1936:1936

I had noticed that the startup order of the containers was not always predictable, and sometimes the Spring Boot container would start before the MongoDB container was up. This can be fixed by adding the depends_on element. I’m not sure if I really needed to add all the dependencies that I did in order to force a very specific startup order, but this seems to work for me. The order I have in the complete docker-compose.yml is (from first to last):

  • mongodata (data container)
  • mongo
  • addressbook (REST backend)
  • web (AngularJS frontend)
  • haproxy

The complete source for the AddressBook backend is available in this project on github. The deploy-* folders contain the individual dockerfiles:

https://github.com/kevinhooke/SpringBootRESTAddressBook

 

Adding Spring Boot Actuator Metrics to a Jersey App

Spring Boot Actuator adds a number of health monitoring endpoints to support monitoring of your Spring Boot based applications. To add to an existing Spring Boot app, you just add a dependency on spring-boot-starter-actuator as described here.

This works out of the box for a Spring MVC based Spring Boot app, but if you’re building JAX-RS endpoints using Jersey, by default the Jersey resources and Actuator resources both get mapped to the root ( / ), so you need to map either Jersey or Actuator to something other than /. You can easily re-map Actuator be adding this line to application.properties (change /system as needed):

server.servlet-path=/system

This is described here: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-actuator.html#howto-use-actuator-with-jersey

Watch for dependency name typos when creating new Spring Boot projects

If you use the Spring Boot CLI, it’s pretty easy to get a new project created from the templates with all required dependencies in place with something like:

spring init --build=maven --dependencies=web project-name.zip

This generates you a zip that you can unzip and import into your IDE and off you go. The trouble with the CLI currently though is that there’s no validation on the dependency names, so if you call

spring init --build=maven --dependencies=wb project-name.zip

You’ll get the same zip generated, but there will be a subtle typo in your dependencies which unless you’ve seen these messages from maven before, you’ll spend a bunch of time trying to work out what’s wrong.

I created a ticket for this issue here, and hopefully this gets picked up as an enhancement.

The maven error in case you’re interested looks like this:

[INFO] Scanning for projects...
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project com.example:testbaddep:0.0.1-SNAPSHOT (/Users/kev/develop/spring-boot/testbaddep/pom.xml) has 1 error
[ERROR]     'dependencies.dependency.version' for org.springframework.boot:spring-boot-starter-wb:jar is missing. @ line 27, column 15
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException

Lightweight Java microservice frameworks/libraries

This is primarily a personal todo list of Java-based microservice frameworks to investigate. If I’ve already looked at any of these then I most likely have a separate post about them elsewhere.

As I investigate each of these, you may find implementation examples as part of this Git repo here (https://github.com/kevinhooke/JavaRESTFrameworkComparison) – check the readme in the repo for those which I’ve built examples already.

Libraries and Frameworks:

Useful/Interesting REST and microservice related articles: