持續集成理論和實踐的新進展
轉自:
最近雷鎮同學將Martin Fowler先生的著名論文《持續集成》第二版翻譯成中文併發布出來,掀起了國內對於持續集成理論和實踐討論的新的高潮。筆者在本文中將全面對比持續集成 論文前後兩版的異同,分析並展示ThoughtWorks在持續集成領域的理論和實踐方面的研究成果,以圖對國內企業實施持續集成起到參考和借鑑作用。需 要說明的是,本文所介紹的內容畢竟限於筆者的水平,並且主要是ThoughtWorks內部開發和對外諮詢實踐的總結,所以未必對讀者所遇到的情況是適用 的,請自行甄別。
《持續集成》第二版雖然是最近才翻譯出來,但是實際上Martin Fowler先生完成此文是在5年前的事情。這五年恰好是ThoughtWorks中國公司快速成長的五年。在這五年內ThoughtWorks中國在持 續集成領域也有很多的發展,這包括:著名的持續集成工具Cruise主要是由中國公司負責開發1; 中國公司幫助國內很多大中型企業完成持續集成實施和相關的流程改進;2009年中國公司的很多同事對於持續集成的度量進行了深入的討論並且最終由胡凱將其 實現爲一款軟件iAnalysis;2010年至2011年成功的交付了從需求提供方到多個技術服務提供商的持續集成方案,以及企業級自動化中心方案。所 以,本文主要包括兩部分內容,一部分是通過對比第一版與第二版的異同介紹2000年到2006年之間持續集成領域的主要發展,另一部分則是介紹第二版發表 之後持續集成領域的新進展。讀者如果之前沒有閱讀過《持續集成》論文的第二版,建議將本文第一部分一同閱讀,因爲本文並非對論文的重述,所以很多地方還需 要參考原文中的內容。
第一部分 《持續集成》第一版與第二版
《持續集成》第一版由ThoughtWorks首席科學家Martin Fowler先生和Matthew Foemmel共同完成2,第二版由Martin Fowler先生更新。
《持續集成》的第一版中並沒有給出比較正式的定義,雖然作者在文中說是借鑑了XP實踐中的術語,但是目前能看到的XP實踐中對持續集成的定義實際上大多數都是指向了Martin的文章。那麼我們還是來看看第二版中給出的定義。
持續集成是一種軟件開發實踐。在持續集成中,團隊成員頻繁集成他們的工作成果,一般每人每天至少集成一次,也可以多次。每次集成會經過自動構建(包 括自動測試)的驗證,以儘快發現集成錯誤。許多團隊發現這種方法可以顯著減少集成引起的問題,並可以加快團隊合作軟件開發的速度。3
第二版相對於第一版增加了不少內容,其中最重要的幾點包括:
- 詳細介紹了使用持續集成進行軟件開發的工作流程。
- 突出了配置管理在持續集成實踐中的作用。
- 提出分階段構建的概念。
- 增加了持續集成報告的內容。
- 增加了持續部署的內容。
- 給出了引入持續集成的建議。
1) 持續集成的流程
在持續集成領域,我們經常會用到的一個術語就是“構建(Build)”。很多人認爲“構建=編譯+鏈接(Build=Compile+Link)”,Martin在第一版中指出一次成功構建包括:
- 所有最新代碼從配置管理工具中取出(check out或者update)。
- 所有的代碼從乾淨的狀態開始編譯。
- 將編譯結果鏈接並部署,以備執行。
- 執行部署的應用並運行測試套。
- 如果上述所有操作沒有任何錯誤,沒有人工干預,並通過了所有測試,我們認爲這纔是一次成功的構建。
實際上,目前很多團隊對成功持續集成構建的定義基本上是符合上述定義的。這個定義的特點在於它是相對獨立的,它是一個從乾淨狀態的源代碼最終獲得可運行的通過驗證的軟件的過程。
Martin在第二版中則在成功構建的基礎上給出了成功集成的定義。成功集成關注的不是一次“編譯+鏈接+部署+驗證”的過程,而是從開發流程的角度介紹一次完整的在持續集成約束下的代碼提交過程4:
- 將已集成的源代碼複製一份到本地計算機。
- 修改產品代碼和添加修改自動化測試。
- 在自己的計算機上啓動一個自動化構建。
- 構建成功後,把別人的修改更新到我的工作拷貝中。
- 再重新做構建。
- 把修改提交到源碼倉庫。
- 在集成計算機上並基於主線的代碼再做一次構建。
- 只有這次構建成功了,才說明改動被成功的集成了。
下圖展示了Martin對成功集成的定義:
當然在第一版的“代碼提交”這一節,Martin也提到了本地構建的概念,只是不如第二版這麼明確。
2) 配置管理
Martin在第一版中有兩處提及配置管理,分別是:單一代碼源(Single Source Point)和代碼提交(Checking In)。第二版中則包括:通過持續集成構建特性(Building a Feature with Continuous Integration)、只維護一個代碼倉庫(Maintain a Single Source Repository)、每人每天都要向主線提交代碼(Everyone Commits To The Mainline Every day)、每次提交都應在集成計算機上重新構建主線(Every Commit Should Build the Mainline on an Integration Machine)。不僅條目數量上增加明顯,作者提出的很多實踐都是基於配置管理來講的。
工具
配置管理是持續集成的輸入。在第一版中作者所推薦的配置管理工具是CVS,到第二版中作者推薦的配置管理工具已經換成了SVN5(參見第二部分中的配置管理工具部分)。
分支策略
實現進度與質量的平衡是配置管理的重要目的。Martin在第二版中對濫用分支給出了警告:
儘量減少分支數量。典型的情況是保持一條主線,......,每個人都從這條主線開始自己的工作。(對之前發佈版本進行Bug修正或者臨時性的實驗都是創建分支的正當理由。)
但是這裏給出的建議對於大型團隊來說並不十分合適。我們將在第二部分對於配置管理的分支策略進行詳細描述。
內容
Martin在第一版中給出的原則是:
任何人都可以找到一臺乾淨的機器,連上網,通過一個命令就可以取得要構建所開發的系統需要的所有源文件。
第二版中的原則增加了對構建的支持6:
任何人都可以找到一臺乾淨的機器,做一次取出(checkout)動作,然後對系統執行一次完整的構建。
3) 分階段構建(Staged Build)
分階段構建是Cruise(已經更名爲Go)引入的重要概念。其主要的意義在於:
- 分離關注度不同的驗證階段,比如Commit Build和Regression Tests,團隊會對不同的驗證階段採取不同的策略
- 構建流程可視化
- 通過分階段併發構建來縮短反饋週期
當構建的時間過長時,我們通常會要求開發人員只運行速度較快的價值較高的構建階段就可以繼續自己的開發任務,而不必等待漫長的次級構建完成。這裏作者提到ThoughtWorks不同的團隊有很多有趣的實踐,我們將在第二部分向讀者介紹其中的一部分。
報告
作者在第二版中專門拿出一節“每個人都能看到進度(Everyone Can See What's Happening)”來介紹有關持續集成報告的內容。因爲:
持續集成的目的是爲了溝通。
這是第二版相對於第一版來說一個非常明顯的變化。在第一版中通知的手段還主要是電子郵件,實際上在作者撰寫第二版的時候,ThoughtWorks已經不贊成將電子郵件作爲主要的持續集成通知工具了。更好的溝通工具包括音樂、熔岩燈、顯示器等。
對於溝通的重視從工具的角度也可以體現出來。Cruise Control最主要做的事情是任務調度,在報告部分做的相對來說非常粗糙,比較有價值的報告大部分是從Cruise移植過去的。Cruise在從一開始 就非常重視這一點,通過Cruise你可以非常清晰地知道,代碼發生了什麼變化、正在進行的構建的狀態和歷史構建的狀態。網頁的形式對於分佈式團隊來說具 有不可替代的優勢。
正如我們前面所說的,音樂、熔岩燈等物理手段,具有更強的信息輻射能力。站起來往周圍看一看就知道哪個團隊的構建成功了,哪個失敗了。
4) 持續部署
持續集成實踐有一個基本的思想就是:越是痛苦的事情,越要經常做。集成之後更令人心驚膽顫的事情就是——部署。部署到生產環境的流程通常要嚴格得多,然而所有的工作必須經歷了生產環境的驗證纔算是成功的,所以——持續部署纔是王道。Martin在第二版中建議:
你應該有一個腳本幫助你很容易地將系統部署到生產環境中去。......同時要特別考慮的是要能夠自動回滾。
引入持續集成的建議
作者在第二版中特別給出了逐步引入持續集成的建議。包括:
- 引入版本控制。
- 實現自動化構建。
- 添加自動化測試。
- 加快提交構建。
- 尋找幫助。(比如ThoughtWorks)
第二部分 持續集成領域的新進展
正如前文所說,ThoughtWorks中國公司在過去的幾年裏面對於持續集成實踐和幫助客戶實施持續集成都積累了很多的經驗,同時在理論體系方面也更加豐富完整。這也使ThoughtWorks在這個領域繼續保持了行業領先的位置。
正如我們在第一部分講到的,持續集成不應該只作爲一個孤立的實踐來應用。我們的經驗表明如果只把持續集成作爲一個孤立的實踐應用很難從持續集成長期 受益。持續集成往往進入“長紅”或者“長綠”的不正常的狀態。長紅意味着系統長期無法集成;長綠則往往意味着缺少足夠的驗證。爲了術語上的澄清,我們明確 地將持續集成的定義區分爲狹義的持續集成和廣義的持續集成。
狹義的持續集成:基於某種或者某些變化對系統進行的經常性的構建活動。
廣義的持續集成:軟件開發團隊在上述活動的約束下所採用的開發流程。
1) 狹義的持續集成
一般來說,狹義的持續集成包括如下幾個方面:持續檢查、持續編譯(鏈接)、持續驗證、持續部署、持續基礎設施、持續報告等6個方面。
持續檢查
持續檢查的目的是保證代碼風格一致,主要關注於代碼的靜態質量和內部質量,比如變量命名方式、大括號位置等等。大部分的現代集成開發環境(IDE) 都具備實時檢查代碼質量的功能。爲了保證主線上的代碼質量能夠達到一致的標準,應當在持續集成腳本中加入靜態檢查階段。比如,Java的PMD、 FindBugs等等。
持續編譯
持續編譯是一個很樸素的想法,就是保證主線上的代碼始終處於可編譯的狀態。但是這對於很多大中型團隊來說卻並非想當然的簡單。這是因爲很多團隊並未 採用集體代碼所有權策略,導致存在依賴的團隊的代碼無法編譯。針對這樣的問題,一方面我們建議採用集體代碼所有權;另一方面,對於確實因爲安全原因需要隔 離的代碼應該邊界、明確接口,很少存在大部分代碼需要對大部分人保密的情況。
持續檢查和持續編譯是持續集成中最基本的驗證手段。
持續驗證
持續驗證的目的是檢查主線上的代碼是否能夠實現所要求的功能,或者已有的功能是否被破壞。在大部分的構建中,驗證部分是耗時最長、成本最高的部分, 但同時也是收益最大的部分。在這個階段,我們看到的主要問題是驗證不充分和驗證時間過長。針對這樣的問題,我們通常採用分層分級的持續集成策略。後面有詳 細的描述。
持續部署
對於大型軟件應用來說,部署往往是一個費時費力又容易出錯的步驟,因爲這裏面涉及到數據遷移、版本兼容等各種棘手的問題。持續部署的思想是將這些工作標準化自動化,使其能夠可靠地、自動地、快速地運行。持續部署是當前DevOps運動中的熱門話題之一。
持續基礎設施集成
現代大型軟件開發,尤其是互聯網應用開發中經常依賴於一些常見的基礎設施——比如Spring、Tomcat、Database等等。這些基礎設施發生變化的時候,我們應當及時地觸發持續集成,以確保我們的系統是能夠與新的基礎設施一起工作的。
持續報告
報告是將持續集成的狀態以適當的形式展現給干係人的基本手段。報告是持續集成的晴雨表,所以它必須直觀、易懂,而且對關注點不同的角色展現不同的內 容和粒度。比如,開發人員可能更關心錯誤的日誌;項目經理可能更關心測試覆蓋率;產品經理可能更關心持續集成通過率的趨勢等等。
2) 廣義的持續集成及持續集成策略
當要把持續集成實踐應用到團隊的時候,有很多額外的技術或者非技術因素需要考慮。
組織結構
持續集成是一個重要的溝通工具,而開發過程中兩個最需要緊密溝通的角色就是開發和測試。在我們常見的組織結構中開發和測試往往隸屬於不同的部門,甚 至這些部門隸屬於不同的高級經理。這往往會給持續集成的推廣帶來很大的阻力。這是因爲持續集成從環境搭建到運行維護都需要兩種角色的通力合作。我們的經驗 是這類涉及到人力資源的事情除非某一級“共同的大老闆”出面,否則是很難協調的。“借調”這樣的方式往往不能保證效果。
流程
放到團隊的角度看待流程應當更加關注於各個成員之間的配合。每個開發人員提交代碼之前應當確保是經過本地構建的;開發人員在提交之前應該確認主線上的代碼是通過了持續集成的;測試人員測試的版本應該是通過了某次持續集成的,並且有相應的具體版本信息。
爲了保障流程的順利執行,我們還經常採用持續集成看板、提交令牌等輔助手段。
環境
環境是指持續集成運行時所依賴的軟件和硬件的集合。我們經常遇到的一個問題是,軟件在一臺機器上能夠通過持續集成的驗證,而在另一臺機器上則不能通 過。這通常是因爲我們對持續集成環境的定義不明確造成的。所以在搭建持續集成和在組織內推廣持續集成的時候,我們需要特別注意持續集成環境的標準化,明確 指出持續集成運行時依賴哪些第三方庫,機器配置如何,端口和網絡狀況如何等等。
我們經常採用將持續集成環境加入配置管理的方式來解決環境標準化的問題。
分層
在大型團隊(超過100人)中,扁平的開發組織結構是運行起來是比較困難的。常見的做法是按照特性,將團隊劃分爲10人左右的小團隊。一般來說,如 果團隊數量超過10個,還會再增加一層架構。這時候,配置管理的策略也應當做出調整。常見的做法是爲每個團隊拉出一個分支,設置一個集成分支用於將各個特 性分支的內容整合在一起。需要注意的是,這裏每個分支都應該具備所有代碼的訪問權限,也就是所有分支是同根的、等價的。
測試分級
在實施持續集成的時對於測試的類型應該有比較明確的定義。一般來說,我們經常把測試分爲三級——單元測試、集成測試和系統測試。這是一個很大的話 題,這裏只是說明此處的單元測試並不是指針對函數的測試。雖然單元測試主要是函數基本的測試,但是每個單元測試應該針對的是特性或者對應代碼在實現該特性 上所發揮的作用。
單元測試的運行速度通常非常快,應該在數秒到數分鐘。單元測試應該覆蓋絕大部分的特性需求。集成測試單個測試的運行速度通常會比單元測試慢一個數量 級,比如存在文件讀寫或者其他的IO和網絡操作。集成測試主要用於保證系統的各個組件之間的調用是工作的。系統測試的運行速度一般會比集成測試慢,通常需 要將整個系統運行起來,比如Web開發中的selenium測試。系統測試主要用於測試系統的正確路徑(Happy Path)可以工作。有的團隊會開發很多基於整個系統的迴歸測試,也屬於系統測試。就場景的覆蓋而言,單元測試>集成測試>系統測試,從運行 時間來看則相反。
3) 持續集成的成熟度評估
在幫助客戶實施和推廣持續集成的過程中我們逐漸總結出一些原則,幫助客戶評估現狀,分析和設計未來的目標。該評估方法借鑑了ThoughtWorks敏捷成熟度模型中有關配置管理、測試、構建等內容,並做了適當的簡化。
構建:
級別
|
描述
|
3+:對外防禦的
|
團隊能夠根據自己的需要,協調其他團隊的持續集成,當依賴的其他團隊的代碼和組件或者第三方庫和基礎設施發生改變時自動進行構建。團隊對於外部依賴的可靠性有信心。
|
3:對內防禦的
|
構建是自動的。由測試和檢查來保證團隊內部的開發質量。持續集成的修復具有最高的優先級。團隊對持續集成的結果有信心。
|
2:頻繁的
|
構建是自動的,而且構建的速度較快。構建的觸發條件是明確的,通常每次代碼提交都會觸發構建。團隊中的每個人都會觸發構建,並且瞭解構建的狀態。
|
1:重複執行
|
構建是自動的,但是執行的不夠頻繁。構建的觸發是隨機的或者頻率是非常低的(低於每天一次)。構建的速度通常非常慢,比如一次構建超過半個小時。
|
0:可重複的
|
主要依賴於手動的方式構建軟件,但是每次構建的方式都是相同的或者相似的。通常有相關的文檔的指導。經常團隊指定某個人負責構建軟件,雖然大部分人都能夠做這件事情。
|
-1:手動的
|
主要依賴於手動的方式集成軟件。每次集成的方式可能不一樣。經常團隊中只有個別人能夠將軟件集成起來。
|
測試:
級別
|
描述
|
3+:全面集成的
|
全團隊對測試負責。測試驅動整個開發過程。測試與構建完全集成。
|
3:測試驅動的
|
業務分析人員和開發人員均參與測試。測試在構建過程中自動執行。開發人員實踐測試驅動開發。
|
2:集成的
|
開發人員參與測試。部分測試集成在構建過程中執行。大部分測試在軟件開發過程中執行。
|
1:共享的
|
開發人員參與測試。測試並未集成在構建過程中。部分測試在軟件開發過程中執行,大部分測試在軟件開發結束後執行。
|
0:審查的
|
測試由專門的測試人員負責。有部分測試是在軟件開發過程中執行。但大部分測試在軟件開發結束後執行。
|
-1:獨立的
|
測試由專門的測試人員負責。僅在軟件開發結束後執行。
|
配置管理:
級別
|
描述
|
3+:企業級的
|
企業有統一的配置管理策略。
|
3:跨項目的
|
配置管理在多個項目之間協調。
|
2:自動的
|
配置管理策略與持續集成策略緊密結合,團隊成員有頻繁提交的意識。一般採用樂觀鎖策略,原子提交。
|
1:集成的
|
版本管理下的內容是受控的。通常在版本管理中的代碼是可以編譯的。開發人員能夠訪問到自己工作所需要的代碼。開發人員按照一定的規則訪問配置管理工具和提交代碼。一般採用悲觀鎖策略。版本管理工具和構建過程集成在一起的。
|
0:基本的
|
有基本的版本管理。但版本管理下的內容不全面,或者不足以支撐團隊的開發。
|
-1:無配置管理
|
沒有配置管理。或者使用方式完全錯誤。
|
4) 常用實踐和工具
持續集成看板7
問題:
我們需要讓整個團隊能夠方便快捷的瞭解持續集成的狀態。
上下文:
在完成了基本的構建基礎設施的搭建之後,我們需要讓團隊成員及時獲得持續集成的狀態信息。傳統的郵件方式可能會使人厭煩,或者錯過重要的構建信息。
解決方案和實現:
安裝一個顯示器,將構建的狀態信息以簡明的方式展示在顯示器上。將顯示器放置在團隊所有成員都能夠很容易看到的地方。
個人構建中心
問題:
在某些情況下,構建環境的成本很高,而我們需要每一個開發人員提交之前完成一次個人構建。
上下文:
有些測試是依賴於設備的,而這些設備非常昂貴,並且配置複雜,所以無法給每個開發/測試人員一套構建環境。我們發現根據本地構建的理論模型,每個人的提交應該是串行的,這樣我們實際上可以做到讓這些人共享一套環境,但是從邏輯上就像是每個人有一套自己的環境一樣。
解決方案:
在運行個人構建的時候將本地的代碼同步到一臺共享的機器上執行構建,構建完成後結果反饋給提交這次個人構建的人。
實現:
個人構建中心的實現有很多種方案。這些方案的區別主要在於如何將代碼同步到個人構建中心服務器上。兩種常見的方式:一個是使用rsync或者類似方式同步;另一個是使用分佈式配置管理工具如git/hg同步。其拓撲結構如下:
第一種方式相對獨立靈活;第二種方式穩定、高效,但是對於配置管理工具有依賴。
後果:
個人構建中心節省了大量的計算資源,同時也容易使得中心服務器成爲單點失敗的源頭。一旦中心服務器出現問題,可能會導致團隊的流程受到較大影響。
提交令牌
問題:
在實施本地構建的時候,向目標分支的提交應該是串行的,以避免構建被破壞後難以定位問題來源。但是團隊往往缺乏一種有效的機制來保證這種串行。
上下文:
有些團隊試圖通過技術的手段來解決這個問題,比如通過配置管理上的鎖機制,這種方式和樂觀鎖模式有較大沖突。有些團隊通過團隊內部溝通的方式解決, 比如誰提 交之前都會通知別人,或者通過持續集成監視器來了解當前的構建狀態,以決定自己是否可以提交。這些方式各自有各自的適用情形,較容易理解。
解決方案和實現:
使用一個實物作爲令牌,準備提交的代碼的人首先取得令牌,當代碼提交完成(包括相應的提交構建)之後,將令牌交還。令牌要醒目,並且移動方便。小型獎盃、毛絨玩具、較大的頭飾(如下圖)都是不錯的令牌。
分階段構建
問題:
在某些團隊中完整構建所花費的時間可能很長,如果每次提交都運行完整的構建會浪費很多時間。
上下文:
隨着持續集成的日益完善,我們往往會發現驗證所花費的時間越來越長,而大部分驗證趨於穩定,失敗的情況很少見。通過技術手段縮短構建時間是解決問題的根本辦法,但是縮短構建時間是一個耗時耗力的工作,很難短期內見效。
解決方案和實現:
將構建分爲幾個階段執行,在本地構建中僅執行速度比較快、可信度比較高、出錯概率比較大的驗證。利用晚上或者其他合適的時間執行全面的驗證——我們這次構建稱爲全量構建。需要注意的是,這種情況下仍然要保證提交構建和本地構建的一致性。
iAnalysis
iAnalysis是一款輕量級的持續集成報告工具。該工具的核心思想是將持續集成構建過程中產生的數據以趨勢和對比的方式展示出來。正如前文所 說,我們在2009年的ThoughtWorks Away-Day上討論了敏捷度量的話題,大家最後一致認爲,數據有兩種最基本的用法——橫向對比和縱向對比。橫向對比就是不同的人、不同的團隊之間對 比;縱向對比就是現在和過去對比。iAnalysis正是這種思想的體現。
關於作者
肖鵬,ThoughtWorks資深諮詢師,程序員,敏捷過程教練。擁有7年以上軟件開發實踐經驗,多次擔任大中型企業敏捷流程改進諮詢和培訓,服 務對象涉及通信設備製造、通信運營、互聯網行業等。他關注於設計模式、架構模式、敏捷軟件開發等領域,並致力於軟件開發最佳實踐的推廣和應用。他曾參與翻 譯《Visual Studio 2005技術大全》,主持翻譯《面向模式的軟件架構》第四卷和第五卷等圖書。
2 實際上這篇文章介紹的是Matthew所在團隊在2000年早些時候已經在使用的實踐,ThoughtWorks中國公司的總經理郭曉先生當時也在這個團 隊。
3 爲了與ThoughtWorks常用的術語保持一致,部分術語與雷鎮和熊節同學所譯略有差別,下同。
5 Martin最近在自己的一篇博客上對幾種流行的配置管理工具做了對比。