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官方倉庫。