目前IT行業中,似乎“要不要做持續整合?”已經不再是討論的焦點,取而代之的是“如何進行持續整合?”。在前一篇文章中,我介紹了Cruise團隊持續整合的演化過程。在最後,還曾提及Cruise團隊的持續部署。本文將結合團隊的實際情況,與大家分享持續部署的實踐心得。
“最後一哩”問題
持續整合解決了軟體開發中的部分問題,但還有更為重要的一部分有待解決,即“通過什麼樣的方法,可以讓軟體儘快地在真正的生產環境下運行,從而實現軟體的價值”。在軟體開發過程中,“從功能開發完成開始直到將其部署至生產環境中正事運行”這一階段被稱為“最後一哩 ”。如果從一開始就對產品發布足夠重視的話,那麼這“最後一哩”可能只需要幾分鐘,甚至幾秒鐘就完成了。然而,事實上大多數項目在這一階段會花上幾個星期,更有甚者可能會是幾個月。
為什麼會這樣呢?對於複雜軟體來說,無論什麼環境中的部署(測試環境,試運行環境,還是生產)都很困難。當軟體第一次被部署到非開發環境去測試,或者當軟
件功能及其環境有較大變化時,通常都會暴露出很多問題。而在做使用者驗收測試時,常常會發現更多的問題,例如不能滿足非功能需求,使用者操作不方便,功能與用
戶真正需要的東西相差太遠等等。而Team Dev只有修複這些缺陷後,才能再次部署測試。於是,這個過程會不斷反覆,直至該軟體足夠穩定,才可以部署到生產環境
中。
即然部署到測試環境都這麼困難,那麼在生產環境中部署的風險豈不會更大嗎?而且,更為嚴重的是:當生產環境部署出現問題時,擺在你面前的選擇就所剩無幾啦(通常是復原到以前的狀態,而“復原”這段時間的停機成本是相當高的)。
上述原因就會導致大多數組織對產品的發布採用“保守策略”,即降低軟體的發布頻率,這也導致兩次發布之間的版本特性差異相對較大。這樣一來,發布風險並未
因發布間隔時間加長而降低,反而更高了。當各方面的因素結合在一起時,軟體發布這一環節就變得昂貴而又緩慢啦。而通常“發布過程與頻率”決定了產品在市場
中的位置。
那麼,如何更好地解決“最後一哩”這一問題呢?實現持續部署!
即將持續整合實踐擴充到整個軟體生命週期頻繁且規律性地自動構建代碼並將其部署到測試環境中,然後通過一系列的測試,選擇適當的版本部署到預演環境中試運
行,最後選擇穩定的版本部署到生產環境中,從而使Team Dev儘早從最終客戶那裡得到反饋,而最終客戶儘早得到軟體的價值。
“持續部署”源於部署時的痛苦
在使用Cruise來構建Cruise本身以後的第二周后,當我們想再次升級它時,因沒有事先考慮好升級方式,結果用了兩人天才把它搞定。從那以後,我們就開始了Cruise的持續部署之旅。
持續部署環境
目前Cruise的研發環境中,有一個被稱為“UAT(User Acceptance
Testing)”的測試環境,目前它由一台Cruise
Server和近20台Agent組成,用於Cruise團隊自身的持續整合與部署管理。還有另一個被稱為“Production”的預發布生產環境,它
由一台Cruise
Server和近70台Agent組成,由多重專案組同時使用。“Production”環境是真實的生產環境,部署失敗,就意味著損失。因此,雖然部署
工作可以通過自動化指令碼完成,但我們還是在“UAT”和“Production”兩個Stage之前加上了人工開關(manual
approval),如所示。前三個Stage全部是自動觸發,其後全部為手工觸發。每個待發布的版本都會先被部署到UAT環境中,實際上也就做了試
運行,如果該版本穩定,則部署到“Production”環境中。這樣就使部署風險盡在掌控之中。
讓持續部署成功的要點
1. 充分而廣泛的自動化測試覆蓋
目前我們的測試包括單元測試、End2End測試、功能測試和效能測試。其中單元測試、End2End測試及功能測試都在同一個Pipeline中,每次代碼提交都會運行這些測試。而效能測試在另一個Pipeline中,用於每次部署後,收集UAT環境和Production環境的效能指標。由於部署頻率足夠,我們可以掌握效能資料的微小變化,據此來採取相應的最佳化措施。
寫單元測試已經成為不爭的事實,自不必說。另外,由於Cruise與很多版本管理軟體打交道,這裡所說的End2End測試是指與這些外部介面的測試。而功
能測試是指將Cruise
Server和Agent真正在測試機器上運行起來後,再運行TWIST自動化測試套件。我們對功能測試的原則就是每個Story都要有功能測試覆
蓋,QA與開發人員共用編寫功能測試用例,由開發人員實現之,而且功能測試要讓真實的Cruise Server和Agent進行通訊的基礎上進行。TWIST是我們公司的另一款產品,用於自動化功能測試,其測試編輯介面如下所示:
2. 儘可能短的測試反饋時間
儘管測試數量較大,測試的絕對已耗用時間較長,但結合Cruise本身提供的並行運行特性,團隊成員胡凱,Derek和李彥輝自行開發的測試負載平衡工具(Test-load-balancer)將所有測試分成若干份,Cruise將其分配到Agent叢集中同時運行,使單元測試或其它測試在可接受的相對時間內完成(單元測試在15分鐘之內,功能測試在30分鐘之內)。近期還將增加數個Agent,以便繼續縮短測試需要的時間。
3. 部署過程自動化
當部署複雜軟體時,都會使用人工過程,而且可能會花上幾天的時間。而這種部署過程通常比較複雜,而且很難可靠地重複操作。因此,人們會寫一些文檔協助這一過程,但文檔常常更新不及時。有時,還需要幾個關鍵性人物同時在場才能完成。
當每次由不同的人員進行部署操作時,出錯的機率就增加,所以要儘可能少的人工步驟。
在Cruise的Pipeline中,儘管由人來觸發兩個環境中的部署,但部署過程本身是自動化的。在部署過程中,Cruise的安裝包會自動關閉
伺服器,更新自身程式和升級資料庫,然後再重新啟動。所有的Agent也會以Server為準,自動更新到與其相同的版本,而不必人工去升級每個
Agent(每次為70個Agent的手動升級也是很大的成本,所以我們做了自動升級這個特性)。
4. 部署過程要保證資料安全
如果因為持續部署而導致資料丟失或錯誤,會得不償失。所以,我們每次修改資料庫結構或設定檔的結構後,都會寫出相應的遷移指令碼,並在部署過程中運行。
目前我們使用由Thoughtworkers開發的開源項目DBDeploy來做資料庫升級。對於XML設定檔的修改,我們也自行開發了一個遷移架構。
5. 在穩定的前提下,儘早部署
有人會問:“為什麼要持續部署?你又如何知道部署的版本是否穩定呢?宕機了怎麼辦?”的確,沒有哪個開發人員希望持續整合伺服器在工作時間內宕機。儘管我們無法百分之百確保每個部署版本都穩定,但在可預見的範圍內穩定就可以了,否則我們就無法解決“最後一哩”問題。
Cruise在最初三個迭代(迭代時間為一個星期)後,就開始用Cruise來做自己的持續整合伺服器了(即UAT環境)。我們讓它在UAT環境上
運行了兩周,沒有發現什麼問題,說明版本相對穩定,就將它部署到“Production”環境上了。在那以後,隨著使用者的增多,很多人認為由於部署失敗而
導致持續整合伺服器宕機的風險要高於那些新特性和修複的缺陷。因此,我們的客戶要求新版本部署至
“Production”環境之前,一定要在UAT環境上運行。目前,Cruise部署到UAT環境的頻率不固定(一般為兩天至一周),而部署到
Production環境的頻率為一周。也就是說,Production環境上的版本要落後於UAT一周的時間。
6. 完善的風險降低措施
隨著項目的進行,難免會有部署失敗的情況,所以一定要有風險降低措施。例如:
(1) 部署儘可能在使用者少的時候;(2) 部署時必須有技術人員在場;(2) 每次部署前備份未經處理資料;(3) 時刻準備復原指令碼。
7. 將同樣的產物部署到不同的環境中
讓你的產品可以部署到不同的環境中,如果這些環境的環境配置不同,則把有關環境配置的內容排除在你的產品之外。如果你有很多個組態變數,請讓這些配置儲存在同一處,而且有預設值。
基於這一原則,Cruise唯一的配置就是XML檔案。
8. 不斷的反思與重構
這一點就沒有什麼可說的了。它適用於所有的活動。
小結
實踐表明,建立自動化部署管道的益處很多。在過去的幾年中,ThoughtWorks利用這一方法協助很多項目組和公司解決了他們的“最後一哩”問題。例如,在某項目中,通過自動化部署過程,使部署頻率從幾天一次提高到每天一次,而且該過程耗時少於15中分鐘(其僅有一分鐘的停機時間)。這對軟體整個生命週期的交付階段有著積極作用,只要按下滑鼠就可以準備好所需要測試環境,從而減少了人為失誤造成的不必要的損失,顯著降低軟體發布的風險。另外,頻繁且輕鬆的發布讓開發人員全神貫注於他們想做的事情:開發新的功能。