1. 程式人生 > 實用技巧 >Spring Boot 2.3 為 Docker Image 增加的分層 Jar 包特性

Spring Boot 2.3 為 Docker Image 增加的分層 Jar 包特性

背景

Spring Boot 2.3(截至目前版本為 M3)為容器化部署提供了一個新特性 Layered Jar。通常 Spring Boot 程式都是以 fat jar 的方式構建的,檔案大小動輒 50M、100M 這樣子,對 docker image 其實很不友好。Docker image 本身是分層結構,如果某一層沒有變化在pull時就不必上傳,一旦有變化就要上傳整層。一個程式中,程式自身程式碼、資源的變更頻率要遠大於依賴庫的變更頻率,大多數時候因為幾行程式碼變化導致上傳整個 jar 檔案,無論是儲存佔用還是時間效率上都是很大的浪費,後者在國內網速下更是一個嚴重問題。

Layered Jar

新特性 layered jar 為不同變更頻率內容分離提供了支援工具,在此基礎上分層構建 docker image 就變得很容易了。本質上這個特性是org.springframework.boot:spring-boot-maven-plugin

提供的一種新的layout,當使用新layout打包時,一個spring-boot-layertoolsjar 會打包到 fat jar 中,新特性是由這個 jar 提供的。這裡不深入解析實現細節,而是重點關注如何模式化使用這個特性獲得收益。

要使用這個新特性需要做的事情非常少,首先是在 pom 中增加layout配置,我猜未來這個選項會成為預設值,從而不需任何顯式配置。

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
        <layout>LAYERED_JAR</layout>
      </configuration>
    </plugin>
    ...
  </plugins>
  ...
</build>

新增的部分就是這 3 行

<configuration>
  <layout>LAYERED_JAR</layout>
</configuration>

增加配置之後package打包,然後可以執行以下命令驗證配置正確與否

java -Djarmode=layertools -jar <target.jar>

一個示例輸出:

Usage:
  java -Djarmode=layertools -jar metis-server-0.1.0-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

這是 maven 上所有需要做的事情,gradle 參照 maven 也可以類似處理。然後需要在 Dockerfile 中再做一點處理,這用到了 docker 的多階段構建功能(17.05 增加的功能,幾乎 3 年前,希望沒人說這版本太新~)。一個示例Dockerfile

FROM azul/zulu-openjdk:13 as builder                                #01
                                                                    #02
WORKDIR application                                                 #03
COPY /maven/${project.build.finalName}.jar application.jar          #04
RUN java -Djarmode=layertools -jar application.jar extract          #05
                                                                    #06
FROM azul/zulu-openjdk:13                                           #07
                                                                    #08
WORKDIR /opt/bin/                                                   #09
USER 1000:1000                                                      #10
EXPOSE 8080                                                         #11
                                                                    #12
COPY --from=builder application/dependencies/ ./                    #13
COPY --from=builder application/snapshot-dependencies/ ./           #14
COPY --from=builder application/resources/ ./                       #15
COPY --from=builder application/application/ ./                     #16
                                                                    #17
ENV TZ=Asia/Shanghai                                                #18
                                                                    #19
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]  #20

對上面的Dockerfile逐行解釋一下:

  1. #01#072 個FROM保持統一為執行時使用的基礎映象即可;
  2. #04COPY的源根據使用的 docker 外掛不同會有所變化,本文使用的外掛為io.fabric8:docker-maven-plugin:0.33.0。目的檔案保持application.jar即可,它要跟#05的 jar 檔名一致;
  3. #05如果報錯需要檢查是否遺漏了引數,比如#20需要--enable-preview的話#05也需要加--enable-preview
  4. #09#11#18跟本文無關,但是算是 Spring Boot 容器化的良好實踐,可以照抄,日後有機會專文再講;
  5. #10也是良好實踐——非 root 使用者執行,如果遇到相關問題可以刪除此行;
  6. #13#14#15#16#204 個COPY1 個ENTRYPOINT照抄即可,這是本文新特性的關鍵。

然後,用這個Dockerfile構建出來的映象內容已經是拆解分成多層的映象了,當變更原始碼、資源、快照版依賴、正式版依賴時會依次影響更多層映象,從而實現每次構建上傳倉庫時儲存和傳輸耗時最小化。

總結

無論從易用程度還是達到的效果上講,layered jar 都非常值得使用。要使用該特性只需要開啟 pom 中一個配置項並使用一個幾乎不需要任何修改的Dockerfile模板,幾乎零負擔獲得可觀的收益。

與本文所涉及內容有所關聯的另外 2 篇帖子可供參考
[每日短篇] 23 - 動態給容器指定 Java 啟動引數
[每日短篇] 0 - Linux 的 timezone 設定

最後把不帶行號的Dockerfile再重複一次以方便複製

FROM azul/zulu-openjdk:13 as builder

WORKDIR application
COPY /maven/${project.build.finalName}.jar application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM azul/zulu-openjdk:13

WORKDIR /opt/bin/
USER 1000:1000
EXPOSE 8080

COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/resources/ ./
COPY --from=builder application/application/ ./

ENV TZ=Asia/Shanghai

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]