代码改变世界

详细介绍:Docker 多服务镜像构建完整教程

2025-12-11 23:52  tlnshuju  阅读(10)  评论(0)    收藏  举报

本文将手把手教你如何使用 Docker 多阶段构建技术,将一个 Spring Boot 多模块项目构建成三个独立的服务镜像。适合 Java 全栈开发者学习。

目录


项目背景

我们有一个基于若依框架的 Spring Boot 多模块项目,包含三个服务:

  1. ayy-server:核心业务服务(端口 48087)
  2. ayy-snailjob-server:分布式任务调度服务(端口 9090)
  3. ayy-monitor-admin:监控管理服务(端口 8800, 17888)

项目结构如下:

project-root/
├── pom.xml                          # 父 POM
├── ruoyi-admin/                     # 主服务模块
│   ├── pom.xml
│   └── src/
├── ruoyi-extend/
│   ├── ruoyi-monitor-admin/         # 监控服务模块
│   │   ├── pom.xml
│   │   └── src/
│   └── ruoyi-snailjob-server/       # 任务调度服务模块
│       ├── pom.xml
│       └── src/
├── Dockerfile                        # 多阶段构建文件
└── .dockerignore                     # Docker 忽略文件

技术架构

核心技术栈

  • Java 17:运行环境
  • Maven 3.9.6:项目构建工具
  • Spring Boot:应用框架
  • Docker Multi-stage Build:多阶段构建
  • Alpine Linux:精简基础镜像

为什么使用多阶段构建?

传统方式需要:

  1. 本地编译 → 生成 JAR
  2. 编写 Dockerfile → 复制 JAR
  3. 构建镜像

多阶段构建的优势

一个 Dockerfile 完成所有事情
构建环境与运行环境分离(builder 阶段用完即弃)
最终镜像体积小(只包含 JRE + JAR)
利用 Docker 缓存加速构建
一次构建多个镜像


准备工作

1. 安装 Docker

# macOS
brew install docker
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io
# 验证安装
docker --version

2. 启用 BuildKit(推荐)

BuildKit 是 Docker 的新一代构建引擎,支持并行构建和高级缓存。

# 临时启用
export DOCKER_BUILDKIT=1
# 永久启用(添加到 ~/.bashrc 或 ~/.zshrc)
echo 'export DOCKER_BUILDKIT=1' >> ~/.bashrc
source ~/.bashrc

3. 项目准备

确保项目根目录有以下文件:

  • pom.xml(父 POM)
  • Dockerfile
  • .dockerignore

Dockerfile 详解

我们的 Dockerfile 分为 4 个阶段:1 个构建阶段 + 3 个运行阶段。

第一阶段:构建阶段(builder)

# -------------------------------
# 构建阶段:使用 Maven 构建产物
# -------------------------------
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
# 先复制 pom 文件,利用 Docker 层缓存
COPY pom.xml .
COPY ruoyi-admin/pom.xml ./ruoyi-admin/
COPY ruoyi-extend/ruoyi-monitor-admin/pom.xml ./ruoyi-extend/ruoyi-monitor-admin/
COPY ruoyi-extend/ruoyi-snailjob-server/pom.xml ./ruoyi-extend/ruoyi-snailjob-server/
# 下载依赖(利用缓存层)
RUN --mount=type=cache,target=/root/.m2 \
    mvn dependency:go-offline -B || true
# 复制源码
COPY . .
# 构建(跳过测试)
RUN --mount=type=cache,target=/root/.m2 \
    mvn -B clean package -DskipTests

关键点解析

  1. 分层复制 POM:先复制 POM 文件再复制源码

    • 依赖不变时,Docker 会使用缓存,避免重复下载
  2. Maven 缓存挂载--mount=type=cache,target=/root/.m2

    • Maven 本地仓库会被缓存,大幅加速后续构建
  3. 跳过测试-DskipTests

    • 构建镜像时跳过单元测试,节省时间

第二阶段:ayy-server 运行镜像

