Reusing Docker Layers with Spring Boot – 用Spring Boot重复使用Docker层

最后修改: 2020年 11月 16日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Docker is the de facto standard for creating self-contained applications. From version 2.3.0, Spring Boot includes several enhancements to help us create efficient Docker Images. Thus, it allows the decomposition of the application into different layers.

Docker是创建独立应用程序的事实上的标准。从2.3.0版本开始,Spring Boot包括若干增强功能,以帮助我们创建高效的Docker映像。因此,它允许将应用程序分解为不同的层

In other words, the source code resides in its own layer. Therefore, it can be independently rebuilt, improving efficiency and start-up time. In this tutorial, we’ll see how to exploit the new capabilities of Spring Boot to reuse Docker layers.

换句话说,源代码驻留在它自己的层中。因此,它可以独立重建,提高了效率和启动时间。在本教程中,我们将看到如何利用Spring Boot的新功能来重新使用Docker层。

2. Layered Jars in Docker

2.Docker中的分层Jars

Docker containers consist of a base image and additional layers. Once the layers are built, they’ll remain cached. Therefore, subsequent generations will be much faster:

Docker容器由一个基本镜像和附加层组成。一旦这些层被建立起来,它们将保持缓存。因此,后续生成的速度会更快。

Changes in the lower-level layers also rebuild the upper-level ones. Thus, the infrequently changing layers should remain at the bottom, and the frequently changing ones should be placed on top.

低层的变化也会重建高层的变化。因此,不经常变化的层应该保持在底部,而经常变化的层应该放在顶部。

In the same way, Spring Boot allows mapping the content of the artifact into layers. Let’s see the default mapping of layers:

以同样的方式,Spring Boot允许将工件的内容映射成层。让我们看看层的默认映射。

As we can see, the application has its own layer. When modifying the source code, only the independent layer is rebuilt. The loader and the dependencies remain cached, reducing Docker image creation and startup time. Let’s see how to do it with Spring Boot!

我们可以看到,应用程序有自己的层。当修改源代码时,只有独立层被重建。加载器和依赖性保持缓存,减少Docker镜像的创建和启动时间。让我们看看如何用Spring Boot做到这一点!

3. Creating Efficient Docker Images with Spring Boot

3.用Spring Boot创建高效的Docker镜像

In the traditional way of building Docker images, Spring Boot uses the fat jar approach. As a result, a single artifact embeds all the dependencies and the application source code. So, any change in our source code forces the rebuilding of the entire layer.

在构建Docker镜像的传统方式中,Spring Boot使用fat jar方式。因此,单个工件嵌入了所有的依赖性和应用程序的源代码。因此,我们的源代码的任何变化都会迫使我们重新构建整个层。

3.1. Layers Configuration with Spring Boot

3.1.使用Spring Boot的分层配置

Spring Boot version 2.3.0 introduces two new features to improve the Docker image generation:

Spring Boot 2.3.0版引入了两个新功能,以改进Docker镜像的生成。

  • Buildpack support provides the Java runtime for the application, so it’s now possible to skip the Dockerfile and build the Docker image automatically
  • Layered jars help us to get the most out of the Docker layer generation

In this tutorial, we’ll extend the layered jar approach.

在本教程中,我们将扩展分层jar的方法。

Initially, we’ll set up the layered jar in Maven. When packaging the artifact, we’ll generate the layers. Let’s inspect the jar file:

最初,我们将在Maven中设置layered jar。在打包工件时,我们将生成图层。让我们检查一下这个jar文件。

jar tf target/spring-boot-docker-0.0.1-SNAPSHOT.jar

As we can see, new layers.idx file in the BOOT-INF folder inside the fat jar is created. Certainly, it maps dependencies, resources, and application source code to independent layers:

正如我们所看到的,新的图层.idx文件在胖jar里面的BOOT-INF文件夹中被创建。当然,它将依赖关系、资源和应用程序源代码映射到独立的层。

BOOT-INF/layers.idx

Likewise, the content of the file breaks down the different layers stored:

同样,文件的内容也打破了存储的不同层次。

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

3.2. Interacting with Layers

3.2.与层的交互作用

Let’s list the layers inside the artifact:

让我们列出工件内部的层。

java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar list

The result provides a simplistic view of the content of the layers.idx file:

结果提供了一个简单的layers.idx文件内容的视图。

dependencies
spring-boot-loader
snapshot-dependencies
application

We can also extract the layers into folders:

我们还可以将图层提取到文件夹中。

java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar extract

Then, we can reuse the folders inside the Dockerfile as we’ll see in the next section:

然后,我们可以重复使用Docker文件内的文件夹,我们将在下一节看到。

$ ls
application/
snapshot-dependencies/
dependencies/
spring-boot-loader/

3.3. Dockerfile Configuration

3.3.Dockerfile配置

To get the most out of the Docker capabilities, we need to add the layers to our image.

为了最大限度地发挥Docker的能力,我们需要将图层添加到我们的图像中。

First, let’s add the fat jar file to the base image:

首先,让我们把fat jar文件添加到基础镜像中。

FROM adoptopenjdk:11-jre-hotspot as builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar

Second, let’s extract the layers of the artifact:

第二,让我们提取文物的图层。

RUN java -Djarmode=layertools -jar application.jar extract

Finally, let’s copy the extracted folders to add the corresponding Docker layers:

