If you want to own the cloud, you have to own Kubernetes, which is the most popular container orchestration system that dramatically facilitates the management of cloud-native applications. This article will guide you through containerizing and deploying a Spring Boot app on the Kubernetes cluster.
The code is available on GitHub.
By the way, if you deploy Spring Boot services to the cloud, check out Alpaquita Containers tailor-made for Spring Boot: they can reduce the memory footprint of your containers by up to 30 %! There containers also support CRaC API, so you can easily reduce startup and warmup times of your Spring Boot services from minutes to milliseconds by using Java with CRaC. Give it a try!
Table of Contents
Prerequisites
- JDK 17 (I will use Liberica JDK recommended by the Spring team)
- Docker
- Maven
- Your favorite IDE (I will use IntelliJ IDEA)
Set up the environment
First of all, we need to set up a local Kubernetes cluster. Local clusters are great if you need to master the technology and test application performance without the risk of breaking the production environment.
For our purposes, we will use minikube, a widespread, easy-to-install Kubernetes distribution. To set up a single-node K8s cluster, refer to our previous guide.
Create a Spring Boot application
Head to Spring Initializr to create a simple Spring Boot application. Select Java, Maven, the latest stable version of Spring Boot 3 (I have 3.1.5; yours may be different), and Jar packaging. The name of our project will be spring-boot-app. We will also need two dependencies, Web and Actuator; the latter supports endpoints for convenient application monitoring.
Creating a Spring Boot application
Click “Generate,” unzip the file, and open the application in your IDE.
Configure the application as shown below:
@SpringBootApplication
@RestController
public class SpringBootAppApplication {
@RequestMapping("/")
public String home() {
return "Hello Kubernetes!";
}
public static void main(String[] args) {
SpringApplication.run(SpringBootAppApplication.class, args);
}
}
Finally, build a runnable jar with
$ mvn package
Build a Docker container image
Write a Dockerfile
To dockerize our Spring Boot application, we need to write a Dockerfile. Although the fastest way to build a container is to use buildpacks (such as Paketo buildpacks), good old Dockerfiles provide fine-grain control of our deployment. For example, you can select a base image, such as Alpaquita Container, tailor-made for Spring Boot apps. Based on a lightweight Alpaquita Linux and Liberica JDK Lite, it allows the developers to build performant microcontainers and save up to 30 % RAM!
Add the following file to the root directory of your application:
FROM bellsoft/liberica-runtime-container:jre-17-stream-musl
COPY target/spring-boot-app-0.0.1-SNAPSHOT.jar springbootapp.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/springbootapp.jar"]
Here, we
- import a base image with musl-based Alpaquita (a glibc option is also available) from BellSoft’s repository,
- copy the jar file (spring-boot-app-0.0.1-SNAPSHOT.jar) we generated in the previous section from the target folder into the root folder of the container and name it springbootapp.jar,
- indicate the port for our web application,
- run the springbootapp.jar inside the container with
java -jar
command.
Containerize the app
Build the image with
$ docker build . -t spring-boot-app
You can run the container with
$ docker run -p 8080:8080 spring-boot-app
To verify that it is running, run the following command in another Terminal window:
$ curl localhost:8080/actuator/health
The answer should be
{"status":"UP"}
Stop the container and proceed to the next step.
Push the image to the container registry
Before deploying the application on Kubernetes, you need to publish it on a container registry because this is where Kubernetes pulls the images. We will use the Docker Hub Container Registry, so you must create a Docker ID if you don’t have one yet. After that, run the following commands (replace the <docker-id> with your Docker Hub username):
$ docker tag spring-boot-app <docker-id>/spring-boot-app
$ docker push <docker-id>/spring-boot-app
Deploy the application on Kubernetes
Create Kubernetes deployment file
Create a deployment.yaml file with a following content and place it to the root directory of your project:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
spec:
replicas: 2
selector:
matchLabels:
app: spring-boot-app
template:
metadata:
labels:
app: spring-boot-app
spec:
containers:
- name: spring-boot-app
image: <docker-id>/spring-boot-app
ports:
- containerPort: 8080
Let’s look at the file closely. What exactly do we tell Kubernetes to do?
- First of all, we define the type of our resource, which is
Deployment
, its version (apps/v1
) and name (spring-boot-app
). - After that, we specify how many replicas of our container we want to have (here, two). Containers in Kubernetes run on Pods, the smallest deployable units that run one or several containers with shared network resources, storage, and a specification on how to run containers. Our Deployment file creates a ReplicaSet with two replicated Pods.
- The
metadata-labels
part specifies the label for the Pods (app: spring-boot-app
). This label will be used by the selector to define how the ReplicaSet finds the Pods (thematchLabels
selects the Pods with theapp: spring-boot-app
label). - Finally, we provide the information about the container we want to run by specifying its name (
spring-boot-app
), the name of the image we published to Docker Hub (<docker-id>/spring-boot-app
), and the port the container listens on (8080).
Create a Kubernetes service file
We defined how to run our containerized application in the Kubernetes cluster, but we need to make it accessible from the outside of the cluster. For that purpose, we will create a Service by means of a service.yaml file:
apiVersion: v1
kind: Service
metadata:
name: spring-boot-app
spec:
type: LoadBalancer
selector:
app: spring-boot-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
Here, we define a selector, which selects the Pods to be exposed based on the provided label (app: spring-boot-app
). The type of our Service is LoadBalancer
, which tracks the availability of Pods and distributes network traffic among them. If the Pod is unavailable for some reason, Load Balancer doesn’t route traffic to it and looks for available Pods, ensuring continuous availability of the application.
Note that if we don’t specify the type of our Service, Kubernetes will assign it the default ClusterIP
type, which makes Pods accessible from within the cluster, but not from the outside.
Finally, we define that our Service listens on port 80 and routes the traffic to port 8080.
Deploy the container image to Kubernetes
Start minikube with minikube start
if you haven’t done it yet. Deploy both files to Kubernetes with the commands:
$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
You will get the following output:
deployment.apps/spring-boot-app created
service/spring-boot-app created
Let’s make sure that our application is running. Execute
$ kubectl get all
You will get a similar output:
NAME READY STATUS RESTARTS AGE
pod/spring-boot-app-dbf645dbf-f59ck 1/1 Running 0 5m7s
pod/spring-boot-app-dbf645dbf-qkpw9 1/1 Running 0 5m7s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20h
service/spring-boot-app LoadBalancer 10.102.77.63 <pending> 80:30936/TCP 5m7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/spring-boot-app 2/2 2 2 5m7s
NAME DESIRED CURRENT READY AGE
replicaset.apps/spring-boot-app-dbf645dbf 2 2 2 5m7s
As we specified two replicas in our Deployment file, you will see them both as pod replicas with different names. You may have to repeat kubectl get all
until the STATUS changes to Running.
Access the application
Our application is running successfully, but we can’t access it from the outside yet. You can expose your service with:
$ minikube service spring-boot-app --url
This way, minikube will generate the URL where you can access your application and see our “Hello Kubernetes!” message.
Alternatively, you can create a routable IP with
$ minikube tunnel
After that, run
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21h
spring-boot-app LoadBalancer 10.102.77.63 127.0.0.1 80:30936/TCP 39m
You can see that our LoadBalancer deployment was assigned an external IP. Let’s access our application at <external-ip>:80
$ curl 127.0.0.1:80/actuator/health
{"status":"UP","groups":["liveness","readiness"]}
Congratulations! You successfully deployed your Spring Boot application on Kubernetes!
Conclusion
In this article, we learned how to containerize a Spring Boot application and deploy it on the Kubernetes cluster. From here on, you can experiment with your deployment — scale the application, monitor its behavior, and tune the performance.
So the essential step is done, but there are a lot more exciting things to accomplish! Subscribe to our newsletter if you want to read more guides on making your Spring Boot app feel at home in the cloud.