From Monolith to Kubernetes Architecture — Part II — Dockerfile

Ori Berkovitch
5 min readApr 11, 2022

--

Photo by Glen Carrie on Unsplash

This is a multipart of a series Going From a Monolith App to Kubernetes.

Part I — Containerize

Part II — Dockerfile

Part III — Minikube

Part IV — GKE / GCP

Create the Docker Image

In step 1, we migrated the application properties to environment variables. Environment variables are the basic tools for running applications in Kubernetes, and this is indeed what we’ve done.

So as Step 2 of the process, now we need to actually containerize the application in to a Docker. This requires us to adjust subtle configurations such as the networking and the libraries to be included in the final Docker image.

Principal Considerations and process

We’ve already set the application to use the environment variables for configuration, but nevertheless, our application doesn’t depend solely on the kubernetes environment configuration. Services such as Redis, the rational Databases (Postgres / MySql / etc.), Kafka, and more — these are all stateful services which, in more cases, won’t be managed and executed in the same kubernetes cluster.

Building a Docker image can be done in a “wishful thinking” matter. And that’s what we have done in our case. What do we mean by “wishful thinking”? well, here are the steps we followed:

Step 1: Pick a base image for the Docker image

Step 2: Add environment variables, as planned in Part I

Step 3: Add our compiled and built application to the Docker Image

Step 4: See what’s missing, and fill the gaps 💡

Illustration

The logical structure of the system looks like this

Illustrated architecture

Dockerfile and “docker run” command

Our final (development!) Dockerfile looked like this:

FROM tomcat
COPY server.xml /usr/local/tomcat/conf/
COPY context.xml /usr/local/tomcat/conf/
COPY logging.properties /usr/local/tomcat/conf/logging.properties
#COPY catalina.properties /usr/local/tomcat/conf/catalina.properties
COPY web/target/ROOT/WEB-INF/lib /usr/local/tomcat/lib

ENV ENV DEV
ENV TRUNBACKGROUNDJOB false
ENV DB_USERNAME postgres
ENV DB_PASSWORD postgres
ENV ES_ADDRESS host.docker.internal
ENV INFLUX_ADDRESS host.docker.internal:8086
ENV TREEBUTE_INFLUX_TOKEN 123...
... <-- more ENV stuff hereENV LOGGING_LEVEL_ROOT=INFO
ENV LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=INFO
ENV LOGGING_LEVEL_ORG_HIBERNATE=INFO

ENV JAVA_OPTS -DTREEBUTE_ENV=$ENV -DRUNBACKGROUNDJOB=$TREEBUTE_RUNBACKGROUNDJOB -DDB_USERNAME=$TREEBUTE_DB_USERNAME -DDB_PASSWORD=$TREEBUTE_DB_PASSWORD -DES_ADDRESS=$TREEBUTE_ES_ADDRESS -DTREEBUTE_INFLUX_ADDRESS=$TREEBUTE_INFLUX_ADDRESS -DINFLUX_TOKEN=$TREEBUTE_INFLUX_TOKEN ... <-- more opts here

ENV PATH="/usr/local/tomcat/webapps/ROOT/WEB-INF/lib:${PATH}"

ADD web/target/ROOT.war /usr/local/tomcat/webapps/

EXPOSE 8080
EXPOSE 8000CMD ["/usr/local/tomcat/bin/catalina.sh", "jpda", "run"]

And we build the image with the following command:

docker run — rm -it -e JPDA_ADDRESS=8000 -e JPDA_TRANSPORT=dt_socket -p 8080:8080 -p 8000:8000 — name my-image-name $(docker build -q .)

(Notice that medium.com is causing “minus minus” symbols to look like a large dash 🙀; Know your docker run flags…)

A few things to note:

  1. Notice the COPY lines at the top. We took the original files (e.g server.xml, context.xml, etc.) from the container itself. Then, we modified them as we pleased, and made sure to copy them BACK when the final and actual container is ran.
  2. Notice the last COPY statement. What this does, is to copy all the .jar files required for the application to work. This is a results of getting errors such as: “ClassNotFoundException”; This is related to the requirement of having the jars available on the classpath for the application. The Tomcat / Catalina classpath is configured in catalina.properties; more on the issue can be found here: https://www.mulesoft.com/tcat/tomcat-classpath
  3. Notice the ENV variables, and the ENV JAVA_OPTS line. This is a direct reference to Part I, where we build the application to consume the configuration from the Environment Variables
  4. Notice the ADD line. We are adding the actual application to the image
  5. Notice, in this example, we expose ports 8080 for http, and port 8000 for debugging the application running in the Docker container
  6. Notice the usage of the address “host.docker.internal”; This is a special DNS representing the host machine (your development computer running the Docker daemon). This is how the application running inside the docker communicates with the services running on your local machine

Success Criteria

The success criteria for moving to the next step, is to have the application running and communicating seamlessly with the surrounding services. If this is happening on the development machine, that means the containerization of the application is complete, and now it is time to integrate it to a kubernetes architecture! 😻

Final notice on Kafka, Docker, localhost, and Connection Errors

Since Kafka was running on the local machine, and the containerized application is using Kafka from inside the Docker to the “outside” local network of the dev machine, another setting was required, and that is the “advertised listeners” on the kafka configuration.

Kafka let’s the consumers know how and where they should connect to the cluster. Now, the consumers reside INSIDE the docker container, and they can only find the Kafka cluster on the address host.docker.internal;

Indeed, this is what was required to connect them. So, we had to change the kafka server.properties files to the following:

advertised.listeners=PLAINTEXT://host.docker.internal:9092

Now, the docker container can communicate with Kafka from INSIDE the docker, to the local machine; We got the hint from this article: https://stackoverflow.com/questions/51630260/connect-to-kafka-running-in-docker

One additional small detail: you should add host.docker.internal to your hosts, or otherwise Kafka will start going crazy

127.0.0.1 host.docker.internal

More references:

https://stackoverflow.com/questions/35586778/docker-container-doesnt-expose-ports-when-net-host-is-mentioned-in-the-docker

--

--