Why Kubernetes?
Imagine you're running a pizza restaurant. On a quiet Monday, one chef is enough. But on Super Bowl Sunday, you need 10 chefs, more ovens, and extra delivery drivers. What if your restaurant could automatically scale up and down based on demand?
That's exactly what Kubernetes (K8s) does for your applications. It automatically manages your containers - starting new ones when traffic increases, restarting crashed ones, and distributing load across servers.
Auto-Scaling
Automatically add more containers when traffic spikes and reduce when it's quiet. Pay only for what you use.
Self-Healing
If a container crashes, Kubernetes automatically restarts it. Your app stays running 24/7.
Zero-Downtime Deployments
Roll out updates gradually. If something goes wrong, automatically roll back to the previous version.
Industry Standard
Used by Google, Amazon, Netflix, and most modern companies. Essential skill for cloud-native development.
Prerequisites: Docker First
Before Kubernetes, you need to understand Docker. Your Java app must be containerized first.
Quick Docker Recap
# Dockerfile for Spring Boot app FROM eclipse-temurin:17-jdk-alpine WORKDIR /app # Copy the JAR file COPY target/myapp-0.0.1-SNAPSHOT.jar app.jar # Expose port EXPOSE 8080 # Run the application ENTRYPOINT ["java", "-jar", "app.jar"]
# Build and run locally docker build -t myapp:1.0 . docker run -p 8080:8080 myapp:1.0 # Push to registry (Docker Hub, ECR, GCR, etc.) docker tag myapp:1.0 username/myapp:1.0 docker push username/myapp:1.0
Kubernetes Core Concepts
Think of Kubernetes like managing a fleet of delivery trucks:
Pod
The smallest unit - like a single truck. Contains one or more containers that work together.
Deployment
Manages a fleet of identical pods. "I want 3 trucks running at all times."
Service
The dispatch center - routes requests to available pods. Provides a stable address.
ConfigMap & Secret
Configuration storage - like driver instructions and security codes.
Visual Overview
Internet
|
Ingress (optional - routing rules)
|
Service (load balancer)
/ | \
Pod Pod Pod (your app instances)
| | |
Container Container Container
(Java app) (Java app) (Java app)
Your First Kubernetes Deployment
Step 1: Create a Deployment
A Deployment tells Kubernetes how to run your app:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 3 # Run 3 instances of your app
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: username/myapp:1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
Understanding the YAML
- replicas: 3 - Keep 3 pods running at all times
- image - Your Docker image from a registry
- resources - Memory and CPU limits
- readinessProbe - Is the app ready to receive traffic?
- livenessProbe - Is the app still alive? Restart if not.
Step 2: Create a Service
A Service provides a stable way to access your pods:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp # Match pods with this label
ports:
- protocol: TCP
port: 80 # External port
targetPort: 8080 # Container port
type: LoadBalancer # Creates external IP (cloud providers)
Step 3: Apply to Kubernetes
# Apply configurations kubectl apply -f deployment.yaml kubectl apply -f service.yaml # Check status kubectl get deployments kubectl get pods kubectl get services # View pod logs kubectl logs -f myapp-deployment-abc123 # Describe for debugging kubectl describe pod myapp-deployment-abc123
Configuration: ConfigMaps and Secrets
Don't hardcode configuration in your Docker image. Use ConfigMaps for regular config and Secrets for sensitive data.
ConfigMap for Application Properties
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
SPRING_PROFILES_ACTIVE: "production"
SERVER_PORT: "8080"
LOG_LEVEL: "INFO"
application.properties: |
spring.datasource.url=jdbc:postgresql://db-service:5432/mydb
spring.jpa.hibernate.ddl-auto=validate
server.tomcat.max-threads=200
Secrets for Sensitive Data
# Create secret from command line (values are base64 encoded) kubectl create secret generic myapp-secrets \ --from-literal=DB_USERNAME=admin \ --from-literal=DB_PASSWORD=supersecret123 # Or from YAML (values must be base64 encoded) # echo -n 'admin' | base64 → YWRtaW4= apiVersion: v1 kind: Secret metadata: name: myapp-secrets type: Opaque data: DB_USERNAME: YWRtaW4= DB_PASSWORD: c3VwZXJzZWNyZXQxMjM=
Using Config in Deployment
# Updated deployment.yaml
spec:
containers:
- name: myapp
image: username/myapp:1.0
ports:
- containerPort: 8080
# Environment variables from ConfigMap
envFrom:
- configMapRef:
name: myapp-config
# Environment variables from Secret
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: myapp-secrets
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: DB_PASSWORD
# Mount config file
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: myapp-config
items:
- key: application.properties
path: application.properties
Scaling Your Application
Manual Scaling
# Scale to 5 replicas kubectl scale deployment myapp-deployment --replicas=5 # Check the scaling kubectl get pods -w # Watch pods come up
Auto-Scaling (HPA)
# hpa.yaml - Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale when CPU > 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # Scale when Memory > 80%
# Apply and monitor kubectl apply -f hpa.yaml kubectl get hpa -w # Output: # NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS # myapp-hpa Deployment/myapp-deploy 45%/70% 2 10 3
Zero-Downtime Deployments
Kubernetes can update your app without any downtime using rolling updates.
# Update the image to a new version kubectl set image deployment/myapp-deployment myapp=username/myapp:2.0 # Or edit the deployment kubectl edit deployment myapp-deployment # Watch the rollout kubectl rollout status deployment/myapp-deployment # View rollout history kubectl rollout history deployment/myapp-deployment # Rollback if something goes wrong! kubectl rollout undo deployment/myapp-deployment kubectl rollout undo deployment/myapp-deployment --to-revision=2
Deployment Strategy
# In deployment.yaml
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max pods over desired count during update
maxUnavailable: 0 # Never have less than desired count
Spring Boot Kubernetes Best Practices
1. Enable Actuator Health Endpoints
# pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.properties
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.health.probes.enabled=true
management.health.livenessState.enabled=true
management.health.readinessState.enabled=true
2. Graceful Shutdown
# application.properties server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s
3. Optimized Dockerfile
# Use layered JAR for faster builds FROM eclipse-temurin:17-jdk-alpine as builder WORKDIR /app COPY target/*.jar app.jar RUN java -Djarmode=layertools -jar app.jar extract FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=builder /app/dependencies/ ./ COPY --from=builder /app/spring-boot-loader/ ./ COPY --from=builder /app/snapshot-dependencies/ ./ COPY --from=builder /app/application/ ./ # Run as non-root user RUN addgroup -S spring && adduser -S spring -G spring USER spring EXPOSE 8080 ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
4. Resource Limits
# Set JVM memory based on container limits ENTRYPOINT ["java", \ "-XX:+UseContainerSupport", \ "-XX:MaxRAMPercentage=75.0", \ "-jar", "app.jar"]
Essential kubectl Commands
# Cluster info kubectl cluster-info kubectl get nodes # Viewing resources kubectl get pods kubectl get pods -o wide # More details kubectl get all # All resource types kubectl get pods -n my-namespace # Specific namespace # Debugging kubectl logs pod-name kubectl logs -f pod-name # Follow logs kubectl logs pod-name -c container-name # Specific container kubectl describe pod pod-name kubectl exec -it pod-name -- /bin/sh # Shell into container # Port forwarding (for local testing) kubectl port-forward pod-name 8080:8080 kubectl port-forward service/myapp-service 8080:80 # Resource management kubectl apply -f file.yaml kubectl delete -f file.yaml kubectl delete pod pod-name # Namespaces kubectl create namespace production kubectl get pods -n production kubectl config set-context --current --namespace=production
Local Kubernetes Development
Local K8s Options
- Minikube - Full K8s cluster in a VM (most popular)
- Docker Desktop - Built-in K8s (easiest for Mac/Windows)
- Kind - Kubernetes in Docker (lightweight)
- K3s - Lightweight K8s (great for learning)
# Minikube setup minikube start minikube dashboard # Opens web UI # Docker Desktop # Enable Kubernetes in Docker Desktop settings # Use local images with Minikube eval $(minikube docker-env) docker build -t myapp:1.0 . # Now minikube can use the local image