领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多Spring Boot 2.3.0.M1 刚刚发布,它带来了一些有趣的新特性,可以帮助您将 Spring Boot 应用程序打包成 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 提供的内置支持为开始使用构建包提供了一种很好的方式。由于它是构建包平台规范的实现,因此也很容易迁移到更强大的构建包工具,例如 pack
或 kpack
,并确信会生成相同的镜像。
您可能不希望使用构建包来创建镜像。也许您有围绕 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/
您仍然可以看到引导加载程序类(您仍然可以运行 java -jar
),但现在 lib
和 classes
文件夹已拆分并分类到不同的层中。还有一个新的 layers.idx
文件,用于提供应添加层的顺序。
最初,我们提供了以下开箱即用的层
dependencies
(用于常规发布的依赖项)
snapshot-dependencies
(用于快照依赖项)
resources
(用于静态资源)
application
(用于应用程序类和资源)
这种分层旨在根据代码在应用程序构建之间变化的可能性进行分离。库代码在构建之间不太可能发生变化,因此将其放置在自己的层中以允许工具重用缓存中的层。应用程序代码在构建之间更有可能发生变化,因此将其隔离在单独的层中。
即使使用新的格式,您仍然需要跳过一些步骤才能提取文件,以便 dockerfile
可以复制它们。这些加载程序类需要位于 jar 的根目录中,但是当您构建镜像时,您可能希望它们位于实际的层中。当然,您可以通过组合使用 unzip
和 mv
来实现这一点,但我们尝试通过引入“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 获取任何命令的帮助
在此模式下,您可以 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 上聊天)。
容器化快乐!