Spring Boot CI/CD on Kubernetes using Terraform, Ansible and GitHub: Part 7
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.
- The database connection details have been parameterised to allow them to be defined externally, ie:
db.host
,db.username
,db.password
- In the
application.yml
file, a section has been added (sql.init.schema-locations
) to initialise the database using theschema-postgresql.sql
file, which creates the schema as Hibernate will not create this - 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)
- The REST API is to be deployed on port 9191
- Monitoring has been enabled on port 9000 (
management.endpoints.web.exposure.include
) - 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 queuesGET queues/{id}
— get a specific queuePOST queues
— create a queuePUT queues/{id}
— update a queueDELETE queues/{id}
— create a queueGET queues/{id}/queue-entries
— get all entries for a queueGET queues/{id}/queue-entries/next
— get next entry for a queuePOST queues/{id}/queue-entries
— add entry for a queueDELETE 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.
Previous — Creating a Persistent Volume and connecting it to a postgreSQL database