java应用容器构建优化

Posted by 爱折腾的工程师 on Tuesday, June 7, 2022

1. 背景

jenkins充当CI作用,构建流程:

  1. 拉取gitlab源代码仓库(配置了gitlab admin token凭证)
  2. 在宿主机上使用命令mvn -B -f /xxx/pom.xml clean package -Dmaven.test.skip=true -U构建jar包, 宿主机上配置了maven环境(包含/maven工作目录/conf/settings.xml、且在settings.xml中自定义了缓存目录)
  3. 触发构建脚本,加载Dockerfile,把上面步骤的产物jar包添加到镜像中,并推送至远程仓库

maven settings.xml、pom.xml加载优先级顺序: java项目通过maven自动下载依赖时,会涉级读取三个配置文件,分别是源代码项目下的pom.xml文件、HOME目录下的.m2/settings.xml、maven全局配置settings.xml, 优先级顺序:pom.xml > /$HOME/.m2/settings.xml > /maven工作目录/conf/settings.xml

jenkins构建结果输出: 有配置maven本地缓存目录,下载依赖过程还是很快的

问题:虽然有maven本地缓存加速依赖下载,代码没变化的前提下,每次构建还是耗费了1分9秒

2. 分析

即使有maven本地缓存加速依赖下载,但是每次应用构建新版本镜像时,比如Maven构建产出物是Final.jar, 当依赖的moduleA、moduleB模块中任意一处发生变化时,都会产出一个新的Final.jar。 构建镜像时都要将整个Final.jar重新写入到镜像层,并将整个镜像层推送到镜像仓库中。

是否有java应用程序分层缓存机制的工具?更改代码时,仅重建更改,而不重建整个应用程序; 从而减少镜像存储空间,提升构建镜像、推送镜像的性能。

存在这种工具,Google Jib是谷歌公司推出的开源Java镜像构建工具,它具备以上功能

3. Jib

3.1 简介

Jib可以为Java应用程序构建优化的Docker和OCI镜像,无需Docker守护程序, 也无需深入掌握Docker使用。它可作为Maven和Gradle的插件以及Java库。

3.2 特点

  • 快速 - 快速生效变更。Jib将应用程序分成多个层,将依赖库与类分离。现在不必等待Docker重新构建好整个Java应用程序,只会重建更改的层
  • 幂等性 - 使用相同内容重建容器镜像始终会生成相同的镜像,永远不会再次触发不必要的更新。
  • 无守护进程 - 减少命令行依赖。可直接从Maven或Gradle中构建Docker镜像,然后推送到远程镜像仓库,不需要编写Dockerfile和调用docker push/build命令。

3.3 docker构建流程 vs jib构建流程

docker构建流程

+------------------------+   mvn编译  +---------------------+      +--------------------------------------------------------+
|                        |            |                     |      |                                                        |
|        项目源代码       +------------>      Jar包           |      |                    Docker守护进程                       |
|                        |            |                     |      |                                                        |
|                        |            |                     |      |                                                        |
+------------------------+            +-----------+---------+      |   +-------------------+  编译    +------------------+  |推送   +----------------+
                                                  |                |   |                   |          |                  |  |       |                |
                                                  +-------------------->   编译上下文       +---------->   容器镜像         +---------->   镜像仓库      |
                                                  |                |   |                   |          |                  |  |       |                |
                                      +-----------+---------+      |   +-------------------+          +------------------+  |       +----------------+
                                      |                     |      |                                                        |
                                      |      Dockerfile     |      |                                                        |
                                      |                     |      |                                                        |
                                      |                     |      |                                                        |
                                      +---------------------+      +--------------------------------------------------------+

jib构建流程:

+------------------------+          +---------------------+
|                        |          |                     |
|    项目源代码            |          |                     |
|                        +---------->  镜像仓库            |
|                        |          |                     |
+------------------------+          +---------------------+

对比发现,jib构建流程简化了很多

3.3 具体实践

