Академический Документы
Профессиональный Документы
Культура Документы
com/2015/09/07/building-and-deploying-a-multi-container-java-spring-
mongodb-application-using-docker/
Introduction
Application Architecture
Spring Music Environment
Building the Environment
Spring Music Application Links
Helpful Links
Introduction
In this post, we will demonstrate how to build, deploy, and host a multi-tier Java application using Docker. For the
demonstration, we will use a sample Java Spring application, available on GitHub from Cloud Foundry. Cloud Foundry’s
Spring Music sample record album collection application was originally designed to demonstrate the use of database services
on Cloud Foundry and Spring Framework. Instead of Cloud Foundry, we will host the Spring Music application using Docker
with VirtualBox and optionally, AWS.
All files required to build this post’s demonstration are located in the master branch of this GitHub repository. Instructions to
clone the repository are below. The Java Spring Music application’s source code, used in this post’s demonstration, is
located in the master branch of this GitHub repository.
A few changes were necessary to the original Spring Music application to make it work for the this demonstration. At a high-
level, the changes included:
Add Gradle warNoStatic task to build WAR file without the static assets, which will be host separately in
NGINX
Create new Gradle task, zipStatic, to ZIP up the application’s static assets for deployment to NGINX
Application Architecture
The Java Spring Music application stack contains the following technologies:
Java
Spring Framework
NGINX
Apache Tomcat
MongoDB
ELK Stack
Logspout
Logspout-Logstash Adapter
The Spring Music web application’s static content will be hosted by NGINX for increased performance. The application’s
WAR file will be hosted by Apache Tomcat. Requests for non-static content will be proxied through a single instance of NGINX
on the front-end, to one of two load-balanced Tomcat instances on the back-end. NGINX will also be configured to allow for
browser caching of the static content, to further increase application performance. Reverse proxying and caching are
configured thought NGINX’s default.conf file’s server configuration section:
server {
listen 80;
server_name localhost;
location ~* \/assets\/(css|images|js|template)\/* {
root /usr/share/nginx/;
expires max;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
add_header Vary Accept-Encoding;
access_log off;
}
The two Tomcat instances will be configured on NGINX, in a load-balancing pool, using NGINX’s default round-robin load-
balancing algorithm. This is configured through NGINX’s default.conf file’s upstream configuration section:
upstream backend {
server app01:8080;
server app02:8080;
}
The Spring Music application can be run with MySQL, Postgres, Oracle, MongoDB, Redis, or H2, an in-memory Java SQL
database. Given the choice of both SQL and NoSQL databases available for use with the Spring Music application, we will
select MongoDB.
The Spring Music application, hosted by Tomcat, will store and modify record album data in a single instance of MongoDB.
MongoDB will be populated with a collection of album data when the Spring Music application first creates the MongoDB
database instance.
Lastly, the ELK Stack with Logspout, will aggregate both Docker and Java Log4j log entries, providing debugging and
analytics to our demonstration. I’ve used the same method for Docker and Java Log4j log entries, as detailed in this previous
post.
Spring Music Environment
To build, deploy, and host the Java Spring Music application, we will use the following technologies:
Gradle
GitHub
Travis CI
git
Oracle VirtualBox
Docker
Docker Compose
Docker Machine
Docker Hub
All files necessary to build this project are stored in the garystafford/spring-music-docker repository on GitHub. The Spring
Music source code and build artifacts are stored in a seperate garystafford/spring-music repository, also on GitHub.
Build artifacts are automatically built by Travis CI when changes are checked into the garystafford/spring-music repository on
GitHub. Travis CI then overwrites the build artifacts back to a build artifact branch of that same project. The build artifact
branch acts as a pseudo binary repository for the project. The .travis.yaml file, gradle.build file, and deploy.sh
script handles these functions.
.travis.yaml file:
language: java
jdk: oraclejdk7
before_install:
- chmod +x gradlew
before_deploy:
- chmod ugo+x deploy.sh
script:
- bash ./gradlew clean warNoStatic warCopy zipGetVersion zipStatic
- bash ./deploy.sh
env:
global:
- GH_REF: github.com/garystafford/spring-music.git
- secure: <secure hash here>
gradle.build file snippet:
#!/bin/bash
# reference: https://gist.github.com/domenic/ec8b0fc8ab45f39403dd
# Force push from the current repo's master branch to the remote
# repo's build-artifacts branch. (All previous history on the gh-pages branch
# will be lost, since we are overwriting it.) We redirect any output to
# /dev/null to hide any sensitive credential data that might otherwise be exposed.
Environment variables pre-configured on Travis CI.
git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:build-artifacts > /dev/null
2>&1
Base Docker images, such as NGINX, Tomcat, and MongoDB, used to build the project’s images and subsequently the
containers, are all pulled from Docker Hub.
This NGINX and Tomcat Dockerfiles pull the latest build artifacts down to build the project-specific versions of the NGINX
and Tomcat Docker images used for this project. For example, the NGINX Dockerfile looks like:
FROM nginx:latest
proxy:
build: nginx/
ports: "80:80"
links:
- app01
- app02
hostname: "proxy"
app01:
build: tomcat/
expose: "8080"
ports: "8180:8080"
links:
- nosqldb
- elk
hostname: "app01"
app02:
build: tomcat/
expose: "8080"
ports: "8280:8080"
links:
- nosqldb
- elk
hostname: "app01"
nosqldb:
build: mongo/
hostname: "nosqldb"
volumes: "/opt/mongodb:/data/db"
elk:
build: elk/
ports:
- "8081:80"
- "8082:9200"
expose: "5000/upd"
logspout:
build: logspout/
volumes: "/var/run/docker.sock:/tmp/docker.sock"
links: elk
ports: "8083:80"
environment: ROUTE_URIS=logstash://elk:5000
# clone project
git clone -b master
--single-branch https://github.com/garystafford/spring-music-docker.git &&
cd spring-music-docker
# build VM
docker-machine create --driver virtualbox springmusic --debug
Results
Resulting Docker images and containers:
gstafford@gstafford-X555LA:$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
facb6eddfb96 music_proxy "nginx -g 'daemon off" 46 seconds ago Up 46
seconds 0.0.0.0:80->80/tcp, 443/tcp music_proxy_1
abf9bb0821e8 music_app01 "catalina.sh run" About a minute ago Up
About a minute 0.0.0.0:8180->8080/tcp music_app01_1
e4c43ed84bed music_logspout "/bin/logspout" About a minute ago Up
About a minute 8000/tcp, 0.0.0.0:8083->80/tcp music_logspout_1
eca9a3cec52f music_app02 "catalina.sh run" 2 minutes ago Up 2
minutes 0.0.0.0:8280->8080/tcp music_app02_1
b7a7fd54575f mongo:latest "/entrypoint.sh mongo" 2 minutes ago Up 2
minutes 27017/tcp music_nosqldb_1
cbfe43800f3e music_elk "/usr/bin/supervisord" 2 minutes ago Up 2
minutes 5000/0, 0.0.0.0:8081->80/tcp, 0.0.0.0:8082->9200/tcp music_elk_1
Partial result of the curl test, calling NGINX. Note the two different upstream addresses for Tomcat. Also, note the sharp
decrease in request times, due to caching.
HTTP/1.1 200 OK
Server: nginx/1.9.4
Date: Mon, 07 Sep 2015 17:56:11 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2090
Connection: keep-alive
Accept-Ranges: bytes
ETag: W/"2090-1441648256000"
Last-Modified: Mon, 07 Sep 2015 17:50:56 GMT
Content-Language: en
Request-Time: 0.521
Upstream-Address: 172.17.0.121:8080
Upstream-Response-Time: 1441648570.774
HTTP/1.1 200 OK
Server: nginx/1.9.4
Date: Mon, 07 Sep 2015 17:56:11 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2090
Connection: keep-alive
Accept-Ranges: bytes
ETag: W/"2090-1441648256000"
Last-Modified: Mon, 07 Sep 2015 17:50:56 GMT
Content-Language: en
Request-Time: 0.326
Upstream-Address: 172.17.0.123:8080
Upstream-Response-Time: 1441648571.506
HTTP/1.1 200 OK
Server: nginx/1.9.4
Date: Mon, 07 Sep 2015 17:56:12 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2090
Connection: keep-alive
Accept-Ranges: bytes
ETag: W/"2090-1441648256000"
Last-Modified: Mon, 07 Sep 2015 17:50:56 GMT
Content-Language: en
Request-Time: 0.006
Upstream-Address: 172.17.0.121:8080
Upstream-Response-Time: 1441648572.050
HTTP/1.1 200 OK
Server: nginx/1.9.4
Date: Mon, 07 Sep 2015 17:56:12 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2090
Connection: keep-alive
Accept-Ranges: bytes
ETag: W/"2090-1441648256000"
Last-Modified: Mon, 07 Sep 2015 17:50:56 GMT
Content-Language: en
Request-Time: 0.006
Upstream-Address: 172.17.0.123:8080
Upstream-Response-Time: 1441648572.266
NGINX: 192.168.99.100/nginx_status
Kibana: 192.168.99.100:8081
Elasticsearch: 192.168.99.100:8082
Elasticsearch: 192.168.99.100:8082/_status?pretty
Logspout: 192.168.99.100:8083/logs
Helpful Links
Cloud Foundry’s Spring Music Example
Introduction to Gradle
Spring Framework