簡介
在很久很久以前,我們是怎么創(chuàng)建Spring Boot的docker image呢?最最通用的辦法就是將Spring boot的應(yīng)用程序打包成一個fat jar,然后寫一個docker file佳恬,將這個fat jar制作成為一個docker image然后運行寞蚌。
今天我們來體驗一下Spring Boot 2.3.3 帶來的快速創(chuàng)建docker image的功能。
傳統(tǒng)做法和它的缺點
現(xiàn)在我們創(chuàng)建一個非常簡單的Spring Boot程序:
@SpringBootApplication
@RestController
public class Application {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(Application.class, args);
? ? }
? ? @GetMapping("/getInfo")
? ? public String getInfo() {
? ? ? ? return "www.flydean.com";
? ? }
}
福利 福利 福利 免費領(lǐng)取Java架構(gòu)技能地圖 注意了是免費送
?麸折、???
?
默認情況下锡凝,我們build出來的是一個fat jar:springboot-with-docker-0.0.1-SNAPSHOT.jar
我們解壓看一下它的內(nèi)容:
?
Spring boot的fat jar分為三個部分,第一部分就是BOOT-INF, 里面的class目錄放的是我們自己編寫的class文件垢啼。而lib目錄存放的是項目依賴的其他jar包窜锯。
第二部分是META-INF,里面定義了jar包的屬性信息芭析。
第三部分是Spring Boot的類加載器锚扎,fat jar包的啟動是通過Spring Boot的jarLauncher來創(chuàng)建LaunchedURLClassLoader,通過它來加載lib下面的jar包馁启,最后以一個新線程啟動應(yīng)用的Main函數(shù)驾孔。
這里不多講Spring Boot的啟動。
我們看一下,如果想要用這個fat jar來創(chuàng)建docker image應(yīng)該怎么寫:
FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/springboot-with-docker-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
復(fù)制代碼
這樣寫有兩個問題助币。
第一個問題:我們是用的far jar浪听,在使用far jar的過程中會有一定的性能問題,肯定要比解壓過后的性能要低眉菱,尤其是在容器環(huán)境中運行的情況下迹栓,可能會更加突出。
第二個問題:我們知道docker的image是按layer來構(gòu)建的俭缓,按layer構(gòu)建的好處就是可以減少image構(gòu)建的時間和重用之前的layer克伊。
但是如果使用的是fat jar包,即使我們只修改了我們自己的代碼华坦,也會導(dǎo)致整個fat jar重新更新愿吹,從而影響docker image的構(gòu)建速度。
使用Buildpacks
傳統(tǒng)的辦法除了有上面的兩個問題惜姐,還有一個就是需要自己構(gòu)建docker file犁跪,有沒有一鍵構(gòu)建docker image的方法呢?
答案是肯定的歹袁。
Spring Boot在2.3.0之后坷衍,引入了Cloud Native 的buildpacks,通過這個工具条舔,我們可以非常非常方便的創(chuàng)建docker image枫耳。
在Maven和Gradle中,Spring Boot引入了新的phase: spring-boot:build-image
我們可以直接運行:
mvn? spring-boot:build-image
復(fù)制代碼
運行之孟抗,很不幸的是迁杨,你可能會遇到下面的錯誤:
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) on project springboot-with-docker: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:2.3.3.RELEASE:build-image failed: Docker API call to 'localhost/v1.24/images/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase-platform-api-0.3' failed with status code 500 "Internal Server Error" and message "Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)" -> [Help 1]
復(fù)制代碼
這是因為我們無法從gcr.io中拉取鏡像!
沒關(guān)系凄硼,如果你會正確的上網(wǎng)方式的話铅协,那么我估計你已經(jīng)找到了一個代理。
將你的代理配置到Docker的代理項里面帆喇,我使用的是Docker desktop,下面是我的配置:
?
重新運行 mvn spring-boot:build-image
等待執(zhí)行結(jié)果:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ springboot-with-docker ---
[INFO] Building image 'docker.io/library/springboot-with-docker:0.0.1-SNAPSHOT'
[INFO]
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
[INFO]? > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
復(fù)制代碼
你可以看到警医,我們的確是需要從gcr.io拉取image。
Layered Jars
如果你不想使用Cloud Native Buildpacks坯钦,還是想使用傳統(tǒng)的Dockerfile。 沒關(guān)系侈玄,SpringBoot為我們提供了獨特的分層jar包系統(tǒng)婉刀。
怎么開啟呢? 我們需要在POM文件中加上下面的配置:
? ? <build>
? ? ? ? <plugins>
? ? ? ? ? ? <plugin>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId>
? ? ? ? ? ? ? ? <configuration>
? ? ? ? ? ? ? ? ? ? <layers>
? ? ? ? ? ? ? ? ? ? ? ? <enabled>true</enabled>
? ? ? ? ? ? ? ? ? ? </layers>
? ? ? ? ? ? ? ? </configuration>
? ? ? ? ? ? </plugin>
? ? ? ? </plugins>
? ? </build>
復(fù)制代碼
再次打包序仙,看下jar包的內(nèi)容:
?
看起來和之前的jar包沒什么不同,只不過多了一個layers.idx 這個index文件:
- "dependencies":
? - "BOOT-INF/lib/"
- "spring-boot-loader":
? - "org/"
- "snapshot-dependencies":
- "application":
? - "BOOT-INF/classes/"
? - "BOOT-INF/classpath.idx"
? - "BOOT-INF/layers.idx"
? - "META-INF/"
復(fù)制代碼
index文件主要分為4個部分:
dependencies - 非SNAPSHOT的依賴jar包
snapshot-dependencies - SNAPSHOT的依賴jar包
spring-boot-loader - Spring boot的class loader文件
application - 應(yīng)用程序的class和resources文件
注意,這里的index文件是有順序的日杈,它和我們將要添加到docker image中的layer順序是一致的。
最少變化的將會最先添加到layer中爬橡,變動最大的放在最后面的layer。
我們可以使用layertools jarmode來對生成的fat jar進行校驗或者解壓縮:
java -Djarmode=layertools -jar springboot-with-docker-0.0.1-SNAPSHOT.jar
Usage:
? java -Djarmode=layertools -jar springboot-with-docker-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
復(fù)制代碼
使用list命令棒动,我們可列出jar包中的layer信息糙申。使用extract我們可以解壓出不同的layer。
我們執(zhí)行下extract命令船惨,看下結(jié)果:
?
可以看到柜裸,我們根據(jù)layers.idx解壓出了不同的文件夾。
我們看一下使用layer的dockerFile應(yīng)該怎么寫:
FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM adoptopenjdk:11-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"]
復(fù)制代碼
這樣我們的一個分層的DockerImage就創(chuàng)建完成了粱锐。
自定義Layer
如果我們需要自定義Layer該怎么做呢疙挺?
我們可以創(chuàng)建一個獨立的layers.xml文件:
<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="company-dependencies">
? ? ? ? ? ? <include>com.flydean:*</include>
? ? ? ? </into>
? ? ? ? <into layer="dependencies"/>
? ? </dependencies>
? ? <layerOrder>
? ? ? ? <layer>dependencies</layer>
? ? ? ? <layer>spring-boot-loader</layer>
? ? ? ? <layer>snapshot-dependencies</layer>
? ? ? ? <layer>company-dependencies</layer>
? ? ? ? <layer>application</layer>
? ? </layerOrder>
</layers>
復(fù)制代碼
怎么使用這個layer.xml呢?
添加到build plugin中就可以了:
? ? <build>
? ? ? ? <plugins>
? ? ? ? ? ? <plugin>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId>
? ? ? ? ? ? ? ? <configuration>
? ? ? ? ? ? ? ? ? ? <layers>
? ? ? ? ? ? ? ? ? ? ? ? <enabled>true</enabled>
? ? ? ? ? ? ? ? ? ? ? ? <configuration>${project.basedir}/src/main/resources/layers.xml</configuration>
? ? ? ? ? ? ? ? ? ? </layers>
? ? ? ? ? ? ? ? </configuration>
? ? ? ? ? ? </plugin>
? ? ? ? </plugins>
? ? </build>