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?”
“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 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:
… and brought down with:
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)
- addressbook (REST backend)
- web (AngularJS frontend)
The complete source for the AddressBook backend is available in this project on github. The deploy-* folders contain the individual dockerfiles: