spring boot介紹
Spring Boot目前流行的java web應用開發框架,相比傳統的spring開發,spring boot極大簡化了配置,並且遵守約定優於配置的原則即使0配置也能正常運行,這在spring中是難以想象的。spring boot應用程序可以獨立運行,框架內嵌web容器,使得web應用程序可以像本地程序一樣啟動和調試,十分的方便,這種設計方式也使得spring boot應用程序非常適合容器化進行大規模部署。生態方面,spring boot提供了非常豐富的組件,目前流行的java web框架基本都有spring boot版本,生態十分龐大,是目前java web開發最好的方案。
spring boot部署問題
Springboot應用程序有兩種運行方式
以jar包方式運行
以war包方式運行
兩種方式應用場景不一樣,各有優缺點
jar包運行
通過maven插件spring-boot-maven-plugin,在進行打包時,會動態生成jar的啟動類
org.springframework.boot.loader.JarLauncher,借助該類對springboot應用程序進行啟動。
優點
本地無需搭建web容器,方便開發和調試。
因為自帶web容器,可以避免由於web容器的差異造成不同環境結果不一致問題。
一個jar包就是全部,方便應用擴展。
借助容器化,可以進行大規模的部署。
缺點
應用過於獨立,難以統一管理。
數據源無法通過界面進行管理。
應用體積過大。
修改web容器相關配置較為困難,需要借助代碼實現。
war包運行
以war包方式運行,通過maven插件spring-boot-maven-plugin進行相關配置后,最終生成一個可運行在tomcat,weblogic等java web容器中的war包。
優點
可以借助web容器管理界面對應用進行管理。
可以管理JNDI數據源。
web容器配置較為靈活,配置和程序分離。
應用體積較小,甚至可以借助web容器的包管理功能(比如weblogic Library)進一步減小應用大小。
缺點
本地需要搭建web容器,對本地環境要求更高點,學習成本也響應更高。
調試較為困難,需要借助web容器。
無法兼容所有web容器(比如spring boot2.x無法運行在weblogic 11g上)。
部署較為困難(比如和weblogic有較多的類沖突)
在實際的項目中,並沒有哪一種方式是最好的,根據客戶不同的需求制定不同的部署方案,比如有些客戶比較看中管理功能,要求數據源和tomcat相關配置必須由管理員進行管理,那么選擇war包方式,有些客戶希望借助容器化進行大規模部署,那么jar方式更適合。不管選擇哪種方式,在部署時都會遇到下面的問題
如果需要打war包,那么不僅是pom文件需要修改,應用程序也要做相應的改動,改動完后,應用程序就無法本地運行,需要打完包后將配置信息修改回來,這樣不僅麻煩,還容易出錯。
不管是war包還是jar包,如何管理不同環境的配置文件,保證不會出錯,雖然spring boot有提供spring.profiles.active配置設置不同的環境,但一方面需要人為修改配置文件,只要是人為的就有可能出錯,另一方面,客戶有時出於安全考慮不會提供生產環境配置信息,那么這時候就無法指定prifiles.active。
如何將多個spring boot模塊打包在一起。
jar包需要配合容器化才能發揮出最大的優勢,如果沒有容器,spring boot jar包就是一個玩具,隨處運行的jar包,缺少統一管理,是達不到生產的要求,那么如果從jar包到容器也是一個問題。
早期碰到這些問題,都是人工解決,不僅效率十分低下,部署一次都需要十幾分鍾,而且很容易出錯,一百次出錯一次算是概率低了,但是生產出錯一次都是重大事件,所以我們也在思考如何通過自動化解決以上問題,如何將開發和部署分離,開發人員只關心開發,開發完提交代碼,打包和部署都是后台透明的完成。以下就是我們的解決方案。
打包war包打包問題解決
spring boot打war包的步驟如下
在pom.xml中將打包方式改為war。<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ... war ...設置spring-boot-starter-tomcat范圍為provided org.springframework.boot spring-boot-starter-tomcat provided修改spring boot的啟動類,繼承SpringBootServletInitializerpublic class DemoApplication extends SpringBootServletInitializer{ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoApplication.class); }}
每打包一次都要修改pom.xml和啟動類,打包完再修改回來,十分的繁瑣,因為,我們提出以下整改方案
從pom.xml復制一個pom-war.xml文件,將pom-war.xml修改為war包配置
在根目錄下(除了src目錄外都可以)復制一份啟動類的代碼,修改為war包的配置方式。
編寫shell腳本進行打包。
shell腳本打包過程為
- 備份當前啟動類的java代碼。
- 將war包啟動類的代碼替換掉當前啟動類的代碼。
- maven指定pom-war.xml文件進行打包。
- 打包結束后恢復啟動類文件。
以下就是參考腳本
app-war.sh#!/usr/bin/env bashv1=src/main/java/com/definesys/demo/DemoApplication.javav2=war/DemoApplication.javav3=war/DemoApplication-bak.javacp -rf $v2 $v1mvn clean package -Dmaven.test.skip=true -f war-pom.xml#recoverycp -rf $v3 $v1
通過預先配置好pom文件和啟動類文件,開發人員只要運行app-war.sh腳本無需修改任何文件即可生成war包。
更優的方案
以上方案pom文件和啟動類文件都需要預先准備好,未實現完全的自動化,通過優化方案做到完全自動化。
腳本可以通過find命令搜索以*Application.java結尾的文件,作為啟動類文件,讀取文件名獲取類名,通過字符串替換方式動態生成war包啟動類文件。
在pom.xml中用注釋設置好錨點,腳本通過替換錨點動態生成pom.xml文件。
如果不希望通過錨點實現,可以借助更高級的腳本語言,比如python對xml進行解析,再動態生成xml。
多模塊打包
這里的多模塊指的是maven中的多模塊,項目工程中的代碼多模塊,一個項目按功能划分模塊后,在創建工程時一般也按照功能層面上的模塊進行創建,這樣避免一個模塊代碼過於龐大,也利於任務的分工,但打包卻更麻煩了。
每個模塊都是獨立的spring boot程序,整合到一個包的時候會出現多個啟動類,多個配置文件沖突的問題。
每個模塊有引用相同的依賴,依賴包版本升級后,需要每個pom文件都做修改。
通過優化項目結構解決以上問題
父項目的pom指定spring boot的依賴和公共的依賴。
創建一個spring boot的子項目,作為啟動項目,我們稱為start項目。
其余子項目為普通的java maven項目,parent設置為第一步創建的spring boot父項目。
start項目的pom引用其他子項目的依賴。
本地調試可以直接運行start的啟動類,ide會自動編譯其他模塊並引用。
打包可以在父項目上進行install后再進入start項目進行打包,腳本參考如下mvn clean installcd startmvn clean package
目錄結構如下
.├── pom.xml├── role│ ├── pom.xml│ └── src│ ├── main│ │ ├── java│ │ │ └── com│ │ │ └── definesys│ │ │ └── demo│ │ │ └── controller│ │ │ └── RoleController.java│ │ └── resources├── start│ ├── pom.xml│ ├── src│ │ ├── main│ │ │ ├── java│ │ │ │ └── com│ │ │ │ └── definesys│ │ │ │ └── demo│ │ │ │ └── DemoApplication.java│ │ │ └── resources│ │ │ └── application.properties└── user ├── pom.xml └── src └── main ├── java │ └── com │ └── definesys │ └── demo │ └── controller │ └── UserController.java └── resources
start項目包含包含啟動類和配置文件,pom文件引用其余子項目。
start pom.xml<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> blog0915 com.definesys.demo 1.0-SNAPSHOT 4.0.0 start com.definesys.demo user 1.0.0 com.definesys.demo role 1.0.0 org.springframework.boot spring-boot-maven-plugin
父項目parent為spring boot,引用spring boot相關依賴和各個子項目公共的依賴
父項目 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 pom user role start org.springframework.boot spring-boot-starter-parent 2.1.2.RELEASE com.definesys.demo blog0915 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web
所有非start的子項目需要指定版本號並且父項目都設為根目錄項目。
子項目 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> blog0915 com.definesys.demo 1.0-SNAPSHOT 4.0.0 role 1.0.0
所有子項目的包路徑前綴必須一樣,並且以start項目作為基本路徑。
配置文件問題
spring boot提供spring.profiles.active指定配置文件,但生產環境有時候客戶出於安全考慮不提供配置信息給開發人員,而是預先將配置文件上傳到服務器指定路徑,程序需要在運行時去引用該配置文件,如果運行環境是kubernetes,則會提供一個config map作為配置文件,這時候就要求spring boot程序讀取外部配置文件。
這里討論的是線上環境配置文件方案,本地調試參考子模塊打包相關內容,可以將配置文件統一寫在start項目中。
jar包外部配置文件讀取
jar運行可以通過指定參數spring.config.location引用外部文件,命令參考如下:
java -jar start-1.0-SNAPSHOT.jar --spring.config.location=/Users/asan/workspace/config
config目錄存放properties配置文件
可以通過配合spring.profiles.active參數可以指定目錄下配置文件,如:
java -jar start-1.0-SNAPSHOT.jar --spring.profiles.active=prod --spring.config.location=/Users/asan/workspace/config
則會讀取
/Users/asan/workspace/config/appliction-prod.properties文件作為配置文件。
war包外部配置文件讀取
以tomcat為例,需要在tomcat啟動時指定-Dspring.config.location參數,可以設置服務器環境變量CATALINA_OPTS達到目的。可以編輯用戶 prifile文件
export CATALINA_OPTS=/Users/asan/workspace/config
同樣,也可以通過-Dspring.profiles.active指定配置文件名稱。
容器化
spring boot借助容器化,可以如虎添翼,發揮出更大的威力,也只有通過容器化,才能體會到spring boot開發的高效。通過以上的介紹,你可以很順利的打好一個jar包或者war包,那么可以通過編寫dockerfile文件進行鏡像的構建。spring boot在構建鏡像時有兩個地方需要考慮
時區問題,基礎鏡像的時區默認是UTC,比北京時間早8小時,需要指定鏡像時區。
配置文件問題,需要指定外部配置文件(根據項目具體情況選擇)。
app-jar-dockerfile.DockerfileFROM openjdk:8-jdk-alpineMAINTAINER definesys.comVOLUME /tmpADD start-1.0-SNAPSHOT.jar app.jarRUN echo "Asia/Shanghai" > /etc/timezoneEXPOSE 8080ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","--spring.config.location=/Users/asan/workspace/config","/app.jar"]app-war.dockerfile.DockerfileFROM tomcatMAINTAINER definesys.comENV CATALINA_OPTS -Dspring.config.location=file:/middleware/config/ADD start-1.0-SNAPSHOT.war /usr/local/tomcat/webapps/app.jarRUN echo "Asia/Shanghai" > /etc/timezoneEXPOSE 8080
EOF部署
早期我們采用的是以下部署過程

