In the cloud-native world, where microservices are developed, distributed, and operated, the operational burden becomes more complicated as the services become smaller and smaller. Fortunately, there are technologies available to vastly reduce both the time and costs associated with development and operations. 

This article will cover two essential technologies to run Java applications in a cloud-native way: Spring Boot and Kubernetes. We’ll cover the basics of each tool, give you a step-by-step guide to run an actual Spring app on Kubernetes, plus cover further action you can take for better reliability and high availability. 

What Is Spring Boot?

Spring Framework is a programming and configuration approach for Java-based and production-ready enterprise applications. Applications made on the Spring Framework can run on any platform and are highly adopted in the industry. However, getting started with the Spring Framework is not so straightforward due to configuration challenges. Luckily, Spring Boot is a tool that lets you create standalone Spring applications with minimum input, plus the following advantages:

  • Flexible configuration for JavaBeans, XML, and database connections
  • Batch processing and creation of REST endpoints
  • Automatic configuration with no manual intervention
  • Creating annotation-based Spring applications 
  • Minimum dependency on third-party libraries
  • Configured and ready to use embedded servlet containers

In short, Spring Boot is the tool you need to easily dive in and start developing Spring applications, allowing you to achieve both higher productivity and reduced development effort. 

What Is Kubernetes?

For running containers, Kubernetes is the industry standard. An open-source container orchestration platform that supports a rich ecosystem for distributed systems, it offers high-availability and self-healing capabilities like autoscaling, liveness probes, and service discovery.

The Kubernetes project was initially developed by Google and open-sourced in 2014 based on previous experiences with Borg for running containerized workloads in production. If you’d like to learn more, Kubernetes Basics is a great place to start and get your hands dirty.

Now, let’s bring the two worlds together by running a Spring Boot application on Kubernetes. 

Spring Boot on Kubernetes

Spring applications have enterprise-grade features such as an extensive REST API configuration, internal logging, and graceful shutdown of web servers. Meanwhile, Kubernetes boasts numerous integration points including health check probes, configuration management, and load balancing. When these two sets of features come together, you can create reliable, scalable, and highly available production-ready applications. 

In the following steps, we’ll start off by bootstrapping a Spring application and finally end up with a scalable Kubernetes application.

1) Initialize the Spring App

The first step is to generate a boilerplate Spring Boot application. Open the Spring Initializr web application, and ensure the following is selected:

first step for starting with Spring Boot and Kubernetes

Figure 1: Spring Initializr web application

Next, click “Add Dependencies” under the Dependencies tab and choose “Spring Web.” Then, click “Generate” to create and download a ZIP package that consists of your project skeleton.

2) Add a REST Endpoint

The second step is adding a REST endpoint to the skeleton application. So, extract the ZIP package, and open it with your favorite code editor. Then create a new file named HelloKubernetes.java under the com.epsagon.springbootk8s.web package:

package com.epsagon.springbootk8s.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloKubernetes {
    @RequestMapping("/")
    String home() {
        return "Hello World from Kubernetes!";
    }
}

This class is a static REST endpoint located at the path “/” to respond with a “Hello World” message. 

3) Create the Docker Container Image

The third step is to build and package your application into a Docker container. Run the following command to build the Docker image:

$ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=<DOCKER_USERNAME>/spring-boot-k8s
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.epsagon:spring-boot-k8s >---------------------
[INFO] Building spring-boot-k8s 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.3.5.RELEASE:build-image (default-cli) > package @ spring-boot-k8s >>>
...
[INFO] Successfully built image 'docker.io/.../spring-boot-k8s:latest'
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 05:14 min
[INFO] Finished at: 2020-11-07T22:47:18+01:00
[INFO] ------------------------------------------------------------------------
The long output will end with the successful message of your Docker container image name. You can test the container locally before sending it to the cloud:
$ docker run -p 8080:8080 -t <DOCKER_USERNAME>/spring-boot-k8s
...
2020-11-07 21:49:05.639 INFO 1 --- [ main] c.e.s.SpringBootK8sApplication : Started SpringBootK8sApplication in 2.73 seconds (JVM running for 3.314)

After the initialization, open localhost:8080 in the browser and check for the message:

Figure 2: Response from Docker container

Now, you can push the Docker container to Docker Hub:

$ docker push <DOCKER_USERNAME>/spring-boot-k8s

4) Deploy the App to Kubernetes

Create a Kubernetes deployment with the following command:

$ kubectl create deployment spring-boot --image=<DOCKER_USERNAME>/spring-boot-k8s
deployment.apps/spring-boot created

Check for the pods to ensure that your application is running:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
spring-boot-cbb57f47-v7wcf 1/1 Running 0 52s

