1 使用FatJar构建Docker镜像的弊端
在项目工程中,我们经常使用Dockerfile把项目的fatjar打入Docker 镜像的方式来构建。举个简单的例子,你可能在你的项目工程根目录里加一个Dockerfile,内容类似于这样:
FROM java:8-alpineADD ./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>/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 /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 /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: -11-03T13:28:24+08:00[INFO] ------------------------------------------------------------------------
命令docker pull /isc/isc-struct-data-linux
结果如下:
[root@localhost ~]# docker pull /isc/isc-struct-data-linuxUsing default tag: latestlatest: Pulling from isc/isc-struct-data-linuxe110a4a17941: 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:32e8c596c06f64bd7ea55528fedffcb8dce4512b718e9ceabb8283b86413a56aStatus: Downloaded newer image for /isc/isc-struct-data-linux:/isc/isc-struct-data-linux:latest
接下来,把这个镜像运行起来,通过docker exec -it
进入容器看一下与直接把FatJar丢进镜像的区别是什么:
[root@localhost ~]# docker exec -it a9576 /bin/bashbash-4.3# lsappbindevetcextlib homeliblib64 linuxrc media mntoptprocrootrunsbinsrvsystmpusrvarbash-4.3# cd appbash-4.3# lsclasses libs resourcesbash-4.3# apk add tree(1/1) Installing tree (1.7.0-r0)Executing busybox-1.24.2-r14.triggerOK: 22 MiB in 23 packagesbash-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=/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官方仓库。