# -------------------------------
# 运行阶段:ayy-server
# -------------------------------
FROM eclipse-temurin:17-jre-alpine AS ayy-server
# 创建非 root 用户
RUN addgroup -S adduser && adduser -S adduser -G adduser
WORKDIR /app
# 拷贝构建产物
COPY --from=builder --chown=adduser:adduser /build/ruoyi-admin/target/*.jar ./app.jar
# 创建日志和配置目录
RUN mkdir -p /app/logs /app/config && \
    chown -R adduser:adduser /app
# 时区设置
ENV TZ=Asia/Shanghai \
    JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Djava.security.egd=file:/dev/./urandom" \
    SPRING_PROFILES_ACTIVE=prod \
    SERVER_PORT=48087
EXPOSE 48087
USER adduser
# 健康检查(可选)
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:${SERVER_PORT}/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -jar app.jar"]

关键点解析

  1. 使用 JRE 而非 JDK

    • eclipse-temurin:17-jre-alpine:精简镜像(~180MB)
    • Alpine Linux:基于 musl libc 的超轻量级发行版
  2. 安全性考虑

    • 创建非 root 用户运行应用
    • 使用 --chown 直接设置文件权限
  3. JVM 参数优化

    • -Xms512m -Xmx1024m:堆内存配置
    • -XX:+UseG1GC:使用 G1 垃圾回收器
    • -XX:MaxGCPauseMillis=200:最大停顿时间 200ms
  4. 健康检查

    • 需要应用开启 Spring Boot Actuator
    • 自动检测服务是否健康
  5. 灵活配置

    • 通过环境变量控制 JVM 参数和 Spring Profile
    • 运行时可通过 -e 参数覆盖

第三、四阶段:另外两个服务

结构与 ayy-server 类似,只是:

  • 复制的 JAR 路径不同
  • 暴露的端口不同

.dockerignore 配置

.dockerignore 文件告诉 Docker 哪些文件不需要复制到镜像中

# Maven 构建产物(避免复制本地编译的 target 目录)
target/
*/target/
**/target/
**/dependency-reduced-pom.xml
# IDE 配置文件
.idea/
*.iml
.vscode/
.history/
.eclipse/
.settings/
# Git 相关
.git/
.gitignore
.gitattributes
# 日志文件
**/logs/
*.log
# 临时文件
**/*.tmp
**/*.temp
**/*.swp
**/*.swo
**/*.bak
**/*.DS_Store
*~
# Docker 相关(避免递归)
Dockerfile
docker-compose.yml
.dockerignore
# 文档
README.md
docs/
# CI/CD 配置
.github/
.gitlab-ci.yml
Jenkinsfile
# 前端构建产物
**/node_modules/
**/dist/
# 测试覆盖率
**/coverage/
**/.nyc_output/
# 其他
.gitee/
.run/
.image/
script/

为什么需要 .dockerignore?

  1. 减少构建上下文大小:避免复制无用文件
  2. 加快构建速度:传输到 Docker daemon 的数据更少
  3. 避免缓存失效:无关文件变化不会触发重新构建

构建镜像

方式一:单独构建(适合开发测试)

1. 构建 ayy-server
docker build --target ayy-server -t ayy-server:latest .

命令解析

  • --target ayy-server:指定构建目标阶段
  • -t ayy-server:latest:镜像名称和标签
  • .:构建上下文(当前目录)
2. 构建 ayy-snailjob-server
docker build --target ayy-snailjob-server -t ayy-snailjob-server:latest .
3. 构建 ayy-monitor-admin
docker build --target ayy-monitor-admin -t ayy-monitor-admin:latest .

方式二:带版本号构建(适合生产发布)

# 定义版本号
VERSION=1.0.0
# 构建三个镜像(同时打 VERSION 和 latest 标签)
docker build --target ayy-server -t ayy-server:${VERSION} -t ayy-server:latest .
docker build --target ayy-snailjob-server -t ayy-snailjob-server:${VERSION} -t ayy-snailjob-server:latest .
docker build --target ayy-monitor-admin -t ayy-monitor-admin:${VERSION} -t ayy-monitor-admin:latest .

方式三:使用脚本批量构建

创建 build.sh

