使用Docker實現絲般順滑的持續集成

持續集成(Continuous Integration,簡稱CI)作爲先進的項目實踐之一,近年來逐漸受到國內軟件公司的重視;但對於許多朋友來說,可能從未聽說過持續集成這個詞,抑或只是瞭解概念但並沒有實踐過。
什麼是持續集成?它對軟件開發有哪些好處呢?

持續集成的概念

隨着軟件開發複雜度的不斷提高,團隊開發成員間如何更好地協同工作以確保軟件開發的質量已經慢慢成爲開發過程中不可迴避的問題。尤其是近些年來,敏捷(Agile)在軟件工程領域越來越紅火,如何能在不斷變化的需求中快速適應和保證軟件質量也顯得尤其重要。

持續集成正是針對這類問題的一種軟件開發實踐:它倡導團隊開發成員必須經常集成他們的工作,甚至每天都可能發生多次。而每次集成都是通過自動化的構建來驗證,包括自動編譯、發佈和測試,從而儘快地發現集成錯誤,讓團隊能夠更快的開發產品。

讓我們以A項目爲例描述一個普通團隊是如何使用CI的:

首先,解釋下集成:所有的項目代碼都託管在SVN或者Git服務器上(以下簡稱代碼服務器)。每個項目都有若干單元測試和集成測試。集成測試是單元測試的邏輯擴展:在單元測試的基礎上,將所有模塊按照設計要求組裝成爲子系統或系統進行集成測試。實踐表明,一些模塊雖然能夠單獨地工作,但並不能保證連接起來也能正常工作。一些局部反映不出來的問題,在全局上很可能暴露出來(關於單元測試及集成測試的詳述,讀者可以查閱相關文檔)。

簡單來說,集成測試就是把所有的單元測試跑一遍,以及其他能自動完成的測試。只有通過了集成測試的代碼才能上傳到代碼服務器,確保上傳的代碼沒有問題。集成一般指集成測試。

持續,顯而易見就是長期對代碼進行的集成測試。既然是長期進行,那麼最好是自動執行,否則人工執行既沒保證,而且耗人力。

基於此種目的,我們需要有一臺服務器,它將定期從代碼服務器中拉取,並進行編譯,然後自動運行集成測試;並且每次集成測試的結果都會記錄在案。
在A項目中,設定執行這個工作的週期是1天。也就是說服務器每天都會準時地對代碼服務器上的最新代碼自動進行一次集成測試。

持續集成的特點

  1. 它是一個自動化的週期性的集成測試過程,從拉取代碼、編譯構建、運行測試、結果記錄、測試統計等都是自動完成的,無需人工干預;
  2. 需要有專門的集成服務器來執行集成構建;
  3. 需要有代碼託管工具支持。

持續集成的作用

  1. 保證團隊開發人員提交代碼的質量,減輕了軟件發佈時的壓力;
  2. 任何一個環節都是自動完成的,無需太多的人工干預,有利於減少重複過程以節省時間、費用和工作量;
  3. 筆者在實踐的過程中總結出了以下心得:
  4. 代碼越早push出去,用戶能越早用到,快就是商業價值;
  5. 用戶越早用到就越早反饋,團隊越早得到反饋,好壞都是有價值的輸入;
  6. 用戶不反饋,說明我們做了用戶不想要的東西(通過用例跟蹤)或者市場沒做好,能幫助產品市場人員調整策略;
  7. 代碼庫存越是積壓,就越得不到生產檢驗,積壓越多,代碼間交叉感染的概率越大,下個release的複雜度和風險越高;
  8. 代碼庫存越多,workflow的包袱越重,管理成本越大。

瞭解了持續集成的優點,讀者是不是有立馬部署一套的衝動呢?然而,部署持續集成環境,並不簡單。

從一個Java項目持續集成的典型場景爲例。我們只要建立一個基於Jenkins + Maven + Git(SVN) 的持續集成環境,再加上持續集成所要求的測試和流程就可以大致運行。

但在搭建好一整套持續集成的流水線之後,卻發現它並不像想象中絲般順滑,甚至比在本地開發測試的效率還低。爲什麼會出現這樣的情況?
目前常見的在持續集成過程中遇到的問題有如下幾種。

【編譯時依賴和運行時依賴】