jib构建工具主要包含了四个强大的功能,如果编译构建是在没有docker环境的情况下构建,使用build命令和dockerBuild命令并不能制作出镜像,只能使用buildTar命令制作出一个包含镜像的tar文件。

  • build提供了创建镜像并推送到远程仓库功能。
  • buildTar提供创建一个包含镜像的tar文件功能。
  • dockerBuild提供创建docker镜像到本地功能。
  • exportDockerContext提供创建dockerfile功能。

3.3.1 引入jib插件

编辑pom.xml,增加jib插件内容

<plugin>
     <groupId>com.google.cloud.tools</groupId>
     <artifactId>jib-maven-plugin</artifactId>
     <version>3.2.1</version>
     <configuration>
         <from>
             <image>reg.xxx.com/base/centos:7.9.2009</image>
         </from>
         <to>
             <!--镜像名称和tag,使用了mvn内置变量${project.version},表示当前工程的version-->
             <image>reg.xxx.com/sme/sme-account-service:${project.version}</image>
         </to>
         <!--容器相关的属性-->
         <container>
             <!--创建时间戳参数-->
             <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
             <!--jvm内存参数-->
             <jvmFlags>
                 <jvmFlag>-Djava.security.egd=file:/dev/./urandom</jvmFlag>
                 <jvmFlag>-Xms4g</jvmFlag>
                 <jvmFlag>-Xmx4g</jvmFlag>
             </jvmFlags>
             <!--要暴露的端口-->
             <ports>
                 <port>80</port>
             </ports>
           <mainClass>com.xxx.sme.account.service.AccountServiceApplication</mainClass>
         </container>
     </configuration>
</plugin>
  • From标签:设置基础镜像,相当于dockerfile中的FROM关键字
  • To标签:设置构建出来的镜像的镜像名称和tag
  • Container标签:设置容器的相关属性,如jvm内存参数、容器端口等。
  • mainClass标签:设置项目启动的主程序,如Spring Boot的Application类

更多标签使用查阅:https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin

3.3.2 执行jib构建

# mvn -f account-service/pom.xml clean package jib:build -Dmaven.test.skip=true -U -e -X

