Spring Boot Tutorial
Deploying Spring Boot to Kubernetes
Kubernetes has become the standard platform for running containerised services: it schedules containers onto machines, restarts failed instances, scales on load, rolls out new versions without downtime, and manages configuration and secrets. Spring Boot is designed to work well there — health probes, graceful shutdown, externalised configuration and metrics all map directly onto Kubernetes features.
This lesson deploys a Spring Boot application with a Deployment, Service, ConfigMap and Secret, configures probes, resources and graceful shutdown for zero-downtime rollouts, adds autoscaling, and explains the settings that most often cause trouble.
The Core Objects
A Deployment declares how many replicas of your container to run and how to update them. A Service gives the pods a stable name and load-balances between them; an Ingress or Gateway exposes it outside the cluster. A ConfigMap holds non-secret configuration and a Secret holds credentials; both can be provided as environment variables or files that Spring Boot reads.
Configuration from ConfigMaps and Secrets
Environment variables map to properties through relaxed binding (SPRING_DATASOURCE_URL). For many settings, mount a ConfigMap as files and import them with spring.config.import=configtree:/etc/config/, where each file name becomes a property. Spring Cloud Kubernetes can also read ConfigMaps directly, but plain environment variables and config trees are usually enough.
Probes, Resources and Shutdown
Point the livenessProbe at /actuator/health/liveness and the readinessProbe at /actuator/health/readiness, with a startupProbe so slow startups are not killed. Set CPU and memory requests and limits; the JVM respects container memory limits, so size the heap with -XX:MaxRAMPercentage. For zero-downtime rollouts, Spring Boot's graceful shutdown and readiness state handle in-flight requests, and a short preStop sleep gives load balancers time to stop sending traffic before the app shuts down.
Scaling
A HorizontalPodAutoscaler adds or removes replicas based on CPU or custom metrics (for example request rate from Prometheus). Because every replica runs its own scheduler, caches and connection pools, remember the multi-instance concerns covered earlier: ShedLock for scheduled jobs, Redis for shared caches and sessions, and database pool sizes multiplied by replica count.
Examples
Deployment with probes, resources, config and graceful shutdown
apiVersion: apps/v1
kind: Deployment
metadata:
name: webnest-shop
spec:
replicas: 3
selector:
matchLabels: { app: webnest-shop }
strategy:
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
template:
metadata:
labels: { app: webnest-shop }
spec:
terminationGracePeriodSeconds: 45
containers:
- name: app
image: ghcr.io/webnest/shop:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=75"
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef: { name: shop-db, key: password }
envFrom:
- configMapRef: { name: shop-config }
resources:
requests: { cpu: 500m, memory: 768Mi }
limits: { memory: 768Mi }
startupProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
periodSeconds: 10
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
lifecycle:
preStop:
sleep: { seconds: 10 } # let load balancers remove the pod first
kubectl apply -f deployment.yaml
deployment.apps/webnest-shop created
kubectl get pods
webnest-shop-7d9c6b5f4-2xkqp 1/1 Running 0 42s
webnest-shop-7d9c6b5f4-8mzvn 1/1 Running 0 42s
webnest-shop-7d9c6b5f4-tq4rw 1/1 Running 0 42s
ConfigMap, Secret, Service and Ingress
apiVersion: v1
kind: ConfigMap
metadata:
name: shop-config
data:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres.db.svc.cluster.local:5432/webnest
SPRING_DATASOURCE_USERNAME: webnest
LOGGING_STRUCTURED_FORMAT_CONSOLE: ecs
---
apiVersion: v1
kind: Secret
metadata:
name: shop-db
type: Opaque
stringData:
password: change-me # in practice created by a secret manager / sealed secrets
---
apiVersion: v1
kind: Service
metadata:
name: webnest-shop
spec:
selector: { app: webnest-shop }
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webnest-shop
spec:
rules:
- host: shop.webneststudio.co.in
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: webnest-shop, port: { number: 80 } }
curl https://shop.webneststudio.co.in/actuator/health/readiness
{"status":"UP"}
Rolling update, rollback and autoscaling
# Deploy a new version with zero downtime
kubectl set image deployment/webnest-shop app=ghcr.io/webnest/shop:1.1.0
kubectl rollout status deployment/webnest-shop
# Something wrong? Roll back
kubectl rollout undo deployment/webnest-shop
# Scale on CPU between 3 and 10 replicas
kubectl autoscale deployment webnest-shop --cpu-percent=70 --min=3 --max=10
Waiting for deployment "webnest-shop" rollout to finish: 1 of 3 updated replicas are available...
deployment "webnest-shop" successfully rolled out
horizontalpodautoscaler.autoscaling/webnest-shop autoscaled
Common Mistakes
- Using the database health check in the liveness probe, causing restart storms during database incidents.
- No startupProbe for a slow-starting app, so the liveness probe kills it before it finishes starting.
- Setting a memory limit but letting the JVM heap use all of it, leaving no room for metaspace and threads (OOMKilled).
- terminationGracePeriodSeconds shorter than Spring's shutdown timeout, cutting off in-flight requests.
- Storing secrets in ConfigMaps or committing Secret manifests with real passwords to Git.
Key Points to Remember
- Deployment + Service + Ingress run and expose the app; ConfigMaps and Secrets configure it.
- Liveness, readiness and startup probes map to Spring Boot's health groups.
- Set resources and MaxRAMPercentage so the JVM fits the container.
- Graceful shutdown + readiness + preStop give zero-downtime rolling updates.
- Autoscale with HPA and handle multi-instance concerns (locks, shared caches, pool sizes).
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.