最后,让我们复制提取的文件夹来添加相应的Docker层。

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

With this configuration, when we change our source code, we’ll only rebuild the application layer. The rest will remain cached.

有了这个配置,当我们改变我们的源代码时,我们将只重建应用层。其余的将保持缓存。

4. Custom Layers

4.自定义图层

It seems everything is working like a charm. But if we look carefully, the dependency layer is not shared between our builds. That is to say, all of them come to a single layer, even the internal ones. Therefore, if we change the class of an internal library, we’ll rebuild again all the dependency layers.

似乎一切都工作得很顺利。但如果我们仔细观察,依赖层在我们的构建之间并不共享。也就是说,所有的都是一个层,即使是内部的也是如此。因此,如果我们改变了一个内部库的类,我们将再次重建所有的依赖层。

4.1. Custom Layers Configuration with Spring Boot

4.1.使用Spring Boot的自定义层配置

In Spring Boot, it’s possible to tune custom layers through a separate configuration file:

在Spring Boot中,可以通过一个单独的配置文件来调整自定义层

<layers xmlns="http://www.springframework.org/schema/boot/layers"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
                     https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
    <application>
        <into layer="spring-boot-loader">
            <include>org/springframework/boot/loader/**</include>
        </into>
        <into layer="application" />
    </application>
    <dependencies>
        <into layer="snapshot-dependencies">
            <include>*:*:*SNAPSHOT</include>
        </into>
        <into layer="dependencies" />
    </dependencies>
    <layerOrder>
        <layer>dependencies</layer>
        <layer>spring-boot-loader</layer>
        <layer>snapshot-dependencies</layer>
        <layer>application</layer>
    </layerOrder>
</layers>

As we can see, we’re mapping and ordering the dependencies and resources into layers. Furthermore, we can add as many custom layers as we want.

正如我们所看到的,我们正在将依赖关系和资源映射和排序到层中。此外,我们可以根据自己的需要添加任意多的自定义层。

Let’s name our file layers.xml. Then, in Maven, we can configure this file to customize the layers:

我们把文件命名为layers.xml。然后,在Maven中,我们可以配置这个文件来定制层。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
            <configuration>${project.basedir}/src/layers.xml</configuration>
        </layers>
    </configuration>
</plugin>

If we package the artifact, the result will be similar to the default behavior.

如果我们将工件打包,结果将与默认行为类似。

4.2. Adding New Layers

4.2.添加新的图层

Let’s create an internal dependency adding our application classes:

让我们创建一个内部依赖关系,添加我们的应用程序类。

<into layer="internal-dependencies">
    <include>com.baeldung.docker:*:*</include>
</into>

In addition, we’ll order the new layer:

此外,我们将订购新层。

<layerOrder>
    <layer>internal-dependencies</layer>
</layerOrder>

As a result, if we list the layers inside the fat jar, the new internal dependency appears:

结果是,如果我们列出脂肪罐内的层,就会出现新的内部依赖关系。

dependencies
spring-boot-loader
internal-dependencies
snapshot-dependencies
application

4.3. Dockerfile Configuration

4.3.Dockerfile配置

Once extracted, we can add the new internal layer to our Docker image:

一旦提取出来,我们就可以把新的内部层添加到我们的Docker镜像中。

COPY --from=builder internal-dependencies/ ./

So, if we generate the image, we’ll see how Docker builds the internal dependency as a new layer:

因此,如果我们生成镜像,我们会看到Docker是如何将内部的依赖关系构建为一个新的层。

$ mvn package
$ docker build -f src/main/docker/Dockerfile . --tag spring-docker-demo
....
Step 8/11 : COPY --from=builder internal-dependencies/ ./
 ---> 0e138e074118
.....

After that, we can check in the history the composition of layers in the Docker image:

之后,我们可以在历史中检查Docker镜像中的层的组成。

$ docker history --format "{{.ID}} {{.CreatedBy}} {{.Size}}" spring-docker-demo
c0d77f6af917 /bin/sh -c #(nop)  ENTRYPOINT ["java" "org.s… 0B
762598a32eb7 /bin/sh -c #(nop) COPY dir:a87b8823d5125bcc4… 7.42kB
80a00930350f /bin/sh -c #(nop) COPY dir:3875f37b8a0ed7494… 0B
0e138e074118 /bin/sh -c #(nop) COPY dir:db6f791338cb4f209… 2.35kB
e079ad66e67b /bin/sh -c #(nop) COPY dir:92a8a991992e9a488… 235kB
77a9401bd813 /bin/sh -c #(nop) COPY dir:f0bcb2a510eef53a7… 16.4MB
2eb37d403188 /bin/sh -c #(nop)  ENV JAVA_HOME=/opt/java/o… 0B

As we can see, the layer now includes the internal dependencies of the project.

我们可以看到,该层现在包括项目的内部依赖。

5. Conclusion

5.总结

In this tutorial, we showed how to generate efficient Docker images. In short, we used the new Spring Boot features to create layered jars. For simple projects, we can use the default configuration. We also demonstrated a more advanced configuration to reuse the layers.

在本教程中,我们展示了如何生成高效的Docker镜像。简而言之,我们使用了Spring Boot的新功能来创建分层jar。对于简单的项目,我们可以使用默认配置。我们还演示了一种更高级的配置,以重用各层。

As always, the code is available over on GitHub.

像往常一样,代码可在GitHub上获得