容器已經成為將應用程式及其所有軟體和作業系統相依性打包並將它們交付到各種環境的首選方法。
本文討論了容器化 Spring Boot 應用程式的不同方法:
- 使用 Dockerfile 建立 Docker 映像,
- 使用 Cloud-Native Buildpack 從原始碼建立 OCI 鏡像,
- 並透過使用分層工具將 JAR 部分分成不同的層來優化運行時影像。
示例代碼
本文附帶一個可運行的程式碼範例。 .
容器術語
我們將從文章中使用的容器術語開始:
- 容器鏡像:特定格式的文件。我們透過運行建置工具將我們的應用程式轉換為容器鏡像。
- 容器:容器鏡像的可執行實例。
- 容器引擎:負責運行容器的守護程式。
- 容器主機:容器引擎運轉的宿主機。
- 容器註冊表:用於發布和分發容器鏡像的共用位置。
- OCI標準: - 是一個輕量、開放式的管理結構,形成於以下框架內: Linux 基礎。 OCI 鏡像規範定義了容器鏡像格式和運行時的業界標準,以確保所有容器引擎都能運行任何建置工具創建的容器鏡像。
為了將應用程式容器化,我們將應用程式包裝在容器鏡像中,並將該鏡像發佈到共用註冊表。容器運行時從註冊表中檢索此映像,將其解壓縮,然後在其中運行應用程式。
Spring Boot 2.3 版本提供了用於建立 OCI 映像的插件。
— 是最常用的容器實現,我們在範例中使用 Docker,因此本文中所有後續對容器的引用均指 Docker。
以傳統方式建構容器鏡像
只需在 Dockerfile 中新增一些指令,即可輕鬆為 Spring Boot 應用程式建立 Docker 映像。
首先,我們建立一個可執行 JAR 文件,作為 Dockerfile 指令的一部分,我們在應用必要的自訂後將可執行 JAR 檔案複製到基礎 JRE 映像上。
讓我們創建我們的 Spring 應用程式 具有依賴關係 web, lombokи actuator。我們還添加了一個 rest 控制器來提供 API GET方法。
建立 Docker 文件
然後,我們透過添加一個 Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]我們的 Docker 檔案包含一個基礎映像,來自 adoptopenjdk,我們將 JAR 檔案複製到該目錄,然後打開端口, 8080它將監聽請求。
組裝應用程式
首先,您需要使用 Maven 或 Gradle 建立您的應用程式。這裡我們使用Maven:
mvn clean package這將為應用程式建立一個可執行 JAR 檔案。我們需要將這個可執行 JAR 轉換為 Docker 映像以便在 Docker 引擎上運作。
建立容器鏡像
然後,我們透過運行命令將此可執行 JAR 檔案放入 Docker 映像中 docker build從包含先前建立的 Docker 檔案的專案根目錄:
docker build -t usersignup:v1 .我們可以使用以下命令在列表中看到我們的圖像:
docker images 上述命令的輸出包含我們的圖像 usersignup連同基礎影像, adoptopenjdk,在我們的 Dockerfile 中指定。
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MB查看容器鏡像內的層
讓我們來看看圖像內部的圖層堆疊。我們將使用 要查看這些圖層:
dive usersignup:v1以下是運行 Dive 命令的一些輸出:

我們可以看到,應用層佔據了圖像大小的很大一部分。作為優化的一部分,我們希望在接下來的部分中減少這一層的尺寸。
使用 Buildpack 建構容器鏡像
() 是各種平台即服務 (PAAS) 產品使用的通用術語,用於從原始程式碼建立容器映像。它由 Heroku 於 2011 年推出,此後被 Cloud Foundry、Google App Engine、Gitlab、Knative 和其他一些公司採用。

