使用jib-maven-plugin分層構建Docker鏡像——避免直接使用FatJar

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章