使用 Docker 的 Spring Boot

本指南将引导您完成为运行 Spring Boot 应用程序构建 Docker 镜像的过程。我们从基本的 Dockerfile 开始,并进行一些调整。然后,我们展示了几个使用构建插件(用于 Maven 和 Gradle)而不是 docker 的选项。这是一个“入门”指南,因此范围仅限于一些基本需求。如果您要为生产使用构建容器镜像,则需要考虑许多因素,并且无法在简短的指南中全部涵盖。

还有一篇关于 Docker 的 专题指南,其中更详细地涵盖了我们在本文中以及更广泛的选项。

您将构建什么

Docker 是一种 Linux 容器管理工具包,具有“社交”方面,允许用户发布容器镜像并使用其他人发布的镜像。Docker 镜像是运行容器化进程的配方。在本指南中,我们为一个简单的 Spring Boot 应用程序构建了一个镜像。

您需要什么

如果您没有使用 Linux 机器,则需要一个虚拟化服务器。如果您安装了 VirtualBox,其他工具(如 Mac 的 boot2docker)可以无缝地为您管理它。访问 VirtualBox 的下载站点 并选择适合您机器的版本。下载并安装。不用担心实际运行它。

您还需要 Docker,它仅在 64 位机器上运行。请参阅 https://docs.docker.net.cn/installation/#installation,了解有关在您的机器上设置 Docker 的详细信息。在继续操作之前,请验证您可以从 shell 运行 docker 命令。如果您使用 boot2docker,则需要先运行它。

从 Spring Initializr 开始

您可以使用此 预初始化项目 并单击“生成”以下载 ZIP 文件。此项目配置为适合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。此服务会提取应用程序所需的所有依赖项,并为您完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。

  3. 单击“依赖项”并选择“Spring Web”。

  4. 单击“生成”。

  5. 下载生成的 ZIP 文件,它是使用您选择的配置构建的 Web 应用程序的归档文件。

如果您的 IDE 集成了 Spring Initializr,则可以在 IDE 中完成此过程。
您还可以从 Github 分叉项目并在您的 IDE 或其他编辑器中打开它。

设置 Spring Boot 应用程序

现在您可以创建一个简单的应用程序

src/main/java/hello/Application.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello Docker World";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

该类被标记为 @SpringBootApplication@RestController,这意味着它已准备好供 Spring MVC 用于处理 Web 请求。@RequestMapping/ 映射到 home() 方法,该方法发送 Hello World 响应。main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动应用程序。

现在,我们可以在没有 Docker 容器的情况下运行应用程序(即在主机操作系统中)

如果您使用 Gradle,请运行以下命令

./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar

如果您使用 Maven,请运行以下命令

./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar

然后转到 localhost:8080 查看您的“Hello Docker World”消息。

将其容器化

Docker 具有一个简单的 "Dockerfile" 文件格式,它用于指定镜像的“层”。在您的 Spring Boot 项目中创建以下 Dockerfile

示例 1. Dockerfile
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

如果您使用 Gradle,则可以使用以下命令运行它

docker build --build-arg JAR_FILE=build/libs/\*.jar -t springio/gs-spring-boot-docker .

如果您使用 Maven,则可以使用以下命令运行它

docker build -t springio/gs-spring-boot-docker .

此命令构建一个镜像并将其标记为 springio/gs-spring-boot-docker

此 Dockerfile 非常简单,但它是运行 Spring Boot 应用程序所需的一切:只需 Java 和 JAR 文件即可。构建创建了一个 spring 用户和一个 spring 组来运行应用程序。然后,它将项目 JAR 文件(通过 COPY 命令)复制到容器中作为 app.jar,并在 ENTRYPOINT 中运行它。Dockerfile ENTRYPOINT 的数组形式用于确保没有 shell 包裹 Java 进程。关于 Docker 的专题指南 将更详细地介绍此主题。

为了减少 Tomcat 启动时间,我们过去会添加一个指向 /dev/urandom 作为熵源的系统属性。使用 JDK 8 或更高版本 后,这不再必要。

以用户权限运行应用程序有助于降低某些风险(例如,请参阅 StackExchange 上的一个主题)。因此,对 Dockerfile 的一项重要改进是将应用程序作为非 root 用户运行

示例 2. Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

构建并运行应用程序时,您可以在应用程序启动日志中看到用户名

docker build -t springio/gs-spring-boot-docker .
docker run -p 8080:8080 springio/gs-spring-boot-docker

请注意第一个 INFO 日志条目中的 started by

 :: Spring Boot ::        (v2.2.1.RELEASE)

2020-04-23 07:29:41.729  INFO 1 --- [           main] hello.Application                        : Starting Application on b94c86e91cf9 with PID 1 (/app started by spring in /)
...

此外,Spring Boot fat JAR 文件中存在依赖项和应用程序资源之间的清晰分离,我们可以利用这一事实来提高性能。关键是在容器文件系统中创建层。这些层在构建时和运行时(在大多数运行时中)都会被缓存,因此我们希望最常更改的资源(通常是应用程序本身中的类和静态资源)在更改较慢的资源之后分层。因此,我们使用 Dockerfile 的略微不同的实现

示例 3. Dockerfile
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

此 Dockerfile 具有一个 DEPENDENCY 参数,指向我们已解压缩 fat JAR 的目录。要将 DEPENDENCY 参数与 Gradle 一起使用,请运行以下命令

mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)

要将 DEPENDENCY 参数与 Maven 一起使用,请运行以下命令

mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

