Creating Docker Images with Spring Boot – 用Spring Boot创建Docker镜像

最后修改: 2020年 7月 24日

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

1. Introduction

1.绪论

As more organizations move towards containers and virtual servers, Docker is becoming a more significant part of software development workflows. To that end, one of the great new features in Spring Boot 2.3 is the ability to create a Docker image for Spring Boot applications easily.

随着越来越多的组织转向容器和虚拟服务器,Docker正在成为软件开发工作流程中更重要的一部分。为此,Spring Boot2.3中的一个伟大的新功能是能够为Spring Boot应用程序轻松创建Docker镜像。

In this tutorial, we’ll look at how to create Docker images for a Spring Boot application.

在本教程中,我们将了解如何为Spring Boot应用程序创建Docker镜像。

2. Traditional Docker Builds

2.传统的Docker构建

The traditional way of building Docker images with Spring Boot is to use a Dockerfile. Below is a simple example:

用Spring Boot构建Docker镜像的传统方法是使用Dockerfile。下面是一个简单的例子。

FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/demo-app-1.0.0.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

We could then use the docker build command to create a Docker image. This works fine for most applications, but there are a couple of drawbacks.

然后我们可以使用docker build命令来创建一个Docker镜像。这对大多数应用程序来说都很好用,但也有几个缺点。

First, we are using the fat jar created by Spring Boot. This can impact startup time, especially in a containerized environment. We can save startup time by adding the exploded contents of the jar file instead.

首先,我们使用的是Spring Boot创建的fat jar。这可能会影响启动时间,尤其是在容器化环境中。我们可以通过添加jar文件的爆炸性内容来节省启动时间。

Second, Docker images are built in layers. The nature of Spring Boot fat jars causes all application code and 3rd party libraries to be put into a single layer. This means even when only a single line of code changes, the entire layer has to be rebuilt.

第二,Docker镜像是分层构建的。Spring Boot脂肪罐的性质导致所有的应用程序代码和第三方库都被放在一个单独的层中。这意味着即使只有一行代码发生变化,整个层也必须重新构建

By exploding the jar before building, application code and 3rd party libraries each get their own layer. This allows us to take advantage of Docker’s caching mechanism. Now, when one line of code is changed, only that corresponding layer needs to be rebuilt.

通过在构建前对jar进行爆炸,应用程序代码和第三方库都有自己的层。这使我们能够利用Docker的缓存机制。现在,当一行代码被改变时,只有相应的层需要被重新构建。

With this in mind, let’s look at how Spring Boot has improved the process of creating Docker images.

考虑到这一点,让我们看看Spring Boot是如何改进创建Docker镜像的过程的。

3. Buildpacks

3.建设包

Buildpacks are a tool that provides framework and application dependencies.

Buildpacks是一种提供框架和应用程序依赖性的工具

For example, given a Spring Boot fat jar, a buildpack would provide the Java runtime for us. This allows us to skip the Dockerfile and get a sensible Docker image automatically.

例如,给定一个Spring Boot fat jar,buildpack将为我们提供Java运行时。这使得我们可以跳过Dockerfile,自动获得一个合理的Docker镜像。

Spring Boot includes both Maven and Gradle support for buildpacks. For example, building with Maven, we would run the command:

Spring Boot包括Maven和Gradle对构建包的支持。例如,用Maven构建,我们可以运行以下命令。

./mvnw spring-boot:build-image

Let’s look at some of the pertinent output to see what is happening:

让我们看一下一些相关的输出,看看正在发生什么。