Now, you’ve deployed your Spring Boot application to Kubernetes, but to access it, you need to expose the app to the outside world:

$ kubectl expose deployment spring-boot --type=LoadBalancer --name=spring-boot --port=8080
service/spring-boot exposed

Now, check the external IP of the exposed service with the following command:

$ kubectl get svc spring-boot                                
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
spring-boot LoadBalancer 10.104.102.36 127.0.0.1
8080:31472/TCP 2m14s

Open <EXTERNAL_IP>:8080 in the browser:

Figure 3: Response from Kubernetes pod

The output shows that your Spring Boot application is running in the Kubernetes cluster, and its REST endpoints are reachable.

Extra Features

In addition to deploying and exposing Spring Boot apps, Kubernetes offers various features for high availability and reliability, which we’ll discuss in the following section. 

Manual Scaling 

You deployed the boilerplate application above with only one pod. But Kubernetes provides manual scalability out of the box if you need a higher number of available instances. You simply change the scale of the deployment: 

$ kubectl scale deployment/spring-boot --replicas=5         
deployment.apps/spring-boot scaled

Next, check for the pods:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
spring-boot-cbb57f47-jrbtg 1/1 Running 0 21s
spring-boot-cbb57f47-lrj47 1/1 Running 0 21s
spring-boot-cbb57f47-sm6ps 1/1 Running 0 22s
spring-boot-cbb57f47-v7wcf 1/1 Running 0 11m
spring-boot-cbb57f47-wvj9w 1/1 Running 0 21s

Now, you should have five instances of the same application running instead of one.

Autoscaling

Unfortunately, manually scaling with the kubectl scale command is not a feasible solution because you have to continuously watch the metrics and take action each time an increase is required. Fortunately, Kubernetes provides autoscaling based on an application’s resource usage. For instance, you can ensure that Spring Boot deployments will have an average of 50% CPU usage with a minimum of 1 and maximum of 10 replicas with the following command:

$ kubectl autoscale deployment/spring-boot --cpu-percent=50 --min=1 --max=10

Resource Limits 

In Kubernetes, some applications consume more resources than others. But if you don’t limit resource usage, you can end up with noisy neighbors who don’t let other containers perform their operations. So, it’s a good practice to set resource requests and limits to the pod definitions:

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

With this pod definition, Kubernetes will schedule the pod to a node ensuring a resource request of 0.25 CPU and 64MiB of memory. It will also make sure the containers will not consume more than 0.5 CPU and 128 MiB memory.

Readiness and Liveness Probes 

Kubernetes uses readiness probes to determine if a pod is ready so that your request is redirected to one that is. You only need to add the following to a pod definition:

     readinessProbe:
       httpGet:
         path: /health
         port: 3000
       timeoutSeconds: 2

When the /health endpoint responds with 200, then Kubernetes sets the pod as Ready, and new requests are sent to the pod. Readiness probes are critical, especially when Spring applications spend a couple of seconds to initialize all components. Similarly, you can add liveness probes to pods as well. When the liveness probes fail, Kubernetes restarts the containers, making the deployed applications self-healing and more reliable.

Summary and Next Steps

In this blog, we covered the basics of the Spring Boot’s tool and its bootstrapping capabilities. We also deployed an application to Kubernetes, ensured that it was running, and covered some best practices for deploying Spring Boot applications to Kubernetes. 

There are three essential points to check out further for long-term Kubernetes deployments:

  • Ingress Controllers: To reach Kubernetes services from outside the cluster with a URL, you need to use ingress resources. There are various community-maintained ingress implementations such as Istio, NGINX, or Traefik. You can check the list of ingress controllers in Kubernetes’ concept documentation.
  • TLS Certificate: Secure communication between applications and the outside world is a necessity for an enterprise environment. Kubernetes thus has a certificates.k8s.io API to provision TLS certificates signed by a certificate authority (CA). Further information on managing TLS certificates in the cluster can be found in Kubernetes’ official documentation here.
  • Helm: As applications become more complicated, the number of resources you need to deploy and operate also increases; for example, you need to have multiple containers, volumes, secrets, and load balancers when you deploy a production-grade application. But creating and operating each resource is difficult and prone to errors. Helm simplifies this work by managing packages for Kubernetes. There are thousands of official and community-maintained packaged applications, namely charts, available on Helm, letting you deploy complex applications with only a single command and configuration.

Read More:

How to Deploy Java Spring Boot on AWS Fargate

What You Need to Know About Kubernetes Monitoring

AWS Lambda and Java Spring Boot: Getting Started

How-to Guide: Debugging a Kubernetes Application