從字面上不難理解這兩種依賴。雖然編譯時依賴通常也是運行時依賴,但並不能由此判定編譯時依賴一定是運行時依賴。例如,在開發過程中需要某些提供API的Jar包,但在運行時可能需要的是具體API實現的Jar包。其次,被依賴的包會有它自身的依賴,這樣的情況下,項目會對這些包產生間接依賴(運行時依賴),依此類推,最終形成一個依賴樹。當項目運行時,這些依賴樹上的包必須全部就位,否則項目無法運行。

Maven在POM中通過scope來判定依賴的類型,從而幫助開發和運維人員擺脫手動處理依賴樹的工作,然而運行時所有依賴包最終是要安裝到生產環境的,這部分工作Maven並不能自動完成。因此,一個常用方式是將運行時所依賴的包拷貝到項目文件中,比如Java Web應用的WEB-INF/lib,然後將項目全部打成一個包。在安裝項目包後,修改環境變量,將這些依賴包所在的路徑加入相應的環境變量中,如ClassPath。

再舉個典型的例子,目前的操作系統和其他系統框架都考慮到了運行時依賴樹的處理問題,比如Ubuntu的apt-get,CentOS的yum,Ruby的RubyGem,Node的npm等等,在安裝某個軟件包時,系統會自動將所需的依賴包按順序下載並安裝。

【依賴時的複雜度】

項目除了對程序包的依賴,對於運行環境也有些具體的要求。比如,Web應用需要安裝和配置Web服務器、應用服務器、數據服務器等,企業應用中可能需要消息隊列、緩存、定時作業,或是對其他系統以Web Service或API的方式暴露服務等。這些可以看成項目在系統層面對外部的依賴。這些依賴有些可以由項目自行處理,而有些則是項目無法處理的,比如運行容器,操作系統等,這些是項目的運行環境。

總之,依賴的複雜度主要有兩個:

  1. 依賴包間的版本兼容性問題。兼容性問題是軟件開發者的噩夢。
  2. 間接依賴,或多重依賴問題。例如:A依賴於Python 2.7,A還依賴於B,但B卻依賴於Python 3,苦逼的是Python 2.7和Python 3不兼容。依賴中最痛苦的事莫過於此。

【不一致的環境】

簡單項目中,開發和運行環境都由開發人員搭建,當公司變大時,系統的運行環境將由運維人員搭建,而開發測試環境如果由運維人員搭建則工作量太大,由開發人員自己搭建則操作複雜又容易產生不一致的情況。假設公司爲項目A和項目B開發了新版本。但環境和軟件升級不是同步進行,出錯的可能性非常大(想一想間接依賴和多重依賴的情況)。大家對這樣的場景有沒有印象:當新版本部署時,發現問題,測試或部署人員說:“版本有問題,無法運行!”開發人員卻說:“我這裏沒問題啊,運行正常!”

【氾濫的部署】

如果項目簡單,沒有任何歷史項目和代碼的拖累,且各項目之間也沒有任何的關聯,只需進行資源方面的管理:分配機器,初始化系統,分配IP地址等。各個項目的運行環境、數據庫、開發環境等都由具體項目的開發人員手動完成,這樣的環境出問題怎麼辦?很簡單,涼拌——重裝系統。

理想很豐滿,現實很骨感,歷史遺留問題往往對現在的項目有很大影響:多語言(Java,PHP、C ),多系統(各種Windows、Linux),多構建工具版本(Java7、8),各種配置文件和各種黑科技補丁腳本散落在系統的各個角落,沒人能找得到,也沒人搞得懂。配置被誰改掉了,服務宕掉了,根本無從管理。

問題分析完了,我們又該如何克服這些問題,使用什麼工具和方法,從而達到絲般順滑的持續集成呢?這就是接下來要介紹的主角:Docker、AppoSoar及AppHouse。

Docker如今在國內已經如火如荼,Docker可以做到一次構建,處處運行;各種實踐和生產部署也紛紛上馬,Docker的基本知識和好處筆者也就不在這裏科普了。

AppSoar,以Docker爲基礎,以企業應用爲導向,以安全穩定爲目標,打造專業、簡單易用的企業級容器雲解決方案。它提供了企業應用商店、友好圖形化界面管理、多環境管理、混合雲支持、容器持久存儲、容器網絡模式、容器可擴展性、容器負載均衡與調度、API接口支持、系統高可用等一系列強大功能。

