领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Spring Boot 2.3.0.M1 刚刚发布,它带来了一些有趣的新功能,可以帮助您将 Spring Boot 应用程序打包成 Docker 镜像。在这篇博客文章中,我们将探讨开发人员创建 Docker 镜像的典型方法,并展示如何利用这些新功能进行改进。
虽然一直可以将 Spring Boot 生成的胖 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 文件未解压。运行胖 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 提供的内置支持是开始使用构建包的绝佳方式。由于它是构建包平台规范的实现,因此也可以轻松迁移到更强大的构建包工具,例如 pack 或 kpack,并且可以确信会生成相同的镜像。
您可能不想使用构建包来创建镜像。也许您有围绕 dockerfile 构建的现有工具,或者您只是更喜欢它们。无论哪种方式,我们都希望通过引入“分层 JAR”支持,让使用常规 dockerfile 构建优化的 Docker 镜像变得更容易。
Spring Boot 一直支持自己的“胖 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/
您仍然可以看到引导加载器类(您仍然可以运行 java -jar),但现在 lib 和 classes 文件夹已被拆分并分类为层。还有一个新的 layers.idx 文件,它提供了应该添加层的顺序。
最初,我们提供以下开箱即用的层:
dependencies(用于常规发布的依赖项)
snapshot-dependencies(用于快照依赖项)
resources(用于静态资源)
application(用于应用程序类和资源)
这种分层设计旨在根据代码在应用程序构建之间发生变化的 likelihood 来分离代码。库代码在构建之间发生变化的可能性较小,因此它被放置在其自己的层中,以允许工具从缓存中重用这些层。应用程序代码在构建之间发生变化的可能性更大,因此它被隔离在单独的层中。
即使采用新格式,您仍然需要经过一些步骤才能提取文件,以便您的 dockerfile 可以复制它们。那些加载器类需要位于 JAR 的根目录中,但您可能希望在构建镜像时将它们放在实际的层中。当然,您可以使用 unzip 和 mv 的组合来完成此操作,但我们试图通过引入“JAR 模式”的概念使其变得更容易。
jarmode 是一个特殊的系统属性,您可以在启动 JAR 时设置它。它允许引导代码运行与您的应用程序完全不同的东西。例如,一个提取层的东西。
以下是您如何使用 layertools JAR 模式启动 JAR 的方法:
$ java -Djarmode=layertools -jar my-app.jar
这将提供以下输出
用法: java -Djarmode=layertools -jar my-app.jar
可用命令: list 列出可从jar中提取的层 extract 从jar中提取层以创建镜像 help 关于任何命令的帮助
在此模式下,您可以 list 或 extract 层。
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 上聊天)。
愉快的容器化!