使用Spring Boot創(chuàng)建docker image

簡(jiǎn)介

在很久很久以前禽绪,我們是怎么創(chuàng)建Spring Boot的docker image呢铜邮?最最通用的辦法就是將Spring boot的應(yīng)用程序打包成一個(gè)fat jar萨蚕,然后寫一個(gè)docker file,將這個(gè)fat jar制作成為一個(gè)docker image然后運(yùn)行担猛。

今天我們來(lái)體驗(yàn)一下Spring Boot 2.3.3 帶來(lái)的快速創(chuàng)建docker image的功能幕垦。

傳統(tǒng)做法和它的缺點(diǎn)

現(xiàn)在我們創(chuàng)建一個(gè)非常簡(jiǎn)單的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";
    }
}

默認(rèn)情況下,我們build出來(lái)的是一個(gè)fat jar:springboot-with-docker-0.0.1-SNAPSHOT.jar

我們解壓看一下它的內(nèi)容:

image

Spring boot的fat jar分為三個(gè)部分傅联,第一部分就是BOOT-INF, 里面的class目錄放的是我們自己編寫的class文件先改。而lib目錄存放的是項(xiàng)目依賴的其他jar包。

第二部分是META-INF蒸走,里面定義了jar包的屬性信息仇奶。

第三部分是Spring Boot的類加載器,fat jar包的啟動(dòng)是通過Spring Boot的jarLauncher來(lái)創(chuàng)建LaunchedURLClassLoader比驻,通過它來(lái)加載lib下面的jar包该溯,最后以一個(gè)新線程啟動(dòng)應(yīng)用的Main函數(shù)。

這里不多講Spring Boot的啟動(dòng)别惦。

我們看一下狈茉,如果想要用這個(gè)fat jar來(lái)創(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"]

這樣寫有兩個(gè)問題。

第一個(gè)問題:我們是用的far jar步咪,在使用far jar的過程中會(huì)有一定的性能問題论皆,肯定要比解壓過后的性能要低,尤其是在容器環(huán)境中運(yùn)行的情況下猾漫,可能會(huì)更加突出点晴。

第二個(gè)問題:我們知道docker的image是按layer來(lái)構(gòu)建的,按layer構(gòu)建的好處就是可以減少image構(gòu)建的時(shí)間和重用之前的layer悯周。

但是如果使用的是fat jar包粒督,即使我們只修改了我們自己的代碼,也會(huì)導(dǎo)致整個(gè)fat jar重新更新禽翼,從而影響docker image的構(gòu)建速度屠橄。

使用Buildpacks

傳統(tǒng)的辦法除了有上面的兩個(gè)問題,還有一個(gè)就是需要自己構(gòu)建docker file闰挡,有沒有一鍵構(gòu)建docker image的方法呢锐墙?

答案是肯定的。

Spring Boot在2.3.0之后长酗,引入了Cloud Native 的buildpacks溪北,通過這個(gè)工具,我們可以非常非常方便的創(chuàng)建docker image。

在Maven和Gradle中之拨,Spring Boot引入了新的phase: spring-boot:build-image

我們可以直接運(yùn)行:

 mvn  spring-boot:build-image

運(yùn)行之茉继,很不幸的是,你可能會(huì)遇到下面的錯(cuò)誤:

[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]

這是因?yàn)槲覀儫o(wú)法從gcr.io中拉取鏡像蚀乔!

沒關(guān)系烁竭,如果你會(huì)正確的上網(wǎng)方式的話,那么我估計(jì)你已經(jīng)找到了一個(gè)代理吉挣。

將你的代理配置到Docker的代理項(xiàng)里面派撕,我使用的是Docker desktop,下面是我的配置:

image

重新運(yùn)行 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%

你可以看到,我們的確是需要從gcr.io拉取image听想。

Layered Jars

如果你不想使用Cloud Native Buildpacks腥刹,還是想使用傳統(tǒng)的Dockerfile。 沒關(guān)系汉买,SpringBoot為我們提供了獨(dú)特的分層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>

再次打包蛙粘,看下jar包的內(nèi)容:

image

看起來(lái)和之前的jar包沒什么不同,只不過多了一個(gè)layers.idx 這個(gè)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/"

index文件主要分為4個(gè)部分:

  • dependencies - 非SNAPSHOT的依賴jar包
  • snapshot-dependencies - SNAPSHOT的依賴jar包
  • spring-boot-loader - Spring boot的class loader文件
  • application - 應(yīng)用程序的class和resources文件

注意威彰,這里的index文件是有順序的出牧,它和我們將要添加到docker image中的layer順序是一致的。

最少變化的將會(huì)最先添加到layer中歇盼,變動(dòng)最大的放在最后面的layer舔痕。

我們可以使用layertools jarmode來(lái)對(duì)生成的fat jar進(jìn)行校驗(yàn)或者解壓縮:

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

使用list命令,我們可列出jar包中的layer信息豹缀。使用extract我們可以解壓出不同的layer伯复。

我們執(zhí)行下extract命令,看下結(jié)果:

image

可以看到邢笙,我們根據(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"]

這樣我們的一個(gè)分層的DockerImage就創(chuàng)建完成了。

自定義Layer

如果我們需要自定義Layer該怎么做呢氮惯?

我們可以創(chuàng)建一個(gè)獨(dú)立的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>

怎么使用這個(gè)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>

本文的例子:springboot-with-docker

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/springboot-docker-image/

本文來(lái)源:flydean的博客

歡迎關(guān)注我的公眾號(hào):「程序那些事」最通俗的解讀,最深刻的干貨妇汗,最簡(jiǎn)潔的教程帘不,眾多你不知道的小技巧等你來(lái)發(fā)現(xiàn)!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杨箭,一起剝皮案震驚了整個(gè)濱河市寞焙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖棺弊,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶密,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡模她,警方通過查閱死者的電腦和手機(jī)稻艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侈净,“玉大人尊勿,你說我怎么就攤上這事⌒笳欤” “怎么了元扔?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)旋膳。 經(jīng)常有香客問我澎语,道長(zhǎng),這世上最難降的妖魔是什么验懊? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任擅羞,我火速辦了婚禮,結(jié)果婚禮上义图,老公的妹妹穿的比我還像新娘减俏。我一直安慰自己,他們只是感情好碱工,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布娃承。 她就那樣靜靜地躺著,像睡著了一般怕篷。 火紅的嫁衣襯著肌膚如雪历筝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天匙头,我揣著相機(jī)與錄音漫谷,去河邊找鬼。 笑死蹂析,一個(gè)胖子當(dāng)著我的面吹牛舔示,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播电抚,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惕稻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蝙叛?” 一聲冷哼從身側(cè)響起俺祠,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蜘渣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淌铐,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蔫缸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腿准。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拾碌,死狀恐怖吐葱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情校翔,我是刑警寧澤弟跑,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站防症,受9級(jí)特大地震影響孟辑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔫敲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一扑浸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燕偶,春花似錦、人聲如沸础嫡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榴鼎。三九已至伯诬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巫财,已是汗流浹背盗似。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留平项,地道東北人赫舒。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像闽瓢,于是被迫代替她去往敵國(guó)和親接癌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354