AppHouse爲容器運行平臺提供了一種集鏡像管理、鏡像安全、鏡像高可用及鏡像高速訪問爲一體的企業級鏡像倉庫管理方案。AppHouse支持HTTPS訪問;基於角色的權限控制;系統高可用;部署靈活;一鍵安裝,一鍵升級;友好的圖形化界面,LDAP/AD用戶集成;支持swift/Glusterfs/公有云存儲接入;提供豐富的API接口;支持連接Git源碼倉庫,代碼自動構建;提供webhook接口,支持持續部署。解決了docker build、push、pull的一攬子問題。

言歸正傳,既然爲了達到順滑的持續集成要求,那麼爲什麼選擇用Docker、AppSoar、AppHouse來部署?

上面我們提到了傳統方式搭建持續集成平臺經常遇到4個問題:編譯和運行時依賴、依賴時的複雜度、不一致的環境和氾濫的部署,接下來我們來分析下Docker+AppSoar+AppHouse組合如何解決這些問題。

首先,Docker可以讓你非常容易和方便地以“容器化”的方式去部署應用。 它就像集裝箱一樣,打包了所有依賴,再在其他服務器上部署很容易,不至於換服務器後發現各種配置文件散落一地,這樣就解決了編譯時依賴和運行時依賴的問題。

其次,Docker的隔離性使得應用在運行時就像處於沙箱中,每個應用都認爲自己是在系統中唯一運行的程序,就像剛纔例子中,A依賴於python 2.7,同時A還依賴於B,但B卻依賴於Python 3,這樣我們可以在系統中部署一個基於Python 2.7的容器和一個基於Python 3的容器,這樣就可以很方便地在系統中部署多種不同環境來解決依賴複雜度的問題。這裏有些朋友可能會說,虛擬機也可以解決這樣的問題。誠然,虛擬化確實可以做到這一點,但是這需要硬件支持虛擬化及開啓BIOS中虛擬化相關的功能,同時還需要在系統中安裝兩套操作系統,虛擬機的出現是解決了操作系統和物理機的強耦合問題。但Docker就輕量化很多,只需內核支持,無需硬件和BIOS的強制要求,可以輕鬆迅速地在系統上部署多套不同容器環境,容器的出現解決了應用和操作系統的強耦合問題。

正因爲Docker是以應用爲中心,鏡像中打包了應用及應用所需的環境,一次構建,處處運行。這種特性完美解決了傳統模式下應用遷移後面臨的環境不一致問題。

同時,Docker壓根不管內部應用怎麼啓動,你自己愛咋來咋來,我們用docker start或run作爲統一標準。這樣應用啓動就標準化了,不需要再根據不同應用而記憶一大串不同啓動命令。

基於Docker的特徵,現在常見的利用Docker進行持續集成的流程如下:

  1. 開發者提交代碼;
  2. 觸發鏡像構建;
  3. 構建鏡像上傳至私有倉庫;
  4. 鏡像下載至執行機器;
  5. 鏡像運行。

其基本拓撲結構如圖1所示。

圖1   利用 Docker 進行持續集成基本拓撲結構

圖1 利用 Docker 進行持續集成基本拓撲結構

熟悉Docker的朋友都知道,Docker啓動非常快,可以說是秒啓。在上述的五步中,1和5的耗時較短,整個持續集成主要耗時集中在中間的3個步驟,也就是docker build、docker push、docekr pull這樣還是無法達到順滑的極致要求,下來我們來分析下build、push、pull的耗時和解決方法:

docker build

  1. 網絡優化
    dockerhub的官方鏡像在國外,由於衆所周知的原因,在國內進行構建時網絡會是很大的瓶頸,甚至某些公司的環境是無Internet連接的。
    在這種情況下,建議使用國內的鏡像源,或者自己搭建私有倉庫,保存項目需要的基礎鏡像,把構建過程中的網絡傳輸都控制在國內或者內網,這樣就不用再考慮網絡方面的問題。
  2. 使用 .dockerignore文件
    dockerignore文件的設計是爲了在docker build的過程中排除不需要用到的文件以及目錄,目的是爲了docker build這個過程可以儘可能地快速高效以及構建出來的image沒有多餘的“垃圾”。
  3. 最小化鏡像層數(layers)
    把鏡像層數減到最少,能加快容器的啓動速度,但是這裏也要權衡另一個問題:dockerfile的可讀性。你可以把一個dockerfile寫得很複雜以達到構建出最小層數的鏡像,但同時你的dockerfile可讀性也降低了。所以我們要在鏡像層數和dockerfile可讀性之間做出妥協。

