Spring Boot CI/CD on Kubernetes using Terraform, Ansible and GitHub: Part 7

Martin Hodges
6 min readNov 6, 2023

--

Part 7: Creating a Spring Boot Application to add to your cluster

This is part of a series of articles that creates a project to implement automated provisioning of cloud infrastructure in order to deploy a Spring Boot application to a Kubernetes cluster using CI/CD. In this part we create the Spring Boot Application and install it on the cluster.

Follow from the start — Introduction

In this article we are going to set up our Spring Boot project to allow the Quick Queue application to run on your new Kubernetes cluster.

You can find example code for Quick Queue application here: https://github.com/MartinHodges/Quick-Queue-Application/tree/part7. This article does describe what you need to create the application from scratch if you would prefer.

Creating Quick Queue

The easiest way to kick start your Spring Boot application is by using the Initializr web site at https://start.spring.io. Here you should choose the following (clearly you can select your own options for your own project but I am assuming you will follow the Quick Queue example here):

  • Java 17
  • Gradle — Groovy
    (the CI/CD examples in these articles use this)
  • Spring Boot 3.1.4

Spring Packages

  • Spring Web
  • Spring Data JPA
  • PostgreSQL Driver
  • Lombok (install this into your IDE)
  • springdoc-openapi-starter-webmvc-ui
    (need to add this dependency later)

You are now able to create your Quick Queue application. It is not the intention of these articles to tell you how to do this and you can create your own or copy one from here.

Quick Queue Application Description

The Quick Queue application is a Spring Boot application. It is not my intention to describe what a Spring Boot application is or how it is built. I do want to highlight a few things that allow it to be used as part of a Kubernetes Pod.

  1. The database connection details have been parameterised to allow them to be defined externally, ie: db.host, db.username, db.password
  2. In the application.yml file, a section has been added (sql.init.schema-locations) to initialise the database using the schema-postgresql.sql file, which creates the schema as Hibernate will not create this
  3. Hibernate has been set up to automatically create the tables and sequences it needs (this should be done using tools such as liquibase or Flyway)
  4. The REST API is to be deployed on port 9191
  5. Monitoring has been enabled on port 9000 (management.endpoints.web.exposure.include)
  6. Swagger documentation has been enabled

Running Your Application Locally

As Spring Boot application, you can run this in your local development. You may need to set up a local postgres database (you can do this with Docker by running a postgres image) and you will need to configure the environment variables for the database URL, username and password, eg:

-Ddb.host=localhost -Ddb.username=postgres -Ddb.password=<password>

Once it is running, you can then test the REST APIs using postman or a similar tool. Note that there are two sets of endpoints:

  • http://localhost:9191/api/v1
  • http://localhost:9000/actuator

The first is the application API whilst the second is an monitoring API. The application has the following endpoints:

  • GET queues — get a list of all queues
  • GET queues/{id} — get a specific queue
  • POST queues — create a queue
  • PUT queues/{id} — update a queue
  • DELETE queues/{id} — create a queue
  • GET queues/{id}/queue-entries — get all entries for a queue
  • GET queues/{id}/queue-entries/next — get next entry for a queue
  • POST queues/{id}/queue-entries — add entry for a queue
  • DELETE queues/{id}/queue-entries/{id} — use/pop entry for a queue

After testing your application, you are ready to deploy it manually to your Kubernetes cluster.

Dockerising Your Application

You should now have a Spring Boot application. To run this in your Kubernetes cluster, it needs to be created as a Docker image.

A Docker image is a lightweight virtual machine. Docker uses the host’s system resources to run a Docker image and it does this by creating a container for the image to run in.

The image that is loaded into the container is built in layers, each layer being able to overwrite aspects of the previous layer. In this way, images can be extended and enhanced.

When Kubernetes schedules a Pod, it creates a Docker container in which it runs the given Docker image.

To transform your Spring Boot application into a Docker image, you need to use Docker to build it. Docker needs to know how to build it and it does this through a file in the root of the project called Dockerfile (no extension).

Create Dockerfile:

FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY build/libs/quick-queue-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT java -jar -Dspring.datasource.url=jdbc:postgresql://${DB_HOST}:5432/postgres?currentSchema=quick_queue -Dspring.datasource.username=${DB_USER} -Dspring.datasource.password=${DB_PW} ./app.jar

The first line tells Docker to base this image on the openjdk:17-jdk-slim image.

The next line tells Docker to map the /tmp folder to a temporary folder on the host. This is required as Spring Boot utilises the /tmp folder. You could use the /tmp folder inside the container but it gives permission problems and is slower than using a host based volume.

Next, your application JAR is copied to the image as app.jar. You may need to adjust this line to match your jar file name.

Finally, the ENTRYPOINT tells Docker how to run your application once the container is created. We use ENTRYPOINT instead of CMD as the latter allow the command to be overwritten from the command line whereas the former does not. We do not pass any commands from the command line but just to make sure, ENTRYPOINT is a better option.

We also execute the ENTRYPOINT in shell mode rather than exec mode as this runs our ENTRYPOINT within a shell and allows us to pass environment variables through shell expansion (eg: ${DB_HOST}). This also allows us to pass in secrets (such as passwords) as external parameters and without having to enter credentials into files stored in git. We shall see how this works with Kubernetes secrets later. For now, know that to run the image file, you will need to define the following environment variables (these override the application.yml file parameters we saw earlier):

  • DB_HOST
  • DB_USER
  • DB_PW

You can build you docker image from within the application folder with:

gradle build -x test
docker build --platform linux/amd64 -t <dockerhub username>/qq_app:latest .

Remember to enter your Docker Hub user name between the < and >. Also, don’t miss the dot on the end of the command to ensure Docker uses the Dockerfile in the local folder.

Running Your Application in a Container

Now you have a dockerised version of your application, you can run it locally within a docker container. For this you will require Docker. On a Mac, you get Docker with Docker Desktop, which can be installed according to these instructions.

The platform option ensures that you create a Docker image that can run in a cloud container.

You can now try your new docker image with:

docker run -d -p 9000:9000 -p 9191:9191 -e DB_HOST=host.docker.internal -e DB_USER=postgres -e DB_PW=<password> <dockerhub username>/qq_app:latest
  • -d = run as a daemon
  • -p = map an internal application port to a host port
  • -e = set an environment variable

You can look at the application logs with:

docker ps
docker logs -f <container id>

If you want to stop the container:

docker ps
docker stop <container id>
docker container rm <container id>
docker image ls
docker image rm <image id>

Creating a Docker Hub Image

You should now push the image to Docker Hub. For this you will need a Docker Hub Container Image Library account. If you want your repositories to be private, you will need to subscribe to a plan for a small, monthly fee.

Before you can push images (or download private ones) you will need to login.

docker login
docker push <dockerhub username>/qq_app:latest

Note that the :latest is a tag for the repository and refers to a particular version of the image. Each time you push with :latest, it will overwrite the previous version. If you want to maintain versions, also push to a tag :<version number>. We will make use of this in our CI/CD pipeline.

You can test your application using:

docker pull <dockerhub username>/qq_app:latest
docker run -d -p 9000:9000 -p 9191:9191 -e DB_HOST=host.docker.internal -e DB_USER=postgres -e DB_PW=<password> <dockerhub username>/qq_app:latest

Deploying to Kubernetes

You now have a working Spring Boot Application in your Docker Hub repository. In the next article we will look at how to deploy this Docker image to your Kubernetes cluster.

Summary

In this article we created a Spring Boot application which we could test on our development machine by connecting it to a local instance of a postgres database. We then created a Docker image that includes the application and then ran this image under Docker.

Finally we pushed our new image to the Docker Hub repository to allow it to be ready to be downloaded and installed onto our Kubernetes cluster.

Next we will do just that, connecting our Spring Boot application to the postgres database we previously installed onto our cluster.

Series Introduction

Previous — Creating a Persistent Volume and connecting it to a postgreSQL database

Next — Adding a Spring Boot Application to Your Cluster

--

--