[DEBUG] TIMED   Building dependencies layer : 10201.0 ms
[DEBUG] TIMING  Pushing BLOB digest: sha256:42258b82b21362f321faa42aeed4e2c39b2709f57356c65996007123
aa50ff49, size: 119520216
[DEBUG] TIMING  Building container configuration
[INFO] 
[INFO] Container entrypoint set to [java, -Djava.security.egd=file:/dev/./urandom, -Xms4g, -Xmx4g, -
cp, /app/resources:/app/classes:/app/libs/*, com.xxx.sme.account.service.AccountServiceApplication]
[DEBUG] TIMED   Building container configuration : 1.0 ms
[DEBUG] TIMING  Pushing container configuration
[DEBUG] TIMING  Building a manifest list or a single manifest
[DEBUG] Building a single manifest
[DEBUG] Skipping push; BLOB already exists on target registry : digest: sha256:42258b82b21362f321faa
42aeed4e2c39b2709f57356c65996007123aa50ff49, size: 119520216
[DEBUG] TIMED   Pushing BLOB digest: sha256:42258b82b21362f321faa42aeed4e2c39b2709f57356c65996007123
aa50ff49, size: 119520216 : 24.0 ms
[DEBUG] TIMED   Building a manifest list or a single manifest : 58.0 ms
[DEBUG] TIMING  Pushing BLOB digest: sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6cdf
87946c2f, size: 5658
[DEBUG] TIMING  Checking existence of manifest
[DEBUG] Checking existence of manifest for sha256:bc1125eb42a203c58d579030d98b1c0d0ca37444292ab66667
06b04ea97236a0...
[DEBUG] Skipping manifest existence check; system property set to false
[DEBUG] TIMED   Checking existence of manifest : 0.0 ms
[DEBUG] TIMING  pushBlob
[DEBUG]         TIMING  pushBlob POST sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6cd
f87946c2f
[DEBUG]         TIMED   pushBlob PATCH sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6c
df87946c2f : 62.0 ms
[DEBUG]         TIMED   pushBlob PUT sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6cdf
87946c2f : 48.0 ms
[DEBUG]         TIMED   pushBlob POST sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6cd
f87946c2f : 389.0 ms
[DEBUG] TIMED   pushBlob : 500.0 ms
[DEBUG] TIMED   Pushing BLOB digest: sha256:14b85639ea96206c6e96d1328723dc5a0697d152d6d5b0c42c2d6cdf
87946c2f, size: 5658 : 521.0 ms
[DEBUG] TIMED   Pushing container configuration : 579.0 ms
[DEBUG] TIMING  Preparing manifest pushers
[DEBUG] TIMED   Preparing manifest pushers : 1.0 ms
[DEBUG] TIMING  Pushing manifest
[DEBUG] Pushing manifest for 2.1.0-SNAPSHOT...
[DEBUG] TIMED   Pushing manifest : 349.0 ms
[DEBUG] TIMING  Preparing manifest list pushers
[DEBUG] TIMED   Preparing manifest list pushers : 0.0 ms
[DEBUG] TIMED   Building and pushing image : 11278.0 ms
[INFO] 
[INFO] Built and pushed image as reg.xxx.com/sme/sme-account-service:2.1.0-SNAPSHOT
[INFO] Executing tasks:
[INFO] [============================  ] 91.7% complete
[INFO] > launching layer pushers
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 34.061 s
[INFO] Finished at: 2022-06-09T17:11:35+08:00
[INFO] Final Memory: 116M/1423M
[INFO] ------------------------------------------------------------------------

34秒,单个镜像构建推送耗时相比于上面的1分9秒提升了35秒,提升显著(有jib缓存的前提下)

可使用dive小工具,查看镜像分层信息

# wget -c  https://github.com/wagoodman/dive/releases/download/v0.10.0/dive_0.10.0_linux_amd64.rpm
# rpm -ivh dive_0.10.0_linux_amd64.rpm

问题

mvn jib构建镜像的时候找不到main class,原因是项目里多个地方定义了main class

# mvn -f account-service/pom.xml clean package jib:dockerBuild -Dmaven.test.skip=true -U -e -X
[ERROR] Failed to execute goal com.google.cloud.tools:jib-maven-plugin:3.2.1:dockerBuild (default-cli) 
on project account-service: Multiple valid main classes were found: com.xxx.sme.account.service.AccountServiceApplication, 
com.xxx.sme.account.service.jobhandler.UrgeCustRepayMentTaskJobHandler, 
com.xxx.sme.account.service.jobhandler.ZgcReconcileAtDayEndHandler, 
com.xxx.sme.account.service.jobhandler.loan.AvictcPayScheduleHandler, 
com.xxx.sme.account.service.jobhandler.loan.FmDownFileJobHandler, com.xxx.sme.account.service.jobhandler.loan.IcbcBatchFileImportJobHandler, 
com.xxx.sme.account.service.jobhandler.loan.IcbcDownFileJobHandler, com.xxx.sme.account.service.jobhandler.loan.SnDownFileJobHandler, 
com.xxx.sme.account.service.service.AccountLoanDealForFbService, com.xxx.sme.account.service.service.AccountLoanDealForSnService, 
com.xxx.sme.account.service.service.bank.CmbService, com.xxx.sme.account.service.util.DateTools, 
perhaps you should add a `mainClass` configuration to jib-maven-plugin -> [Help 1]

解决方法: 编辑pom.xml,增加mainClass配置

# 精确定义mainClass
<container>
    <!--jvm内存参数-->
    <jvmFlags>
        <jvmFlag>-Xms4g</jvmFlag>
        <jvmFlag>-Xmx4g</jvmFlag>
    </jvmFlags>
    <!--要暴露的端口-->
    <ports>
        <port>80</port>
    </ports>
  <mainClass>com.xxx.sme.account.service.AccountServiceApplication</mainClass>
</container>

参考链接

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付