docker push

docker registry升級到v2後加入了很多安全相關檢查,在v2中的鏡像的存儲格式變成了gzip ,鏡像在壓縮過程中佔用的時間也比較多。我們簡單分解一下docker push的流程。

  1. buffer to disk,將該層文件系統壓縮成本地的一個臨時文件;
  2. 上傳文件至registry;
  3. 本地計算壓縮包digest,刪除臨時文件,digest傳給registry;
  4. registry計算上傳壓縮包digest並進行校驗;
  5. registry將壓縮包傳輸至後端存儲文件系統;
  6. 重複1-5直至所有層傳輸完畢;
  7. 計算鏡像的manifest並上傳至registry重複 3-5。

這樣的設計導致push會很慢,如果採用官方的dockerhub,需要考慮docker build一節中提及的網絡方面影響,dockerhub公有鏡像庫還需考慮安全方面的因素。

同時docker和registry設置了過多的安全防範措施(如雙向證書認證等),主要是爲了防止在公有云的環境下鏡像的僞造和越權獲取。但是在一個可信的環境內,如果build和push過程都是自己掌控,很多措施都是多餘的。

docker pull

docker pull 鏡像的速度對服務啓動速度至關重要,好在registry v2後可以並行pull了,速度有了很大改善。但是依然有一些小的問題影響了啓動的速度:

  1. 下載鏡像和解壓鏡像是串行的;
  2. 串行解壓,由於v2都是gzip要解壓,儘管並行下載了還是串行解壓,內網的話解壓時間比網絡傳輸都要長;
  3. 和registry通信, registry在pull的過程中並不提供下載內容只是提供下載url和鑑權,這一部分加長了網絡傳輸,而且一些metadata還是要去後端存儲獲取,延時還是有一些的。

通過剛纔的分析,大家可以看到,其實docker build、push、pull其實主要耗時是在網絡傳輸(主要)及安全防範措施(輕微)上,整個傳輸過程甚至大大超過了其他所有步驟的時間;這樣可以藉助我們的AppHouse方便的搭建本地企業級鏡像倉庫,將網絡傳輸轉移至內網,同時完全掌控了 build、push和pull的過程,這樣提高效率的同時也解決了安全問題,可謂一舉兩得。

經過Docker、AppHouse的幫助,我們距極致追求的如絲般順滑的持續集成目標只有一步之遙,Docker解決了依賴和環境問題,AppHouse解決了鏡像安全快速傳輸的問題,接下來就是容器的部署和管理問題。

Docker實現了底層技術的創新,它的出現將開發者從與系統的糾纏中釋放了出來,但是阻礙企業使用Docker的問題是容器的大規模部署、管理問題和缺少企業級容器工具及系統。

鏡像創建完成後,需要把它發佈到測試和生產環境。因爲Docker佔用資源小,在單個服務器上部署成百上千個容器也不足爲奇。這個階段中如何更合理地使用Docker也是一個難點,開發團隊需要考慮如何打造一個可伸縮擴展的分發環境。

AppSoar提供人性化的Web管理界面,豐富的Compose文件格式和功能完備的API接口,通過Compose實現以十分簡單的文件描述複雜的應用結構,讓部署變得更簡單。並且,AppSoar還提供豐富的企業應用商店,讓在一鍵創建服務成爲可能。這樣可以快速搭建應用場景,開發者只需要關注開發本身即可。
打通最後一個環節後,整個持續集成平臺架構演進到如圖2所示。

圖2   整體持續集成平臺架構演進

圖2 整體持續集成平臺架構演進

總結

通過Docker+AppSoar+AppHouse的組合,開發團隊面對複雜的環境時,可以結合自己團隊的實際情況,定製出適合自己的方案,從而打造出一套如絲般順滑的持續集成系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章