雲端建置包的優勢
使用 Buildpack 建立鏡像的主要優點之一是 影像配置的變更可以集中管理(由建構器)並分發到使用建構器的所有應用程式。
建置包與平台緊密結合。 Cloud-Native Buildpacks 透過支援 OCI 映像格式提供跨平台的標準化,確保映像能夠被 Docker 引擎運作。
使用 Spring Boot 插件
Spring Boot 外掛程式使用 Buildpack 從原始碼建立 OCI 鏡像。圖像創建使用 bootBuildImage任務(Gradle)或 spring-boot:build-image目標(Maven)和本地 Docker 安裝。
我們可以透過指定名稱來自訂需要推送到 Docker 註冊表的映像名稱 image tag:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>docker.io/pratikdas/${project.artifactId}:v1</name>
</image>
</configuration>
</plugin>讓我們使用 Maven 來實現這一點 build-image建立應用程式和建立容器鏡像的目標。我們目前不使用任何 Dockerfile。
mvn spring-boot:build-image結果將會是這樣的:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Building image 'docker.io/pratikdas/usersignup:v1'
[INFO]
[INFO] > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator] Adding label 'org.springframework.boot.version'
.. [creator] *** Images (c311fe74ec73):
.. [creator] docker.io/pratikdas/usersignup:v1
[INFO]
[INFO] Successfully built image 'docker.io/pratikdas/usersignup:v1'從輸出我們可以看到 paketo Cloud-Native buildpack用於建立正在運行的 OCI 映像。與之前一樣,我們可以透過執行以下命令來查看作為 Docker 映像列出的映像:
docker images 結論:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MB使用 Jib 建構容器鏡像
Jib 是 Google 的鏡像生成插件,它提供了一種從原始碼生成容器鏡像的替代方法。
配置 jib-maven-plugin在pom.xml中:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.5.2</version>
</plugin>接下來,我們使用 Maven 命令運行 Jib 插件來建立應用程式並建立容器鏡像。和以前一樣,我們在這裡不使用任何 Docker 檔案:
mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1執行上述 Maven 指令後,我們得到以下輸出:
[INFO] Containerizing application to pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Built and pushed image as pratikdas/usersignup:v1
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete輸出顯示容器鏡像已建置並推送到註冊表。
創建優化圖像的動機和方法
我們進行優化的主要原因有二:
- Производительность:在容器編排系統中,容器鏡像會從鏡像註冊表拉取到運行容器引擎的主機上。這個過程稱為規劃。從登錄中提取大型映像會導致容器編排系統中的調度時間過長以及 CI 管道中的建置時間過長。
- 安全:大圖像也具有更大的脆弱區域。
Docker 映像由多層組成,每一層代表 Dockerfile 中的一條指令。 每一層都代表下一層的變化量。當我們從註冊表中提取 Docker 映像時,它會被分層提取並緩存在主機上。
Spring Boot 使用 作為預設的包裝格式。當我們查看胖 JAR 時,我們會發現應用程式只佔整個 JAR 的很小一部分。這是變化最頻繁的部分。其餘部分由 Spring Framework 依賴項組成。
最佳化公式的核心是將應用程式與 Spring Framework 依賴項隔離在一個單獨的層級。
構成 fat JAR 檔案主體的依賴層僅下載一次並快取在主機系統上。
在應用程式更新和容器調度期間僅提取應用程式的薄層, 如下圖所示:

在以下部分中,我們將研究如何為 Spring Boot 應用程式建立這些最佳化影像。
使用 Buildpack 為 Spring Boot 應用程式建立最佳化的容器鏡像
Spring Boot 2.3 透過將厚 JAR 檔案的各個部分提取到單獨的層來支援分層。分層功能預設是停用的,必須使用 Spring Boot Maven 插件明確啟用:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>我們將使用此配置首先使用 Buildpack 建立我們的容器映像,然後在以下部分中使用 Docker 建置。
讓我們開始吧 build-imageMaven 建置容器鏡像的目標:
mvn spring-boot:build-image如果我們運行 Dive 來查看結果圖像中的圖層,我們可以看到,與使用 fat JAR 格式得到的相比,應用程式層(紅色圈出)在千字節範圍內要小得多:

