简介

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

明天咱们来体验一下Spring Boot 2.3.3 带来的疾速创立docker image的性能。

传统做法和它的毛病

当初咱们创立一个非常简单的Spring Boot程序:

@SpringBootApplication@RestControllerpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }    @GetMapping("/getInfo")    public String getInfo() {        return "www.flydean.com";    }}

默认状况下,咱们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-alpineEXPOSE 8080ARG JAR_FILE=target/springboot-with-docker-0.0.1-SNAPSHOT.jarADD ${JAR_FILE} app.jarENTRYPOINT ["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.jarAvailable 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 builderWORKDIR applicationARG JAR_FILE=target/*.jarCOPY ${JAR_FILE} application.jarRUN java -Djarmode=layertools -jar application.jar extractFROM adoptopenjdk:11-jre-hotspotWORKDIR applicationCOPY --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>

本文的例子:springboot-with-docker

本文作者:flydean程序那些事

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

本文起源:flydean的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!