A Kubernetes Tale: Part I — Building your Java project

A practical (and somewhat opinionated) guide to kick-off your project with Docker and Helm

Image for post
Image for post
Photo by Loik Marras on Unsplash
  • Running and orchestration — on a Kubernetes cluster
  • Configuration management — using Helm to manage releases and secrets
  • CI/CD pipelines — using CircleCI
  • [Appendix] Monitoring — using New Relic
  • Kubernetes 1.15 (on GCP)
  • Helm 3.2.4
  • New Relic agent 5.9.0

Generating our application

Our first step is to create an executable version of our application. In this case, we’ll be creating a Spring Boot application exposed through a REST API. To create an application, you can use Spring initializr.

Image for post
Image for post
  • Spring Actuator: Includes a set of production-ready features. We’ll use the health functionality later on.
./gradlew bootRun

Running the application

Running the application standalone

To run this application in a Docker container we’ll have to use a fat jar (also known as uber-jar). This is a jar file that contains all our project classes and resources plus all its dependencies.

A full list of the subtasks run by the build task: assemble, bootBuildImage, bootJar, build, buildDependents, buildNeeded…
A full list of the subtasks run by the build task: assemble, bootBuildImage, bootJar, build, buildDependents, buildNeeded…
A full list of the subtasks run by the build task
If you want to check the contents of the jar you can run jar tf build/libs/api-0.0.1-SNAPSHOT.jar
java -jar build/libs/dockeriser-0.0.1-SNAPSHOT.jar

Running the application with different flavours

We’ll leverage Spring profiles to have the flexibility of configuring the application in different ways, depending on the environment. So for this we’ll create 4 profiles:

  • Dev — for development environments
  • Prod — for production environments
  • Container — to execute the application in a container
  • application-dev.yaml
  • application-prod.yaml
  • application-container.yaml
java -Dspring.profiles.active=dev,container -jar build/libs/dockeriser-0.0.1-SNAPSHOT.jar

Why a “container” profile?

A good use case for profiles is obviously environment-based configuration. Depending on this, you might need different DB access URLs, storage bucket IDs or simply a specific configuration you want to apply to an environment without affecting the rest (i.e.: logging verbosity). However, there might be cases where you don’t need to do this because you can either externalise that configuration or multiple environments share the same set of parameters. I’ll use an example in one of the next articles of the series and it’ll all make sense, I promise.

The main course: creating a Dockerfile

Docker is a tool to build and run applications in containers — in case you’ve never heard of Docker, think of these containers as super-lightweight VMs, and of the Dockerfiles as the specs that define how they should run. By default these containers are stateless: meaning they can be destroyed and started from scratch based solely on their specs. Think of it as the shopping list of the container.

The JVM flags

The details of the flags used to run the application have some good practices I collected for this type of applications:

  • -XX:MaxRAMPercentage is a new flag introduced in Java 11 (then backported to Java 8) that will limit the RAM used by the JVM as a fraction of the overall available memory (if you’re using older versions of Java, you might need to use MaxRAMFraction instead, which is not as flexible as this one but it should do the trick). Take into account that, while the heap is the part of the JVM that will take most of the RAM, there are some other parts, such as thread allocation and code cache that will require memory.
  • -Dspring.profiles.active=container as I mentioned before, the container profile will allow us to have the same configuration that will be required when running our application in a container environment, so it makes sense to anchor it independently of dev/staging/prod. If you noticed, I’m not including the environment yet, so we can deploy the same Docker image in multiple clusters across envs. This configuration will be later externalised and managed by a setting in Kubernetes.
  • -Duser.timezone=Europe/London sets a default timezone regardless of the VMs locale. This can be useful when deploying the same application in different regions, without causing differences in the way your application interprets timezone when rendering/storing datetime fields. This on its own it might not be enough (i.e.: JDBC requires an extra setting to recognise this) but I’ll leave the long explanation for another article. Just keep this in mind! Below a really comprehensive article around this topic, just take the time to read it! (pun intended)
  • -Djava.security.egd=file:/dev/./urandom tells the VM to use a non-blocking stream of higher entropy to generate random values. Huh? Yeah, I know, it seems like a concatenation of random words but I swear it’s not. It’s a stream that uses signal noise from different parts of your computer to produce strings that look “more random”. Want to see what it looks like? Just run cat /dev/urandom. If you’re on Windows, sorry mate, cannot help you there. There’s been a few fixes on this matter since JDK8, so you might want to check the SO post before just in case.

Building and running your Docker image

Once this Dockerfile is generated, it needs to be built (-t specifies a tag) into an image:

docker build -t dockeriser .
docker run -P dockeriser:latest
A detailed list of the active running containers: image name, container id and age, bound ports, etc
A detailed list of the active running containers: image name, container id and age, bound ports, etc
If you run docker ps you’ll see a list of the running containers.
docker build -t dockeriser:`date +%Y%m%d_%H%M` .

Automate, automate, automate

Lastly, one piece of advice: automate, automate, automate. It can seem like an obvious thing to say, but it’s not uncommon to see projects where there’s documentation (or sometimes not even that) with the detailed explanation of all the steps to follow to build and deploy an application. Don’t be that person. Please. I’m serious.

./gradlew build
docker build -t dockeriser:`date +%Y%m%d_%H%M` .

Technology enthusiast. Less is more.

Get the Medium app