基于Spring Boot 2.3以上版本方便创建分层Docker镜像


Spring Boot 2.3.0.M1刚刚发布,它带来了一些有趣的新功能,可以帮助您将Spring Boot应用程序打包到Docker镜像中。在此博客文章中,我们将介绍开发人员创建Docker镜像的典型方式,并展示如何使用这些新功能进行改进。

普通Docker技术:
创建一个类似以下内容的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"]

尽管这种方法很好用,简洁明了,但是有些事情不是最理想的。
Spring Boot 2.3.0.M1中引入了两个新功能,以帮助改进这些现有技术:buildpack支持和分层jar。

buildpack
Spring Boot 2.3.0.M1直接支持Maven和Gradle的buildpack。这意味着您只需键入一个命令,即可将明智的映像快速获取到本地运行的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

第一次运行将花费一些时间,但后续调用会更快。
您的应用程序已编译,打包并转换为Docker映像。您可以使用以下方法进行测试:

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

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

分层jar
Spring Boot始终支持其自己的“胖jar”格式,该格式允许您创建可以使用java -jar运行。如果您曾经研究过该jar的内容,则会看到一个类似以下的结构:

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

分为三个主要部分:

  • classes是用于引导jar加载的类
  • Spring Boot应用程序类是在 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/
    <name>/
      classes/
        ...
      lib/
        ...
    <name>/
      classes/
        ...
      lib/
        ...
  layers.idx

现在lib和classes文件夹已拆分并分类为图层。还有一个新layers.idx文件,提供了添加图层的顺序。
最初,我们提供了以下现成的图层:

  • dependencies (对于常规发布的依赖项)
  • snapshot-dependencies (对于快照依赖性)
  • resources (用于静态资源)
  • application (用于应用程序类和资源)

此分层旨在根据应用程序构建之间更改的可能性来分离代码。库代码不太可能在内部版本之间进行更改,因此将其放置在自己的层中,以允许工具重新使用缓存中的层。应用程序代码更可能在内部版本之间进行更改,因此将其隔离在单独的层中。


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

编写dockerfile

<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用于提取并复制每个图层的文件。这是一个例子:

ROM 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

总结
使用buildpacks,dockerfiles和现有插件(例如jib),这些都是创建Docker镜像的方法。每种方法都有其优点和缺点,但是希望无论您选择哪种方法,我们在Spring Boot 2.3中提供的新功能都将有所帮助。