[INFO] Building jar: target/demo-0.0.1-SNAPSHOT.jar
...
[INFO] Building image 'docker.io/library/demo:0.0.1-SNAPSHOT'
...
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 100%
...
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     5 of 15 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 2.8.1
[INFO]     [creator]     paketo-buildpacks/executable-jar    1.2.8
[INFO]     [creator]     paketo-buildpacks/apache-tomcat     1.3.1
[INFO]     [creator]     paketo-buildpacks/dist-zip          1.3.6
[INFO]     [creator]     paketo-buildpacks/spring-boot       1.9.1
...
[INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
[INFO] Total time:  44.796 s

The first line shows we built our standard fat jar, just like any typical maven package.

第一行显示我们建立了标准的fat jar,就像任何典型的maven包一样。

The next line begins the Docker image build. Right after, we see the build pulls in the Packeto builder.

下一行开始构建Docker镜像。紧接着,我们看到构建过程拉入了Packeto>构建器。

Packeto is an implementation of cloud-native buildpacks. It does the work of analyzing our project and determining the required frameworks and libraries. In our case, it determines that we have a Spring Boot project and adds in the required buildpacks.

Packeto是云原生构建包的一个实现。它负责分析我们的项目并确定所需的框架和库。在我们的案例中,它确定我们有一个Spring Boot项目,并将所需的构建包添加进来。

Finally, we see the generated Docker image and total build time. Notice how the first time we build, we spend a fair amount of time downloading buildpacks and creating different layers.

最后,我们看到生成的Docker镜像和总构建时间。注意到在第一次构建时,我们花了相当多的时间下载buildpacks和创建不同的层。

One of the great features of buildpacks is that the Docker image is multiple layers. So if we only change our application code, subsequent builds will be much faster:

buildpacks的一大特点是,Docker镜像是多层的。因此,如果我们只改变我们的应用程序代码,后续的构建会更快。

...
[INFO]     [creator]     Reusing layer 'paketo-buildpacks/executable-jar:class-path'
[INFO]     [creator]     Reusing layer 'paketo-buildpacks/spring-boot:web-application-type'
...
[INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
...
[INFO] Total time:  10.591 s

4. Layered Jars

4.分层的罐子

In some cases, we may prefer not to use buildpacks — perhaps our infrastructure is already tied to another tool, or we already have custom Dockerfiles we want to re-use.

在某些情况下,我们可能倾向于不使用buildpacks–也许我们的基础设施已经与另一个工具绑定,或者我们已经有了想要重新使用的自定义Dockerfiles。

For these reasons, Spring Boot also supports building Docker images using layered jars. To understand how it works, let’s look at a typical Spring Boot fat jar layout:

由于这些原因,Spring Boot也支持使用分层jar构建Docker镜像。为了了解它是如何工作的,让我们看看一个典型的Spring Boot fat jar布局。

org/
  springframework/
    boot/
  loader/
...
BOOT-INF/
  classes/
...
lib/
...

The fat jar is composed of 3 main areas:

脂肪罐由3个主要区域组成。

  • Bootstrap classes required to launch the Spring application
  • Application code
  • 3rd party libraries

With layered jars, the structure looks similar, but we get a new layers.idx file that maps each directory in the fat jar to a layer:

对于分层的jar,结构看起来类似,但我们得到一个新的layers.idx文件,将胖jar中的每个目录映射到一个层。

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

Out-of-the-box, Spring Boot provides four layers:

开箱即用,Spring Boot提供了四个层次。

  • dependencies: typical dependencies from third parties
  • snapshot-dependencies: snapshot dependencies from 3rd parties
  • resources: static resources
  • application: application code and resources

The goal is to place application code and third-party libraries into layers that reflect how often they change.

目标是将应用程序代码和第三方库放入反映其变化频率的层中

For example, application code is likely what changes most frequently, so it gets its own layer. Further, each layer can evolve on its own, and only when a layer has changed will it be rebuilt for the Docker image.

例如,应用程序代码可能是变化最频繁的,所以它有自己的层。此外,每个层都可以自行发展,只有当一个层发生变化时,才会为Docker镜像重新构建。

Now that we understand the new layered jar structure, let’s look at how we can utilize it to make Docker images.

现在我们了解了新的分层jar结构,让我们来看看如何利用它来制作Docker镜像。

4.1. Creating Layered Jars

4.1.创建分层Jars

First, we have to set up our project to create a layered jar. With Maven, this means adding a new configuration to the Spring Boot plugin section of our POM:

首先,我们必须设置我们的项目,以创建一个分层的jar。在Maven中,这意味着在POM的Spring Boot插件部分添加一个新配置。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
        </layers>
    </configuration>
</plugin>

With this configuration, the Maven package command (along with any of its dependent commands) will generate a new layered jar using the four default layers mentioned previously.

有了这个配置,Maven package命令(连同其任何依赖命令)将使用之前提到的四个默认层生成一个新的分层jar。

4.2. Viewing and Extracting Layers

4.2.查看和提取图层

Next, we need to extract the layers from the jar so that the Docker image will have the proper layers.

接下来,我们需要从jar中提取图层,这样Docker镜像就会有适当的图层。

To examine the layers of any layered jar, we can run the command:

为了检查任何分层的罐子的层数,我们可以运行命令。

java -Djarmode=layertools -jar demo-0.0.1.jar list

Then to extract them, we would run:

然后为了提取它们,我们会运行。

java -Djarmode=layertools -jar demo-0.0.1.jar extract

4.3. Creating the Docker Image

4.3.创建Docker镜像

The easiest way to incorporate these layers into a Docker image is by using a Dockerfile:

将这些层纳入Docker镜像的最简单方法是使用Docker文件。

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

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"]

This Dockerfile extracts the layers from our fat jar, then copies each layer into the Docker image. Each COPY directive results in a new layer in the final Docker image.

这个Dockerfile从我们的fat jar中提取层,然后将每个层复制到Docker镜像中。每个COPY指令都会在最终的Docker镜像中产生一个新层

If we build this Dockerfile, we can see each layer from the layered jar get added to the Docker image as its own layer:

如果我们构建这个Docker文件,我们可以看到分层jar中的每一层都被添加到Docker镜像中,成为自己的一层。

...
Step 6/10 : COPY --from=builder dependencies/ ./
 ---> 2c631b8f9993
Step 7/10 : COPY --from=builder snapshot-dependencies/ ./
 ---> 26e8ceb86b7d
Step 8/10 : COPY --from=builder spring-boot-loader/ ./
 ---> 6dd9eaddad7f
Step 9/10 : COPY --from=builder application/ ./
 ---> dc80cc00a655
...

5. Conclusion

5.总结

In this tutorial, we have seen various ways to build Docker images with Spring Boot. Using buildpacks, we can get suitable Docker images with no boilerplate or custom configurations. Or, with a little more effort, we can use layered jars to get a more tailored Docker image.

在本教程中,我们已经看到了用Spring Boot构建Docker镜像的各种方法。使用buildpacks,我们可以得到合适的Docker镜像,不需要模板或自定义配置。或者,只要多花点力气,我们可以使用分层的jars来获得更有针对性的Docker镜像。

All of the examples in this tutorial can be found over on GitHub.

本教程中的所有例子都可以在GitHub上找到

For further information on using Java and Docker, check out the tutorial on jib.

有关使用Java和Docker的进一步信息,请查看jib的教程