写点什么

使用 Spring Boot 创建 docker image

发布于: 2020 年 10 月 16 日
使用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@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.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 builderWORKDIR applicationARG JAR_FILE=target/*.jarCOPY ${JAR_FILE} application.jarRUN java -Djarmode=layertools -jar application.jar extract
FROM 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 的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


发布于: 2020 年 10 月 16 日阅读数: 59
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
使用Spring Boot创建docker image