1. 引言
Docker 是創建自包含應用程序的行業標準。從 2.3.0 版本開始,Spring Boot 包含了多個增強功能,以幫助我們創建高效的 Docker 鏡像。因此,它 允許將應用程序分解為不同的層。
換句話説,源代碼位於其自身的層中。因此,它可以獨立重建,從而提高效率並縮短啓動時間。在本教程中,我們將學習如何利用 Spring Boot 的新功能來重用 Docker 層。
2. Docker 中的分層罐
Docker 容器由基礎鏡像和附加層組成。一旦層構建完成,它們將保持緩存狀態。因此,後續生成將更加快速:
對底層層中的更改也會重新構建頂層層。因此,變化頻率較低的層應位於底部,而變化頻率較高的層應位於頂部。
同樣地,Spring Boot 允許將 Artifact 的內容映射到層中。以下是默認層映射:
如我們所見,應用程序擁有自己的層。修改源代碼時,僅重新構建獨立的層。加載器和依賴項保持緩存,從而減少 Docker 鏡像的創建和啓動時間。
3. 使用 Spring Boot 構建高效 Docker 鏡像
在傳統的 Docker 鏡像構建方式中,Spring Boot 採用“胖 JAR”方法。因此,單個 Artifact 包含了所有依賴項和應用程序的源代碼。任何源代碼的修改都迫使整個層重新構建。
3.1. 使用 Spring Boot 配置分層
Spring Boot 2.3.0 版本引入了兩個新功能以改進 Docker 鏡像生成:
- 構建包支持 提供應用程序的 Java 運行時,因此現在可以跳過 Dockerfile 並自動構建 Docker 鏡像。
- 分層 JAR 有助於我們充分利用 Docker 鏡像分層功能。
在本教程中,我們將擴展分層 JAR 方法。
最初,我們將分層 JAR 設置在 Maven 中。在打包 Artifact 時,我們將生成層。讓我們檢查 JAR 文件:
jar tf target/spring-boot-docker-0.0.1-SNAPSHOT.jar我們可以看到,在胖 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. 與圖層交互
讓我們列出該 Artifact 中的圖層:
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 的功能,我們需要將層添加到鏡像中。
首先,我們將“胖 JAR”文件添加到基礎鏡像中:
FROM openjdk:17-jdk-alpine as builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
第二,我們來提取該文物中的各層:
RUN java -Djarmode=layertools -jar application.jar extract
最後,讓我們複製提取的文件夾以添加相應的 Docker 層:
FROM openjdk:17-jdk-alpine
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.baeldung.docker:*:*</include>
</into>此外,我們還將添加新的層:
<layerOrder>
<layer>internal-dependencies</layer>
</layerOrder>結果,如果我們列出脂肪罐內部的層級,新的內部依賴關係就會出現:
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如我們所見,該層現在包含項目的內部依賴關係。
5. 結論
在本教程中,我們演示瞭如何生成高效的 Docker 鏡像。簡而言之,我們利用 Spring Boot 的新特性創建了分層 JAR 包。對於簡單的項目,我們可以使用默認配置。我們還展示了一種更高級的配置,用於重用層。