为什么用容器
容器技术解决了软件开发中最经典的痛点——"在我本地是好的"。容器将应用与其运行时依赖(JDK 版本、库文件、配置)打包在一起,确保在任何环境(开发机、CI/CD、生产服务器)中行为一致。
- 环境一致性:消除"在我机器上能跑"问题,开发、测试、生产使用相同镜像
- 快速扩缩容:启动一个容器通常只需秒级,而非分钟级(传统 VM)
- 资源隔离:容器间 CPU/内存隔离,单个服务崩溃不影响其他服务
- 不可变基础设施:每次部署都是创建新容器而非修改旧容器,回滚简单可靠
核心概念名词解释
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 场景。