Back to blog

Spring Boot: Triển Khai với Docker & Kubernetes

javaspring-bootdockerkubernetesdevopsdeployment
Spring Boot: Triển Khai với Docker & Kubernetes

Spring Boot application của bạn chạy ngon lành trên máy. Giờ ship lên production. Nghĩa là containers, orchestration, health checks, scaling, và configuration management — mà không biến deployment thành ngôi nhà xếp bằng bài.

Bài này đưa bạn từ một Spring Boot app hoạt động đến container production-ready chạy trên Kubernetes. Chúng ta sẽ cover Docker best practices cho Java/Spring Boot, rồi deploy lên Kubernetes với proper health probes, configuration, và auto-scaling.

Những Gì Bạn Sẽ Học

✅ Multi-stage Dockerfiles — images nhỏ, an toàn cho Spring Boot
✅ Layered JARs — builds nhanh hơn với Spring Boot layer extraction
✅ Docker Compose — chạy app cùng PostgreSQL và Redis locally
✅ Kubernetes Deployments — declarative manifests cho application
✅ Services và Ingress — expose app ra bên ngoài
✅ ConfigMaps và Secrets — externalize configuration an toàn
✅ Health probes — liveness và readiness với Spring Boot Actuator
✅ Resource limits — ngăn một pod chiếm hết resources của cluster
✅ Horizontal Pod Autoscaling — scale dựa trên CPU và memory
✅ Local development — minikube và kind để test Kubernetes locally

Prerequisites


1. Tại Sao Containerize Spring Boot?

Spring Boot applications là self-contained — chúng embed Tomcat và tạo ra single JAR. Nghe có vẻ deployment-friendly cho đến khi bạn nhận ra:

  • "Works on my machine" — JDK version, environment variables, và OS trên máy bạn khác production
  • Dependency conflicts — nhiều apps trên cùng server tranh giành ports, libraries, system resources
  • Scaling — thêm instances nghĩa là provision servers, configure load balancers, quản lý state

Containers giải quyết cả ba. Đóng gói app với JDK chính xác, dependencies, và configuration vào image. Chạy image đó ở bất kỳ đâu — laptop, staging, production. Scale bằng cách chạy thêm bản sao.

Không Có ContainersCó Containers
Cài JDK trên mỗi serverJDK đóng gói trong image
Cấu hình env vars thủ côngEnv vars khai báo trong manifests
Port conflicts giữa appsMỗi container có networking riêng
Snowflake serversImages giống nhau ở mọi nơi
Scale bằng mua server lớn hơnScale bằng chạy thêm containers

2. Multi-Stage Dockerfile

Dockerfile đơn giản copy source code và build trong image. Kết quả: image 600MB+ với build tools, source code, và JDK compiler — không cái nào cần ở runtime.

Multi-stage builds fix điều này. Build ở stage một, copy chỉ JAR sang stage cuối:

# Stage 1: Build
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
 
# Copy build files trước (cache dependencies)
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B
 
# Copy source và build
COPY src src
RUN ./mvnw package -DskipTests -B
 
# Stage 2: Runtime
FROM eclipse-temurin:21-jre AS runtime
WORKDIR /app
 
# Tạo non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
 
