Dockerizing the Modeshape REST and JCR App
Overview
Modeshape is a Java Content Repostiory (JCR-283) implementation, which provides an open-source, hierarchical content management system.
The goal of this exercise is to make a single Docker image which can be used in all environments. Any environment specific or volatile parameters should not be part of the image, but rather supplied as parameters (or a file) when running the image. This simplifies deployment and assures the run-time environment is identical. While these instructions are specific Modeshape, they can also serve as a general approach for externalizing settings and making other applications into Docker images.
An upcoming post will detail deploying and configuring the docker image in DC/OS.
Topics
- Externalized Settings
- Customizations
- Template Systems
- Creating a Docker Image
- Addendum
Externalize Settings
In order to make a single docker image for the app, all environment specific settings must be identified, and moved into template files. Below are the parameters identified for this exercise.
Externalized Settings
Variable | Description | Sample Value |
ENVIRONMENT | Environment acronym, used in files and | dev |
CATALINA_OPTS | Options to pass to tomcat | -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m |
REST_INIT_WAIT | Amount of time to wait, after starting app, before initiating rest call to modeshape to initialize the rest API |
45 |
DB_CONN_URL | JDBC Connection URL | |
DB_DRIVER_CLASS | JDBC Driver Class | |
DB_USER | Database User Name | modeshape |
DB_PASSWORD | Database User’s Password | REDACTED |
REINDEX_ASYNC | If true, uses separate thread to update the index. Otherwise, reindexing happens in main thread. async=false is more consistent, but slows down the app | if_missing |
REINDEX_MODE | Reindex mode. We’re using if_missing. Incremental is only for use in journaling and has limitations. I don’t want to deal with right now. | true |
Customizations
- Application’s
- Context
- web.xml
- Repository Configuration
- JGroups Configuration
- Tomcat
- server.xml
Context
In this application example, the context is named “modeshape-dbh.xml”. In it, the JDBC datasource for Modeshape is defined. In order to make this work for all environments, each with a different database, the context file is supplied as a template.
Database parameters have been extracted out.
web.xml
Modeshape’s REST web.xml file was modified to look in /etc/…/classpath/modeshape for it’s repository configuration file. This is outside of the WAR file so that definition of content types, indexes, or other configuration related items are separate from the WAR.
Repository Configuration
File: repository-config.json.tmpl
It is important that the configuration be outside of the WAR file and main folder. This is necessary so the templating system can create environment specific copies of the file, when the docker image is run.
The following parameters were changed into replacement variables within the repository configuration.
Parent Property | Property | Description |
reindexing | async | This controls whether Modeshape performs re-indexing synchronously (with blocking) or asynchronously (without blocking). |
reindexing | mode | Determines how reindexing is performed. In all cases, I’ve selected “if_missing” rather than “incremental”. This is based on reading the documentation. |
clustering | clusterName | Each environment should have a different cluster name. I’m including the environment’s short name as part of the custerName. |
JGroups Clustering Configuration
File: jgroups-config.xml.tmpl
Parent Property | Property | Description |
tcp | bind_addr | Pass in specific host name (or IP) to use for clustering. |
tcp | bind_port | Pass in specific port to use for clustering. |
Template Systems
There are numerous solutions for dynamically passing in parameters into a Docker Image. The two I’m most familiar with are Compose (Environment Variables) and Mo (Mustache Templates in Bash).
.For the scenario I encountered, I’m running Docker images within Mesosphere DC/OS and Compose is not installed there. We chose to use keep it simple and use Mo. It allows for doing variable replacement in template files, as part of the Docker image startup process.
Example Template – Application Context
Below is an example template for Application Content, modeshape-dbh.xml.tmpl. It is used in generating modeshape-dbh.xml, with all of the {{PARAMETERS}}’s replaced. The configuration file is generated upon starting the docker image, with the parameters passed in.
<?xml version="1.0" encoding="UTF-8"?> <Context antiJARLocking="true"> <Resource name="jdbc/modeshape" factory="com.zaxxer.hikari.HikariJNDIFactory" auth="Container" type="javax.sql.DataSource" dataSource.user="{{DB_USER}}" dataSource.password="{{DB_PASSWORD}}" driverClassName="{{DB_DRIVER_CLASS}}" jdbcUrl="{{DB_CONN_URL}}" connectionTestQuery="select 1" leakDetectionThreshold="30000" minimumIdle="10" maximumPoolSize="50" connectionTimeout="300000" idleTimeout="600000" maxLifetime="1800000" /> </Context>
Example Template – Jgroups-config.xml.tmpl
When running the application in DC/OS, it is DC/OS that automatically assigns and provides the HOST and PORT_JGROUPS parameters.
<config xmlns="urn:org:jgroups"<config xmlns="urn:org:jgroups" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/JGroups-3.1.xsd"> <TCP bind_addr="{{HOST}}" bind_port="{{PORT_JGROUPS}}" recv_buf_size="${tcp.recv_buf_size:130k}" send_buf_size="${tcp.send_buf_size:130k}" max_bundle_size="64K" sock_conn_timeout="300" thread_pool.min_threads="0" thread_pool.max_threads="20" thread_pool.keep_alive_time="30000" /> <FILE_PING location="/tmp/jgroups"/> <!-- using convoy for shared folder --> <!-- other configuration details --> </config>
Creating a Docker Image
Prerequisites
- PostgreSQL or other database with JDBC Drivers.
- Docker Tool installed
- Mo – Mustache Templates In Bash
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | FROM tomcat:8.5.15-jre8 RUN wget https://raw.githubusercontent.com/tests-always-included/mo/master/mo -O /tmp/mo && chmod 777 /tmp/mo # Create directories, outside of the unpacked WAR RUN mkdir -p /etc/dbh/classpath/modeshape RUN mkdir -p /usr/local/modeshape RUN mkdir -p /usr/local/tomcat/conf/Catalina/localhost # Copy all Custom Node Definitions (CND) files to external folder ADD cnd /etc/dbh/classpath/modeshape/cnd # Add modeshape WAR file to image and copy it to Tomcat's webapps folder ADD modeshape-dbh.war /tmp/modeshape-dbh.war RUN cp /tmp/modeshape-dbh.war /usr/local/tomcat/webapps/ # Server.xml.tmpl allows passing in a custom port number. ADD server.xml.tmpl /tmp/server.xml.tmpl # Modeshape.xml.tmpl allows passing in database parameters ADD modeshape.xml.tmpl /tmp/modeshape.xml.tmpl # repository-config.json.tmpl allows setting indexing params # and environment in jgroups cluster name ADD repository-config.json.tmpl /tmp/repository-config.json.tmpl # jgroups-config.xml.tmpl allows setting the IP address and port, # which is auto-generated by DC/OS. ADD jgroups-config.xml.tmpl /tmp/jgroups-config.xml.tmpl # Set any specific environment variables for Tomcat ADD setenv.sh /usr/local/tomcat/bin/setenv.sh # Setup and run the starter.sh script as the default CMD. # It uses run-time parameters, mo, generating the environment specific files # and starts Tomcat. ADD starter.sh /user/local/modeshape/starter.sh RUN chmod 777 /user/local/modeshape/starter.sh CMD /bin/bash /user/local/modeshape/starter.sh |
Starter file
1 2 3 4 5 6 7 8 | #!/bin/bash mkdir -p /usr/local/tomcat/conf/Catalina/localhost /bin/bash /tmp/mo < /tmp/modeshape-dbh.xml.tmpl > /usr/local/tomcat/conf/Catalina/localhost/modeshape-dbh.xml /bin/bash /tmp/mo < /tmp/repository-config.json.tmpl > /etc/dbh/classpath/modeshape/repository-config.json /bin/bash /tmp/mo < /tmp/jgroups-config.xml.tmpl > /etc/dbh/classpath/modeshape/jgroups-config.xml /bin/bash /tmp/mo < /tmp/server.xml.tmpl > /usr/local/tomcat/conf/server.xml ( sleep $REST_INIT_WAIT ; touch /tmp/7 ; /usr/bin/curl --user wakeup:V9AH6V2zlVwo -H "Accept: application/json" "http://localhost:$PORT_HTTP/modeshape-dbh/rest/repo/default/items" ) & catalina.sh run "$@" |
Make the Docker Image
1 | docker build -t modeshape-dbh . |
Initial Database Setup
On the first time, setup your database server, database, and database user. For this example, I’m using PostgreSQL in Docker.
1 2 3 4 5 | # Run an instance of postgresql docker run --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -d postgres:9.6.2-alpine |
Next, create a database and database user. In this example, the following will work with PostgreSQL.
1 2 | CREATE USER modeshape PASSWORD 'REDACTED'; CREATE DATABASE modeshape WITH OWNER modeshape; |
Now, run ModeShape
The initial run requires specifying the parameters along with the selected Docker image.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #docker stop modeshape; docker rm modeshape docker run --name modeshape -p 8080:8080 -p 7800:7800 -m 2G -e DB_CONN_URL=jdbc:postgresql://192.168.99.100:5432/modeshape -e ENVIRONMENT=dbh -e DB_DRIVER_CLASS=org.postgresql.Driver -e DB_USER=modeshape -e DB_PASSWORD=REDACTED -e CATALINA_OPTS='-Xmx1g -XX:+HeapDumpOnOutOfMemoryError' -e JAVA_OPTS='' -e PORT_HTTP=8080 -e PORT_JGROUPS=7800 -e HOST=localhost -e REINDEX_ASYNC=true -e REINDEX_MODE=if_missing -e REST_INIT_WAIT=30 -d modeshape-dbh docker attach modeshape |
Later runs of Modeshape can be performed with the commands below
1 2 3 | docker start postgres docker start modeshape docker attach modeshape |
Addendum
TMI or Perhaps not so relevant notes
- Since I am more familiar with Tomcat than WildFly (Modeshape’s default container), I chose a Tomcat docker image as the base.
- Apache Shiro is used for authentication.