javascript
Spring Boot 2.3.x 分层构建 Docker 镜像实战
目錄
一、什么是鏡像分層
二、SpringBoot 2.3.x 新增對分層的支持
三、創建測試的 SpringBoot 應用
- 1、Maven 中引入相關依賴和插件
- 2、創建測試的 Controller 類
- 3、創建 SpringBoot 啟動類
四、創建兩種構建鏡像的 Dockerfile 腳本
- 1、普通鏡像構建腳本文件 dockerfile-normal
- 2、分層鏡像構建腳本文件 dockerfile-layer
五、使用兩種 Dockerfile 構建項目鏡像
- 1、在服務器一構建普通 Docker 鏡像
- 2、在服務器二構建分層 Docker 鏡像
六、鏡像推送到鏡像倉庫測試
- 1、推送鏡像到鏡像倉庫測試
- 2、鏡像倉庫拉取鏡像測試
七、鏡像構建、推送、拉取時間匯總
- 1、不使用分層構建鏡像
- 2、使用分層構建鏡像
- 3、總結
系統環境:
- Docker 版本:19.03.13
- Open JDK 基礎鏡像版本:openjdk:8u275
- 私有的 Harbor 鏡像倉庫:自建 Harbor 私庫
- 項目 Github:SpringBoot 2.3.x 分層構建 Docker 鏡像示例
作者:超級小豆丁,鏈接:www.mydlq.club/article/98/
一、什么是鏡像分層
鏡像的構成
現在一談起鏡像大部分都是指 Docker 引擎構建的鏡像,一般 Docker 鏡像是由很多層組成,底層是操作系統,然后在其之上是基礎鏡像或者用戶自定義 Dockerfile 腳本中定義的中間層。其中鏡像在構建完成后,用戶只能對鏡像進行讀操作,而不能進行寫操作,只有鏡像啟動后變為容器,才能進行讀寫操作。鏡像整體結構,可以觀看下圖:
該圖中展示了鏡像的基本組成,但是圖中這一個個中間層是什么呢?要想了解這些層具體是什么,那得知道如何構建 Docker 鏡像了。平時我們構建 Docker 鏡像時候,都是編寫 Dockerfile 腳本,然后使用 Docker 鏡像構建命令,按照腳本一行行執行構建。
PS:更多 Docker 和 Spring Boot 的文章可以關注微信公眾號「Java后端」回復「666」下載技術棧手冊。
如下就是一個 Dockerfile 腳本,腳本內容就構建 Java 項目鏡像常用的 Dockerfile 命令:
FROM openjdk:8u275 VOLUME /tmp ADD target/*.jar app.jar ENV TZ="Asia/Shanghai" ENV JAVA_OPTS="" ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0" ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar"]有了 Dockerfile 腳本,我們需要執行 Docker 的構建鏡像命令對執行 Dockerfile 腳本構建鏡像,其中構建鏡像的過程如下:
## 構建鏡像的命令 $ docker build -t java-test:latest . ## 命令執行的過程 Step 1/7 : FROM openjdk:8u275---> 82f24ce79de6 Step 2/7 : VOLUME /tmp---> Running in a6361fdfc193 Removing intermediate container a6361fdfc193---> a43948bf1b98 Step 3/7 : ADD target/*.jar app.jar---> 18f4bc60818f Step 4/7 : ENV TZ="Asia/Shanghai"---> Running in cc738aa5865b Removing intermediate container cc738aa5865b---> 538adb85609e Step 5/7 : ENV JAVA_OPTS=""---> Running in f8b635d32b2b Removing intermediate container f8b635d32b2b---> 34e7a8cd7b6e Step 6/7 : ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"---> Running in 9331cb6e443e Removing intermediate container 9331cb6e443e---> 232b9c6c1d29 Step 7/7 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]---> Running in c3a24fba3a10 Removing intermediate container c3a24fba3a10---> a41974d5f0e3可以看到總共存在 7 個構建步驟,每步都與 Dockerfile 里面一行指令對應。樣子和下圖相似:
如果這時候,我們改變原來 Dockerfile 內容,創建一個新的鏡像,其 Dockerfile 如下:
FROM openjdk:8u275 VOLUME /tmp ADD target/*.jar app.jar ENV TZ="Asia/Macao" #與原來 Dockerfile 不同 ENV JVM_OPTS="-Xmx512m -Xss256k" #與原來 Dockerfile 不同 ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]執行 Docker 命令構建鏡像:
$ docker build -t java-test2:latest .Step 1/6 : FROM openjdk:8u275---> 82f24ce79de6 Step 2/6 : VOLUME /tmp---> Using cache---> a43948bf1b98 Step 3/6 : ADD target/*.jar app.jar---> Using cache---> 18f4bc60818f Step 4/6 : ENV TZ="Asia/Macao"---> Running in fd98b90a5485 Removing intermediate container fd98b90a5485---> afab3fcdab07 Step 5/6 : ENV JVM_OPTS="-Xmx512m -Xss256k"---> Running in 19a99576fba9 Removing intermediate container 19a99576fba9---> 4eeab7d7c720 Step 6/6 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]---> Running in 2dba72e1eef4 Removing intermediate container 2dba72e1eef4---> 7c706ecf7698可以觀察到執行過程中,從一開始執行的構建步驟中顯示,并沒有生成新的中間層鏡像,而是直接使用了已經存在的緩存鏡像。直至 4?6這部中,由于新的 Dockerfile 與原來 Dockerfile 發生變動,所以這部中間層鏡像直接是新創建的,并沒有使用緩存中間層鏡像。
然后往下觀察,發現之后的全部構建都是新創建的中間層鏡像,即是腳本最后的一行和原來相同,也沒有使用緩存中間層鏡像。
上面現象說明,Docker 鏡像在構建過程中按照 Dockerfile 自上往下的執行順序中,如果從最上層開始,其腳本內容和已有的緩存中間層鏡像內容一致,就會引入緩存中的中間層鏡像(并不是直接復制緩存鏡像,而是引入鏡像文件地址,多個鏡像共享這些中間層鏡像)。但是,如果執行過程中中間任意一行鏡像構建的內容發生變化,那么當前行和之后的全部行在執行時就不會使用緩存中的中間層鏡像,而是全部創建新的鏡像。
這就是 Docker 鏡像中緩存中間層鏡像的復用,學會使用緩存構建鏡像將大大減少存儲空間的占用以及鏡像的構建的構建速度,鏡像的緩存不僅僅體現在鏡像的構建上,在執行”鏡像推送”、”鏡像拉取”操作時都能觀察到其的好處。
- 鏡像緩在鏡像推送的體現: 如鏡像推送時候,也是將鏡像整體構成的中間層鏡像并行推送到鏡像倉庫,如果鏡像倉庫中已經存某個中間層鏡像,那么推送過程就不會再次將該層鏡像推送到鏡像倉庫,而是將倉庫中并不存在中間層鏡像推送到其中。
- 鏡像緩存在鏡像拉取的體現: 在拉取鏡像時候,如果本地某個大鏡像的中間層鏡像的組成中,已經包含新拉取鏡像的中間層部分鏡像,那么將直接復用本地已經鏡像的中間層鏡像,不必再將其進行拉取,而本地不存在的中間層鏡像將會被繼續拉取。
說了這么多,相信大家已經對鏡像緩存的使用有了初步了解,那么再談及為什么需要鏡像分層就很好解釋,其原因就是 Docker 想提高資源的復用率,將一個大鏡像拆分成很多層小鏡像組成,以達到鏡像中間層的復用的目的。
二、SpringBoot 2.3.x 新增對分層的支持
SpringBoot 2.3.x 以后支持分層打包應用,需要 Pom.xml 中引入 SpringBoot 2.3.x 后的父依賴和使用 SpringBoot 打包插件 spring-boot-maven-plugin,并且開啟 layers 功能,然后執行 Maven 編譯源碼構建 Jar 包,使用該 Jar 包就可以構建基于分層模式的 Docker 鏡像:
項目 pom.xml 中引入 SpringBoot 2.3.x 依賴:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/> </parent>項目 pom.xml 中引入 spring-boot-maven-plugin 打包插件,并且開啟分層功能:
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><!--開啟分層編譯支持--><layers><enabled>true</enabled></layers></configuration></plugin></plugins> </build>執行 Maven 命令,構建分層的 JAR 包,命令和平時的 Maven 構建命令相同:
$ mvn install觀察 Jar 結構,可以看到里面多了 classpath.idx 與 layers.idx 兩個文件:
- classpath.idx: 文件列出了依賴的 jar 包列表,到時候會按照這個順序載入。
- layers.idx: 文件清單,記錄了所有要被復制到 Dokcer 鏡像中的文件信息。
根據官方介紹,在構建 Docker 鏡像前需要從 Jar 中提起出對應的分層文件到 Jar 外面,可用使用下面命令列出可以從分層 Jar 中提取出的文件夾信息:
$ java -Djarmode=layertools -jar target/springboot-layer-0.0.1.jar list可用該看到以下輸出,下面的內容就是接下來使用分層構建后,生成的 Jar 提取出對應資源后的結構:
dependencies spring-boot-loader snapshot-dependencies application上面即是使用分層工具提取 Jar 的內容后生成的文件夾,其中各個文件夾作用是:
- dependencies: 存儲項目正常依賴 Jar 的文件夾。
- snapshot-dependencies: 存儲項目快照依賴 Jar 的文件夾。
- resources: 用于存儲靜態資源的文件夾。
- application: 用于存儲應用程序類相關文件的文件夾。
三、創建測試的 SpringBoot 應用
創建測試的 SpringBoot 項目,并且在 pom.xml 中開啟鏡像分層。
1、Maven 中引入相關依賴和插件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version></parent><artifactId>springboot-dockerfile-layer</artifactId><packaging>jar</packaging><name>springboot-dockerfile-layer</name><description>springboot build layer example</description><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><layers><enabled>true</enabled></layers></configuration></plugin></plugins></build></project>2、創建測試的 Controller 類
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class TestController {@GetMapping("/hello")public String hello() {return "hello world!";}}3、創建 SpringBoot 啟動類
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}四、創建兩種構建鏡像的 Dockerfile 腳本
為了方便體現出 SpringBoot 2.3.x 支持的分層構建 Dockerfile 的優點,這里在 Java 源碼文件夾下,創建普通與分層兩種構建鏡像的 Dockerfile 腳本,后續會使用這兩種腳本構建 Docker 鏡像進行構建速度、推送速度、拉取速度的對比。
1、普通鏡像構建腳本文件 dockerfile-normal
FROM openjdk:8u275 VOLUME /tmp ADD target/*.jar app.jar RUN sh -c 'touch /app.jar' ENV TZ="Asia/Shanghai" RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0" ENV JAVA_OPTS="" ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]說明:
- TZ: 時區設置,而 Asia/Shanghai 表示使用中國上海時區。
- JVM_OPTS: 指定 JVM 啟動時候的參數,-XX:MaxRAMPercentage 參數和 -Xmx 類似,都是限制堆內存大小,只不過 -Xmx 需要手動指定限制大小,而 -XX:MaxRAMPercentage 則是根據虛擬機可用內存百分比限制。
- JAVA_OPTS: 在鏡像啟動時指定的自定義 Java 參數,例如 -Dspring.application.name=xxx。
2、分層鏡像構建腳本文件 dockerfile-layer
FROM openjdk:8u275 as builder WORKDIR application COPY target/*.jar application.jar RUN java -Djarmode=layertools -jar application.jar extract FROM openjdk:8u275 WORKDIR application COPY --from=builder application/dependencies/ ./ COPY --from=builder application/snapshot-dependencies/ ./ COPY --from=builder application/spring-boot-loader/ ./ COPY --from=builder application/application/ ./ ENV TZ="Asia/Shanghai" RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0" ENV JAVA_OPTS="" ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]說明:
- TZ: 時區設置,而 Asia/Shanghai 表示使用中國上海時區。
- -Djarmode=layertools: 指定構建 Jar 的模式。
- extract: 從 Jar 包中提取構建鏡像所需的內容。
- -from=builder 多級鏡像構建中,從上一級鏡像復制文件到當前鏡像中。
五、使用兩種 Dockerfile 構建項目鏡像
1、在服務器一構建普通 Docker 鏡像
(1) 第一次構建
## 執行 Maven 命令,將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.1 .Docker 鏡像構建總共花費 24.98s 時間。
(2) 第二次構建(修改依賴 pom.xml 文件)
## 修改 pom.xml 里面的依賴,隨意添加一個新的依賴包,然后再次將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.2 .Docker 鏡像構建總共花費 1.27s 時間。
(3) 第三次構建(修改代碼內容)
## 修改源代碼任意內容后,然后再次將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.3 .Docker 鏡像構建總共花費 1.32s 時間。
2、在服務器二構建分層 Docker 鏡像
(1) 第一次構建
## 執行 Maven 命令,將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.1 .Docker 鏡像構建總共花費 26.12s 時間。
(2) 第二次構建(修改依賴 pom.xml 文件)
## 修改 pom.xml 里面的依賴,隨意添加一個新的依賴包,然后再次將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.2 .(3) 第三次構建(修改代碼內容)
## 修改源代碼任意內容后,然后再次將源代碼構建 Jar 包 $ mvn clean install## 構建 SpringBoot 應用的 Docker 鏡像 $ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.3 .Docker 鏡像構建總共花費 2.82s 時間。
六、鏡像推送到鏡像倉庫測試
1、推送鏡像到鏡像倉庫測試
服務器一推送普通鏡像到鏡像倉庫1:
## 第一次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real 0m35.215s## 第二次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real 0m14.051s## 第三次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real 0m14.183s服務器二推送分層鏡像到鏡像倉庫2:
## 第一次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-layer:0.0.1real 0m34.121s## 第二次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-layer:0.0.2real 0m13.605s## 第三次推送鏡像 $ time docker push hub.mydlq.club/library/springboot-layer:0.0.3real 0m4.805s2、鏡像倉庫拉取鏡像測試
服務器一推送從鏡像倉庫1拉取鏡像:
## 清理全部鏡像 $ docker rm --force $(docker images -qa)## 拉取鏡像 springboot-normal:0.0.1 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real 0m35.395s## 拉取鏡像 springboot-normal:0.0.2 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real 0m6.501s## 拉取鏡像 springboot-normal:0.0.3 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real 0m6.993s服務器二推送從鏡像倉庫2拉取鏡像:
## 清理全部鏡像 $ docker rm --force $(docker images -qa)## 拉取鏡像 springboot-layer:0.0.1 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real 0m30.615s## 拉取鏡像 springboot-layer:0.0.2 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real 0m4.811s## 拉取鏡像 springboot-layer:0.0.3 $ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real 0m1.293s七、鏡像構建、推送、拉取時間匯總
1、不使用分層構建鏡像
如下圖:
2、使用分層構建鏡像
如下圖:
3、總結
上面進行了使用 SpringBoot2.3.x 分層的方式構建鏡像與普通的方式構建鏡像,在鏡像的構建、推送、拉取方面進行了執行速度對比,總結出如下結論:
- 鏡像構建: 在構建上,使用分層 Jar 構建鏡像可能比普通方式構建鏡像更繁瑣,所以也更耗時,故而在構建上分層 Jar 構建鏡像沒有太多優勢。
- 鏡像推送: 在推送上,如果每次構建鏡像都只是修改構建鏡像項目的源碼,使用分層 Jar 構建鏡像,可以大大加快鏡像推送速度。
- 如果是修改構建鏡像項目中的依賴包,則和普通構建一樣速度很慢。
- 鏡像拉取: 拉取和推送類似,如果只修改構建鏡像項目的源碼,只會拉取源碼相關的中間層鏡像,該層非常小(一般幾百KB),拉取速度自然非常快。
而對構建鏡像項目的依賴包進行變動(增加依賴、刪除依賴、修改依賴版本等),則會和普通方式構建鏡像一樣,拉取速度很慢,這是因為依賴包層是中間層鏡像最大的一層(一般在10MB~200MB之間),如果該層發生變動則整個層會進行重新拉取,這樣速度自然會很慢。
總結
以上是生活随笔為你收集整理的Spring Boot 2.3.x 分层构建 Docker 镜像实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: npm install -g -S -D
- 下一篇: Java API访问HDFS集群(HA)