Spring Boot Deployment with Docker and Kubernetes
A comprehensive guide to containerizing and orchestrating Spring applications
Introduction
Welcome to this comprehensive guide on deploying Spring Boot applications using Docker and Kubernetes. This tutorial will walk you through the entire process from setting up your Spring application to deploying it on a Kubernetes cluster.
Spring Boot
A popular Java framework for building microservices and web applications with minimal configuration.
Docker
A platform for developing, shipping, and running applications in containers.
Kubernetes
An open-source container orchestration system for automating deployment, scaling, and management.
The Deployment Workflow
Spring Application
Build your application with Spring Boot
Package
Create a JAR file
Containerize
Build a Docker image
Deploy
Deploy to Kubernetes
Spring Boot Basics
Before we dive into containerization and deployment, let's review the key Spring Boot concepts that will be relevant for our deployment process.
Creating a Spring Boot Application
You can create a Spring Boot application using Spring Initializr (https://start.spring.io) or using your IDE. Here's a simple example of a REST controller:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/")
public String hello() {
return "Hello, Docker and Kubernetes!";
}
}
Important Configuration for Containerization
When preparing your Spring Boot application for containerization, consider these key aspects:
- Externalized Configuration: Use environment variables or config maps for configuration
- Health Checks: Implement Spring Boot Actuator for health monitoring
- Graceful Shutdown: Configure proper shutdown behavior
- Logging: Configure logging to output to console (stdout/stderr)
Adding Actuator for Health Checks
Add the following dependency to your pom.xml (Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Or to your build.gradle (Gradle):
implementation 'org.springframework.boot:spring-boot-starter-actuator'
Configure Actuator in application.properties or application.yml:
# application.properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
Containerizing with Docker
Docker allows you to package your application and its dependencies into a standardized unit for software development and deployment.
Writing a Dockerfile
Create a Dockerfile in the root directory of your project:
FROM eclipse-temurin:17-jdk as build
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM eclipse-temurin:17-jre
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplication"]
This is a multi-stage Dockerfile that first builds the application and then creates a lightweight runtime image with just the necessary components.
Building the Docker Image
Build your Docker image with the following command:
docker build -t spring-demo:latest .
Running the Container Locally
Test your Docker container locally before deploying to Kubernetes:
docker run -p 8080:8080 spring-demo:latest
Using Docker Compose for Development
Create a docker-compose.yml file for local development with additional services like databases:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=dev
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/demo
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=postgres
depends_on:
- db
db:
image: postgres:14
ports:
- "5432:5432"
environment:
- POSTGRES_DB=demo
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:
Run your development environment with:
docker-compose up
Kubernetes Basics
Kubernetes is a container orchestration platform that automates the deployment, scaling, and management of containerized applications.
Key Kubernetes Concepts
A Pod is the smallest deployable unit in Kubernetes that can be created and managed. It's a group of one or more containers with shared storage and network resources.
apiVersion: v1
kind: Pod
metadata:
name: spring-demo-pod
spec:
containers:
- name: spring-app
image: spring-demo:latest
ports:
- containerPort: 8080
A Deployment manages a replicated application and provides declarative updates to Pods.
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-demo
spec:
replicas: 3
selector:
matchLabels:
app: spring-demo
template:
metadata:
labels:
app: spring-demo
spec:
containers:
- name: spring-app
image: spring-demo:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
A Service is an abstraction that defines a logical set of Pods and a policy by which to access them.
apiVersion: v1
kind: Service
metadata:
name: spring-demo-service
spec:
selector:
app: spring-demo
ports:
- port: 80
targetPort: 8080
type: ClusterIP
A ConfigMap is an API object used to store non-confidential data in key-value pairs.
apiVersion: v1
kind: ConfigMap
metadata:
name: spring-demo-config
data:
application.properties: |
spring.application.name=spring-demo
logging.level.org.springframework=INFO
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
A Secret is an object that contains a small amount of sensitive data such as passwords, tokens, or keys.
apiVersion: v1
kind: Secret
metadata:
name: spring-demo-secrets
type: Opaque
data:
db-password: cG9zdGdyZXM= # base64 encoded 'postgres'
api-key: c2VjcmV0LWtleS0xMjM= # base64 encoded 'secret-key-123'
Setting Up a Local Kubernetes Cluster
For local development and testing, you can use:
- Minikube: A tool that runs a single-node Kubernetes cluster locally
- kind (Kubernetes IN Docker): Runs Kubernetes clusters using Docker containers as nodes
- Docker Desktop: Comes with Kubernetes support built-in
To start Minikube:
minikube start
To verify your cluster is running:
kubectl cluster-info
Deploying to Kubernetes
Now let's put everything together and deploy our Spring Boot application to Kubernetes.
Pushing Your Docker Image
Before deploying to Kubernetes, you need to push your Docker image to a registry that Kubernetes can access:
# Tag your image
docker tag spring-demo:latest your-registry/spring-demo:latest
# Push to registry
docker push your-registry/spring-demo:latest
For local development with Minikube, you can use the Minikube's built-in Docker daemon:
eval $(minikube docker-env)
docker build -t spring-demo:latest .
Creating Kubernetes Manifests
Create a deployment.yaml file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-demo
spec:
replicas: 2
selector:
matchLabels:
app: spring-demo
template:
metadata:
labels:
app: spring-demo
spec:
containers:
- name: spring-app
image: spring-demo:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: spring-demo-secrets
key: db-password
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: spring-demo-config
Create a service.yaml file:
apiVersion: v1
kind: Service
metadata:
name: spring-demo-service
spec:
selector:
app: spring-demo
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Create an ingress.yaml file (if needed):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spring-demo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: spring-demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-demo-service
port:
number: 80
Deploying the Application
Apply your Kubernetes manifests:
# Apply ConfigMap and Secret first
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
# Apply Deployment and Service
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Apply Ingress (if needed)
kubectl apply -f ingress.yaml
Verifying the Deployment
Check if your pods are running:
kubectl get pods
Check the service:
kubectl get services
If you're using Minikube, you can access your service with:
minikube service spring-demo-service
Monitoring and Management
Properly monitoring your Spring Boot application in Kubernetes is crucial for maintaining reliability and performance.
Using Spring Boot Actuator
Spring Boot Actuator provides built-in endpoints for monitoring:
- /actuator/health: Shows application health information
- /actuator/info: Displays application information
- /actuator/metrics: Shows metrics information
- /actuator/prometheus: Exposes metrics in Prometheus format
Setting Up Prometheus and Grafana
For advanced monitoring, set up Prometheus and Grafana:
- Add the Prometheus dependency to your Spring Boot application
- Install Prometheus in your Kubernetes cluster
- Configure Prometheus to scrape metrics from your application
- Install Grafana and connect it to Prometheus
- Import Spring Boot dashboards for Grafana
Log Management
For log management, consider using:
- Elasticsearch, Logstash, and Kibana (ELK Stack)
- Fluentd for log collection
Configure Spring Boot to output logs in JSON format for better integration:
# application.properties
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
Best Practices and Tips
Follow these best practices for a successful Spring Boot deployment in Kubernetes:
Spring Boot Configuration
- Use environment variables or ConfigMaps for configuration
- Implement proper health checks using Actuator
- Optimize JVM memory settings for containers
- Configure graceful shutdown
Docker Best Practices
- Use multi-stage builds to keep images small
- Don't run as root user
- Use specific image tags instead of 'latest'
- Scan images for vulnerabilities
Kubernetes Best Practices
- Use resource limits and requests
- Implement proper liveness and readiness probes
- Use horizontal pod autoscaling
- Use namespaces to organize resources
- Implement proper security with RBAC and Network Policies
CI/CD Pipeline Integration
Integrate your deployment process into a CI/CD pipeline using tools like:
- Jenkins
- GitHub Actions
- GitLab CI
- ArgoCD for GitOps
Common Issues and Solutions
Problem: JVM uses more memory than allocated by container.
Solution: Set appropriate JVM flags:
-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0
Problem: Spring Boot application takes too long to start.
Solution: Use Spring Native or optimize startup:
- Disable unused auto-configurations
- Use lazy initialization where appropriate
- Consider GraalVM native images
Problem: Services can't connect to each other.
Solution: Check:
- Service selectors match pod labels
- Network policies allow traffic
- Use Service DNS names for connections
- Check for port mismatches