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