基于Docker和Java的持續(xù)集成實(shí)踐
本次分享從持續(xù)集成的幾個(gè)進(jìn)階、團(tuán)隊(duì)協(xié)作IM服務(wù)Grouk如何通過Docker讓集成測(cè)試更容易,詳解集成測(cè)試***實(shí)踐、實(shí)現(xiàn)分支并行集成測(cè)試面臨的困難,以及團(tuán)隊(duì)協(xié)作IM服務(wù)Grouk基于Docker的改造計(jì)劃。
我們是一個(gè)初創(chuàng)團(tuán)隊(duì),Grouk是我們研發(fā)的團(tuán)隊(duì)通訊工具。我們的Docker使用經(jīng)驗(yàn)還比較淺,來這里和大家探討學(xué)習(xí)。
我在一篇持續(xù)集成的演進(jìn)之路中分析了持續(xù)集成的幾個(gè)進(jìn)階:
- 代碼級(jí)別的集成
- 集成工作流
- 持續(xù)部署與交付
- 并行多工作流集成以及個(gè)性化集成
這次分享相當(dāng)于是這篇文章的實(shí)踐篇。
代碼級(jí)別的集成就是只做單元測(cè)試,和代碼檢查。這階是用不到Docker的。到第二階,要做workflow了,需要部署環(huán)境,才需要 Docker。第三進(jìn)階,需要部署到生產(chǎn)環(huán)境,使用Docker也會(huì)降低部署成本。***第四進(jìn)階,每個(gè)分支都進(jìn)行環(huán)境部署和集成測(cè)試。如果沒有 Docker支持的話,實(shí)現(xiàn)成本就太高了。
我們的持續(xù)集成流程
我們使用的語(yǔ)言主要是Java,build工具使用的是Gradle,持續(xù)集成使用的是Teamcity。 下面是我們的持續(xù)集成workflow,是Teamcity的buildchain截圖。
1)build 基本上是代碼級(jí)別的編譯,單元測(cè)試,代碼檢查。
2) integration_test 單實(shí)例集成測(cè)試。我們所有依賴的資源都有內(nèi)存版的替代,這樣我們可以將所有服務(wù)在單進(jìn)程中啟動(dòng)進(jìn)行集成測(cè)試。這樣做有幾個(gè)好處:一是減少集成測(cè)試的耗費(fèi)的時(shí)間,這非常重要,持續(xù)集成就是要能做到快速反饋。二是方便統(tǒng)計(jì)集成測(cè)試的測(cè)試覆蓋率。三是方便本地開發(fā)測(cè)試,直接可以在IDE中啟動(dòng)服務(wù)進(jìn)行 debug。
3)build_docker_image 打包Docker鏡像。我們是將代碼以及配置一起打包到鏡像里的。開始我們打包鏡像使用的是shell,后來我們改成Gradle插件。主要原因是我們有 5個(gè)模塊要打包5個(gè)鏡像。打包每個(gè)鏡像需要5分鐘多,5個(gè)就將近半個(gè)小時(shí)。改為Gradle后,打包可以多線程并行,現(xiàn)在可以在10分鐘內(nèi)。
另外說下,Gradle的Docker插件gradle-docker有bug,我們做了一些改進(jìn),詳細(xì)可以參看https://github.com/GroukLab/gradle-docker
Gradle打包Docker的配置例子:
- docker {
- useApi true
- hostUrl "${docker_host}"
- baseImage "${base_image}"
- maintainer "xxx@email"
- registry "${docker_registry}"
- apiUsername 'username'
- apiPassword ''
- apiEmail 'xxxx@domain'
- }
4)deploy_test_env 部署測(cè)試環(huán)境。當(dāng)前還沒用到復(fù)雜的Docker集群編排工具,直接使用『腳本+Docker』直接部署的。包含了MySQL、Redis、 MongoDB、Elasticsearch、Logstash以及自己的服務(wù),一起部署,初始化。測(cè)試環(huán)境每次進(jìn)行集成測(cè)試都是重新初始化的。
5)test_env_integration 測(cè)試環(huán)境的集成測(cè)試。
6)deploy_sandbox_env 部署沙箱環(huán)境。
沙箱環(huán)境和測(cè)試環(huán)境基本是一樣的,唯一區(qū)別是沙箱環(huán)境的數(shù)據(jù)是多次積累的,不會(huì)每次初始化。
7)sandbox_env_integration 沙箱環(huán)境的集成測(cè)試。
8)deploy_production_env 部署生產(chǎn)環(huán)境。我們線上環(huán)境的服務(wù)也是用Docker部署的,但資源服務(wù)使用的是云服務(wù)提供的,并沒有部署到Docker中。
9)production_env_integration 進(jìn)行生產(chǎn)環(huán)境回歸測(cè)試。
10)clean-up 清理環(huán)境。
持續(xù)集成***實(shí)踐
持續(xù)集成的演進(jìn)之路中列舉了一些持續(xù)集成***實(shí)踐,下面我摘幾點(diǎn)介紹下我們的具體做法:
1)集成測(cè)試用例***使用項(xiàng)目本身開發(fā)語(yǔ)言編寫和單元測(cè)試類似,至少是團(tuán)隊(duì)開發(fā)人員都熟悉的語(yǔ)言。并且項(xiàng)目代碼要和集成測(cè)試用例在同一個(gè)源碼倉(cāng)庫(kù)里。
我們的集成測(cè)試是直接用Java寫的,放到單元測(cè)試的目錄里。不同環(huán)境的集成測(cè)試通過環(huán)境變量進(jìn)行控制。直接通過Gradle的task調(diào)用進(jìn)行集成測(cè)試。 下面是Gradle的集成測(cè)試task例子:
- task testTestEnv(type: Test) {
- systemProperty "ums.env", "test"
- }
2)服務(wù)***不依賴外部容器,可以獨(dú)立運(yùn)行。我們當(dāng)前是內(nèi)嵌Netty和Jetty,通過main方法直接運(yùn)行服務(wù),然后通過Gradle的application插件生成啟動(dòng)腳本。這樣的好處是應(yīng)用可以直接啟動(dòng),方便開發(fā)調(diào)試以及集成測(cè)試。
Java的容器是企業(yè)應(yīng)用為了降低部署成本帶來的習(xí)慣,但當(dāng)前虛擬化,Docker等技術(shù)這樣成熟的情況下,應(yīng)用容器已經(jīng)完全沒必要了。
這點(diǎn)上Go、Node.JS等新的語(yǔ)言做的比較好。Java也可以用spring-boot。
3)***提供一種直接可以單進(jìn)程運(yùn)行整個(gè)系統(tǒng)而不依賴外部資源的配置。我們是提供了一套專門用于dev環(huán)境的配置,MySQL用h2這樣的內(nèi)存數(shù)據(jù)庫(kù)替代,Redis、MongoDB用Java版本的內(nèi)嵌server,服務(wù)可以不依賴外部資源直接啟動(dòng)。這樣的好處前面也說了,可以快速集成測(cè)試,以及統(tǒng)計(jì)集成測(cè)試覆蓋率。
我們遇到的問題
1)鏡像版本問題。Teamcity的BuildChain是可以并行的,如果一直使用latest,會(huì)出現(xiàn)后面的部署操作把前面的尚未進(jìn)行集成測(cè)試的鏡像給部署了。所以我們改造了下,鏡像是按照CI的build number設(shè)置版本號(hào)的,整個(gè)workflow的每一步共享一個(gè)版本號(hào)。源碼的tag,Java的jar包版本號(hào),以及Docker鏡像的版本號(hào)都是可以對(duì)應(yīng)的。
2)鏡像更新頻繁導(dǎo)致Docker Storage分區(qū)空間用完。因?yàn)槊看胃麓蠹s要拉取100多M的增量變更,時(shí)間長(zhǎng)了storage分區(qū)空間用完,Docker Deamon 掛掉。改進(jìn)了部署腳本,***增加了cleanup腳本。
3)部署腳本問題。雖然Docker降低了部署成本,我們可以實(shí)現(xiàn)一鍵部署一整套環(huán)境,但由于大量依賴Shell腳本,失敗檢測(cè)等機(jī)制做的也不完善。存在部署風(fēng)險(xiǎn)。
4)Pets和Cattle。
這是云時(shí)代無論是虛擬機(jī)還是容器想要解決的問題。對(duì)待機(jī)器節(jié)點(diǎn)要像Cattle而不是Pets。
我們現(xiàn)在的方案還是把容器當(dāng)做Pets,要關(guān)心容器到主機(jī)的端口映射,要關(guān)心網(wǎng)絡(luò)的互通,本地磁盤的映射路徑等等,部署,遷移,變更都比較復(fù)雜。
#p#
當(dāng)前正在進(jìn)行的改進(jìn)
我們當(dāng)前還沒做到我說的持續(xù)集成的第四進(jìn)階,多分支并行的集成測(cè)試。 理想的CI流程測(cè)試如下圖:
- 每個(gè)分支的提交也都需要進(jìn)行集成測(cè)試。
- 從分支發(fā)起MergeRequest(MR)后,CI在本地進(jìn)行merge后進(jìn)行集成測(cè)試,將測(cè)試結(jié)果匯報(bào)到MR頁(yè)面。
- Code Review后,MR合并到master,重新進(jìn)行整體的CI流程,直到自動(dòng)化部署完成。
要做到這步的困難點(diǎn)有幾個(gè):
- 要能快速?gòu)?fù)制一整套環(huán)境,并進(jìn)行初始化。
- 要有服務(wù)發(fā)現(xiàn)以及負(fù)載均衡服務(wù),給新的測(cè)試環(huán)境分配域名和負(fù)載均衡入口。
我們開始想嘗試直接通過腳本調(diào)用云服務(wù)提供的api做這個(gè)事情,但發(fā)現(xiàn)比較困難,虛擬機(jī)啟動(dòng)也比較變慢,遂放棄。 去年也嘗試過搭建kubernetes,但發(fā)現(xiàn)還不太成熟,沒太多精力嘗試,也放棄。
前一段時(shí)間Kubernetes的1.0發(fā)布,感覺應(yīng)該相對(duì)比較成熟了,我們又開始嘗試搭建Kubernetes集群,想通過Kubernetes來做這個(gè)事情。
我們?cè)贙ubernetes上也遇到了難點(diǎn):
- Docker節(jié)點(diǎn)的網(wǎng)絡(luò)互通 通訊服務(wù)和其他服務(wù)區(qū)別比較大的是節(jié)點(diǎn)之間需要直接連接進(jìn)行轉(zhuǎn)發(fā)消息。而Kubernetes/Docker容器直接網(wǎng)絡(luò)互通這塊的解決方案都還不是太成熟。
- 長(zhǎng)時(shí)間運(yùn)行的數(shù)據(jù)庫(kù)服務(wù)在Kubernetes中如何調(diào)度運(yùn)維?數(shù)據(jù)遷移和HA如何實(shí)現(xiàn)?
- 依賴多種資源,多個(gè)服務(wù)模塊,并且模塊有依賴關(guān)系的應(yīng)用如何整體定義?如何升級(jí)?
我們嘗試Kubernetes,其實(shí)不僅僅是想在持續(xù)集成中使用,還有個(gè)想法是給企業(yè)提供私有部署。
假設(shè)Kubernetes可能會(huì)成為未來的云操作系統(tǒng),企業(yè)的私有云上也部署的是Kubernetes,這樣我們提供基于Kubernetes的部署,就可以實(shí)現(xiàn)一鍵部署到企業(yè)內(nèi)網(wǎng),降低了企業(yè)應(yīng)用私有部署的部署運(yùn)維成本。
這方面還得期望各位Docker的大牛們和各云廠商給提供解決方案。
Q&A
Q: CI過程中test需要連接數(shù)據(jù)庫(kù)的代碼時(shí),您在寫測(cè)試案例方面有哪些經(jīng)驗(yàn)分享?
A:?jiǎn)卧獪y(cè)試不能依賴外部資源,用mock,或者用h2等內(nèi)存數(shù)據(jù)庫(kù)替代。集成測(cè)試的時(shí)候是從接口層直接調(diào)用測(cè)試的,測(cè)試用例對(duì)數(shù)據(jù)庫(kù)無感知。
Q: 請(qǐng)問部署到生產(chǎn)環(huán)境是自動(dòng)觸發(fā)還是需要手動(dòng)審批?SQL執(zhí)行或回滾是否自動(dòng)化?
A:當(dāng)前是需要手動(dòng)觸發(fā)。SQL更新當(dāng)前沒做到自動(dòng)化,這塊正在改進(jìn),因?yàn)椴渴鹚接协h(huán)境需要。SQL不支持回滾,代碼做兼容。Docker鏡像回滾沒有自動(dòng)化。
Q: 問一下你們的Redis內(nèi)存版是用的什么?
A:我們用的內(nèi)存版的redis是 https://github.com/spullara/redis-protocol 中的server實(shí)現(xiàn)。不過這個(gè)實(shí)現(xiàn)部分功能沒支持,比如lua腳本,我們自己做了改進(jìn)。
Q:介紹下workflow帶來的好處。
A:workflow的好處我那篇文章中有說明,如果沒有workflow,所有的步驟都在同一個(gè)配置的不同step實(shí)現(xiàn),如果后面的失敗,要重新從頭開始。workflow可以中途開始,并且每一步驟完成都會(huì)觸發(fā)通知。
Q:h2并不完全兼容MySQL腳本,你們?nèi)绾翁幚淼?
A:我們通過一些hack的辦法,會(huì)探測(cè)下數(shù)據(jù)庫(kù)是什么類型的,替換掉一些不兼容的SQL,進(jìn)行容錯(cuò)。
Q:請(qǐng)問你們?cè)跇?gòu)建的時(shí)候,你說有些需要半個(gè)小時(shí)左右,那么構(gòu)建過程的進(jìn)度監(jiān)控和健康監(jiān)控你們?cè)趺醋龅哪?,如果有build失敗了怎么處理呢?
A:CI的每一步都有進(jìn)度的,并且我們的團(tuán)隊(duì)通訊工具可以和CI集成,如果失敗會(huì)發(fā)消息到群里通知大家。
Q:cleanup腳本做哪些?
A:主要是清理舊的Docker鏡像,以及清理自動(dòng)化測(cè)試產(chǎn)生的垃圾數(shù)據(jù)。
Q:請(qǐng)問你們文件存儲(chǔ)怎么解決的呢,使用自己的網(wǎng)絡(luò)文件系統(tǒng)還是云服務(wù)?
A:文件系統(tǒng)支持多種storage配置,可以是本地目錄(便于測(cè)試),也可以使云服務(wù)(比如s3)。
Q:剛才說你們能通過一鍵部署,但是中間無法監(jiān)控,測(cè)試環(huán)境可以這么玩,那生產(chǎn)環(huán)境你們是怎么做的呢?還有你們后續(xù)的改造方向是自己開發(fā)?還是采用集成第三方軟件?
A:生產(chǎn)環(huán)境shell當(dāng)前只能是多加錯(cuò)誤判斷。這塊我們?cè)诟倪M(jìn),比如通過ansible等工具,以及使用Kubernetes內(nèi)置的rolling-update。自動(dòng)化部署這塊還沒有好的開源工具。
Q:你們的測(cè)試用了很多代替方案、如h2代MySQL,要保證測(cè)試效果,除了你們用的hack方法之外,是不是從寫代碼的時(shí)候就開始做了方便測(cè)試的設(shè)計(jì)?
A:對(duì)。這也是我文章中分享的觀點(diǎn)之一。測(cè)試用例的編寫人員要有業(yè)務(wù)代碼的修改權(quán)限,***是同一個(gè)人。要做自動(dòng)化測(cè)試,業(yè)務(wù)代碼必須要給測(cè)試留各種鉤子以及后門。
Q:請(qǐng)問你們的集群應(yīng)用編排怎么做的?
A:上面說了,還沒用到編排。一直等編排工具的成熟。正在測(cè)試k8s。
Q:你們做這個(gè)項(xiàng)目選型是出于什么考慮的,介紹里有提到使用一些腳本來管理容器解決開發(fā)和測(cè)試各種問題, 感覺這種管理容器方式過于簡(jiǎn)單會(huì)帶來管理問題,為何不用第三方開源項(xiàng)目來做二次開發(fā),如:Kubernetes;另一個(gè)問題是,下一步有沒有考慮如何讓你的Docker和云服務(wù)平臺(tái)結(jié)合,要解決運(yùn)營(yíng)成本問題(Docker***吸引力在這里),而不只是解決開發(fā)測(cè)試問題?
A:因?yàn)槲覀冏钤缬玫臅r(shí)候k8s 1.0 還沒有,變化太大,創(chuàng)業(yè)團(tuán)隊(duì)沒精力跟進(jìn),腳本是粗暴簡(jiǎn)單的辦法。一直在等待各種基于Docker的云解決方案呀,肯定考慮結(jié)合。
Q:對(duì)于Docker storage分區(qū)用完問題,我想問一下,你們是使用Docker官方提供的Registry倉(cāng)庫(kù)嗎,如何解決倉(cāng)庫(kù)單點(diǎn)問題,這機(jī)器要是故障了怎么辦?
A:Registry用的是官方的,后端存儲(chǔ)是掛載到s3上的。沒有s3, 推薦使用京東田琪團(tuán)隊(duì)開源的Speedy,實(shí)現(xiàn)了分布式存儲(chǔ)。
Q:除了介紹的Java相關(guān)的CI方案,對(duì)于C/C++開發(fā)語(yǔ)言有沒有推薦的CI方案?
A:Teamcity/Jenkins等CI工具支持任何語(yǔ)言的。其實(shí)任何語(yǔ)言的CI都差不多,單元測(cè)試,集成測(cè)試。關(guān)鍵還在于依賴環(huán)境的準(zhǔn)備以及集成測(cè)試用例的管理。
Q:我看到你們?yōu)榱朔奖銣y(cè)試和調(diào)試會(huì)有獨(dú)立的集合Docker環(huán)境,這種環(huán)境和上線環(huán)境其實(shí)是有差別的,這樣測(cè)試的結(jié)果能夠代表線上環(huán)境嗎?這種問題怎么看待?
A:所以我們有多個(gè)流程。清理數(shù)據(jù)的測(cè)試環(huán)境,以及不清理環(huán)境的沙箱環(huán)境。但這也不能避免一部分線上環(huán)境的數(shù)據(jù)導(dǎo)致的bug。另外就是配合灰度上線機(jī)制。當(dāng)前我們的灰度是通過代碼中的開關(guān)實(shí)現(xiàn)的,使用這種方案的也很多,比如facebook的Gatekeeper。
Q:請(qǐng)問Grouk有涉及前端(Node.js方面的)并結(jié)合Docker的CI/CD經(jīng)歷嗎,可以分享下嗎?
A:這我們也在嘗試。當(dāng)前js的測(cè)試主要還是基于https://github.com/ariya/phantomjs ,純粹的js庫(kù)比較方便測(cè)試,但如果牽扯到界面,就比較復(fù)雜些了。
分享人簡(jiǎn)介
王淵命,團(tuán)隊(duì)協(xié)作IM服務(wù)@Grouk聯(lián)合創(chuàng)始人&CTO,技術(shù)極客,曾任新浪微博架構(gòu)師,微米技術(shù)總監(jiān),2014年作為聯(lián)合創(chuàng)始人創(chuàng)立團(tuán)隊(duì)協(xié)作IM服務(wù)Grouk,長(zhǎng)期關(guān)注團(tuán)隊(duì)協(xié)作基礎(chǔ)工具和研發(fā)環(huán)境建設(shè),Docker深度實(shí)踐者。