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

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 &amp;&amp; 
  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 &lt; /tmp/modeshape-dbh.xml.tmpl &gt; /usr/local/tomcat/conf/Catalina/localhost/modeshape-dbh.xml
/bin/bash /tmp/mo &lt; /tmp/repository-config.json.tmpl &gt; /etc/dbh/classpath/modeshape/repository-config.json
/bin/bash /tmp/mo &lt; /tmp/jgroups-config.xml.tmpl &gt; /etc/dbh/classpath/modeshape/jgroups-config.xml
/bin/bash /tmp/mo &lt; /tmp/server.xml.tmpl &gt; /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" ) &amp;
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.

Leave a Reply

Your email address will not be published. Required fields are marked *