领先一步
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 中引入了两个新特性,以帮助改进这些现有技术:buildpack 支持和分层 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] Building image 'docker.io/library/demo:0.0.1-SNAPSHOT' [INFO] [INFO] > Pulling builder image 'docker.io/cloudfoundry/cnb:0.0.43-bionic' 100% [INFO] > Pulled builder image 'cloudfoundry/cnb@sha256:c983fb9602a7fb95b07d35ef432c04ad61ae8458263e7fb4ce62ca10de367c3b' [INFO] > Pulling run image 'docker.io/cloudfoundry/run:base-cnb' 100% [INFO] > Pulled run image 'cloudfoundry/run@sha256:ba9998ae4bb32ab43a7966c537aa1be153092ab0c7536eeef63bcd6336cbd0db' [INFO] > Executing lifecycle version v0.5.0 [INFO] > Using build cache volume 'pack-cache-5cbe5692dbc4.build' [INFO] [INFO] > Running detector [INFO] [detector] 6 of 13 buildpacks participating ... [INFO] [INFO] > Running restorer [INFO] [restorer] Restoring cached layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b' ... [INFO] [INFO] > Running cacher [INFO] [cacher] Reusing layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b' [INFO] [cacher] Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar' [INFO] [cacher] Caching layer 'org.cloudfoundry.springboot:spring-boot' [INFO] [cacher] Reusing layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150' [INFO] [INFO] Successfully built image '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 一直支持自己的“胖 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
,我们提供了一种新的 layout
类型,称为 LAYERED_JAR
。
如果您选择使用分层格式并查看 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
Available commands: 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 镜像的方法当然不乏。每种方法都有优缺点,但希望我们在 Spring Boot 2.3 中发布的新功能无论您选择哪种方法都能有所帮助。
Spring Boot 2.3 目前计划于四月底发布,在此之前,我们非常期待关于 Docker 镜像的反馈(提交问题、在此评论或在 Gitter 上聊天)。
愉快的容器化!