使用 Docker 為 Spring Boot 應用程式建置最佳化的容器映像
除了使用 Maven 或 Gradle 插件之外,我們還可以使用 Dockerfile 建立分層的 Docker JAR 映像。
當我們使用 Docker 時,我們需要執行兩個額外的步驟來提取圖層並將它們複製到最終的映像中。
使用啟用分層的 Maven 建置後,產生的 JAR 內容將如下所示:
META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx輸出顯示一個名為 spring-boot-jarmode-layertoolsи layersfle.idx文件。這個額外的 JAR 檔案提供了分層處理的能力,如下一節所述。
提取各個層上的依賴關係
要從分層 JAR 中檢視和擷取層,我們使用系統屬性 -Djarmode=layertools開始 spring-boot-jarmode-layertoolsJAR 而不是應用程式:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar執行此命令會產生包含可用命令選項的輸出:
Usage:
java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar
Available commands:
list List layers from the jar that can be extracted
extract Extracts layers from the jar for image creation
help Help about any command輸出顯示命令 list, extractи helpс help是預設的。讓我們執行以下命令 list選項:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
application我們看到了可以作為層添加的依賴項列表。
預設圖層:
圖層名稱
Содержание
dependencies
任何版本不包含 SNAPSHOT 的依賴項
spring-boot-loader
JAR載入器類
snapshot-dependencies
任何相依性的版本包含 SNAPSHOT
application
應用程式類別和資源
層定義在 layers.idx檔案按照新增到 Docker 映像的順序排列。由於這些層不會改變,因此在第一次檢索後它們會被緩存在主機中。 僅將更新的應用程式層載入到主機上,由於其尺寸減小,速度更快。 .
建置將依賴項提取到單獨層中的映像
我們將使用稱為 。第一步,我們將提取依賴項,第二步,我們將提取的依賴項複製到最終影像中。
讓我們修改 Dockerfile 以進行多階段建置:
# the first stage of our build will extract the layers
FROM adoptopenjdk:14-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
# the second stage of our build will copy the extracted layers
FROM adoptopenjdk:14-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]我們將此配置保存在單獨的文件中 - Dockerfile2.
我們使用以下命令建置 Docker 映像:
docker build -f Dockerfile2 -t usersignup:v1 .執行此命令後我們得到以下輸出:
Sending build context to Docker daemon 20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Successfully built a9ebf6970841
Successfully tagged userssignup:v1我們可以看到Docker映像建立時帶有一個映像ID,然後被貼上標籤。
最後,我們像以前一樣運行 Dive 命令來檢查生成的 Docker 映像內的層。我們可以指定圖像 ID 或標籤作為 Dive 命令的輸入:
dive userssignup:v1從輸出中可以看到,包含應用程式的層現在只有 11 KB,並且依賴項被緩存在單獨的層中。

提取各層的內部依賴關係
我們可以透過將任何自訂依賴項提取到單獨的層中來進一步減少應用程式層的大小,而不是透過在應用程式中聲明它們來將它們打包在一起 yml同名文件 layers.idx:
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "custom-dependencies":
- "io/myorg/"
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/classpath.idx"
- "BOOT-INF/layers.idx"
- "META-INF/"在此文件中 layers.idx我們新增了一個名為的自訂依賴項, io.myorg包含從公共儲存庫取得的組織的依賴項。
產量
在本文中,我們研究如何使用 Cloud-Native Buildpacks 直接從原始碼建立容器鏡像。這是使用 Docker 以通常方式建立容器映像的替代方法:首先建立一個厚的可執行 JAR 文件,然後透過在 Dockerfile 中指定指令將其打包到容器映像中。
我們還研究了透過啟用分層功能來優化我們的容器,該功能將依賴項提取到快取在主機上的單獨層中,並在容器的運行時引擎中進行調度時載入應用程式的薄層。
您可以在以下位置找到本文使用的所有原始程式碼 .
命令參考
以下是我們在本文中使用的命令的簡要概述,以供快速參考。
清除上下文:
docker system prune -a使用 Docker 檔案建立容器映像:
docker build -f <Docker file name> -t <tag> .從原始碼建立容器映像(不使用 Dockerfile):
mvn spring-boot:build-image查看依賴層。在建置應用程式 JAR 檔案之前,請確保在 spring-boot-maven-plugin 中啟用了分層功能:
java -Djarmode=layertools -jar application.jar list提取依賴層。在建置應用程式 JAR 檔案之前,請確保在 spring-boot-maven-plugin 中啟用了分層功能:
java -Djarmode=layertools -jar application.jar extract查看容器鏡像列表
docker images從容器鏡像內部左側查看(確保安裝了 dive 工具):
dive <image ID or image tag>來源: www.habr.com