#!/bin/bash
set -e
VERSION=${1:-latest}
echo "开始构建镜像,版本: ${VERSION}"
# 启用 BuildKit
export DOCKER_BUILDKIT=1
# 构建 ayy-server
echo "构建 ayy-server..."
docker build --target ayy-server -t ayy-server:${VERSION} -t ayy-server:latest .
# 构建 ayy-snailjob-server
echo "构建 ayy-snailjob-server..."
docker build --target ayy-snailjob-server -t ayy-snailjob-server:${VERSION} -t ayy-snailjob-server:latest .
# 构建 ayy-monitor-admin
echo "构建 ayy-monitor-admin..."
docker build --target ayy-monitor-admin -t ayy-monitor-admin:${VERSION} -t ayy-monitor-admin:latest .
echo "✓ 所有镜像构建完成!"
# 显示镜像列表
docker images | grep -E "ayy-server|ayy-snailjob-server|ayy-monitor-admin"

使用方法:

# 赋予执行权限
chmod +x build.sh
# 构建 latest 版本
./build.sh
# 构建指定版本
./build.sh 1.0.0

验证构建结果

# 查看构建的镜像
docker images | grep ayy
# 输出示例:
# ayy-server              latest    abc123def456   2 minutes ago   350MB
# ayy-snailjob-server     latest    def456ghi789   3 minutes ago   340MB
# ayy-monitor-admin       latest    ghi789jkl012   4 minutes ago   345MB

运行容器

基础运行

1. 运行 ayy-server
docker run -d \
--name ayy-server \
-p 48087:48087 \
ayy-server:latest

参数说明

  • -d:后台运行
  • --name ayy-server:容器名称
  • -p 48087:48087:端口映射(宿主机:容器)
2. 运行 ayy-snailjob-server
docker run -d \
--name ayy-snailjob-server \
-p 9090:9090 \
ayy-snailjob-server:latest
3. 运行 ayy-monitor-admin
docker run -d \
--name ayy-monitor-admin \
-p 8800:8800 \
-p 17888:17888 \
ayy-monitor-admin:latest

高级运行(带配置)

docker run -d \
--name ayy-server \
-p 48087:48087 \
-e SPRING_PROFILES_ACTIVE=prod \
-e JAVA_OPTS="-Xms1g -Xmx2g" \
-e SPRING_DATASOURCE_URL="jdbc:mysql://192.168.1.100:3306/ayy?useUnicode=true&characterEncoding=utf8" \
-e SPRING_DATASOURCE_USERNAME=root \
-e SPRING_DATASOURCE_PASSWORD=password \
-e SPRING_REDIS_HOST=192.168.1.100 \
-e SPRING_REDIS_PORT=6379 \
-v /data/logs/ayy-server:/app/logs \
-v /data/config/ayy-server:/app/config:ro \
--restart unless-stopped \
ayy-server:latest

高级参数说明

  • -e:环境变量注入

    • SPRING_PROFILES_ACTIVE:Spring 环境配置
    • JAVA_OPTS:JVM 参数
    • SPRING_DATASOURCE_*:数据库配置
    • SPRING_REDIS_*:Redis 配置
  • -v:数据卷挂载

    • /data/logs/ayy-server:/app/logs:日志持久化
    • /data/config/ayy-server:/app/config:ro:配置文件(只读)
  • --restart unless-stopped:自动重启策略

查看容器状态

# 查看运行中的容器
docker ps
# 查看容器日志
docker logs -f ayy-server
# 查看容器资源使用情况
docker stats ayy-server
# 进入容器(调试用)
docker exec -it ayy-server sh

停止和清理

# 停止容器
docker stop ayy-server ayy-snailjob-server ayy-monitor-admin
# 删除容器
docker rm ayy-server ayy-snailjob-server ayy-monitor-admin
# 删除镜像
docker rmi ayy-server:latest ayy-snailjob-server:latest ayy-monitor-admin:latest

生产部署

使用 Docker Compose(推荐)

创建 docker-compose.yml

