使用 Spring Boot 2.3.0.M1 创建 Docker 镜像

工程 | Phil Webb | 2020年1月27日 | ...

Spring Boot 2.3.0.M1 刚刚发布,它带来了一些有趣的新特性,可以帮助您将 Spring Boot 应用程序打包成 Docker 镜像。在这篇博文中,我们将介绍开发人员创建 Docker 镜像的典型方法,并展示如何利用这些新特性改进它们。

常见的 Docker 技术

虽然一直以来都可以将 Spring Boot 生成的 fat jar 转换为 Docker 镜像,但很容易得到不太理想的结果。如果您在网上搜索“dockerize spring boot app”,很可能找到一篇建议您创建如下所示 Dockerfile 的文章或博文

FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/my-application.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

虽然这种方法可以正常工作,而且简洁明了,但有一些方面不是最佳的。

上述文件第一个问题是 jar 文件没有解包。运行 fat jar 总是会有一定的开销,在容器化环境中,这可能会很明显。通常最好解包您的 jar 并以展开的形式运行。

该文件的第二个问题是,如果您经常更新应用程序,它效率不高。Docker 镜像是分层构建的,在这种情况下,您的应用程序及其所有依赖项都放在单个层中。由于您可能比升级使用的 Spring Boot 版本更频繁地重新编译代码,因此通常最好将它们分开。如果您将 jar 文件放在应用程序类之前的层中,Docker 通常只需要更改最底层的层,并且可以从其缓存中获取其他层。

Spring Boot 2.3.0.M1 引入了两个新特性来帮助改进这些现有技术:构建包支持和分层 jar。

构建包

如果您曾经使用过 Cloud Foundry 或 Heroku 等应用程序平台,那么您可能已经使用过构建包,也许甚至没有意识到!构建包是平台的一部分,它接收您的应用程序并将其转换为平台实际可以运行的内容。例如,Cloud Foundry 的 Java 构建包会注意到您正在推送一个 .jar 文件,并自动添加相关的 JRE。

直到最近,构建包才与平台紧密耦合,您无法轻松地独立使用它们。值得庆幸的是,它们现在已经摆脱了这种限制,并且借助 Cloud Native Buildpacks,您可以使用它们创建可在任何地方运行的与 Docker 兼容的镜像。

Spring Boot 2.3.0.M1 直接为 Maven 和 Gradle 包含构建包支持。这意味着您只需键入一个命令,即可快速将一个合理的镜像放入本地运行的 Docker 守护程序中。对于 Maven,您可以键入 mvn spring-boot:build-image,对于 Gradle,则是 gradle bootBuildImage。发布的镜像名称将是您的应用程序名称,标签将是版本号。

让我们看一个使用 Maven 的例子

首先使用 start.spring.io 创建一个新的 Spring Boot 项目

$ curl https://start.spring.io/starter.zip -d bootVersion=2.3.0.M1 -d dependencies=web -o demo.zip $ unzip demo.zip

接下来,确保您已安装并运行本地 Docker,然后键入

$ ./mvnw spring-boot:build-image

第一次运行需要一些时间,但后续调用会更快。您应该在构建日志中看到类似以下内容

[INFO] 正在构建镜像 'docker.io/library/demo:0.0.1-SNAPSHOT' [INFO] [INFO] > 正在拉取构建器镜像 'docker.io/cloudfoundry/cnb:0.0.43-bionic' 100% [INFO] > 已拉取构建器镜像 'cloudfoundry/cnb@sha256:c983fb9602a7fb95b07d35ef432c04ad61ae8458263e7fb4ce62ca10de367c3b' [INFO] > 正在拉取运行时镜像 'docker.io/cloudfoundry/run:base-cnb' 100% [INFO] > 已拉取运行时镜像 'cloudfoundry/run@sha256:ba9998ae4bb32ab43a7966c537aa1be153092ab0c7536eeef63bcd6336cbd0db' [INFO] > 正在执行生命周期版本 v0.5.0 [INFO] > 使用构建缓存卷 'pack-cache-5cbe5692dbc4.build' [INFO] [INFO] > 正在运行检测器 [INFO] [detector] 13 个构建包中的 6 个正在参与... [INFO] [INFO] > 正在运行还原器 [INFO] [restorer] 正在还原缓存层 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b' ... [INFO] [INFO] > 正在运行缓存器 [INFO] [cacher] 正在重用层 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b' [INFO] [cacher] 正在重用层 'org.cloudfoundry.jvmapplication:executable-jar' [INFO] [cacher] 正在缓存层 'org.cloudfoundry.springboot:spring-boot' [INFO] [cacher] 正在重用层 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150' [INFO] [INFO] 成功构建镜像 'docker.io/library/demo:0.0.1-SNAPSHOT'

就是这样!您的应用程序已编译、打包并转换为 Docker 镜像。您可以使用以下命令测试它

$ docker run -it -p8080:8080 demo:0.0.1-SNAPSHOT

注意

