Skip to main content

Spring Kubernetes Deployment

Introduction

Kubernetes has become the industry standard for deploying, scaling, and managing containerized applications. When combined with Spring Boot's powerful features for building Java applications, you get a robust platform for creating and deploying cloud-native microservices.

This guide will walk you through the process of deploying Spring Boot applications to Kubernetes. We'll cover everything from containerizing your Spring application to deploying and managing it on a Kubernetes cluster. By the end, you'll have a clear understanding of how to leverage Kubernetes for your Spring applications.

Prerequisites

Before you begin, make sure you have:

  • Basic knowledge of Spring Boot
  • Docker installed on your machine
  • Access to a Kubernetes cluster (local like Minikube or remote)
  • kubectl command-line tool installed
  • JDK 11 or newer
  • Maven or Gradle build tools

Understanding Spring Boot and Kubernetes Integration

What Makes Spring Boot Kubernetes-Friendly?

Spring Boot offers several features that make it well-suited for Kubernetes deployments:

  1. Externalized Configuration: Spring Boot's property management works well with Kubernetes ConfigMaps and Secrets
  2. Health Checks: Built-in actuator endpoints that Kubernetes can use for liveness and readiness probes
  3. Metrics: Integration with Prometheus for monitoring in Kubernetes
  4. Self-contained JARs: Spring Boot's executable JARs are easy to containerize

Step 1: Prepare Your Spring Boot Application

Let's start with a simple Spring Boot application. You can use an existing one or create a basic application using Spring Initializr.

Make sure your application includes the Spring Actuator dependency:

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

To ensure Kubernetes can properly monitor your application's health, enable health check endpoints in your application.properties:

properties
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.health.show-details=always

Step 2: Containerize Your Spring Boot Application

Creating a Dockerfile

Create a Dockerfile in the root of your project:

dockerfile
FROM openjdk:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Building the Docker Image

Build your Spring Boot application:

bash
./mvnw clean package

Build and tag the Docker image:

bash
docker build -t myorg/myapp:1.0.0 .

Push the image to a container registry (Docker Hub, GCR, ECR, etc.):

bash
docker push myorg/myapp:1.0.0

Step 3: Create Kubernetes Deployment Files

Now, let's create the necessary Kubernetes manifests for deploying your application.

Deployment YAML

Create a file named deployment.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-app
spec:
replicas: 2
selector:
matchLabels:
app: spring-app
template:
metadata:
labels:
app: spring-app
spec:
containers:
- name: spring-app
image: myorg/myapp:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"

Service YAML

Create a file named service.yaml:

yaml
apiVersion: v1
kind: Service
metadata:
name: spring-app
spec:
selector:
app: spring-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP

ConfigMap YAML (for application configuration)

Create a file named configmap.yaml:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: spring-app-config
data:
application.properties: |
server.port=8080
spring.application.name=spring-kubernetes-demo
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.health.show-details=always
logging.level.org.springframework=INFO

Step 4: Deploy to Kubernetes

Apply the Kubernetes manifests:

bash
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

Monitor the deployment:

bash
kubectl get pods
kubectl get services
kubectl get deployments

You should see output similar to:

NAME                         READY   STATUS    RESTARTS   AGE
spring-app-d4f7d8f9b-2xvjr 1/1 Running 0 45s
spring-app-d4f7d8f9b-8zkq7 1/1 Running 0 45s

Step 5: Expose Your Application

To access your application from outside the cluster, you can create an Ingress resource or use a LoadBalancer service:

yaml
apiVersion: v1
kind: Service
metadata:
name: spring-app-external
spec:
selector:
app: spring-app
ports:
- port: 80
targetPort: 8080
type: LoadBalancer

Apply this service:

bash
kubectl apply -f service-external.yaml

Step 6: Advanced Configuration with Spring Cloud Kubernetes

For more integrated Kubernetes support, you can use Spring Cloud Kubernetes, which provides features like service discovery, configuration management, and more.

Add the Spring Cloud Kubernetes dependencies:

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>

With these dependencies, your application can:

  1. Read configuration from Kubernetes ConfigMaps and Secrets
  2. Discover other services running in the cluster
  3. Reload configuration when ConfigMaps or Secrets change

Enable this in your application by adding the following annotation to your main class:

java
@SpringBootApplication
@EnableDiscoveryClient
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

Real-World Example: Creating a Scalable Microservice

Let's put together everything we've learned to create a scalable REST API microservice:

Sample Spring Boot Application

java
@RestController
@SpringBootApplication
public class ProductServiceApplication {

private final List<Product> products = new CopyOnWriteArrayList<>();

public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}

@PostConstruct
public void init() {
products.add(new Product("1", "Laptop", 1200.0));
products.add(new Product("2", "Phone", 800.0));
}

@GetMapping("/products")
public List<Product> getProducts() {
return products;
}

@GetMapping("/products/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
return products.stream()
.filter(p -> p.getId().equals(id))
.findFirst()
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@PostMapping("/products")
public Product createProduct(@RequestBody Product product) {
products.add(product);
return product;
}

// Product class
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Product {
private String id;
private String name;
private Double price;
}
}

Kubernetes Horizontal Pod Autoscaler

To make your service automatically scale based on load, create an HPA:

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spring-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

Apply the HPA:

bash
kubectl apply -f hpa.yaml

Production Readiness

For a production-ready deployment, consider:

  1. Database configuration: Use Kubernetes Secrets for database credentials
  2. Persistent storage: Use Persistent Volumes for any stateful components
  3. Logging: Configure centralized logging with Elasticsearch, Fluentd, and Kibana (EFK stack)
  4. Monitoring: Set up Prometheus and Grafana for monitoring your Spring Boot metrics

Common Challenges and Solutions

JVM Memory in Containers

Containers have specific memory limits that the JVM needs to respect. For Java 11+, the JVM is container-aware, but it's good practice to set memory limits explicitly:

dockerfile
FROM openjdk:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]

Graceful Shutdown

Ensure your Spring Boot application shuts down gracefully when Kubernetes terminates the pod:

properties
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=20s

Summary

In this guide, we've covered the essentials of deploying Spring Boot applications to Kubernetes:

  1. Preparing your Spring Boot application for containerization
  2. Creating a Docker image of your application
  3. Defining Kubernetes deployment manifests
  4. Configuring health checks and resource limits
  5. Advanced integration with Spring Cloud Kubernetes
  6. Setting up autoscaling for production workloads

By following these steps, you can successfully deploy, manage, and scale your Spring Boot applications on Kubernetes clusters, enabling you to build resilient, cloud-native microservices.

Additional Resources

Exercises

  1. Deploy a simple Spring Boot REST API to a local Kubernetes cluster using Minikube
  2. Configure a Spring Boot application to read configuration from Kubernetes ConfigMap
  3. Set up autoscaling for a Spring Boot application and test it with a load generator
  4. Create a CI/CD pipeline that builds a Spring Boot app, creates a container image, and deploys it to Kubernetes
  5. Implement service-to-service communication between two Spring Boot microservices running in Kubernetes

With the knowledge gained from this guide, you're now equipped to deploy and manage Spring Boot applications in Kubernetes environments, from local development to production deployments.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)