微服务容器化部署
微服务上云的第一步是容器化:把每个服务打包成可移植的 Docker 镜像,用统一的运行环境消除"在我机器上能跑"的问题。本文覆盖 Dockerfile 多阶段构建、镜像瘦身、健康检查、优雅关闭与 Compose 编排的完整实践。
为什么容器化
容器带来的价值
容器化的收益:
├─ 环境一致性:开发/测试/生产同一镜像
├─ 隔离性:每个服务独立运行环境
├─ 可移植性:镜像可运行在任何 Docker 主机
├─ 快速启动:秒级拉起,便于扩缩容
└─ 标准化:为 Kubernetes 编排打基础Java 微服务容器化的特殊性
Java 应用的注意点:
├─ JVM 内存:容器内存限制要传给 JVM(-Xmx)
├─ 时区与编码:镜像内设置 Asia/Shanghai 与 UTF-8
├─ 基础镜像:选带 JRE 的镜像而非完整 JDK
└─ 优雅关闭:配合 SIGTERM 让 Spring 平滑停机一、Dockerfile 多阶段构建
为什么多阶段
单阶段构建的问题:
├─ 镜像包含 JDK + Maven + 源码 + 依赖 → 巨大(1GB+)
└─ 构建产物(依赖、中间文件)泄漏到运行镜像
多阶段构建:
├─ 阶段一(builder):用 Maven 镜像编译出 JAR
├─ 阶段二(runtime):只拷 JAR + JRE,运行
└─ 最终镜像只含运行所需 → 大幅瘦身多阶段 Dockerfile 示例
dockerfile
# ========== 阶段一:构建 ==========
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
# 先拷 pom 单独拉依赖,利用 Docker 层缓存
COPY pom.xml .
RUN mvn dependency:go-offline
# 再拷源码编译
COPY src ./src
RUN mvn clean package -DskipTests
# ========== 阶段二:运行 ==========
FROM eclipse-temurin:21-jre
# 基础环境:时区、编码
ENV TZ=Asia/Shanghai \
LANG=C.UTF-8 \
JAVA_OPTS="-Xms512m -Xmx512m"
WORKDIR /app
# 只拷贝 JAR(来自构建阶段)
COPY --from=builder /app/target/order-service-1.0.0.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# 非 root 运行(安全)
USER 10001
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]构建原理
多阶段构建执行流程:
├─ Docker 按阶段顺序执行
├─ builder 阶段产出 JAR(用完即弃)
├─ 最终镜像只从 builder 拷贝需要的文件
└─ 中间层镜像不进入最终产物
层缓存优化:
├─ COPY pom.xml + RUN go-offline 先执行(依赖不变则缓存命中)
├─ COPY src 后执行(只有代码变化才重新编译)
└─ 调整顺序可以极大提升构建速度二、镜像瘦身
瘦身手段清单
镜像瘦身常用手段:
├─ 1. 多阶段构建:构建工具不进运行镜像(收益最大)
├─ 2. 精简基础镜像:
│ eclipse-temurin-jre(推荐)≈ 200MB
│ alpine + jlink 定制 ≈ 50-100MB
├─ 3. jlink 定制 JRE:
│ jlink --add-modules java.base,java.sql,... --output jre
│ 只打包用到的模块
├─ 4. 排除无用文件:
│ .dockerignore 排除 target、.git、IDE 文件
├─ 5. Spring Boot 分层(layers):
│ bootJar 按依赖/资源/类分层,复用层缓存
└─ 6. 压缩:GZIP 层面(较少用).dockerignore
dockerignore
target
.git
.gitignore
.idea
*.iml
Dockerfile
README.md
logsSpring Boot 分层打包
gradle
// 或 pom.xml 中配置 bootJar layered
bootJar {
layered {
enabled = true
}
}分层效果:
├─ 依赖层(外部 jar):基本不变 → 镜像层缓存命中
├─ 资源层:变化少
└─ 应用类层:每次变
构建时依赖层不重传 → CI/CD 加速三、健康检查与优雅关闭
为什么需要健康检查
容器的"活着"不等于"健康":
├─ 进程存活但 JVM 卡死 → 不健康
├─ Spring 上下文未加载完 → 不应接流量
└─ 编排平台(K8s/Compose)靠健康检查做重启/摘除Spring Boot Actuator 健康端点
yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
probes:
enabled: true # 暴露 liveness/readiness 探针三种探针:
├─ Liveness(存活):进程卡死 → 重启
├─ Readiness(就绪):依赖未就绪 → 摘除流量
└─ Startup(启动):启动耗时长 → 给足时间
Docker HEALTHCHECK 与 K8s 探针二选一或并用优雅关闭
优雅关闭流程:
1. 收到 SIGTERM(停止信号)
2. Spring 停止接收新请求
3. 处理完在途请求(超时兜底)
4. 释放资源(连接池、线程池)
5. 进程退出
Spring Boot 配置:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30syaml
server:
shutdown: graceful # 优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 停机宽限时间启动脚本的注意事项
├─ JVM 收到 SIGTERM 默认退出,Spring 会注册 shutdown hook
├─ 不要在 ENTRYPOINT 中用 shell 子进程导致信号丢失
│ sh -c "java -jar ..." 会转发信号,直接 java 也行
└─ 容器停止要配合超时:K8s terminationGracePeriodSeconds四、Docker Compose 编排
场景
Compose 适合:
├─ 本地开发环境(一条命令拉起整个链路)
├─ 测试环境(依赖服务 + 中间件)
└─ 小规模生产(不引入 K8s 时)
一个微服务链路的 Compose:
├─ gateway、order、account、stock 服务
├─ nacos、mysql、redis 中间件
└─ 服务间通过服务名互通docker-compose.yml 示例
yaml
version: "3.8"
services:
nacos:
image: nacos/nacos-server:v2.3.2
environment:
MODE: standalone
JVM_XMS: 256m
JVM_XMX: 256m
ports:
- "8848:8848"
- "9848:9848"
networks: [app-net]
order-service:
build:
context: ./order-service
image: registry.example.com/order-service:1.0.0
environment:
SPRING_PROFILES_ACTIVE: dev
NACOS_ADDR: nacos:8848 # 服务名互通
JAVA_OPTS: "-Xms256m -Xmx256m"
depends_on:
nacos:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 3
networks: [app-net]
gateway:
build: ./gateway
ports:
- "8080:8080"
environment:
NACOS_ADDR: nacos:8848
depends_on: [nacos, order-service]
networks: [app-net]
networks:
app-net:
driver: bridgeCompose 使用
bash
# 启动整个链路
docker compose up -d
# 查看状态
docker compose ps
# 查看日志
docker compose logs -f order-service
# 重建某个服务(代码变更后)
docker compose up -d --build order-service
# 停止
docker compose down五、镜像仓库与安全
镜像仓库
镜像仓库选型:
├─ 私有化:Harbor(功能全,支持漏洞扫描)
├─ 云厂商:阿里云 ACR / 腾讯云 TCR
└─ 本地轻量:registry(Docker 官方)
镜像命名与标签:
├─ registry/repo:tag
├─ tag 建议:版本号(1.0.0)或 Git 短哈希
└─ 避免 latest(不可追溯)镜像安全
镜像安全清单:
├─ 基础镜像漏洞扫描(Trivy / Harbor 扫描)
├─ 非 root 用户运行(USER 10001)
├─ 最小化组件(不装 shell、curl 等多余工具)
├─ 依赖版本锁定(pom 锁定版本,禁止 SNAPSHOT)
├─ 敏感信息不入镜像(配置走环境变量/Secret)
└─ 定期更新基础镜像(CVE 修复)六、容器化部署最佳实践
容器化部署清单:
├─ 1. 多阶段构建,最终镜像只含运行环境
├─ 2. JVM 内存与容器内存对齐(-Xmx ≤ 容器限制)
├─ 3. 配置探针:liveness + readiness 都配
├─ 4. 优雅关闭:shutdown=graceful + 停机超时
├─ 5. .dockerignore 减少构建上下文
├─ 6. 非 root 运行 + 漏洞扫描
├─ 7. 标签可追溯(版本/哈希),不用 latest
└─ 8. 镜像仓库私有化 + 访问控制总结
容器化部署的核心是**"一次构建,处处运行"**:多阶段 Dockerfile 把构建与运行分离实现镜像瘦身,健康检查与优雅关闭保证服务在容器世界里"可用且平滑",Compose 让本地与测试环境一键拉起整个微服务链路。做好镜像瘦身、探针与优雅关闭这三件事,容器化就为后续的 Kubernetes 编排打好了地基。