简介
在很久很久以前,咱们是怎么创立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的博客
欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
发表回复