使用 Docker 部署 Spring Boot 应用
# 一、简介
# 1. Docker 镜像简介
Docker 镜像是一个轻量级、独立的可执行软件包,它包含运行应用程序所需的一切:代码、运行时、系统工具、系统库和设置。镜像可以用来创建 Docker 容器,容器是镜像的运行实例。Docker 镜像采用分层结构,每个镜像都由一系列只读层组成,这些层叠加在一起形成最终的文件系统。这种分层结构使得镜像的构建、分发和共享非常高效。
Docker 镜像的主要特点:
- 轻量级: 镜像只包含运行应用程序所需的最小文件集,体积小。
- 可移植性: 镜像可以在任何支持 Docker 的平台上运行,实现“一次构建,到处运行”。
- 隔离性: 每个容器都在隔离的环境中运行,互不影响。
- 版本控制: 镜像可以进行版本控制,方便回滚和管理。
- 可重复性: 镜像可以保证每次构建出的容器都是一致的。
# 2. Spring Boot 简介
Spring Boot 是一个用于创建独立的、生产级别的基于 Spring 的应用程序的框架。它简化了 Spring 应用程序的开发过程,通过自动配置和起步依赖,减少了大量的样板代码和配置工作。Spring Boot 内嵌了 Tomcat、Jetty 或 Undertow 等 Web 服务器,使得应用程序可以直接打包成可执行的 JAR 文件,无需部署到外部应用服务器。
Spring Boot 的主要特点:
- 简化配置: 通过自动配置减少了大量的 XML 配置。
- 起步依赖: 提供了一系列预定义的依赖项,简化了项目构建配置。
- 内嵌服务器: 内置了 Web 服务器,可以直接运行应用程序。
- 生产就绪特性: 提供了健康检查、指标监控等生产环境所需的功能。
- 易于集成: 可以轻松集成各种 Spring 项目和其他第三方库。
# 3. 使用 Spring Boot 构建 Docker 镜像的优势
将 Spring Boot 应用程序构建成 Docker 镜像具有以下优势:
- 简化部署: 将应用程序及其依赖项打包成一个独立的镜像,简化了部署流程,无需手动配置环境。
- 环境一致性: 确保应用程序在开发、测试和生产环境中运行的一致性,避免了“在我机器上可以运行”的问题。
- 可移植性: 可以在任何支持 Docker 的平台上运行,提高了应用程序的可移植性。
- 资源隔离: 每个容器都在隔离的环境中运行,互不影响,提高了应用程序的稳定性和安全性。
- 弹性伸缩: 可以通过 Docker 快速创建和销毁容器,实现应用程序的弹性伸缩。
- 持续集成/持续交付(CI/CD): 可以将 Docker 镜像构建集成到 CI/CD 流程中,实现自动化构建、测试和部署。
# 二、准备工作
# 1. 安装 Docker
Docker 的安装因操作系统而异,请参考 Docker 官方文档进行安装:
- Windows: https://docs.docker.com/desktop/install/windows-install/ (opens new window)
- macOS: https://docs.docker.com/desktop/install/mac-install/ (opens new window)
- Linux: https://docs.docker.com/engine/install/ (opens new window) (选择对应的发行版)
安装完成后,在命令行终端运行以下命令验证安装是否成功:
docker --version
docker run hello-world
如果能看到 Docker 版本信息和 "Hello from Docker!" 的输出,则表示安装成功。
# 2. 安装 JDK
Spring Boot 项目通常需要 Java Development Kit (JDK) 来编译和运行。请确保安装了 JDK 8 或更高版本。你可以从 Oracle 官网、OpenJDK 官网或其他 JDK 发行版提供商处下载并安装 JDK。
安装完成后,在命令行终端运行以下命令验证安装是否成功:
java -version
javac -version
如果能看到 Java 和 javac 的版本信息,则表示安装成功。
# 3. 安装 Maven 或 Gradle
Maven 和 Gradle 是 Java 项目常用的构建工具。你可以选择其中一个来构建 Spring Boot 项目。
Maven:
Gradle:
安装完成后,在命令行终端运行以下命令验证安装是否成功:
# Maven
mvn -version
# Gradle
gradle -version
如果能看到 Maven 或 Gradle 的版本信息,则表示安装成功。
# 4. 创建 Spring Boot 项目
你可以使用 Spring Initializr (https://start.spring.io/ (opens new window)) 来快速创建一个 Spring Boot 项目。
- 访问 Spring Initializr 网站。
- 选择项目类型(Maven 或 Gradle)。
- 选择 Spring Boot 版本。
- 选择 Java 版本。
- 填写项目元数据(Group、Artifact 等)。
- 添加所需的依赖项(例如,"Spring Web" 依赖用于创建 Web 应用程序)。
- 点击 "Generate" 按钮下载项目压缩包。
- 解压压缩包,并使用你喜欢的 IDE(如 IntelliJ IDEA、Eclipse 等)导入项目。
或者,你也可以使用 IDE 内置的 Spring Boot 项目创建向导来创建项目。
创建项目后, 可以简单修改代码,添加一个REST Controller。例如:
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello from Spring Boot!";
}
}
这个 HelloController
类定义了一个简单的 REST API,当访问根路径 /
时,返回 "Hello from Spring Boot!"。
现在,你已经完成了构建 Spring Boot Docker 镜像的所有准备工作。
# 三、使用 Dockerfile 构建镜像
# 1. 编写 Dockerfile
Dockerfile 是一个文本文件,其中包含一系列指令,用于告诉 Docker 如何构建镜像。在 Spring Boot 项目的根目录下创建一个名为 Dockerfile
的文件(没有扩展名),然后添加以下内容:
# 1.1 基础镜像选择
选择一个合适的 Java 基础镜像。这里我们选择 OpenJDK 17 作为基础镜像,因为它是一个轻量级的、官方维护的镜像:
FROM openjdk:17-jdk-slim
FROM
指令指定了基础镜像。openjdk:17-jdk-slim
表示使用 OpenJDK 17 的 slim 版本,它只包含运行 Java 应用程序所需的最小组件,可以减小镜像大小。你也可以选择其他适合你的基础镜像。
# 1.2 添加 Spring Boot 应用
将 Spring Boot 应用程序的 JAR 文件添加到镜像中。假设你的 Spring Boot 项目使用 Maven 构建,并且已经通过 mvn package
命令生成了 JAR 文件,通常位于 target/
目录下。
COPY target/*.jar app.jar
COPY
指令将本地文件复制到镜像中。target/*.jar
表示将 target/
目录下所有以 .jar
结尾的文件复制到镜像的根目录下,并重命名为 app.jar
。你可以根据你的实际情况修改路径和文件名。
# 1.3 配置启动命令
指定容器启动时要执行的命令,即运行 Spring Boot 应用程序的命令。
CMD ["java", "-jar", "app.jar"]
CMD
指令指定了容器启动时要执行的命令。java -jar app.jar
表示使用 Java 命令运行 app.jar
文件。
完整的 Dockerfile
示例:
FROM openjdk:17-jdk-slim
COPY target/*.jar app.jar
CMD ["java", "-jar", "app.jar"]
# 2. 构建镜像
在 Dockerfile
所在的目录下,打开命令行终端,运行以下命令构建镜像:
docker build -t springboot-docker-image .
docker build
:构建镜像的命令。-t springboot-docker-image
:给镜像打上标签(tag),这里将镜像命名为springboot-docker-image
。你可以自定义镜像名称。.
:表示 Dockerfile 所在的当前目录。
构建过程可能需要一些时间,具体取决于你的网络速度和项目大小。构建完成后,可以使用以下命令查看已构建的镜像:
docker images
# 3. 运行容器
使用以下命令运行刚刚构建的镜像,创建一个 Docker 容器:
docker run -p 8080:8080 springboot-docker-image
docker run
:运行容器的命令。-p 8080:8080
:将容器的 8080 端口映射到主机的 8080 端口。你可以根据你的应用程序配置修改端口映射。springboot-docker-image
:要运行的镜像名称。
容器启动后,你的 Spring Boot 应用程序将在容器内运行。你可以通过浏览器访问 http://localhost:8080
来验证应用程序是否正常运行(假设你的应用程序监听的是 8080 端口,并且有一个根路径的 API)。如果一切正常,你将看到应用程序的输出,例如 "Hello from Spring Boot!"。
现在,你已经成功地使用 Dockerfile 构建了 Spring Boot 应用程序的 Docker 镜像,并运行了容器。
# 四、使用 Maven 插件构建镜像
除了使用 Dockerfile,还可以使用 Maven 插件来简化 Docker 镜像的构建过程。常用的 Maven 插件有 docker-maven-plugin
(Spotify)、jib-maven-plugin
(Google),以及 Spring Boot 官方提供的 spring-boot-maven-plugin
。
# 1. 添加 Maven 插件
# 1.1 Jib 插件 (Google)
在 Spring Boot 项目的 pom.xml
文件中,找到 <build>
-> <plugins>
部分,添加 jib-maven-plugin
:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<to>
<image>your-docker-registry/your-image-name:tag</image>
</to>
</configuration>
</plugin>
<groupId>
和<artifactId>
:指定插件的坐标。<version>
:指定插件的版本。建议使用最新稳定版本。<configuration>
部分对插件进行配置。
# 1.2 Spring Boot Maven 插件
Spring Boot 2.3 及以上版本,spring-boot-maven-plugin
提供了内置的镜像构建支持。它利用了 Cloud Native Buildpacks 来创建镜像,无需编写 Dockerfile。确保你的 Spring Boot 版本 >= 2.3。如果低于这个版本,请考虑升级。
如果你的项目已经使用了spring-boot-maven-plugin
,通常不需要额外添加,因为它已经是 Spring Boot 项目的标准插件。如果你的pom.xml
中没有,可以这样添加:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>build-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
这里添加了<executions>
,显式地告诉插件在Maven构建过程(package
)中执行build-image
目标。
# 2. 配置插件
# 2.1 Jib 插件配置
在 <configuration>
部分,你需要配置以下内容:
<to>
:指定目标镜像的名称和标签。<image>
:镜像名称。通常采用仓库地址/镜像名称:标签
的格式。- 如果你要推送到 Docker Hub,仓库地址可以省略,直接写
your-dockerhub-username/your-image-name:tag
。 - 如果你要推送到私有仓库,需要指定完整的仓库地址,例如
my-private-registry.com/my-project/my-image:1.0
。 tag
是镜像的标签,通常用于表示版本,例如latest
、v1.0
等。
- 如果你要推送到 Docker Hub,仓库地址可以省略,直接写
示例:
<configuration>
<to>
<image>mydockerhubuser/myspringbootapp:latest</image>
</to>
</configuration>
这个配置表示构建的镜像名为mydockerhubuser/myspringbootapp
, 标签是latest
其他常用配置(可选):
<from>
:指定基础镜像。默认情况下,Jib 会根据你的项目自动选择一个合适的基础镜像。你也可以手动指定:<from> <image>openjdk:17-jdk-slim</image> </from>
<container>
: 对容器进行配置<container> <ports> <port>8080</port> </ports> <jvmFlags> <jvmFlag>-Xms512m</jvmFlag> <jvmFlag>-Xmx1024m</jvmFlag> </jvmFlags> </container>
# 2.2 Spring Boot Maven 插件配置
spring-boot-maven-plugin
的 build-image
目标会自动检测项目并构建镜像,通常不需要太多配置。但是,你可以通过以下方式自定义构建过程:
指定镜像名称:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <name>your-docker-registry/your-image-name:${project.version}</name> </image> </configuration> </plugin>
这里使用
${project.version}
作为标签,它会自动获取项目的版本号。指定构建器(Builder):
Buildpacks 使用构建器(Builder)来创建镜像。默认情况下,
spring-boot-maven-plugin
会自动选择一个合适的构建器。你也可以手动指定:<configuration> <image> <builder>paketobuildpacks/builder-jammy-base</builder> </image> </configuration>
常用的builder有
paketobuildpacks/builder-jammy-base
,gcr.io/buildpacks/builder:v1
等绑定到 Maven 生命周期
默认情况下,build-image
目标不会自动绑定到 Maven 的任何生命周期阶段。在前面的<executions>
配置中,我们已经手动绑定了build-image
到package
阶段。你也可以选择其他阶段,例如install
。当然,你也可以不绑定,而是使用 mvn spring-boot:build-image
命令。
# 3. 构建镜像
# 3.1 使用 Jib 构建
构建并推送到 Docker Hub 或私有仓库:
mvn compile jib:build
这个命令会先编译项目,然后构建 Docker 镜像,并将其推送到你在
<to>
-><image>
中指定的仓库。在推送之前,你需要确保已经登录到目标仓库(使用docker login
命令)。仅构建镜像到本地 Docker 守护进程(不推送):
mvn compile jib:dockerBuild
这个命令会构建镜像,但不会推送到远程仓库,而是直接加载到本地 Docker 守护进程中。这样你就可以在本地使用
docker run
命令运行镜像了。
# 3.2 使用 Spring Boot Maven 插件构建
mvn spring-boot:build-image
或者,如果你已经将build-image
绑定到了package
阶段
mvn package
这个命令会使用 Buildpacks 构建镜像。默认情况下,镜像会被加载到本地 Docker 守护进程。
推送到远程仓库:
spring-boot-maven-plugin
本身不直接支持推送到远程仓库。你需要先构建镜像到本地,然后使用 docker tag
和 docker push
命令手动推送:
构建镜像:
mvn spring-boot:build-image
标记镜像:
docker tag your-image-name:tag your-docker-registry/your-image-name:tag
推送镜像:
docker push your-docker-registry/your-image-name:tag
# 4. 运行容器
构建完成后,可以使用 docker run
命令运行容器,方式与使用 Dockerfile 构建的镜像相同:
docker run -p 8080:8080 your-docker-registry/your-image-name:tag
或者
docker run -p 8080:8080 mydockerhubuser/myspringbootapp:latest
将 your-docker-registry/your-image-name:tag
替换为你的实际镜像名称和标签。
使用 Maven 插件构建 Docker 镜像的好处是:
- 无需编写 Dockerfile:简化了构建过程(
spring-boot-maven-plugin
和 Jib)。 - 自动优化:Jib 等插件会自动优化镜像的分层,提高构建速度和镜像的缓存利用率。
spring-boot-maven-plugin
使用的 Buildpacks 也会进行优化。 - 与 Maven 集成:可以方便地将镜像构建集成到 Maven 的构建生命周期中。
- 易于推送到仓库:Jib 可以直接将镜像推送到 Docker Hub 或私有仓库。
spring-boot-maven-plugin
需要配合docker
命令手动推送。
选择哪个插件取决于你的具体需求和偏好。如果你希望更简单、更自动化的构建过程,并且不需要推送到远程仓库,spring-boot-maven-plugin
是一个不错的选择。如果你需要更精细的控制、推送到远程仓库,或者希望与其他工具(如 Skaffold)集成,Jib 可能更适合你。
# 五、优化镜像大小:jar和lib分离
在 Spring Boot 项目中,默认情况下会将所有依赖和应用程序代码打包到一个可执行的 fat jar
中。然而,在某些场景下,我们可能希望将应用程序的 jar
和依赖的 lib
分离,以便更好地管理依赖、优化镜像构建流程或减少镜像体积。
以下是实现这一目标的具体步骤,包括如何配置 Spring Boot 项目以及如何编写 Dockerfile 构建镜像。
# 1. Spring Boot 配置:分离 jar
和 lib
Spring Boot 提供了 Maven 和 Gradle 插件来支持将依赖分离为独立的目录结构。以下是基于 Maven 的配置方法:
# (1)修改 pom.xml
在 pom.xml
中启用 spring-boot-maven-plugin
的 layout
属性为 ZIP
或 DIR
,以生成分离的依赖目录。
<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<layout>ZIP</layout>
<!-- 解决windows命令行窗口中文乱码 -->
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<!-- 不包含任何依赖 -->
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
</plugin>
<!-- Maven Dependency Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!-- 复制稳定的依赖到 lib 目录 -->
<execution>
<id>copy-stable-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
<excludeArtifactIds>your-dynamic-artifact-id-1,your-dynamic-artifact-id-2</excludeArtifactIds>
</configuration>
</execution>
<!-- 复制动态的依赖到 dynamic-lib 目录 -->
<execution>
<id>copy-dynamic-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dynamic-lib</outputDirectory>
<includeArtifactIds>your-dynamic-artifact-id-1,your-dynamic-artifact-id-2</includeArtifactIds>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
关键配置解析:
(1)复制稳定依赖:
<outputDirectory>${project.build.directory}/lib</outputDirectory>
:指定稳定的依赖输出到target/lib
目录。<includeScope>runtime</includeScope>
:只包含运行时所需的依赖。<excludeArtifactIds>
:排除经常变动的 JAR 文件(通过artifactId
指定)。
(2)复制动态依赖:
<outputDirectory>${project.build.directory}/dynamic-lib</outputDirectory>
:指定动态的依赖输出到target/dynamic-lib
目录。<includeArtifactIds>
:只包含经常变动的 JAR 文件(通过artifactId
指定)。
# 示例说明:
假设项目中有两个经常变动的 JAR 文件,分别为 module-a
和 module-b
,则可以在 includeArtifactIds
中指定它们:
<includeArtifactIds>module-a,module-b</includeArtifactIds>
# (2)执行打包命令
运行以下命令,生成分离的 lib
和 dynamic-lib
目录:
mvn clean package
执行后,target
目录中将包含以下内容:
lib/
:存放稳定的第三方依赖。dynamic-lib/
:存放经常变动的 JAR 文件。your-app.jar
:不包含依赖的应用程序主 JAR。
# (3)验证启动方式
使用以下命令验证分离后的应用是否可以正常启动:
java -Dloader.path=target/lib,target/dynamic-lib -jar target/your-app.jar
-Dloader.path=target/lib,target/dynamic-lib
指定了依赖的路径。
# 2. Dockerfile 构建镜像
在 Dockerfile 中,我们将应用程序的 jar
和 lib
目录分别拷贝到容器中,并通过正确的启动命令运行应用。
# 示例 Dockerfile
# 使用官方 OpenJDK 镜像作为基础镜像
FROM openjdk:17-jdk-slim AS builder
# 设置工作目录
WORKDIR /app
# 将 lib、dynamic-lib 目录和主 JAR 文件拷贝到镜像中
COPY target/lib /app/lib
COPY target/dynamic-lib /app/dynamic-lib
COPY target/your-app.jar /app/your-app.jar
# 设置环境变量(可选)
ENV JAVA_OPTS=""
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dloader.path=/app/lib,/app/dynamic-lib -jar /app/your-app.jar"]
# 3. 使用Docker构建Maven项目
如果不想在本地环境中执行Maven构建,也可以使用Docker进行项目构建。得到构建后的组件(jar和lib),再打包到镜像中。
# 第一阶段:构建阶段
FROM maven:3.8.6-openjdk-17 AS builder
# 设置工作目录
WORKDIR /build
# 拷贝 Maven 配置文件和源码
COPY pom.xml .
COPY src ./src
RUN mvn clean package
# 第二阶段:运行阶段
FROM openjdk:17-jdk-slim
# 设置工作目录
WORKDIR /app
# 拷贝稳定的 lib 依赖
COPY /build/target/lib /app/lib
# 拷贝动态的 dynamic-lib 依赖
COPY /build/target/dynamic-lib /app/dynamic-lib
# 拷贝主 JAR 文件
COPY /build/target/your-app.jar /app/your-app.jar
# 设置启动命令
ENV JAVA_OPTS=""
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dloader.path=/app/lib,/app/dynamic-lib -jar /app/your-app.jar"]
# 4. 运行和验证
# (1)构建镜像
在项目根目录下运行以下命令构建镜像:
docker build -t your-app-image .
# (2)运行容器
使用以下命令启动容器:
docker run -d -p 8080:8080 --name your-app-container your-app-image
# (3)验证应用
访问应用的健康检查端点或其他接口,确保应用正常运行。例如:
curl http://localhost:8080/actuator/health
# 5. 参考
可以参考如下资源:
- SpringBoot 将 jar 包和 lib 依赖分离,Dockerfile 构建镜像 (opens new window) 文中使用了maven-jar-plugin插件来配合实现jar和lib分离。注意:如果mainClass改名字,一定要注意同步到插件配置上
注意:
- 上述分离的方式适用于jar包,不使用war包。因为war的打包规则特殊,使用
loader.path
配置并不起作用。
# 六、总结
本文介绍了如何将 Spring Boot 应用打包成 Docker 镜像,并对构建过程中的关键步骤和优化方法进行了详细讲解。
通过本文的学习,你应该能够:
- 编写 Dockerfile 来构建 Spring Boot 应用的 Docker 镜像。
- 使用
docker build
、docker run
等命令构建和运行镜像。 - 使用 Maven 插件(
jib-maven-plugin
或spring-boot-maven-plugin
)简化镜像构建过程。 - 掌握优化 Docker 镜像大小的方法。
将 Spring Boot 应用容器化是现代应用部署的重要一步。Docker 提供了轻量级、可移植、可扩展的容器化解决方案,结合 Spring Boot 的快速开发特性,可以大大提高应用的开发、部署和运维效率。
祝你变得更强!
- 01
- Spring中的Web访问:响应式栈 WebFlux06-28
- 02
- Spring中的Web访问:WebSocket支持06-19
- 03
- Spring中的Web访问:Servlet API支持06-02