使用jib-maven-plugin分层构建Docker镜像——避免直接使用FatJar
1 使用FatJar构建Docker镜像的弊端在项目工程中,我们经常使用Dockerfile把项目的fatjar打入Docker 镜像的方式来构建。举个简单的例子,你可能在你的项目工程根目录里加一个Dockerfile,内容类似于这样:FROM java:8-alpineADD ./target/xxx.jar /app/CMD java -jar /app/xxx.jar虽然Dock...
1 使用FatJar构建Docker镜像的弊端
在项目工程中,我们经常使用Dockerfile把项目的fatjar打入Docker 镜像的方式来构建。举个简单的例子,你可能在你的项目工程根目录里加一个Dockerfile,内容类似于这样:
FROM java:8-alpine
ADD ./target/xxx.jar /app/
CMD java -jar /app/xxx.jar
虽然Docker镜像是通过分层存储的,但是Spring Boot FatJar的大小动不动就过100MB,这样每次都把FatJar添加入镜像,由于基础镜像都会重用,所以你项目的每个版本的镜像的大小几乎就是FatJar的大小。
所以使用FatJar构建镜像弊端:
- 如果你保留了较多的历史构建版本,则会占用更大的存储
- 每次镜像 push 和 pull 的时候,也会占用更大的网络带宽
- 基于以上两点,构建、push、pull的效率更定也相对较低
2 使用jib-maven-plugin避免直接把FarJar丢入镜像
jib-maven-plugin是Google开源的maven插件,作为开发者,你可以在本地不安装Docker的情况下构建并push镜像。按照官方介绍:
- Fast - Deploy your changes fast. Jib separates your application into multiple layers, splitting dependencies from classes. Now you don’t have to wait for Docker to rebuild your entire Java application - just deploy the layers that changed.
- Reproducible - Rebuilding your container image with the same contents always generates the same image. Never trigger an unnecessary update again.
- Daemonless - Reduce your CLI dependencies. Build your Docker image from within Maven or Gradle and push to any registry of your choice. No more writing Dockerfiles and calling docker build/push.
2.1 quick-start
在项目的pom.xml中加入以下配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.6.1</version>
<configuration>
<!--基础镜像,来自dockerhub,如果是私服,需要加上鉴权信息,和to下的auth节点相同-->
<from>
<image>thinkerwu/xxx:v1</image>
</from>
<!--构建后的镜像名称以及私服地址、鉴权信息-->
<to>
<image>registry.xxx.com/xxx/${project.name}:latest</image>
<auth>
<username>xxx</username>
<password>xxx</password>
</auth>
</to>
<!--允许非https-->
<allowInsecureRegistries>true</allowInsecureRegistries>
</configuration>
<executions>
<execution>
<id>build-and-push-docker-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
先后执行mvn compile
命令和mvn jib:build
命令,插件就会自己的构建镜像并且push到私服上去。
2.2 对jib-maven-plugin构建的镜像说明
举个例子,我下面的isc-struct-data-linux项目构建了很多个版本,但是在开发阶段,我每次使用:latest版本号push和pull的时候都只是对我最新更改的进行的操作。
mvn jib:build
操作
E:\WorkCode\isc-struct-data\isc-struct-data-linux>mvn jib:build
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< com.xxx.isc:isc-struct-data-linux >---------------
[INFO] Building isc-struct-data-linux 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- jib-maven-plugin:1.6.1:build (default-cli) @ isc-struct-data-linux ---
[INFO]
[INFO] Containerizing application to registry.xxx.com/isc/isc-struct-data-linux...
[INFO] The base image requires auth. Trying again for thinkerwu/hcnetsdk-jre8:v1...
[INFO]
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, com.xxx.isc.struct.data.linux.StructDataLinuxApplication]
[INFO]
[INFO] Built and pushed image as registry.xxx.com/isc/isc-struct-data-linux
[INFO] Executing tasks:
[INFO] [=========================== ] 90.0% complete
[INFO] > launching layer pushers
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18.303 s
[INFO] Finished at: 2019-11-03T13:28:24+08:00
[INFO] ------------------------------------------------------------------------
命令docker pull registry.xxx.com/isc/isc-struct-data-linux
结果如下:
[root@localhost ~]# docker pull registry.xxx.com/isc/isc-struct-data-linux
Using default tag: latest
latest: Pulling from isc/isc-struct-data-linux
e110a4a17941: Already exists
26b4e58aedd1: Already exists
82bc5b7de68a: Already exists
db13ed7f6533: Already exists
ce54ff63d84d: Already exists
12686f067d48: Already exists
cb10ab70f6f7: Already exists
5ba6e9db8d44: Already exists
14d466d88edc: Pull complete
Digest: sha256:32e8c596c06f64bd7ea55528fedffcb8dce4512b718e9ceabb8283b86413a56a
Status: Downloaded newer image for registry.xxx.com/isc/isc-struct-data-linux:latest
registry.xxx.com/isc/isc-struct-data-linux:latest
接下来,把这个镜像运行起来,通过docker exec -it
进入容器看一下与直接把FatJar丢进镜像的区别是什么:
[root@localhost ~]# docker exec -it a9576 /bin/bash
bash-4.3# ls
app bin dev etc extlib home lib lib64 linuxrc media mnt opt proc root run sbin srv sys tmp usr var
bash-4.3# cd app
bash-4.3# ls
classes libs resources
bash-4.3# apk add tree
(1/1) Installing tree (1.7.0-r0)
Executing busybox-1.24.2-r14.trigger
OK: 22 MiB in 23 packages
bash-4.3# tree .
.
├── classes
│ └── com
│ └── xxx
│ └── isc
│ └── struct
│ └── data
│ └── linux
│ ├── ServerRunner.class
│ ├── StructDataLinuxApplication.class
│ ├── config
│ │ ├── ExceptionHandleConfig.class
│ │ ├── InitConfiguration.class
│ │ ├── OkHttp3Config$1.class
│ │ ├── OkHttp3Config.class
│ │ └── RedisConfig.class
│ ├── constant
│ │ ├── ConfigConst$IsApiCommand.class
│ │ ├── ConfigConst$RedisKey.class
│ │ ├── ConfigConst$Storage.class
│ │ ├── ConfigConst$Topic.class
│ │ ├── ConfigConst$XmlTemplate.class
│ │ ├── ConfigConst.class
├── libs
│ ├── HdrHistogram-2.1.9.jar
│ ├── LatencyUtils-2.0.3.jar
│ ├── animal-sniffer-annotations-1.17.jar
│ ├── annotations-3.0.1.jar
│ ├── common-api-1.0.0.jar
│ ├── commons-codec-1.11.jar
│ ├── commons-collections-3.2.2.jar
│ ├── commons-collections4-4.3.jar
│ ├── commons-compress-1.18.jar
└── resources
├── bootstrap.yml
├── com
│ └── xxx
│ └── isc
└── logback-logstash.xml
以上对 tree命令的输出做了删减,能够说明问题即可。通过以上可知,此插件构建的镜像,把应用放在了/app下面,/app下有三个路径:
- libs 存放项目的第三方jar的依赖
- resources 项目的资源文件,也就是maven 项目src/main/resources下的文件
- classes项目的java文件编译的class文件
通过以上我们可以看出,镜像里保存的不在是一个FatJar。因此,对于该Java应用过的运行,也不在是
java -jar xxx.jar
了。对于此,通过docker inspect
命令查看一下镜像,只关注启动命令如下:
"Entrypoint": [
"java",
"-cp",
"/app/resources:/app/classes:/app/libs/*",
"com.xxx.isc.struct.data.linux.StructDataLinuxApplication"
]
通过以上我们可以看出,是直接通过java xxx的方式直接执行的Spring Boot引导类中的main方法,如果java -jar xxx.jar
需要传递参数,自然可以通过java -server -Darg1=1 -Darg2=2 xxx
方式传递。也就是说,镜像构建的时候,把参数配置到 Entrypoint 即可。怎么做呢?往下接着读。
3 开发灵活配置建议
回到quick-start中,你会发现,在pom.xml中配置镜像构建还配置了docker 私服的鉴权信息。作为开发人员,其实是可以没有必要知道和维护镜像私服鉴权信息,地址以及镜像的版本信息。也就是说,上面的配置中,除了一些命令是必须的,from节点中配置的基础镜像是开发人员需要了解的,其他都是要删除的。所以在开发过程的,我们应该删除上面的to节点 建议配置如下:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.6.1</version>
<configuration>
<from>
<image>thinkerwu/hcnetsdk-jre8:v1</image>
</from>
</configuration>
<executions>
<execution>
<id>build-and-push-docker-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
至于 to 节点中的信息,应该通过mvn
命令传递。不但如此,还有上面提到的Entrypoint中Java应用启动的参数,也是要通过mvn
命令传递的。例如:这个项目使用了nacos,应用运行的时候需要指定nacos服务器地址和命名空间。参考如下:
mvn compile jib:build \
-Djib.allowInsecureRegistries=true \
-Djib.to.image=registry.xxx.com/isc/isc-struct-data-linux:latest \
-Djib.to.auth.username=xxx\
-Djib.to.auth.password=xxx\
-Djib.container.jvmFlags=-server,-Dspring.cloud.nacos.config.namespace=xxx,-Dspring.cloud.nacos.discovery.namespace=xxx,-Dspring.cloud.nacos.config.server-addr=10.0.0.1:8848,-Dspring.cloud.nacos.discovery.server-addr=10.0.0.1:8848
再通过 docker inspect
查看一下Entrypoint 信息如下:
"Entrypoint": [
"java",
"-server",
"-Dspring.cloud.nacos.config.namespace=xxx",
"-Dspring.cloud.nacos.discovery.namespace=xxx",
"-Dspring.cloud.nacos.config.server-addr=10.0.0.1:8848",
"-Dspring.cloud.nacos.discovery.server-addr=10.0.0.1:8848",
"-cp",
"/app/resources:/app/classes:/app/libs/*",
"com.xxx.isc.struct.data.linux.StructDataLinuxApplication"
]
更多配置与命令参数信息,请看github官方仓库。
更多推荐
所有评论(0)