Spring Boot Deployment with Docker and Kubernetes

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

Pod
Deployment
Service
ConfigMap
Secret

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:

  1. Add the Prometheus dependency to your Spring Boot application
  2. Install Prometheus in your Kubernetes cluster
  3. Configure Prometheus to scrape metrics from your application
  4. Install Grafana and connect it to Prometheus
  5. 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

Memory Issues
Slow Startup
Connection Issues

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