使用Spring Boot创建docker image

简介

在很久很久以前,我们是怎么创建Spring Boot的docker image呢?最最通用的办法就是将Spring boot的应用程序打包成一个fat jar,然后写一个docker file,将这个fat jar制作成为一个docker image然后运行。

今天我们来体验一下Spring Boot 2.3.3 带来的快速创建docker image的功能。

传统做法和它的缺点

现在我们创建一个非常简单的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";

    }

}


福利 福利 福利 免费领取Java架构技能地图 注意了是免费送

​、​​​

点击我获取

默认情况下,我们build出来的是一个fat jar:springboot-with-docker-0.0.1-SNAPSHOT.jar

我们解压看一下它的内容:

Spring boot的fat jar分为三个部分,第一部分就是BOOT-INF, 里面的class目录放的是我们自己编写的class文件。而lib目录存放的是项目依赖的其他jar包。

第二部分是META-INF,里面定义了jar包的属性信息。

第三部分是Spring Boot的类加载器,fat jar包的启动是通过Spring Boot的jarLauncher来创建LaunchedURLClassLoader,通过它来加载lib下面的jar包,最后以一个新线程启动应用的Main函数。

这里不多讲Spring Boot的启动。

我们看一下,如果想要用这个fat jar来创建docker image应该怎么写:

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

复制代码

这样写有两个问题。

第一个问题:我们是用的far jar,在使用far jar的过程中会有一定的性能问题,肯定要比解压过后的性能要低,尤其是在容器环境中运行的情况下,可能会更加突出。

第二个问题:我们知道docker的image是按layer来构建的,按layer构建的好处就是可以减少image构建的时间和重用之前的layer。

但是如果使用的是fat jar包,即使我们只修改了我们自己的代码,也会导致整个fat jar重新更新,从而影响docker image的构建速度。

使用Buildpacks

传统的办法除了有上面的两个问题,还有一个就是需要自己构建docker file,有没有一键构建docker image的方法呢?

答案是肯定的。

Spring Boot在2.3.0之后,引入了Cloud Native 的buildpacks,通过这个工具,我们可以非常非常方便的创建docker image。

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

我们可以直接运行:

mvn  spring-boot:build-image

复制代码

运行之,很不幸的是,你可能会遇到下面的错误:

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

复制代码

这是因为我们无法从gcr.io中拉取镜像!

没关系,如果你会正确的上网方式的话,那么我估计你已经找到了一个代理。

将你的代理配置到Docker的代理项里面,我使用的是Docker desktop,下面是我的配置:

重新运行 mvn spring-boot:build-image

等待执行结果:

[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,还是想使用传统的Dockerfile。 没关系,SpringBoot为我们提供了独特的分层jar包系统。

怎么开启呢? 我们需要在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包的内容:

看起来和之前的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/"

复制代码

index文件主要分为4个部分:

dependencies - 非SNAPSHOT的依赖jar包

snapshot-dependencies - SNAPSHOT的依赖jar包

spring-boot-loader - Spring boot的class loader文件

application - 应用程序的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

复制代码

使用list命令,我们可列出jar包中的layer信息。使用extract我们可以解压出不同的layer。

我们执行下extract命令,看下结果:

可以看到,我们根据layers.idx解压出了不同的文件夹。

我们看一下使用layer的dockerFile应该怎么写:

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

复制代码

这样我们的一个分层的DockerImage就创建完成了。

自定义Layer

如果我们需要自定义Layer该怎么做呢?

我们可以创建一个独立的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>

复制代码

怎么使用这个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>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342