services:
mysql:
image: mysql:8.0
container_name: ayy-mysql
environment:
MYSQL_ROOT_PASSWORD: your_password
MYSQL_DATABASE: ayy
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- ayy-network
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: ayy-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- ayy-network
restart: unless-stopped
command: redis-server --appendonly yes
ayy-server:
image: ayy-server:latest
container_name: ayy-server
ports:
- "48087:48087"
environment:
SPRING_PROFILES_ACTIVE: prod
JAVA_OPTS: "-Xms1g -Xmx2g"
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ayy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: your_password
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
volumes:
- ./logs/ayy-server:/app/logs
networks:
- ayy-network
depends_on:
- mysql
- redis
restart: unless-stopped
ayy-snailjob-server:
image: ayy-snailjob-server:latest
container_name: ayy-snailjob-server
ports:
- "9090:9090"
environment:
SPRING_PROFILES_ACTIVE: prod
JAVA_OPTS: "-Xms512m -Xmx1g"
volumes:
- ./logs/ayy-snailjob-server:/app/logs
networks:
- ayy-network
depends_on:
- mysql
- redis
restart: unless-stopped
ayy-monitor-admin:
image: ayy-monitor-admin:latest
container_name: ayy-monitor-admin
ports:
- "8800:8800"
- "17888:17888"
environment:
SPRING_PROFILES_ACTIVE: prod
JAVA_OPTS: "-Xms512m -Xmx1g"
volumes:
- ./logs/ayy-monitor-admin:/app/logs
networks:
- ayy-network
depends_on:
- mysql
restart: unless-stopped
networks:
ayy-network:
driver: bridge
volumes:
mysql-data:
redis-data:

使用方法

# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f ayy-server
# 停止所有服务
docker-compose down
# 停止并删除数据卷
docker-compose down -v

推送到镜像仓库

# 登录镜像仓库(以阿里云为例)
docker login --username=your_username registry.cn-hangzhou.aliyuncs.com
# 打标签
docker tag ayy-server:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
docker tag ayy-snailjob-server:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-snailjob-server:1.0.0
docker tag ayy-monitor-admin:latest registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-monitor-admin:1.0.0
# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-snailjob-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-monitor-admin:1.0.0

生产服务器拉取运行

# 拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0
# 运行
docker run -d \
--name ayy-server \
-p 48087:48087 \
-e SPRING_PROFILES_ACTIVE=prod \
-v /data/logs:/app/logs \
--restart unless-stopped \
registry.cn-hangzhou.aliyuncs.com/your_namespace/ayy-server:1.0.0

常见问题

Q1: 构建很慢,如何优化?

A: 使用以下技巧:

  1. 启用 BuildKit
export DOCKER_BUILDKIT=1
  1. 使用国内 Maven 镜像

在项目根目录创建 .mvn/maven.config

-Dmaven.repo.local=/root/.m2/repository

在 Dockerfile 添加镜像配置:

RUN mkdir -p /root/.m2 && \
    echo 'aliyun*https://maven.aliyun.com/repository/public' > /root/.m2/settings.xml
  1. 利用缓存

分层复制 POM 文件,依赖不变时会使用缓存。

Q2: 健康检查一直失败?

A: 检查以下几点:

  1. 确认应用已开启 Actuator

pom.xml 添加依赖:

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

application.yml 配置:

management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
  1. 临时禁用健康检查

在 Dockerfile 中注释掉 HEALTHCHECK 行。

Q3: 镜像太大怎么办?

A: 优化建议:

  1. 已使用 Alpine 基础镜像(~180MB)
  2. 清理 Maven 缓存(仅在构建阶段)
  3. 移除不必要的依赖

查看镜像层大小:

docker history ayy-server:latest

Q4: 如何查看容器内的日志?

A: 三种方式:

# 方式 1: 查看容器标准输出
docker logs -f ayy-server
# 方式 2: 挂载日志目录(推荐)
docker run -v /data/logs:/app/logs ...
# 方式 3: 进入容器查看
docker exec -it ayy-server sh
cd /app/logs
tail -f application.log

Q5: 如何覆盖配置文件?

A: 使用数据卷挂载:

# 准备配置文件
mkdir -p /data/config/ayy-server
cp application-prod.yml /data/config/ayy-server/
# 挂载配置目录
docker run -v /data/config/ayy-server:/app/config:ro ...

application.yml 中配置:

spring:
config:
additional-location: file:/app/config/

Q6: 如何回滚到旧版本?

A: 使用版本标签:

# 停止当前版本
docker stop ayy-server
docker rm ayy-server
# 运行旧版本
docker run -d \
--name ayy-server \
-p 48087:48087 \
ayy-server:1.0.0  # 指定旧版本

总结

本教程涵盖了 Docker 多阶段构建的完整流程:

Dockerfile 编写:多阶段构建、层缓存优化
.dockerignore 配置:减少构建上下文
镜像构建:单独构建、批量构建、版本管理
容器运行:基础运行、高级配置、日志管理
生产部署:Docker Compose、镜像仓库、自动化


参考资料