简介
在很久很久以前,咱们是怎么创立 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";}
}
默认状况下,咱们 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>
本文的例子:springboot-with-docker
本文作者:flydean 程序那些事
本文链接:http://www.flydean.com/springboot-docker-image/
本文起源:flydean 的博客
欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!