問題簡述
通過Jib插件將SpringBoot工程制作成Docker鏡像成功,但是運行鏡像的時候報錯(Could not find or load main class ${start-class}),今天來一起分析這個問題,希望能幫讀者跳過小坑。
關於Jib插件
在Maven工程中可以使用Jib插件將當前Java工程構建成Docker鏡像,詳情請參考:
環境信息
- 操作系統:macOS Mojave 10.14.6 (18G103)
- JDK:10.14.6 (18G103)
- Docker:10.14.6 (18G103)
- SpringBoot:2.1.8.RELEASE
- 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文件夾下,如下圖紅框所示:
問題:
- 在pom.xml文件所在目錄執行命令mvn clean compile -U,鏡像可以構建成功,但是控制台輸出了警告信息,如下圖:
- 嘗試用此鏡像創建容器,行命令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}
- 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
- 不甘心,用命令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
- 這次有新發現,容器啟動時執行命令是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類,如下圖紅框所示:
經過上面的設置,問題也可以解決。
接下來,如果您有興趣了解更深層次的原因,咱們一起來深度探險吧。
查找問題
- 這個問題在Jib的官方GitHub上是有記錄的,先看第一條,地址是:https://github.com/GoogleContainerTools/jib/issues/1601 ,如下圖紅框所示,同樣的問題,最后issue的發起人自己關閉了這個issue,因為他發現這自己的項目中有兩個帶有main方法的類:
- 再來看看這個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}這個字符串了:
- 170這個issue的后續情節很有意思,Jib作者Q Chen對這個問題也很糾結,如果Java工程中發現了多個帶有main方法的類,Jib究竟該如何處理呢?Q Chen最后決定輸出警告,如下圖:
- 一起來看看這段代碼吧,也就是上圖中#288,地址是:https://github.com/GoogleContainerTools/jib/pull/228/files/c8757e1f9ea47edd78df18142de7836a68f22034 ,如果mainClass不像一個class類的名稱,就輸出警告,這個邏輯在Gradle和Maven插件中都寫入了:
- 最后一個問題:上面代碼中的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
- 熟悉SpringBoot的同學其實對${start-class}並不陌生,當工程中多個類中都有main方法時,使用該參數來指定SpringBoot的啟動類;
- 先看SpringBoot官方文檔熟悉一下start-class,地址是:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/ ,下圖內容比較關鍵:我們設置的啟動類被指定到Start-Class屬性中,而Main-Class屬性變成了org.springframework.boot.loader.JarLauncher,這才是SpringBoot真正的啟動類:
- 如下圖,這是個補充說明,Main-Class屬性的值被轉移到Start-Class屬性這個動作,是maven插件在構建jar的時候做的:
- 所以start-class的值是來自main-class,再看main-class的值從哪里來,如下圖紅框所示,maven插件會去查找帶有public static void main(String[] args)的類:
至此,Jib構建的鏡像問題分析完畢,一個小小的問題引發了這么多學習和探索,雖然有點費時間,但是可以讓人再次感受到"技術是相通的"感覺,不知道您有沒有這種感覺呢?