Jib構建鏡像的問題分析(Could not find or load main class ${start-class})


問題簡述

通過Jib插件將SpringBoot工程制作成Docker鏡像成功,但是運行鏡像的時候報錯(Could not find or load main class ${start-class}),今天來一起分析這個問題,希望能幫讀者跳過小坑。

關於Jib插件

在Maven工程中可以使用Jib插件將當前Java工程構建成Docker鏡像,詳情請參考:

  1. 《Docker與Jib(maven插件版)實戰》;
  2. 《Jib使用小結(Maven插件版)》;

環境信息

  1. 操作系統:macOS Mojave 10.14.6 (18G103)
  2. JDK:10.14.6 (18G103)
  3. Docker:10.14.6 (18G103)
  4. SpringBoot:2.1.8.RELEASE
  5. Jib插件版本:1.6.1

源碼下載

為了重現問題,我將出現問題的SpringBoot工程上傳到GitHub,地址和鏈接信息如下表所示:

名稱 鏈接 備注
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議

這個git項目中有多個文件夾,本章的應用在jib-error-demo文件夾下,如下圖紅框所示:
在這里插入圖片描述

問題:

  1. 在pom.xml文件所在目錄執行命令mvn clean compile -U,鏡像可以構建成功,但是控制台輸出了警告信息,如下圖:
    在這里插入圖片描述
  2. 嘗試用此鏡像創建容器,行命令docker run --name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT,報錯如下:
CN0014005932:~ zhaoqin$ docker run --name=test bolingcavalry/hellojib:0.0.1-SNAPSHOT
Error: Could not find or load main class ${start-class}
  1. docker ps -a查看容器信息如下,只能看到狀態是"退出",別的沒啥了:
CN0014005932:~ zhaoqin$ docker ps -a
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS                     PORTS               NAMES
d618f6588821        bolingcavalry/hellojib:0.0.1-SNAPSHOT   "java -Xms4g -Xmx4g …"   4 minutes ago       Exited (1) 4 minutes ago                       test
  1. 不甘心,用命令docker ps -a --no-trunc查看未截斷的容器信息:
CN0014005932:~ zhaoqin$ docker ps -a --no-trunc
CONTAINER ID                                                       IMAGE                                   COMMAND                                                                           CREATED             STATUS                     PORTS               NAMES
d618f6588821f00d3bd0b67a85ff92988b90dfff710370c9d340d5c544c550af   bolingcavalry/hellojib:0.0.1-SNAPSHOT   "java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* ${start-class}"   7 minutes ago       Exited (1) 7 minutes ago                       test
  1. 這次有新發現,容器啟動時執行命令是java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* ${start-class},怪哉!這個${start-class}是什么?我們來看一個正常鏡像的啟動命令:
java -Xms4g -Xmx4g -cp /app/resources:/app/classes:/app/libs/* com.bolingcavalry.jiberrordemo.JibErrorDemoApplication

如上所示,com.bolingcavalry.jiberrordemo.JibErrorDemoApplication是main方法所在類,此命令可以正常運行JibErrorDemoApplication類的main方法;
6. 小結問題:容器啟動時執行java命令,把${start-class}作為參數傳給java,導致java無法處理此參數,所以進程報錯,導致容器退出;

問題原因

此問題的原因很簡單:java工程中帶有main方法的類不止一個,去查看jib-error-demo工程的代碼,發現Utils.java中果然有個main方法:

public class Utils {

    public static String time(){
      return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString();
    }

    public static void main(String[] args){
        System.out.println(time());
    }
}

將上述main方法刪除掉,再構建鏡像並運行容器,證實問題已經解決。

另一種解決問題的方法

如果不想動Utils類的代碼(也許jar包中某個類帶有main方法),請打開pom.xml文件,在jib插件的配置中增加mainClass節點,節點內容是指定的class類,如下圖紅框所示:
在這里插入圖片描述
經過上面的設置,問題也可以解決。

接下來,如果您有興趣了解更深層次的原因,咱們一起來深度探險吧。

查找問題

  1. 這個問題在Jib的官方GitHub上是有記錄的,先看第一條,地址是:https://github.com/GoogleContainerTools/jib/issues/1601 ,如下圖紅框所示,同樣的問題,最后issue的發起人自己關閉了這個issue,因為他發現這自己的項目中有兩個帶有main方法的類:
    在這里插入圖片描述
  2. 再來看看這個issue, https://github.com/GoogleContainerTools/jib/issues/170 ,Jib的作者Q Chen推測是Spring將${start-class}這個字符串設置為Main-Class屬性的值(個人感覺,這里說的Spring應該是spring boot的mave插件吧),於是Jib插件在使用Main-Class的值得時候,拿到的就是${start-class}這個字符串了:
    在這里插入圖片描述
  3. 170這個issue的后續情節很有意思,Jib作者Q Chen對這個問題也很糾結,如果Java工程中發現了多個帶有main方法的類,Jib究竟該如何處理呢?Q Chen最后決定輸出警告,如下圖:
    在這里插入圖片描述
  4. 一起來看看這段代碼吧,也就是上圖中#288,地址是:https://github.com/GoogleContainerTools/jib/pull/228/files/c8757e1f9ea47edd78df18142de7836a68f22034 ,如果mainClass不像一個class類的名稱,就輸出警告,這個邏輯在Gradle和Maven插件中都寫入了:
    在這里插入圖片描述
  5. 最后一個問題:上面代碼中的mainClass從哪來的?打開上圖的源碼,地址是:https://github.com/GoogleContainerTools/jib/blob/c8757e1f9ea47edd78df18142de7836a68f22034/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java ,如下圖紅框,從方法名可以推測,該值來自構建SpringBoot工程的maven插件,所以前面Q Chen提到main-class變量的值是Spring修改的,應該是來自這段代碼:
    在這里插入圖片描述
    此時的您,如果依然意猶未盡,咱們再來鞏固一下SpringBoot的start-class

關於start-class

  1. 熟悉SpringBoot的同學其實對${start-class}並不陌生,當工程中多個類中都有main方法時,使用該參數來指定SpringBoot的啟動類;
  2. 先看SpringBoot官方文檔熟悉一下start-class,地址是:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/ ,下圖內容比較關鍵:我們設置的啟動類被指定到Start-Class屬性中,而Main-Class屬性變成了org.springframework.boot.loader.JarLauncher,這才是SpringBoot真正的啟動類:
    在這里插入圖片描述
  3. 如下圖,這是個補充說明,Main-Class屬性的值被轉移到Start-Class屬性這個動作,是maven插件在構建jar的時候做的:
    在這里插入圖片描述
  4. 所以start-class的值是來自main-class,再看main-class的值從哪里來,如下圖紅框所示,maven插件會去查找帶有public static void main(String[] args)的類:
    在這里插入圖片描述
    至此,Jib構建的鏡像問題分析完畢,一個小小的問題引發了這么多學習和探索,雖然有點費時間,但是可以讓人再次感受到"技術是相通的"感覺,不知道您有沒有這種感覺呢?

歡迎關注我的公眾號:程序員欣宸

在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM