October 13, 2021

Spring Native: Optimize Docker Image using Cloud Buildpacks

Hi, nếu bạn đọc bài này chắc hẳn bạn đã biết về Docker. Thông thường khi build docker image chúng ta thường sử dụng Dockerfile với các cmd như sau:

FROM adoptopenjdk/openjdk11-openj9:alpine-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
COPY templates /templates
ENTRYPOINT ["java","-jar","app.jar"]

Mọi việc điều ổn từ việc build cho đến run container. Nhưng vẫn cần thời gian để start container. Ví dụ đối với project hello-world sử dụng spring-boot thì thời gian start cũng phải 7~10s.

Để cải thiện vấn đề trên, mình sẽ sử dụng Spring Native, đây là library được support từ Spring Boot 2.5.2
Một số điểm khác biệt khi sử dụng Spring Native để build so với cách build thông thường như sau:

A static analysis of your application from the main entry point is performed at build time.
The unused parts are removed at build time.
Configuration is required for reflection, resources, and dynamic proxies.
Classpath is fixed at build time.
No class lazy loading: everything shipped in the executables will be loaded in memory on startup.
Some code will run at build time.
There are some limitations around some aspects of Java applications that are not fully supported.

Việc sử dụng native image để build sẽ giúp cải thiện performance, tuy nhiên sẽ cần nhiều resource hơn để build. Vì vậy cần setup cho Docker sử dụng nhiều resource như sau:

Tiếp theo file pom.xml, mình có sử dụng các library sau: web, thymeleaf, spring-native

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.5.2</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.example</groupId>
   <artifactId>demo</artifactId>
   <version>0.1</version>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>11</java.version>
      <repackage.classifier/>
      <spring-native.version>0.10.1</spring-native.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.experimental</groupId>
         <artifactId>spring-native</artifactId>
         <version>${spring-native.version}</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <classifier>${repackage.classifier}</classifier>
               <image>
                  <builder>paketobuildpacks/builder:tiny</builder>
                  <name>gcr.io/project-demo/spring-boot-buildpacks:dev</name>
                  <env>
                     <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                  </env>
               </image>
            </configuration>
         </plugin>
         <plugin>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-aot-maven-plugin</artifactId>
            <version>${spring-native.version}</version>
            <executions>
               <execution>
                  <id>test-generate</id>
                  <goals>
                     <goal>test-generate</goal>
                  </goals>
               </execution>
               <execution>
                  <id>generate</id>
                  <goals>
                     <goal>generate</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
   <repositories>
      <repository>
         <id>spring-releases</id>
         <name>Spring Releases</name>
         <url>https://repo.spring.io/release</url>
         <snapshots>
            <enabled>false</enabled>
         </snapshots>
      </repository>
   </repositories>
   <pluginRepositories>
      <pluginRepository>
         <id>spring-releases</id>
         <name>Spring Releases</name>
         <url>https://repo.spring.io/release</url>
         <snapshots>
            <enabled>false</enabled>
         </snapshots>
      </pluginRepository>
   </pluginRepositories>

   <profiles>
      <profile>
         <id>native</id>
         <properties>
            <repackage.classifier>exec</repackage.classifier>
            <native-buildtools.version>0.9.1</native-buildtools.version>
         </properties>
         <dependencies>
            <dependency>
               <groupId>org.graalvm.buildtools</groupId>
               <artifactId>junit-platform-native</artifactId>
               <version>${native-buildtools.version}</version>
               <scope>test</scope>
            </dependency>
         </dependencies>
         <build>
            <plugins>
               <plugin>
                  <groupId>org.graalvm.buildtools</groupId>
                  <artifactId>native-maven-plugin</artifactId>
                  <version>${native-buildtools.version}</version>
                  <executions>
                     <execution>
                        <id>test-native</id>
                        <phase>test</phase>
                        <goals>
                           <goal>test</goal>
                        </goals>
                     </execution>
                     <execution>
                        <id>build-native</id>
                        <phase>package</phase>
                        <goals>
                           <goal>build</goal>
                        </goals>
                     </execution>
                  </executions>
               </plugin>
            </plugins>
         </build>
      </profile>
   </profiles>

</project>

Tiếp theo chỉ cần sử dụng maven để build với cmd như sau:

Việc build cũng cần rất nhiều thời gian, nên bạn có thể thưởng thức cafe trong quá trình đợi 🙂

Kết quả sau khi build thành công

Run docker image xem kết quả thế nào.

docker run --rm -p 8080:8080 gcr.io/project-demo/spring-boot-buildpacks:dev

Kết quả start container rất ấn tượng, chỉ 0.155s

Hẹn gặp lại các bạn ở các bài blog tiếp theo. Hãy liên hệ ngay với chúng tôi – Cloud Ace để được tư vấn về Google Workspace, GCP.

Leave a Reply

Your email address will not be published. Required fields are marked *