Create docker image using Spring Boot

Introduction

How did we create Spring Boot's docker image a long time ago? The most common way is to package the Spring boot application into a fat jar, then write a docker file, make the fat jar into a docker image and run it.

Today, let's experience the function of quickly creating docker image brought by Spring Boot 2.3.3.

Traditional practice and its disadvantages

Now we create a very simple Spring Boot program:

@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";
    }
}

By default, we build a fat jar: springboot-with-docker-0.0.1-SNAPSHOT.jar

Let's unzip and take a look at its contents:

The fat jar of Spring boot is divided into three parts. The first part is BOOT-INF. The class directory inside contains the class files we wrote. The lib directory stores other jar packages that the project depends on.

The second part is META-INF, which defines the attribute information of the jar package.

The third part is the Spring Boot class loader. The fat jar package is started by creating the LaunchedURLClassLoader through Spring Boot’s jarLauncher, through which the jar package under lib is loaded, and finally the Main function of the application is started with a new thread.

Not much to talk about the startup of Spring Boot here.

Let's take a look, if you want to use this fat jar to create a docker image, how to write:

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"]

There are two problems with this writing.

The first question: We are using far jar. There will be certain performance problems in the process of using far jar. It will definitely be lower than the performance after decompression, especially if it is running in a container environment. prominent.

The second question: We know that docker's image is built by layer. The advantage of building by layer is that it can reduce the time of image construction and reuse the previous layer.

But if you use the fat jar package, even if we only modify our own code, it will cause the entire fat jar to be re-updated, which will affect the build speed of the docker image.

Use Buildpacks

In addition to the above two problems, the traditional method is to build the docker file yourself. Is there a one-click method to build a docker image?

The answer is yes.

Spring Boot introduced Cloud Native buildpacks after 2.3.0. With this tool, we can create docker images very conveniently.

In Maven and Gradle, Spring Boot introduced a new phase: spring-boot:build-image

We can run directly:

 mvn  spring-boot:build-image

Run it, unfortunately, you may encounter the following error:

[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]

This is because we cannot pull the mirror from gcr.io!

It doesn't matter, if you know how to get online, then I guess you have found an agent.

Configure your proxy into Docker's proxy item. I am using Docker desktop. Below is my configuration:

Re-run mvn spring-boot:build-image

Wait for the execution result:

[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%

As you can see, we do need to pull the image from gcr.io.

Layered Jars

If you don't want to use Cloud Native Buildpacks, you still want to use a traditional Dockerfile. It doesn't matter, SpringBoot provides us with a unique layered jar packaging system.

How to turn it on? We need to add the following configuration to the POM file:

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <layers>
                        <enabled>true</enabled>
                    </layers>
                </configuration>
            </plugin>
        </plugins>
    </build>

Package again and look at the contents of the jar package:

It looks no different from the previous jar package, except that there is an additional layer.idx index file:

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

The index file is mainly divided into 4 parts:

  • dependencies-non-SNAPSHOT dependent jar packages
  • snapshot-dependencies-SNAPSHOT dependency jar package
  • spring-boot-loader - Spring boot的class loader文件
  • application-application class and resources files

Note that the index file here is in order, which is consistent with the layer order we will add to the docker image.

The one with the least change will be added to the layer first, and the one with the biggest change will be placed in the last layer.

We can use layertools jarmode to verify or decompress the generated 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

Using the list command, we can list the layer information in the jar package. Using extract, we can decompress different layers.

We execute the extract command and see the result:

As you can see, we extracted different folders according to layers.idx.

Let's take a look at how to write dockerFile using layer:

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"]

In this way, a layered DockerImage of ours is created.

Custom Layer

What if we need to customize the Layer?

We can create a separate layers.xml file:

<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>

How to use this layer.xml?

Just add it to the 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>

Example in this article: springboot-with-docker

Author: flydean program those things

Link to this article: http://www.flydean.com/springboot-docker-image/

Source of this article: flydean's blog

Welcome to pay attention to my official account: the most popular interpretation of "programs", the most profound dry goods, the most concise tutorials, and many tips you don't know are waiting for you to discover!

Guess you like

Origin blog.csdn.net/superfjj/article/details/109091776