為 Spring Boot 應用程序構建優化的 Docker 鏡像

容器已成為打包應用程式及其所有軟體和作業系統依賴項,然後將它們交付到不同環境的首選方式。

本文介紹了容器化 Spring Boot 應用程式的不同方法:

  • 使用 Docker 檔案建立 Docker 映像,
  • 使用 Cloud-Native Buildpack 從來源建立 OCI 映像,
  • 透過使用多層工具將 JAR 的各個部分分成不同的層來優化運行時影像。

 示例代碼

本文附有工作程式碼範例 在 GitHub 上 .

容器術語

我們將從本文中使用的容器術語開始:

  • 容器鏡像: 特定格式的文件。 我們將透過運行建置工具將應用程式轉換為容器映像。
  • 容器:容器鏡像的可執行實例。
  • 容器引擎:負責運行容器的守護程式。
  • 容器主機:容器引擎運作的主機。
  • 容器註冊中心:用於發布和分發容器鏡像的一般位置。
  • OCI標準開放容器倡議 (OCI) 是 Linux 基金會內部形成的一個輕量級、開放的治理結構。 OCI 鏡像規範定義了容器鏡像和運行時格式的業界標準,以確保所有容器引擎都可以運行任何建置工具創建的容器鏡像。

為了容器化應用程序,我們將應用程式包裝在容器映像中,並將映像發佈到共用註冊表。 容器運行時從註冊表中檢索映像,將其解壓縮,然後在其中執行應用程式。

Spring Boot 2.3 版本提供了用於建立 OCI 映像的插件。

碼頭工人 是最常用的容器實現,並且我們在範例中使用 Docker,因此本文後續所有容器引用都將參考 Docker。

以傳統方式建構容器鏡像

透過為 Docker 檔案添加一些指令,為 Spring Boot 應用程式建立 Docker 映像非常簡單。

我們首先建立一個可執行 JAR 文件,並作為 Docker 文件指令的一部分,在應用必要的設定後將可執行 JAR 檔案複製到基礎 JRE 映像之上。

讓我們創建 Spring 應用程式 春季初始化 有依賴關係 weblombokи actuator。 我們還添加了一個休息控制器來提供 API GET方法。

建立 Dockerfile

然後,我們透過添加來容器化該應用程式 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從包含先前建立的 Dockerfile 的專案根目錄:

docker build  -t usersignup:v1 .

我們可以使用以下命令在列表中查看我們的圖像:

docker images 

上述命令的輸出包括我們的圖像 usersignup與基礎影像一起, adoptopenjdk在我們的 Docker 檔案中指定。

REPOSITORY          TAG                 SIZE
usersignup          v1                  249MB
adoptopenjdk        11-jre-hotspot      229MB

查看容器鏡像內的層

讓我們看看影像內的圖層堆疊。 我們將使用 工具  潛水, 看看這些層:

dive usersignup:v1

以下是 Dive 命令的部分輸出: 

為 Spring Boot 應用程序構建優化的 Docker 鏡像

正如我們所看到的,應用程式層佔據了圖像大小的很大一部分。 作為最佳化的一部分,我們希望在以下部分中減少該層的大小。

使用 Buildpack 建立容器鏡像

組裝包 (建置包)是各種平台即服務 (PAAS) 產品使用的通用術語,用於從原始程式碼建立容器映像。 它由 Heroku 於 2011 年推出,此後已被 Cloud Foundry、Google App Engine、Gitlab、Knative 等多個公司採用。

為 Spring Boot 應用程序構建優化的 Docker 鏡像

雲端建置包的優勢

使用 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 框架相依性組成。

優化公式的重點是在單獨的層級將應用程式與 Spring 框架依賴項隔離。

依賴層構成了厚 JAR 檔案的大部分,僅下載一次並緩存在主機系統上。

在應用程式更新和容器調度期間,僅拉取應用程式的薄層。 如下圖所示:

為 Spring Boot 應用程序構建優化的 Docker 鏡像

在以下部分中,我們將了解如何為 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-image用於建立容器鏡像的 Maven 目標:

mvn spring-boot:build-image

如果我們運行 Dive 來查看生成圖像中的各層,我們可以看到與使用 fat JAR 格式得到的內容相比,應用程式層(以紅色框出)在千字節範圍內要小得多:

為 Spring Boot 應用程序構建優化的 Docker 鏡像

使用 Docker 為 Spring Boot 應用程式建立最佳化的容器映像

我們也可以使用 Docker 檔案建立分層的 Docker JAR 映像,而不是使用 Maven 或 Gradle 插件。

當我們使用 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

輸出顯示命令 listextractи helpс help設為預設值。 讓我們運行命令 list選項:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
dependencies
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,並且依賴項會快取在單獨的層中。 

為 Spring Boot 應用程序構建優化的 Docker 鏡像

提取各層的內部依賴關係

我們可以透過將任何自訂依賴項提取到單獨的層中來進一步減小應用程式層的大小,而不是透過在 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包含從共用儲存庫檢索的組織相依性。

產量

在本文中,我們研究了使用雲端原生 Buildpack 直接從原始碼建立容器映像。 這是使用 Docker 建立容器映像的通常方式的替代方法:首先建立一個厚的可執行 JAR 文件,然後透過在 Docker 文件中指定指令將其打包到容器映像中。

我們還考慮透過啟用分層功能來優化容器,該功能將依賴項拉入快取在主機上的單獨層中,並在容器執行引擎中的調度時載入應用程式的薄層。

您可以在以下位置找到本文中使用的所有原始程式碼 Github上 .

命令參考

以下是我們在本文中使用的命令的快速概述。

上下文清除:

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