
Deploying Spring Boot Applications to Kubernetes: A Complete Guide
Master the process of deploying Spring Boot applications to Kubernetes, from creating optimized Docker images to implementing health checks and managing configurations. This practical guide covers everything you need for successful container deployment.
Deploying Spring Boot applications to Kubernetes can seem daunting at first, but with the right approach, it becomes a straightforward process. In this guide, we'll walk through the steps to containerize and deploy a Spring Boot application to Kubernetes.
Key Takeaways
- Use multi-stage builds to create smaller Docker images
- Leverage Spring Boot Actuator for health checks
- Configure resource limits and readiness probes
- Use ConfigMaps for environment-specific configurations
- Implement rolling updates for zero-downtime deployments
Prerequisites
- A Spring Boot application
- Docker installed locally
- Access to a Kubernetes cluster
- Basic understanding of Docker and Kubernetes concepts
Step 1: Creating an Optimized Docker Image
First, let's create an optimized Docker image using a multi-stage build. This approach helps reduce the final image size by only including necessary components.
FROM amazoncorretto:23-alpine3.20 as corretto-jdk
RUN apk add --no-cache binutils
# Build small JRE image
RUN $JAVA_HOME/bin/jlink \
--verbose \
--add-modules ALL-MODULE-PATH \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output /customjre
FROM alpine:latest
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
COPY --from=corretto-jdk /customjre $JAVA_HOME
ARG JAR_FILE=target/*-SNAPSHOT.jar
# Add app user for security
ARG APPLICATION_USER=appuser
RUN adduser --no-create-home -u 1000 -D $APPLICATION_USER
# Configure working directory
RUN mkdir /app && \
chown -R $APPLICATION_USER /app
USER 1000
COPY --chown=1000:1000 ${JAR_FILE} /app/app.jar
WORKDIR /app
EXPOSE 8080
ENTRYPOINT [ "/jre/bin/java", "-jar", "-Duser.timezone=UTC", "/app/app.jar",
"-XX:+ExitOnOutOfMemoryError",
"-XX:+UseCGroupMemoryLimitForHeap",
"-XX:+UseSerialGC"]
This Dockerfile:
- Uses multi-stage builds to create a minimal JRE
- Runs the application as a non-root user
- Includes important JVM flags for container environments
- Sets up proper file permissions
Step 2: Configuring Kubernetes Resources
Service Definition
First, let's create a Kubernetes Service to expose our application:
apiVersion: v1
kind: Service
metadata:
name: spring-app
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
selector:
app: spring-app
ports:
- name: http-traffic
port: 8080
targetPort: 8080
protocol: TCP
Deployment Configuration
Next, create a Deployment to manage your application pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-app
spec:
replicas: 2
selector:
matchLabels:
app: spring-app
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: spring-app
spec:
containers:
- name: spring-app
image: your-registry/spring-app:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "kubernetes"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
timeoutSeconds: 2
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
failureThreshold: 30
periodSeconds: 10
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: spring-app-config
ConfigMap for Application Properties
Store your application configuration in a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: spring-app-config
data:
application.yaml: |
spring:
datasource:
url: jdbc:postgresql://db-service:5432/mydb
management:
endpoint:
health:
probes:
enabled: true
Step 3: Implementing Health Checks
Add the following dependencies to your build.gradle.kts
:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
}
Configure health probes in your application:
@Configuration
class ActuatorConfig {
@Bean
fun customHealthIndicator(): HealthIndicator {
return HealthIndicator {
val health = Health.Builder()
try {
// Add your health check logic here
health.up()
} catch (e: Exception) {
health.down().withException(e)
}
health.build()
}
}
}
Step 4: Managing Kubernetes Resources with Terraform
For production environments, it's recommended to use Infrastructure as Code (IaC) tools like Terraform to manage your Kubernetes resources. Here's how to define your resources:
# Create namespace
resource "kubernetes_namespace" "app_namespace" {
metadata {
name = "spring-app"
}
}
# Create service
resource "kubernetes_service" "app_service" {
metadata {
namespace = kubernetes_namespace.app_namespace.metadata[0].name
name = "spring-app"
annotations = {
"prometheus.io/scrape" = "true"
"prometheus.io/port" = "8080"
"prometheus.io/path" = "/actuator/prometheus"
}
}
spec {
selector = {
app = "spring-app"
}
port {
name = "http-traffic"
port = 8080
target_port = 8080
protocol = "TCP"
}
}
}
# Create deployment
resource "kubernetes_deployment" "app_deployment" {
metadata {
namespace = kubernetes_namespace.app_namespace.metadata[0].name
name = "spring-app"
}
spec {
replicas = 2
selector {
match_labels = {
app = "spring-app"
}
}
strategy {
type = "RollingUpdate"
}
template {
metadata {
labels = {
app = "spring-app"
}
}
spec {
container {
image = "your-registry/spring-app:latest"
name = "spring-app"
env {
name = "SPRING_PROFILES_ACTIVE"
value = "kubernetes"
}
# Reference secrets
env {
name = "DB_PASSWORD"
value_from {
secret_key_ref {
name = "app-secrets"
key = "db-password"
}
}
}
readiness_probe {
http_get {
path = "/actuator/health/readiness"
port = 8080
}
period_seconds = 5
timeout_seconds = 2
}
}
}
}
}
}
# Create ConfigMap
resource "kubernetes_config_map" "app_config" {
metadata {
namespace = kubernetes_namespace.app_namespace.metadata[0].name
name = "spring-app-config"
}
data = {
"application.properties" = file("${path.module}/config/application.properties")
}
}
Step 5: Deployment Process
- Build your application:
./gradlew build
- Build and push Docker image:
docker build -t your-registry/spring-app:latest .
docker push your-registry/spring-app:latest
- Apply Kubernetes configurations:
kubectl apply -f service.yaml
kubectl apply -f deployment.yaml
kubectl apply -f configmap.yaml
- Verify deployment:
kubectl get pods
kubectl get services
Best Practices
- Resource Management
- Always set resource requests and limits
- Monitor resource usage and adjust accordingly
- Configuration
- Use ConfigMaps for configuration
- Store secrets in Kubernetes Secrets
- Use environment-specific profiles
- Health Checks
- Implement both readiness and liveness probes
- Use Spring Boot Actuator for health endpoints
- Configure appropriate timeout values
- Security
- Run containers as non-root users
- Use network policies to control traffic
- Regularly update base images
Common Issues and Solutions
- Out of Memory Issues
- Ensure proper JVM flags are set
- Monitor heap usage with actuator
- Set appropriate memory limits
- Slow Startup
- Use startup probes with appropriate thresholds
- Consider lazy initialization where appropriate
- Monitor startup time with actuator metrics
Remember to adjust these configurations based on your specific requirements and environment constraints.