Posts

How to deploy Spring Boot application on Kubernetes

Nov 2, 2023
Catherine Edelveis
11.5

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!

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 (the matchLabels selects the Pods with the app: 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.

 

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading