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