Spring Boot创建 Docker 层
1. 简介
Docker 是创建自包含应用程序的事实标准。从 2.3.0 版本开始,Spring Boot 包含多项增强功能,可帮助我们创建高效的 Docker 镜像。因此,它允许将应用程序分解为不同的层。
换句话说,源代码位于它自己的层中。因此,它可以独立重建,提高效率和启动时间。在本教程中,我们将了解如何利用 Spring Boot 的新功能来重用 Docker 层。
2. Docker 中的分层 jars
Docker 容器由基础镜像和附加层组成。构建镜像后,它们将保持缓存状态。因此,后续迭代会快得多:
下层的变化也会重建上层。因此,不经常变化的层应该保留在底部,而经常变化的层应该放在顶部。
同样,Spring Boot 允许将工件的内容映射到层中。让我们看看图层的默认映射:
正如我们所见,应用程序有自己的层。修改源代码时,只重建独立层。**加载器和依赖项保持缓存,减少 Docker 映像创建和启动时间。**让我们看看如何使用 Spring Boot 做到这一点!
3. 使用 Spring Boot 创建高效的 Docker 镜像
在构建 Docker 镜像的传统方式中,Spring Boot 使用胖 jar 方法 。因此,单个应用嵌入了所有依赖项和应用程序源代码。因此,我们源代码中的任何更改都会强制重建整个层。
3.1.使用 Spring Boot 进行层配置
Spring Boot 2.3.0 版本引入了两个新特性来改进 Docker 镜像生成:
- **Buildpack 支持 **为应用程序提供 Java 运行时,因此现在可以跳过 Dockerfile 并自动构建 Docker 映像
- **分层 jars **帮助我们充分利用 Docker 层生成
在本教程中,我们将扩展分层 jar 方法。
最初,我们将在 Maven 中设置分层 jar 。打包工件时,我们将生成图层。让我们检查一下 jar 文件:
jar tf target/spring-boot-docker-0.0.1-SNAPSHOT.jar
如我们所见,在 fat jar 内的 BOOT-INF 文件夹中创建了新的图层.idx 文件。当然,它将依赖关系、资源和应用程序源代码映射到独立的层:
BOOT-INF/layers.idx
同样,文件的内容分解了存储的不同层:
- "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. 与图层交互
让我们列出工件内部的层:
java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar list
结果提供了layers.idx文件内容的简单视图:
dependencies
spring-boot-loader
snapshot-dependencies
application
我们还可以将图层提取到文件夹中:
java -Djarmode=layertools -jar target/docker-spring-boot-0.0.1.jar extract
然后,我们可以重用 Dockerfile 中的文件夹,我们将在下一节中看到:
$ ls
application/
snapshot-dependencies/
dependencies/
spring-boot-loader/
3.3. Dockerfile 配置
为了充分利用 Docker 功能,我们需要将层添加到我们的镜像中。
首先,让我们将 fat jar 文件添加到基础镜像中:
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
最后,让我们复制提取的文件夹以添加相应的 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"]
使用这种配置,当我们更改源代码时,我们只会重新构建应用程序层。其余的将保持缓存。
4. 自定义层
似乎一切都像魅力一样运作。但是如果我们仔细观察,我们的构建之间并没有共享依赖层。也就是说,它们都归于单一层,甚至是内部层。因此,如果我们更改内部库的类,我们将再次重建所有依赖层。
4.1. 使用 Spring Boot 进行自定义层配置
在 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>
如我们所见,我们将依赖项和资源映射和排序到层中。此外,我们可以根据需要添加任意数量的自定义层。 让我们将文件命名为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>
如果我们打包工件,结果将类似于默认行为。
4.2. 添加新层
让我们创建一个内部依赖项,添加我们的应用程序类:
<into layer="internal-dependencies">
<include>com.blogdemo.docker:*:*</include>
</into>
此外,我们将订购新层:
<layerOrder>
<layer>internal-dependencies</layer>
</layerOrder>
结果,如果我们列出胖 jar 中的层,就会出现新的内部依赖:
dependencies
spring-boot-loader
internal-dependencies
snapshot-dependencies
application
4.3. Dockerfile 配置
提取后,我们可以将新的内部层添加到 Docker 映像中:
COPY --from=builder internal-dependencies/ ./
因此,如果我们生成镜像,我们将看到 Docker 如何将内部依赖构建为新层:
$ mvn package
$ docker build -f src/main/docker/Dockerfile . --tag spring-docker-demo
....
Step 8/11 : COPY --from=builder internal-dependencies/ ./
---> 0e138e074118
.....
之后,我们可以在历史记录中查看 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
正如我们所看到的,该层现在包含了项目的内部依赖项。