Hello! In this post we’ll discuss a real customer use case and talk about some of the issues we solved in the process.
The customer’s architecture requirements included interfacing with a third party service into our custom PHP framework code. As the customer’s requirements also stipulated the service code must be written in Java (or .Net) we had to introduce Tomcat into the equation.
Even though PHP and Tomcat shared the server on the initial system, we decided to wrap all services within their own Docker containers.
Let’s start with a docker-compose file that will define our tomcat container. We used tomcat:8.5.13-jre8-alpine as the base image.
At first glance this file contains 3 big sections, volumes, ports and expose.
The Volumes section will map files and folders needed to run a tomcat instance, included our app. You will notice in the example below that I included, in addition with .war file, JKS certificates and some configuration files I will explain later in this post.
The ports section will just bind container’s listen port with internal tomcat port (8080).
In the expose section we open some ports, in particularly 8009, that is required for server admin.
version: '2.1' services: my_tomcat: my_tomcat tomcat:8.5.13-jre8-alpine volumes: - ./conf/catalina.properties:/usr/local/tomcat/conf/catalina.properties - ./client-keystore.jks:/etc/tomcat/client-keystore.jks - ./truststore.jks:/etc/tomcat/truststore.jks - ./myApp.war:/usr/local/tomcat/webapps/myApp.war env_file: ./environment.env ports: - "8080:8080" expose: - "8009" - "8080"
As you may have noticed, we isolated the environment variables out of the docker-composer, in a new environment.env file, specified in env_file.
In addition to defining container variables, it will define JVM requirements, such as JAVA_OPTS.
SHELL=/bin/sh TOMCAT7_GROUP=tomcat7 TOMCAT_CFG_LOADED="1" JAVA_OPTS="-Djavax.net.debug=ssl,handshake,keymanager,verbose -Dendpoint=https://3rd-party.domain.com/services/api.wsdl -Djavax.net.ssl.keyStore=/etc/tomcat/client-keystore.jks -Djavax.net.ssl.keyStorePassword=secret -Djavax.net.ssl.trustStore=/etc/tomcat/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit -Djava.awt.headless=true -Xms512m -Xmx2048m -XX:+UseConcMarkSweepGC -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 -Djdk.tls.client.protocols=TLSv1.1,TLSv1.2 -Dsun.security.ssl.allowUnsafeRenegotiation=true" SECURITY_MANAGER="false"
For this example, we’re telling the JVM to show verbose debug info for ssl and keymanager use (-Djavax.net.debug), which is the endpoint of the service (-Dendpoint), keystore and truststore files and passphrases repectively (-Djavax.net.ssl.keyStore, -Djavax.net.ssl.keyStorePassword, -Djavax.net.ssl.trustStore and -Djavax.net.ssl.trustStorePassword), among others.
Unfortunately, this is not enough with Alpine based container. In shortest explanation, when you start the container it runs catalina.sh that loads default configuration from the image.
But don’t panic, here’s the trick: the custom configuration can be placed in the catalina.properties file, letting those vars available for the system to be accessed like below:
These custom properties file will be mounted as volume, replacing the default one and voila.
Once you have created your certificates stores (there is a powerful tool provided by Java, the keytool, which will be a topic for our future blog post), just copy the catalina.properties from standard tomcat and append variables at the end:
endpoint=https://3rd-party.domain.com/services/api.wsdl javax.net.ssl.keyStore=/etc/tomcat/client-keystore.jks javax.net.ssl.keyStorePassword=secret javax.net.ssl.trustStore=/etc/tomcat/truststore.jks javax.net.ssl.trustStorePassword=changeit
While this was a unique project, the common solution often works well. When I ran into common issues I also found a lot of recommendations for adding or updating a JAVA_OPTS environment variable. We did end up improvising to a point, but this was mostly reserved for edge cases.
I am glad you’re still here! Thank you and hope you have enjoyed reading. See you in next posts.