不幸的是,M1 不支持 Windows,但在 Mac 或 Linux 虚拟机上应该可以正常工作。如果您使用的是 Windows,请暂时使用 2.3.0.BUILD-SNAPSHOT

Spring Boot 提供的内置支持为开始使用构建包提供了一种很好的方式。由于它是构建包平台规范的实现,因此也很容易迁移到更强大的构建包工具,例如 packkpack,并确信会生成相同的镜像。

分层 Jar

您可能不希望使用构建包来创建镜像。也许您有围绕 Dockerfile 构建的现有工具,或者您可能只是更喜欢它们。无论哪种方式,我们都希望简化创建可以使用常规 Dockerfile 构建的优化 Docker 镜像的过程,因此我们添加了对“分层 jar”的支持。

Spring Boot 一直支持自己的“fat jar”格式,允许您创建一个可以使用 java -jar 运行的存档。如果您曾经查看过该 jar 的内容,您会看到如下所示的结构

META-INF/ MANIFEST.MF org/ springframework/ boot/ loader/ ... BOOT-INF/ classes/ ... lib/ ...

该 jar 分为三个主要部分

  • 用于引导 jar 加载的类

  • 您的应用程序类位于 BOOT-INF/classes

  • 依赖项位于 BOOT-INF/lib

由于此格式是 Spring Boot 独有的,因此我们可以以有趣的方式对其进行演变。在 Spring Boot 2.3.0.M1 中,我们提供了一种名为 LAYERED_JAR 的新 layout 类型。

如果您选择加入分层格式并查看 jar 结构,您会看到如下所示的内容

META-INF/ MANIFEST.MF org/ springframework/ boot/ loader/ ... BOOT-INF/ layers// classes/ ... lib/ .../ classes/ ... lib/ ... layers.idx

您仍然可以看到引导加载程序类(您仍然可以运行 java -jar),但现在 libclasses 文件夹已拆分并分类到不同的层中。还有一个新的 layers.idx 文件,用于提供应添加层的顺序。

最初,我们提供了以下开箱即用的层

  • dependencies(用于常规发布的依赖项)

  • snapshot-dependencies(用于快照依赖项)

  • resources(用于静态资源)

  • application(用于应用程序类和资源)

这种分层旨在根据代码在应用程序构建之间变化的可能性进行分离。库代码在构建之间不太可能发生变化,因此将其放置在自己的层中以允许工具重用缓存中的层。应用程序代码在构建之间更有可能发生变化,因此将其隔离在单独的层中。

提取层

即使使用新的格式,您仍然需要跳过一些步骤才能提取文件,以便 dockerfile 可以复制它们。这些加载程序类需要位于 jar 的根目录中,但是当您构建镜像时,您可能希望它们位于实际的层中。当然,您可以通过组合使用 unzipmv 来实现这一点,但我们尝试通过引入“jar 模式”的概念使其变得更加容易。

jarmode 是一种特殊的系统属性,您可以在启动 jar 时设置它。它允许引导代码运行与您的应用程序完全不同的内容。例如,提取层的内容。

以下是如何使用 layertools jar 模式启动 jar

$ java -Djarmode=layertools -jar my-app.jar

这将提供以下输出

Usage: java -Djarmode=layertools -jar my-app.jar

可用命令:list 列出 jar 中可提取的层 extract 提取 jar 中用于创建镜像的层 help 获取任何命令的帮助

在此模式下,您可以 listextract 层。

编写 dockerfile

让我们继续使用上面生成的示例应用程序,并为其添加一个 dockerfile

首先编辑 pom.xml 并添加以下内容

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<configuration>
				<layout>LAYERED_JAR</layout>
			</configuration>
		</plugin>
	</plugins>
</build>

然后重新构建 jar

$ mvn clean package

一切顺利,我们现在应该拥有一个具有 jarmode 支持的分层 jar。使用以下命令进行测试

$ java -Djarmode=layertools -jar target/demo-0.0.1-SNAPSHOT.jar list

您应该会看到以下输出,它告诉我们层及其应添加的顺序

dependencies snapshot-dependencies resources application

现在我们可以制作一个提取和复制每个层的 dockerfile。这是一个示例

FROM adoptopenjdk:11-jre-hotspot as builder WORKDIR application ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} application.jar RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot WORKDIR application COPY --from=builder application/dependencies/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/resources/ ./ COPY --from=builder application/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

这是一个多阶段 dockerfile。builder 阶段提取稍后需要的文件夹。每个 COPY 命令都与我们之前列出的层相关。

要构建镜像,我们可以运行

$ docker build . --tag demo

然后我们可以测试它

$ docker run -it -p8080:8080 demo:latest

总结

使用构建包、dockerfile 和现有的插件(例如 jib),创建 Docker 镜像的方式 certainly 很多。每种方法都有其优缺点,但希望我们在 Spring Boot 2.3 中发布的新功能无论您选择哪种方法都将有所帮助。

Spring Boot 2.3 目前计划于 4 月底发布,我们非常希望在发布之前获得有关 Docker 镜像的反馈(提出问题、在此处发表评论或在 Gitter 上聊天)。

容器化快乐!

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部