# Copy chỉ JAR
COPY --from=builder /app/target/*.jar app.jar
 
# Set ownership
RUN chown -R appuser:appuser /app
USER appuser
 
# JVM flags cho containers
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
 
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Được Gì

Khía CạnhDockerfile Đơn GiảnMulti-Stage
Image size~600MB (JDK + build tools + source)~280MB (chỉ JRE)
SecurityBuild tools, source code bị lộChỉ runtime artifacts
Build cacheRebuild mọi thứ khi code thay đổiDependencies cached riêng
UserChạy rootChạy non-root user

Chi Tiết Quan Trọng

eclipse-temurin:21-jre — dùng JRE, không phải JDK, cho runtime stage. Bạn không cần compiler ở production.

-XX:+UseContainerSupport — bảo JVM respect container memory limits. Không có flag này, JVM thấy toàn bộ RAM của host và có thể allocate quá nhiều.

-XX:MaxRAMPercentage=75.0 — dùng tối đa 75% memory limit của container cho heap. Để 25% cho non-heap memory (metaspace, threads, native memory).

Non-root user — chạy root trong container là security risk. Nếu attacker escape container, họ có root trên host.


3. Layered JARs (Spring Boot Optimization)

Spring Boot 2.3+ hỗ trợ layered JARs. Thay vì single fat JAR, application được chia thành layers mà Docker cache độc lập:

Layer 1: dependencies          (hiếm khi thay đổi — cached)
Layer 2: spring-boot-loader    (hiếm khi thay đổi — cached)
Layer 3: snapshot-dependencies  (đôi khi thay đổi)
Layer 4: application           (thay đổi mỗi build)

Khi bạn đổi code, chỉ Layer 4 rebuild. Layers 1-3 lấy từ cache. Build nhanh hơn đáng kể.

Bật Layered JARs

Thêm vào pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layers>
                    <enabled>true</enabled>
                </layers>
            </configuration>
        </plugin>
    </plugins>
</build>

Layered Dockerfile

# Stage 1: Build
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
 
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B
 
COPY src src
RUN ./mvnw package -DskipTests -B
 
# Extract layers
RUN java -Djarmode=layertools -jar target/*.jar extract --destination extracted
 
# Stage 2: Runtime
FROM eclipse-temurin:21-jre AS runtime
WORKDIR /app
 
RUN groupadd -r appuser && useradd -r -g appuser appuser
 
# Copy layers theo thứ tự (ít thay đổi nhất trước để tận dụng cache)
COPY --from=builder /app/extracted/dependencies/ ./
COPY --from=builder /app/extracted/spring-boot-loader/ ./
COPY --from=builder /app/extracted/snapshot-dependencies/ ./
COPY --from=builder /app/extracted/application/ ./
 
RUN chown -R appuser:appuser /app
USER appuser
 
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
 
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]

Lưu ý: Với layered extraction, entry point dùng JarLauncher thay vì -jar app.jar.

So Sánh Build Time

Build đầu tiên:                ~90 giây
Code thay đổi (không layers):  ~90 giây (rebuild mọi thứ)
Code thay đổi (có layers):     ~15 giây (chỉ application layer rebuild)

4. Docker Compose cho Development

Đừng cài PostgreSQL, Redis, và app riêng biệt. Docker Compose chạy mọi thứ cùng nhau:

# docker-compose.yml
version: "3.8"
 
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: dev
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/taskdb
      SPRING_DATASOURCE_USERNAME: postgres
      SPRING_DATASOURCE_PASSWORD: password
      SPRING_DATA_REDIS_HOST: redis
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: taskdb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
 
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data
 
volumes:
  pgdata:
  redisdata:

Sử Dụng

# Start mọi thứ
docker compose up -d
 
# Xem logs
docker compose logs -f app
 
# Rebuild sau khi đổi code
docker compose up -d --build app
 
# Stop mọi thứ
docker compose down
 
# Stop và xóa volumes (bắt đầu lại)
docker compose down -v

Development vs Production Compose

Cho development, thêm hot-reload bằng cách mount source code:

# docker-compose.dev.yml (override)
version: "3.8"
 
services:
  app:
    build:
      context: .
      target: builder        # Dừng ở build stage
    command: ./mvnw spring-boot:run
    volumes:
      - ./src:/app/src        # Mount source cho hot reload
    environment:
      SPRING_PROFILES_ACTIVE: dev
      SPRING_DEVTOOLS_RESTART_ENABLED: "true"
# Development với hot-reload
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

5. Build và Push Images

Build Locally

# Build với tag
docker build -t my-app:1.0.0 .
 
# Build với nhiều tags
docker build -t my-app:1.0.0 -t my-app:latest .
 
# Build cho platform cụ thể (ví dụ ARM Apple Silicon → x86 cho production)
docker build --platform linux/amd64 -t my-app:1.0.0 .

Push lên Container Registry

# Docker Hub
docker tag my-app:1.0.0 username/my-app:1.0.0
docker push username/my-app:1.0.0
 
# GitHub Container Registry
docker tag my-app:1.0.0 ghcr.io/username/my-app:1.0.0
docker push ghcr.io/username/my-app:1.0.0
 
# AWS ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag my-app:1.0.0 123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0

Spring Boot Maven Plugin (Không Cần Dockerfile)

Spring Boot có thể build OCI images trực tiếp không cần Dockerfile dùng Cloud Native Buildpacks:

./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=my-app:1.0.0

Tạo image tối ưu, layered tự động. Tốt cho quick builds; dùng custom Dockerfile nếu cần kiểm soát nhiều hơn.


6. Kubernetes Cơ Bản cho Spring Boot

Trước khi deploy, hiểu các Kubernetes objects chính:

ObjectMục ĐíchLiên Quan Tới Spring Boot
PodĐơn vị deploy nhỏ nhất (chạy container)Một pod = một instance app
DeploymentQuản lý pod replicas và rolling updatesKhai báo bao nhiêu instances và cách update
ServiceNetwork endpoint ổn định cho podsLoad balance qua các app instances
IngressHTTP routing từ ngoài clusterMap domain names tới Service
ConfigMapConfiguration không nhạy cảmGiá trị application.properties
SecretConfiguration nhạy cảmDatabase passwords, API keys
HPAHorizontal Pod AutoscalerScale pods dựa trên CPU/memory

7. Kubernetes Deployment Manifest

Tạo Deployment chạy Spring Boot application:

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
  labels:
    app: spring-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spring-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1           # Tối đa 1 pod dư trong lúc update
      maxUnavailable: 0     # Tất cả pods cũ vẫn chạy trong lúc update
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      containers:
        - name: spring-app
          image: ghcr.io/username/spring-app:1.0.0
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: spring-app-config
            - secretRef:
                name: spring-app-secrets
          resources:
            requests:
              cpu: "250m"
              memory: "512Mi"
            limits:
              cpu: "1000m"
              memory: "1Gi"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 5
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
            failureThreshold: 30    # 30 * 5s = 150s max startup time

Rolling Update Strategy

maxSurge: 1, maxUnavailable: 0

Nghĩa là: khi update, Kubernetes tạo một pod mới trước, đợi cho tới khi ready, rồi mới terminate một pod cũ. Lặp lại cho đến khi tất cả pods được update. Zero downtime.

Resource Requests vs Limits

resources:
  requests:
    cpu: "250m"       # Đảm bảo: 0.25 CPU cores
    memory: "512Mi"   # Đảm bảo: 512MB RAM
  limits:
    cpu: "1000m"      # Tối đa: 1 CPU core
    memory: "1Gi"     # Tối đa: 1GB RAM (OOM-killed nếu vượt)
  • Requests — pod được đảm bảo có. Kubernetes dùng để scheduling.
  • Limits — giới hạn tối đa. Vượt CPU → bị throttle. Vượt memory → bị OOM-killed.

Cho Spring Boot, 512Mi-1Gi là typical. JVM cần heap + metaspace + thread stacks + native memory. Monitor usage thực tế trước khi set limits.


8. Health Probes với Spring Boot Actuator

Kubernetes dùng ba loại probes để quản lý pods:

ProbeMục ĐíchKhi Fail
StartupApp còn đang start?Tiếp tục đợi (chưa restart)
LivenessApp còn sống?Restart pod
ReadinessApp xử lý traffic được?Rút khỏi load balancer

Bật Actuator Health Probes

Thêm Actuator dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Cấu hình trong application.yaml:

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true           # Bật /actuator/health/liveness và /readiness
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true
    db:
      enabled: true             # Kiểm tra database connectivity
    redis:
      enabled: true             # Kiểm tra Redis connectivity

Cách Probes Phối Hợp

Phân biệt quan trọng:

  • Database down → readiness fail (ngừng gửi traffic), liveness pass (không restart — restart chẳng fix được database outage)
  • App deadlocked → liveness fail (restart pod)
  • App đang start → startup probe tiếp tục chạy, liveness và readiness đợi

Custom Health Indicators

@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {
 
    private final ExternalServiceClient client;
 
    public ExternalServiceHealthIndicator(ExternalServiceClient client) {
        this.client = client;
    }
 
    @Override
    public Health health() {
        try {
            client.ping();
            return Health.up()
                .withDetail("service", "external-api")
                .withDetail("status", "reachable")
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("service", "external-api")
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

9. ConfigMaps và Secrets

Đừng bao giờ hardcode configuration vào Docker image. Dùng ConfigMaps cho giá trị không nhạy cảm và Secrets cho credentials.

ConfigMap

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: spring-app-config
data:
  SPRING_PROFILES_ACTIVE: "prod"
  SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres-service:5432/taskdb"
  SPRING_DATA_REDIS_HOST: "redis-service"
  SERVER_PORT: "8080"
  MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: "health,info,prometheus"

Secret

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: spring-app-secrets
type: Opaque
data:
  SPRING_DATASOURCE_USERNAME: cG9zdGdyZXM=          # base64 encoded "postgres"
  SPRING_DATASOURCE_PASSWORD: cGFzc3dvcmQ=           # base64 encoded "password"
  JWT_SECRET: bXktc3VwZXItc2VjcmV0LWtleQ==          # base64 encoded

Quan trọng: Base64 là encoding, không phải encryption. Cho secrets management thực sự, dùng Sealed Secrets, External Secrets Operator, hoặc secrets manager của cloud provider (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager).

Tạo Secrets Từ Command Line

# An toàn hơn đặt base64 trong YAML
kubectl create secret generic spring-app-secrets \
  --from-literal=SPRING_DATASOURCE_USERNAME=postgres \
  --from-literal=SPRING_DATASOURCE_PASSWORD=password \
  --from-literal=JWT_SECRET=my-super-secret-key

Spring Boot Configuration Hierarchy

Spring Boot resolve configuration theo thứ tự (priority cao nhất trước):

  1. Environment variables (từ ConfigMap/Secret) — priority cao nhất
  2. application-{profile}.yaml — profile-specific
  3. application.yaml — defaults trong JAR
  4. @ConfigurationProperties defaults — priority thấp nhất

Nghĩa là ConfigMap/Secret values override mọi thứ trong application.yaml đóng gói. Bạn ship JAR với sensible defaults, và Kubernetes override những gì cần thay đổi theo environment.


10. Service và Ingress

Service

Service cung cấp cho pods network identity ổn định:

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: spring-app-service
spec:
  type: ClusterIP
  selector:
    app: spring-app
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
Service TypeTruy Cập TừUse Case
ClusterIPChỉ trong clusterInternal services, microservices
NodePortNgoài qua node IP:portDevelopment, testing
LoadBalancerNgoài qua cloud LBProduction trên cloud providers

Cho production, dùng ClusterIP + Ingress (linh hoạt nhất) hoặc LoadBalancer (đơn giản nhất trên cloud).

Ingress

Route external HTTP traffic tới Service:

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: spring-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls-cert
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: spring-app-service
                port:
                  number: 80

Map https://api.example.com tới Spring Boot application với automatic TLS qua cert-manager.


11. Horizontal Pod Autoscaling

Scale Spring Boot application dựa trên CPU và memory usage:

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: spring-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: spring-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70      # Scale up khi CPU > 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80      # Scale up khi memory > 80%
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60  # Đợi 60s trước khi scale up lần nữa
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60           # Thêm tối đa 2 pods mỗi phút
    scaleDown:
      stabilizationWindowSeconds: 300 # Đợi 5 phút trước khi scale down
      policies:
        - type: Pods
          value: 1
          periodSeconds: 120          # Bớt tối đa 1 pod mỗi 2 phút

Cách Hoạt Động

Scale-down thận trọng (5 phút stabilization, 1 pod mỗi lần) vì scale down quá nhanh có thể gây cascading failures. Scale-up nhanh hơn vì latency spikes ảnh hưởng users ngay lập tức.

Prerequisites

HPA yêu cầu Metrics Server:

# Cài Metrics Server (nếu chưa có)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
 
# Kiểm tra
kubectl top pods

12. Database trên Kubernetes

Cho production databases, dùng managed service (AWS RDS, Cloud SQL, Azure Database). Chạy PostgreSQL trên Kubernetes có thể nhưng thêm operational complexity.

Cho development và testing, đây là PostgreSQL deployment:

# k8s/postgres.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          envFrom:
            - secretRef:
                name: postgres-secrets
          volumeMounts:
            - name: pgdata
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
      volumes:
        - name: pgdata
          persistentVolumeClaim:
            claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secrets
type: Opaque
data:
  POSTGRES_DB: dGFza2Ri                    # taskdb
  POSTGRES_USER: cG9zdGdyZXM=              # postgres
  POSTGRES_PASSWORD: cGFzc3dvcmQ=           # password

13. Complete Deployment

Cấu Trúc Thư Mục

k8s/
├── namespace.yaml
├── configmap.yaml
├── secret.yaml
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
└── postgres.yaml        # Chỉ dev/test

Namespace

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: spring-app

Deploy Mọi Thứ

# Tạo namespace
kubectl apply -f k8s/namespace.yaml
 
# Deploy theo thứ tự
kubectl apply -f k8s/configmap.yaml -n spring-app
kubectl apply -f k8s/secret.yaml -n spring-app
kubectl apply -f k8s/postgres.yaml -n spring-app    # Chỉ dev
kubectl apply -f k8s/deployment.yaml -n spring-app
kubectl apply -f k8s/service.yaml -n spring-app
kubectl apply -f k8s/ingress.yaml -n spring-app
kubectl apply -f k8s/hpa.yaml -n spring-app
 
# Hoặc deploy tất cả cùng lúc
kubectl apply -f k8s/ -n spring-app

Kiểm Tra

# Check pods
kubectl get pods -n spring-app
# NAME                          READY   STATUS    RESTARTS   AGE
# spring-app-6d4f8b7c9d-abc12  1/1     Running   0          30s
# spring-app-6d4f8b7c9d-def34  1/1     Running   0          30s
# spring-app-6d4f8b7c9d-ghi56  1/1     Running   0          30s
# postgres-7f8d9e6c5b-xyz78    1/1     Running   0          45s
 
# Check services
kubectl get svc -n spring-app
 
# Check logs
kubectl logs -f deployment/spring-app -n spring-app
 
# Check health
kubectl exec -it deployment/spring-app -n spring-app -- curl localhost:8080/actuator/health
 
# Check HPA status
kubectl get hpa -n spring-app
# NAME             REFERENCE               TARGETS          MINPODS   MAXPODS   REPLICAS
# spring-app-hpa   Deployment/spring-app   25%/70%, 40%/80%   2         10        3

14. Local Kubernetes Development

Không cần cloud cluster để test Kubernetes manifests. Dùng minikube hoặc kind locally.

minikube

# Start local cluster
minikube start --cpus=4 --memory=4096
 
# Bật ingress
minikube addons enable ingress
minikube addons enable metrics-server
 
# Build image bên trong Docker của minikube
eval $(minikube docker-env)
docker build -t spring-app:latest .
 
# Deploy (dùng imagePullPolicy: Never cho local images)
kubectl apply -f k8s/ -n spring-app
 
# Truy cập app
minikube service spring-app-service -n spring-app
 
# Dashboard
minikube dashboard

kind (Kubernetes in Docker)

# Tạo cluster
kind create cluster --name spring-dev
 
# Load image vào kind
docker build -t spring-app:latest .
kind load docker-image spring-app:latest --name spring-dev
 
# Deploy
kubectl apply -f k8s/ -n spring-app
 
# Port forward để truy cập
kubectl port-forward svc/spring-app-service 8080:80 -n spring-app

15. Production Checklist

Trước khi deploy lên production, kiểm tra mỗi item:

CategoryItemLý Do
DockerMulti-stage DockerfileImages nhỏ hơn, không build tools ở production
DockerNon-root userSecurity — giảm thiệt hại nếu container bị escape
DockerJRE, không phải JDKImage nhỏ, attack surface giảm
DockerUseContainerSupport flagJVM respect container memory limits
DockerLayered JARsBuilds nhanh với layer caching
K8sResource requests và limitsNgăn resource starvation
K8sLiveness probeRestart dead pods
K8sReadiness probeKhông gửi traffic tới unready pods
K8sStartup probeCho apps khởi động chậm thời gian boot
K8sConfigMap cho configKhông bake configuration vào images
K8sSecrets cho credentialsKhông lưu passwords trong ConfigMaps
K8sHPAScale khi tải cao
K8sRolling update strategyZero-downtime deployments
K8sManaged databaseKhông chạy production databases trên K8s
K8sIngress với TLSHTTPS cho external traffic

Tổng Kết

✅ Multi-stage Dockerfiles tạo images nhỏ, an toàn — chỉ JRE, không build tools
✅ Layered JARs làm rebuilds nhanh — chỉ application layer thay đổi khi update code
✅ Docker Compose đơn giản hóa local development — app, database, cache trong một lệnh
✅ Kubernetes Deployments khai báo desired state — replicas, updates, resource limits
✅ Health probes là thiết yếu — startup, liveness, readiness phục vụ mục đích khác nhau
✅ ConfigMaps và Secrets externalize configuration — không bao giờ bake credentials vào images
✅ HPA scale app tự động — scale up nhanh, scale down thận trọng
✅ Rolling updates với maxSurge: 1, maxUnavailable: 0 cho zero-downtime deploys
✅ Dùng managed databases ở production — đừng thêm database operations vào K8s workload
✅ Local K8s tools (minikube, kind) cho phép test manifests trước khi deploy lên cloud

Tiếp Theo?

Spring Boot app đã containerized và chạy trên Kubernetes, khám phá tiếp:

Tiếp tục Spring Boot series:

Bài Spring Boot Liên Quan:

Infrastructure Cơ Bản:


Thuộc series Spring Boot Learning Roadmap.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.