Chuyển đến nội dung chính

Lesson 24: CI/CD, Cloud Deployment & Production Best Practices

GitHub Actions CI/CD pipeline. Deploy to AWS/GCP with Kubernetes. Database migration with Flyway. Zero-downtime deployment. Production checklist & best practices.

💻 Programming — Lesson 23 Lesson 24: CI/CD, Cloud Deployment & Production Best Practices

Spring Boot 4: From Basics to Advanced

Part 6: Microservices & Production

xdev.asia

Introduction

The final article of the series summarizes everything needed to bring a Spring Boot application to production: CI/CD pipeline, database migration, Kubernetes deployment, zero-downtime updates, and production checklist.


1. Database Migration with Flyway

1.1 Setup

// build.gradle.kts
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-database-postgresql")
# application.yml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true

1.2 Migration Files

src/main/resources/db/migration/
├── V1__create_users_table.sql
├── V2__create_products_table.sql
├── V3__create_orders_table.sql
└── V4__add_email_index_to_users.sql
-- V1__create_users_table.sql
CREATE TABLE users (
    id          BIGSERIAL PRIMARY KEY,
    username    VARCHAR(50)  NOT NULL UNIQUE,
    email       VARCHAR(255) NOT NULL UNIQUE,
    password    VARCHAR(255) NOT NULL,
    role        VARCHAR(20)  NOT NULL DEFAULT 'USER',
    created_at  TIMESTAMP    NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMP    NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
-- V4__add_email_index_to_users.sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_username
    ON users(username);

Rule: Never edit an already running migration file. Create a new migration to change the schema.


2. CI/CD with GitHub Actions

2.1 Build & Test Pipeline

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17-alpine
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21

      - uses: gradle/actions/setup-gradle@v4

      - name: Run tests
        run: ./gradlew test
        env:
          SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/testdb
          SPRING_DATASOURCE_USERNAME: test
          SPRING_DATASOURCE_PASSWORD: test

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-report
          path: build/reports/tests/

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21

      - name: Build Docker image
        run: ./gradlew bootBuildImage --imageName=ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Push to Container Registry
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

3. Kubernetes Deployment

3.1 Deployment manifest

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0  # Zero-downtime
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: ghcr.io/myorg/myapp:latest
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: production
            - name: SPRING_DATASOURCE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          startupProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            failureThreshold: 30
            periodSeconds: 2

3.2 Service & Ingress

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts:
        - api.example.com
      secretName: myapp-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp
                port:
                  number: 80

3.3 Kubernetes Probes in Spring Boot

# application-production.yml
management:
  endpoint:
    health:
      probes:
        enabled: true
  health:
    readinessstate:
      enabled: true
    livenessstate:
      enabled: true

4. Zero-Downtime Deployment

4.1 Graceful Shutdown

# application.yml
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

4.2 Rolling Update Strategy

Replica 1 (v1) ─── Running ───────────────────►
Replica 2 (v1) ─── Running ───────────────────►
Replica 3 (v1) ─── Running ─── Terminating ──►

Replica 4 (v2) ──── Starting ─── Ready ──────►
Replica 5 (v2) ─────── Starting ─── Ready ──►
Replica 6 (v2) ──────────── Starting ─ Ready►

5. Production Checklist

5.1 Configuration

# application-production.yml
spring:
  jpa:
    open-in-view: false        # Tắt OSIV
    show-sql: false             # Không log SQL
    hibernate:
      ddl-auto: validate        # Chỉ validate, không auto-create

  jackson:
    default-property-inclusion: non_null

server:
  error:
    include-stacktrace: never   # Không expose stacktrace
    include-message: never

logging:
  level:
    root: WARN
    com.example: INFO

5.2 Security Checklist

ItemStatus
HTTPS Only (TLS)☐
Security Headers (HSTS, CSP)☐
JWT with short expiration (15-30min)☐
Rate Limiting☐
Input Validation all endpoints☐
SQL Injection protection (JPA)☐
Secrets in env vars/vault☐
Actuator endpoints protected☐

5.3 Performance Checklist

ItemStatus
Connection pooling (HikariCP)☐
Database indexes for queries☐
Caching (Caffeine/Redis)☐
@Transactional(readOnly=true) for read queries☐
Pagination for list endpoints☐
Async processing for heavy tasks☐
GZip compression☐

5.4 Monitoring

# Grafana Dashboard essentials
Metrics to monitor:
  - JVM Memory (Heap/Non-Heap)
  - GC Pause time
  - HTTP request rate & latency (p50, p95, p99)
  - Error rate (4xx, 5xx)
  - Database connection pool usage
  - Thread pool utilization
  - Custom business metrics

6. Series Summary

Bạn đã học:

Phần 1: Nền tảng Spring Boot
  └── Spring IoC, Auto-Configuration, DI, Bean Lifecycle

Phần 2: Xây dựng REST API
  └── Controller, DTO, Validation, JPA, Specification

Phần 3: Bảo mật ứng dụng
  └── Spring Security, JWT, OAuth2, Method Security

Phần 4: Tính năng nâng cao
  └── Transaction, Cache, Async, Events, WebSocket, OpenAPI

Phần 5: Testing & Chất lượng
  └── JUnit 5, Mockito, Testcontainers, Actuator, Observability

Phần 6: Microservices & Production
  └── Docker, Kubernetes, Kafka/RabbitMQ, CI/CD, Production Best Practices

Summary

  • Flyway manages database migrations: version control for schema, never edit already run migrations
  • GitHub Actions CI/CD: test → build Docker image → push to registry → deploy to Kubernetes
  • Kubernetes: rolling update with readiness/liveness probes, graceful shutdown for zero-downtime deployment
  • Production checklist: security headers, connection pooling, caching, monitoring — ensure the application is production-ready

Exercises

  1. Setup Flyway: create migration files for all entities in the project, test with ./gradlew flywayMigrate
  2. Write GitHub Actions CI/CD: run tests with PostgreSQL service, build Docker image, push to GHCR
  3. Create Kubernetes manifests: Deployment (3 replicas), Service, Ingress. Configure probes and graceful shutdown. Test rolling updates