如果我们正确设置了,它已经包含一个包含依赖项 JAR 的 BOOT-INF/lib 目录,以及一个包含应用程序类的 BOOT-INF/classes 目录。请注意,我们使用应用程序自己的主类:hello.Application。(这比使用 fat JAR 启动程序提供的间接方法更快。)

解压缩 JAR 文件可能导致类路径顺序在 运行时不同。一个行为良好且编写良好的应用程序不应该关心这一点,但是如果您没有仔细管理依赖项,则可能会看到行为变化。
如果您使用 boot2docker,则在使用 Docker 命令行或构建工具执行任何操作之前,需要先运行它(它运行一个守护程序进程,在虚拟机中为您处理工作)。

在 Gradle 构建中,您需要在 Docker 命令行中添加显式构建参数

docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .

要在 Maven 中构建镜像,您可以使用更简单的 Docker 命令行

docker build -t springio/gs-spring-boot-docker .
如果您只使用 Gradle,则可以更改 Dockerfile 以使 DEPENDENCY 的默认值与解压缩的存档位置匹配。

您可能希望使用构建插件,而不是使用 Docker 命令行构建。Spring Boot 通过使用自己的构建插件支持从 Maven 或 Gradle 构建容器。Google 还拥有一个名为 Jib 的开源工具,它具有 Maven 和 Gradle 插件。可能这种方法最有趣的事情是您不需要 Dockerfile。您可以使用与 docker build 获得的相同标准容器格式来构建镜像。此外,它可以在未安装 docker 的环境中工作(在构建服务器中并不罕见)。

默认情况下,默认构建包生成的镜像不会以 root 身份运行您的应用程序。请查看 GradleMaven 的配置指南,了解如何更改默认设置。

使用 Gradle 构建 Docker 镜像

您可以使用 Gradle 通过一个命令构建标记的 Docker 镜像

./gradlew bootBuildImage --imageName=springio/gs-spring-boot-docker

使用 Maven 构建 Docker 镜像

为了快速入门,您可以运行 Spring Boot 镜像生成器,甚至无需更改您的 pom.xml(请记住,如果 Dockerfile 仍然存在,则会忽略它)。

./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=springio/gs-spring-boot-docker

要推送到 Docker 镜像仓库,您需要具有推送权限,默认情况下您没有此权限。将镜像前缀更改为您自己的 Dockerhub ID,并在运行 Docker 之前使用 docker login 确保您已通过身份验证。

推送后

示例中的 docker push 会失败(除非您是 Dockerhub 中“springio”组织的成员)。但是,如果您将配置更改为匹配您自己的 docker ID,则它应该会成功。然后,您将拥有一个新的标记的已部署镜像。

您无需在 docker 中注册或发布任何内容即可运行本地构建的 docker 镜像。如果您使用 Docker(从命令行或从 Spring Boot)构建,则您仍然拥有一个本地标记的镜像,您可以像这样运行它

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker
Container memory limit unset. Configuring JVM for 1G container.
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=86381K -XX:ReservedCodeCacheSize=240M -Xss1M -Xmx450194K (Head Room: 0%, Loaded Class Count: 12837, Thread Count: 250, Total Memory: 1073741824)
....
2015-03-31 13:25:48.035  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037  INFO 1 --- [           main] hello.Application                        : Started Application in 5.613 seconds (JVM running for 7.293)
构建包在运行时使用内存计算器来调整 JVM 大小以适应容器。

然后,应用程序可在 https://127.0.0.1:8080 上访问(访问该地址,显示“Hello Docker World”)。

在使用带有 boot2docker 的 Mac 时,通常会在启动时看到类似以下内容

Docker client to the Docker daemon, please set:
    export DOCKER_CERT_PATH=/Users/gturnquist/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

要查看应用程序,您必须访问 DOCKER_HOST 中的 IP 地址,而不是 localhost — 在这种情况下,为 https://192.168.59.103:8080,即 VM 的公网 IP 地址。

运行时,您可以在容器列表中看到类似以下示例的内容

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
81c723d22865        springio/gs-spring-boot-docker:latest   "java -Djava.secur..."   34 seconds ago      Up 33 seconds       0.0.0.0:8080->8080/tcp   goofy_brown

要再次关闭它,您可以使用上一个列表中的容器 ID 运行 docker stop(您的 ID 会有所不同)

docker stop goofy_brown
81c723d22865

如果您愿意,您也可以在完成使用后删除容器(它会持久存储在文件系统的某个位置,例如 /var/lib/docker 下)

docker rm goofy_brown

使用 Spring Profiles

使用 Spring Profiles 运行新创建的 Docker 镜像非常简单,只需将环境变量传递给 Docker run 命令(对于 prod 配置文件)即可

docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker

您也可以对 dev 配置文件执行相同的操作

docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker

调试 Docker 容器中的应用程序

要调试应用程序,您可以使用 JPDA 传输。我们将容器视为远程服务器。要启用此功能,请在 JAVA_OPTS 变量中传递 Java 代理设置,并在容器运行期间将代理的端口映射到本地主机。对于 Docker for Mac,存在一个限制,因为我们无法在没有 黑魔法使用 的情况下通过 IP 访问容器。

docker run -e "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t springio/gs-spring-boot-docker

总结

恭喜!您已为 Spring Boot 应用程序创建了一个 Docker 容器!默认情况下,Spring Boot 应用程序在容器内的 8080 端口上运行,我们通过在命令行上使用 -p 将其映射到主机上的同一端口。

另请参阅

以下指南可能也有帮助

想要编写新的指南或为现有指南做出贡献?请查看我们的 贡献指南

所有指南均以 ASLv2 许可证发布代码,并以 署名-非衍生作品创作共用许可证 发布写作内容。

获取代码