首先構建測試環境的鏡像,上傳到鏡像倉庫,應用重新部署。
接着構建UAT環境的鏡像,上傳到鏡像倉庫,應用重新部署。
最后構建生產環境的鏡像,上傳到鏡像倉庫,應用重新部署。
每一次發布都是一個新的鏡像,但這種方式有個問題就是如何保證前一個環境驗證沒問題,后一個環境就一定沒問題,因為兩個鏡像是不一樣的,雖然可能兩次構建都是基於同一版本代碼,但因為是重新構建,中間可能因為各種原因,如maven包版本更新等,無法保證兩次構建就是完全一樣的鏡像。因此我們優化了構建的流程,如下:

所有的環境都是用同一個鏡像,環境之間只有配置文件不同,文件通過configmap或者外部配置文件方式進行掛載,這樣保證了配置文件沒問題的前提下,每個環境的程序一定是一樣的。
jenkins自動打包部署
打包和部署在本地進行也是有問題的,本地jdk版本取決於個人電腦,甚至有黑客污染jdk導致編譯的class文件自帶后門,個人電腦環境也是隨着用戶不同操作可能改變,構建出來的包不能保證是穩定的包。因此需要一個遠程服務器用於打包和部署,能夠實現從源碼到鏡像過程。jenkins是一個基於java開發的持續集成工具,通過配置插件和編寫腳本實現程序從代碼到制品再到線上運行的過程。jenkins在spring boot開發中主要完成了以下工作。
通過gitlab插件實現源代碼的獲取。
基於以上介紹的腳本,實現從源碼到制品的過程。
通過docker工具實現從制品到鏡像的過程。
通過kubectl工具,實現從鏡像到上雲的過程。
jenkins在構建鏡像時需要借助docker工具,但jenkins本身也是有docker版本的,所以就面臨着docker in docker的問題,這里選擇的方案是用二進制文件安裝jenkin而非鏡像方式,雖然喪失了docker的便利性,但可以簡化docker方案,降低集成的復雜度。