Chapter 09

容器化部署

Docker 多阶段构建,docker-compose 本地开发环境,Kubernetes 基础,Actuator 健康检查与监控。

为什么用容器

容器技术解决了软件开发中最经典的痛点——"在我本地是好的"。容器将应用与其运行时依赖(JDK 版本、库文件、配置)打包在一起,确保在任何环境(开发机、CI/CD、生产服务器)中行为一致。

核心概念名词解释

Dockerfile
描述如何构建 Docker 镜像的文本文件,包含一系列指令(FROM/COPY/RUN/CMD 等)。每条指令创建一个镜像层,层可以被缓存和复用,提高构建速度。
多阶段构建(Multi-stage Build)
在同一个 Dockerfile 中使用多个 FROM 指令,前一阶段(如编译阶段)的中间产物可以复制到后一阶段,最终镜像只包含运行时所需文件,大幅减小镜像体积(通常可从 600MB+ 压缩到 200MB 以内)。
docker-compose
定义和运行多容器 Docker 应用的工具。通过 docker-compose.yml 文件描述服务(app、数据库、Redis 等)的配置、网络、卷挂载关系,一条命令(docker compose up)启动整个本地开发环境。
Kubernetes(K8s)
容器编排平台,自动化容器的部署、扩缩容、故障恢复、滚动更新。核心资源包括 Pod(最小调度单元)、Deployment(管理 Pod 副本)、Service(服务发现)、ConfigMap/Secret(配置管理)。
Spring Boot Actuator
生产级监控端点库,提供 /health(健康检查)、/metrics(Prometheus 指标)、/info(应用信息)、/env(环境变量)等 HTTP 端点,供 K8s 健康探针和监控系统使用。
Readiness / Liveness Probe
K8s 的容器探针。Liveness Probe 检测容器是否存活(失败则重启);Readiness Probe 检测容器是否准备好接收流量(失败则从 Service 的负载均衡中摘除)。Spring Boot Actuator 的 /health 端点天然适合作为探针。

完整部署架构

互联网流量 │ ▼ ┌────────────────────────────────────────────────────────────┐ │ Kubernetes Cluster │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Ingress Controller(Nginx / Traefik) │ │ │ │ spring-app.example.com → spring-service │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Service(ClusterIP spring-service:8080) │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ ← 副本 │ │ │ spring │ │ spring │ │ spring │ │ │ │ :8080 │ │ :8080 │ │ :8080 │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ ┌─────────┐ └──────────────────────────────────┐ │ │ │ ConfigMap│ │ │ │ │ Secrets │ ←── 环境变量注入 │ │ │ └─────────┘ ▼ │ │ ┌─────────────┤ │ │ PostgreSQL │ │ │ StatefulSet │ │ └─────────────┤ └────────────────────────────────────────────────────────────┘

多阶段 Dockerfile

# ===== 阶段 1: 构建阶段(使用完整 JDK)=====
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# 先复制 pom.xml 利用 Docker 层缓存(依赖不变时跳过下载)
COPY pom.xml .
COPY mvnw .
COPY .mvn .mvn

# 下载依赖(利用缓存层)
RUN ./mvnw dependency:go-offline -B

# 复制源代码并构建
COPY src src
RUN ./mvnw package -DskipTests -B

# ===== 阶段 2: 运行阶段(只用 JRE,体积更小)=====
FROM eclipse-temurin:21-jre-alpine

# 安全:不以 root 用户运行
RUN addgroup -g 1001 spring && adduser -u 1001 -G spring -s /bin/sh -D spring
USER spring

WORKDIR /app

# 从构建阶段复制 JAR(利用 Spring Boot 分层 JAR 优化)
ARG JAR_FILE=target/*.jar
COPY --from=builder /app/${JAR_FILE} app.jar

# JVM 内存配置:容器感知,避免 OOM
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseZGC"

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
    CMD wget -qO- http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

docker-compose.yml — 本地开发环境

version: '3.9'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - DB_HOST=postgres
      - DB_PASSWORD=dev_password
      - REDIS_HOST=redis
      - RABBITMQ_HOST=rabbitmq
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: demo
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: dev_password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/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"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s

  rabbitmq:
    image: rabbitmq:3.13-management-alpine
    ports:
      - "5672:5672"
      - "15672:15672"  # 管理界面
    environment:
      RABBITMQ_DEFAULT_USER: admin
      RABBITMQ_DEFAULT_PASS: admin

volumes:
  postgres_data:

Spring Boot Actuator 与 Kubernetes 探针

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true   # 开启 /health/liveness 和 /health/readiness
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spring-demo
  template:
    metadata:
      labels:
        app: spring-demo
    spec:
      containers:
      - name: app
        image: your-registry/spring-demo:1.0.0
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 45  # JVM 启动需要时间
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
        envFrom:
        - configMapRef:
            name: spring-demo-config
        - secretRef:
            name: spring-demo-secrets
Warning JVM 在容器中默认按宿主机内存计算堆大小,可能超出容器限制导致 OOM Kill。务必加上 -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 参数,让 JVM 感知容器内存限制。Java 11+ 默认已开启 UseContainerSupport,但建议显式声明。
Tip Spring Boot 3.x 支持 GraalVM Native Image 原生编译,可将应用编译为原生可执行文件,启动时间从秒级降至毫秒级,内存占用减少 50-70%。代价是编译时间长(10-20分钟)、部分反射用法需要额外配置。适合 Serverless 和 Sidecar 场景。