Featured image of post Spring Boot3 本机镜像构建

Spring Boot3 本机镜像构建

简单玩玩 Java 构建本机镜像,像 Go 一样直接启动

介绍

我们构建 Java 项目的构件产出通常是 jar 包,运行它需要使用 JRE 环境。

jar 包的优势很明显,跨平台,放哪个平台上都可以跑,很方便。但是对于内存的消耗会大一些。而 Graalvm 可以把 Jar 包编译为本机可执行文件,不再需要 JRE 运行环境,启动很快,内存占用也会少一点。

年初时候我尝试把生产项目改成本机模式,发现复杂度太高了,很难在编译时候让编译器识别到全部需要的依赖。Java的反射和接口一类用的太多了。遂放弃了。

最近用到了一个简单的 Spring boot项目 simple-boot-douban-api 发现可以玩一玩。

升级 Spring Boot 3

在检出项目后,修改项目 pom 配置,把 spring-boot-starter-parent 升级到了 3.3.2

1
2
3
4
5
6
   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

修改 JDK 使用 17 版本, jsoup也跟着升级一下。

1
2
3
4
<properties>
        <java.version>17</java.version>
        <jsoup.version>1.18.1</jsoup.version>
</properties>

Spring boot 2 ==> 3 的升级,Java EE 变成了 Jakarta EE 包路径基本都发生了改变 比如 javax.servlet ==> jakarta.servlet

使用 IDEA 可以在使用重构进行迁移

79ax4t

搞完之后,mvn clean install 后,测试启动项目,ok

增加本机构建

还是在 pom 文件中,增加 native 相关配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    <profiles>
        <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.buildtools</groupId>
                        <artifactId>native-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>build-native</id>
                                <goals>
                                    <goal>compile-no-fork</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

本地要安装 graalvm jdk17 环境,并设置为JAVA_HOME。之后便可以尝试构建了。 命令 mvn -Pnative -DskipTests=true native:compile

在经历一段时间后,会在target目录中生成 simple-boot-douban-api 文件, ./simple-boot-douban-api 直接可以启动。

尝试调用,出现报错 找不到 caffeine cache相关的类。网上搜索一下,有给出解决方案的,使用 spring 提供的接口,把依赖的类型注册进去。测试了一下挺麻烦的,也没解决问题。spring core本身提供的内建的缓存,直接用也行。遂移除依赖 spring-boot-starter-cachecaffeine ,重新构建,功能OK。

  1. 对比 simple-boot-douban-api.jar 20MB的体积,simple-boot-douban-api 大小涨到 70MB,使用 upx 处理后,大约 30MB。体积还行。
  2. 启动速度上 jar包是 1.004,本机启动 0.07,快了不少
  3. 内存占用大约少了1/3

设备 Macbook pro M3 max

Docker镜像构建

目前我需要构建 x64 和 arm64 的镜像,使用docker的buildx 拉起两个环境,分别构建。

示例 Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Start with base image  
FROM vegardit/graalvm-maven:latest-java17 AS build  
WORKDIR /app  
COPY ./ /app  
RUN --mount=type=cache,id=maven,target=/mvn/store mvn -Dmaven.repo.local=/mvn/store -Pnative -DskipTests=true clean  native:compile  
RUN upx /app/target/simple-boot-douban-api  
# Start with base image  
FROM debian:bookworm-slim  
  
WORKDIR /app  
# Add Maintainer Info  
LABEL maintainer="jianyun8023"  
  
# Add a temporary volume  
VOLUME /tmp  
  
# Expose Port 8085  
EXPOSE 8085  
  
ENV DOUBAN_CONCURRENCY_SIZE="5"  
ENV DOUBAN_BOOK_CACHE_SIZE="1000"  
ENV DOUBAN_BOOK_CACHE_EXPIRE="24h"  
ENV DOUBAN_PROXY_IMAGE_URL="true"  
  
# Add Application Jar File to the Container  
COPY --from=build /app/target/simple-boot-douban-api simple-boot-douban-api  
  
# Run the JAR file  
ENTRYPOINT ["bash","-c","/app/simple-boot-douban-api"]

Github Action 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
name: Build simple-boot-douban-api Images  
on:  
  workflow_dispatch:  
  release:  
    types: [published]  
env:  
  IMAGE_NAME: simple-boot-douban-api  
  VERSION: "0.0.1"  
  
jobs:  
  docker:  
    runs-on: ubuntu-latest  
    steps:  
      - name: Set VERSION for release  
        if: github.event_name == 'release'  
        run: echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV  
  
      - name: Checkout  
        uses: actions/checkout@v3  
      - name: Set up Java  
        uses: actions/setup-java@v3  
        with:  
          distribution: 'adopt'  
          java-version: '17'  
  
      - name: Create Maven repository directory  
        run: |  
          mkdir -p ~/.m2/repository  
          chmod 755 ~/.m2/repository  
  
      - name: Cache Maven packages  
        uses: actions/cache@v4  
        id: cache  
        with:  
          path: ~/.m2/repository  
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}  
          restore-keys: |  
            ${{ runner.os }}-maven-  
  
      - name: inject Maven packages cache into docker  
        uses: reproducible-containers/buildkit-cache-dance@v3.1.0  
        with:  
          cache-map: |  
            {  
              "~/.m2/repository": "/mvn/store"  
            }  
          skip-extraction: ${{ steps.cache.outputs.cache-hit }}  
  
      - name: Set up QEMU  
        uses: docker/setup-qemu-action@v2  
      - name: Set up Docker Buildx  
        uses: docker/setup-buildx-action@v2  
      - name: Log in to registry  
        uses: docker/login-action@v2  
        with:  
          registry: ghcr.io  
          username: ${{ github.actor }}  
          password: ${{ secrets.GITHUB_TOKEN }}  
  
      - name: Build and push  
        uses: docker/build-push-action@v4  
        with:  
          context: ./  
          platforms: linux/amd64,linux/arm64  
          cache-from: type=gha  
          cache-to: type=gha,mode=max  
          build-args: |  
            VERSION=${{ env.VERSION }}  
          push: true  
          tags: |  
            ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}  
            ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest

镜像信息 hZECuA

个人感受

使用 GraalVM 可以构建出 Java 的本机镜像,可以降低资源的使用量,提高启动速度。但是它带来的繁琐度和维护成本增加,个人感觉没太大吸引力。使用 Go 做这件事它不香吗?

对企业来说,维护难度低比少点内存,少占一点资源更有吸引力。至于启动速度,我想一分钟以内的差距应该问题不大。

对我而言,非常简单的项目可能会玩一下,如果我写这个项目,我会换成 Go 实现。复杂项目使用它太麻烦了。当然,构建本机镜像可以做到像Go那像简单,我会很乐意使用的。

参考资料

  1. simple-boot-douban-api: https://github.com/fugary/simple-boot-douban-api
  2. Native Images with Spring Boot and GraalVM: https://www.baeldung.com/spring-native-intro
使用 Hugo 构建
主题 StackJimmy 设计