持續交付模式

作者: Jonathan  來源: InfoQ  發布時間: 2013-05-20 08:41  閱讀: 5775 次  推薦: 2   原文鏈接   [收藏]  

  英文原文:Patterns for Continuous Delivery

  當你有了持續集成需要的構建服務器和腳本之后,下一個問題肯定是:“我們該拿這些構建版本怎么辦?”持續交付,以自動化或半自動化方式,將構建版本從一個環境提送(promote)到更接近實際生產的交付準備環境;這常常是公司在這方面演進的下一步。

  任何規模的公司都可以實施持續交付,但是具體流程會根據公司具體情況差異很大。顯而易見,四人全能團隊的需要,與大規模、多團隊、配備正式QA和設備精良的產品支持部門這樣的公司,二者一定有很大差別。本文沒有試圖提出萬能方案,而是覆蓋多種場景和選擇。

  選擇持續交付工具集

  為持續交付選擇工具集,是最不重要的決策。當你制定出工作流程之后,只需要選擇相匹配的工具集即可。考慮到初始設置和配置的工作量,花幾天時間構建自己定制的工具也不是非理性選擇。

  更重要的是,這樣做沒有鎖定的風險。跟選擇版本控制系統不同,你可以使用多種持續交付工具,而且可以互相之間自由切換。QA團隊從開發人員那里拉出構建版本使用一種工具,向準備服務器推送時使用另一種工具,這樣的事情也有所耳聞。

  基線場景

  在基線場景中,我們要以有限的資源找出公司中的模式。像由3到4人構成的IT部門,常常兼做開發和系統管理。這樣規模的團隊常常支持中小型業務,特別是不以技術為主業務的公司。大型公司也有可能這樣組織,把人分成多個、很可能是互相獨立的小組,各小組之間也沒有交互。

  在真正開始進行持續交付之前,需要做一些必要的設置工作。首先,最重要的是要有版本控制系統,以及與之匹配的構建服務器。這第一臺構建服務器將會是你的持續集成服務器,它要保證每次簽入動作都能成功構建。一般來說,你需要一個“現成”的構建服務器擔當此任。能夠監控簽入動作還要自動初始化構建,要想手工開發這樣的東西,真正操作起來常常要比聽起來難得多。即使可以在版本控制系統中加入觸發機制,完成諸如失敗構建通知這樣的功能也不值得花太多功夫。

  即使在資源有限的作坊式公司,交付準備服務器(staging server)也是持續交付的必要組成部分。交付準備服務器應該盡可能模擬出生產環境。“你有多少預算”常常是首要問題。如果你的生產環境有價值幾十萬美元的數據庫服務器,很可能你無法負擔搭建一個同樣的交付準備服務器。而且可能你也不想這么做。

  在模擬生產環境時,有一個常見錯誤:過高匹配硬件環境。假如你的生產環境每秒處理100個請求,如果為交付準備服務器購買同樣硬件,但測試時每秒只執行幾個請求,你的結果就是有問題的。理想情況下,為了模擬生產環境請求,你還應該購買并設置負載服務器,但這么做成本太高,而且耗費時間。對于這種規模的團隊,降低交付準備服務器處理能力是更佳策略。

  另一個常常忽略的需求,是構建的版本化。必須有某種唯一的識別機制,保證可以區分開每個構建和其他構建版本。如果針對單一的公共主干代碼,簡單的時間戳或是自增長版本號也就夠了。本文后面會討論更復雜的場景。

  結構

  有了上面列出來的部件,現在你的環境應該跟下面的圖很像:

  交付策略

  構建服務器編譯代碼,交付準備服務器等候接收構建結果,有了這二者之后,下一步就是決定你的部署策略。小團隊有兩種選擇:“簽入時交付”和“定時交付”。

  簽入時交付

  “簽入時交付”策略的優勢在于馬上產生的滿足感。根據代碼庫的規模,從簽入新功能代碼到能夠在交付準備服務器上測試,一兩分鐘就夠了。

  這種方式的主要問題在于:交付準備服務器會被蹂躪得不穩定。很多時候,我見到有人試圖測試某個功能,突然新的版本推到交付準備服務器上了,破壞了正在運行的測試。更糟糕的是:交付準備服務器常常作為演示服務器使用,在某些重要的演示時,很可能出現嚴重的后果。

  代碼混亂是另一個問題。比如,如果三次簽入在很短的時間內先后進行,那么可能觸發三個構建、交付周期,實際上只有最后一次才是必要的。極端情況,這會帶來很大混亂,交付部署環境將無法有充足的時間展示自己的功用。不過,大多數構建服務器都有選項,可以延遲構建的開始,或是在給定時間間隔內避免多次構建。

  定期交付

  定期交付策略更易于預測。所有人都知道交付何時啟動,并可以規劃自己的代碼簽入是在交付之前還是之后進行。典型做法是一天構建/交付一次或兩次。

  每日構建的缺點在于,它會給工作環境帶來不必要的壓力。開發人員會發現自己要趕著在構建截止時間之前完成一次簽入。在每日深夜構建,這時開發人員應該不在工作,可以減輕壓力,但也意味著除非到第二天,他們無法執行第二層測試。

  當我們開始使用“每日構建”這樣的詞匯時,很容易忘記我們實際上不是在談論構建。我們真正在談論的是完整的構建、交付周期。有了持續集成,如果構建失敗,你馬上就能知道。接下來是要修復問題,還是要回滾簽入,就很簡單了;只要規劃好的交付完成了,軟件就已經準備好部署到生產環境。

  部署到生產環境

  該場景的假設是:實在沒人能夠完成任何特定版本前要做的大量測試工作。只要交付準備服務器上的檢查完成,構建版本就會直接提送到生產環境中。但即使如此,也有多種選項,各有其妥協之處。

  從交付準備階段提送

  將經過驗證的構建版本從交付準備服務器直接提送,是常見的選擇。其優勢在于高度確定性。理論上,測試一個版本的構建、然后錯誤地部署另一個版本,這樣的事情不可能發生。寫腳本把文件從交付準備服務器拷貝到生產環境,也很容易。

  但像很多事情一樣,理論跟現實總是有差距的。有了到交付準備服務器的自動化推送,很可能出現這樣的情況:測試完成,去倒一杯咖啡,回來后工作針對的構建版本可能就完全不同了。更糟糕的情況是:構建代理會開始覆蓋交付準備服務器上的文件,而這時這些文件正在推送到生產環境服務器上。

  從構建服務器提送

  將構建版本從構建服務器直接提送是更好的選擇。這可以消除我上面提到的那種方式中的多種時間沖突。而且,人們更容易知道被推送的是哪個版本。

  這種方法也有弱點:人們很容易選擇錯誤的版本推送到生產環境中。

  重新構建并推送

  第三種選擇是為持續集成服務器創建新的構建選項,其中包括推送到生產環境這一步。我提醒大家要小心這種方式。雖然理論上重新構建的代碼與你測試的代碼沒有區別,但還是有某些東西增加了出錯的可能性。

  另一個問題是:這讓你可以選擇多種編譯選項。如果你可以選擇,你可能希望在交付準備服務器上使用調試構建版本,在生產環境上使用發布構建版本。如果有些行為在兩個構建配置中有變化,這會產生災難性后果。

  雖然使用持續集成服務器做配置很容易,但我再次強調:我不推薦人們選擇這種方式。

  場景2:加入QA

  如果QA加入進來,事情就變得非常復雜了。因為你現在必須處理跨團隊的溝通和規劃,你需要更多的構建環境,還有更具意義的所有制感覺。

  結構

  一旦QA加入,你可能需要至少3個非生產環境服務器。在某些特定條件下,代碼變更從一臺服務器提送到下一臺服務器。

  集成環境

  這是功能代碼簽入后,構建版本到達的第一臺服務器。被稱為“集成環境”,是因為簽入功能代碼會與其他功能代碼完全集成。用來現場檢查一個構建版本是否適于提供給QA。這個環境中允許不穩定,但是不允許構建版本停留太長時間。

  QA環境

  這是QA團隊完成自己大部分工作的地方,根據需要會從集成環境更新代碼。

  交付準備環境

  交付準備環境現在完全用作演示和在構建進入生產環境前的最后檢查。任何構建版本到了這個環境,都應該是牢固可靠的。交付準備環境也許可以連接到生產環境資源,比如數據庫和文件系統,但不是必須這么做。

  QA交付選項

  從構建服務器向集成環境交付代碼,可以使用基線場景中提到的多種方式之一。從集成到QA環境就需要動動腦子,因為涉及多個團隊。下面是我看到的一些成功模式。

  開發者發起

  在“開發者發起”模式中,開發人員決定何時進行編譯檢查和把構建版本提送給QA。說得難聽一點,當QA完全“服從”開發人員時,可以使用該模式。可能第一眼看上去,這對開發人員來說還挺好,實際上常常陰暗著某種問題。比如:如果發生某個質量上的問題,QA人員可能要等很長時間,等著阻礙他們工作的bug得到解決。

  極端情況下,需要設置自動提送機制,定時自動提送到QA環境。

  QA發起

  這是更適合大部分團隊的典型方式。開發人員仍然參與,他們需要在集成環境中檢查他們的構建,然后確定構建版本是否可以提送。

  在這種方式下,QA準備好測試新功能時,他們會拉過來“已知正常”的最新構建版本。通常是QA經理完成該工作,一般來說他對于QA人員的需要最了解。雖說如此,有些QA團隊允許所有成員提取新構建版本。

  Test Runner啟動

  對于想真心做好自動化測試的公司來說,這才是目標。構建版本到達集成服務器后,整個自動化測試套件就開始運行。如果全部通過,構建版本會自動提送到QA。跟其他自動化交付方式一樣,可以采取簽入時方式或定時方式。

  不要低估這種方式需要投入的成本。不僅需要有完整的測試套件,所有的測試必須還都能通過。構建服務器無法區分測試失敗是由于新功能出問題,還是說有些問題需要留待將來解決。

  變通方式,是把測試拆分成必須通過(must-pass)的項目和臨時項目。測試從臨時項目開始,特別是在TDD風格編程中用到的測試。只要測試驗證正確、有用,代碼也可以通過這些測試,就轉而處理必須通過的項目。構建服務器不會運行臨時測試,但是會看重必須通過的測試項目的結果。

  交付準備/生產環境交付選項

  在持續交付思想指導下,QA對于接收到的給定構建版本,只有兩個選項:構建要么失敗,要么提送到交付準備服務器。QA不會一邊處理一個可用的構建版本,同時等待某些功能完成

  這的確提出一個問題:如何定義某個構建版本是“可用的”?任何可以安全放到生產環境的構建版本,都是可用的構建版本。如果其中有未完成的功能,但這些功能可以以as-is方式工作,那么構建就可以往前提送。除非某個失敗的功能會影響應用在生產環境中的使用,構建版本就不能停滯在QA這里。

  進入交付準備服務器后,構建版本在下個發布周期中就要提送到生產環境。盡管持續部署到生產環境很難完全實現,還是經常聽到成功的每周、甚至每日部署。關鍵點在于:一旦一個某件版本證明沒有問題,就需要快速移動到生產環境,這樣團隊就可以聚焦于下一系列要開發的功能。如果構建版本停留在交付準備階段長達幾周乃至幾個月,就會造成無盡的問題。

  把QA環境和交付準備環境分開,是為了推進工作流。只要一個構建版本提送到交付準備服務器,下一個構建版本就會從集成服務器拉過來。這樣一來,交付準備服務器就總會有一個穩定的環境,供利益相關者和其他第三方查看演示版本,同時QA仍有自己可以工作的環境。如果把服務器的職責混合起來,當環境鎖定時,QA的工作就必須要停下來。

  配置相關問題

  一旦有了多個環境之后,配置文件就會成為嚴重的問題。舉個例子,交付準備服務器必須配置正確,避免發生諸如發送測試郵件給所有用戶,或是通過正式支付網關下訂單等問題。我曾與一個剛入門的開發人員一起工作,他當時試圖通過一個配置錯誤的測試服務器購買幾百萬美元的債券。(幸運的是,每張債券的價格比實際價格高,因為訂單沒有成功購買。)

  出現這種狀況,因為生產環境的配置保存在版本控制系統中,并與應用一起部署。這么做,是要防止出現非生產環境的配置放在版本控制系統中,然后被部署到生產環境服務器上的狀況。

  要避免上述問題,有個出人意料的解決方法:不要讓構建代理的網絡賬號對配置文件有寫權限。這樣,如果有人不相信簽入了配置文件,部署將會失敗,錯誤就可以得到糾正。

  不過這么做也有自己的問題。用這種方法,只要文件中的配置需要修改,就必須手工操作。如果失敗了,將會使得環境出現問題,從而帶來風險,因為數據更新隨時可能發生。

  關注點分離和配置文件

  “關注點分離”這個詞常常用來說明正確的設計決策應該怎么做,如果認真思考誰應該負責做什么,這種方法也是很有幫助的。比如,負責生產環境技術支持的人不關心開發人員選擇注入哪種日志記錄框架。然而,他們關心數據庫連接串和警告發送的郵件地址。

  我推薦下面這些類型的配置文件。

  環境設置:與特定環境相關的值,必須按照服務器分開設置。只有某些重要事件發生時,才會需要改動它們,比如新數據庫或新文件服務器上線。

  代碼作為配置:類似于驅動依賴注入框架的XML文件。雖然看起來像配置文件,但除開發人員外任何人都不應該碰它們。這些文件必須要放在版本控制系統中,而且應該把服務器上的文件標記為只讀。

  微調配置:這些配置與環境無關,但也許生產環境支持人員需要訪問。可能包括比如批量上傳批次限制、或是web頁面請求超時設置等。

  這三種配置中,微調配置需要付出最多精力以保證正確。理想狀態下,應該有缺省配置放在文件中,并保存于版本控制系統,并可以根據特定服務器修改出多個文件,并且不必控制這些文件的版本。

  配置和培訓

  避免增加不必要的配置設置,有一個技巧:要求為每個配置項提供文檔和培訓。如果不花時間教給生產環境支持人員何時、為何調整某些值,他們就無法完成這些工作,這些值就成為無法配置的擺設。

  場景3:使用SOA的多個團隊

  使用SOA時,通常采取多團隊方式。比如,一個團隊構建數據庫和服務,另一個團隊處理UI,這種情況很常見。有時,兩個團隊的工作會非常緊密,相關團隊成員會常常互相交換。有時,團隊可能來自地球兩邊的不同公司。不管他們怎么切分,基本的模式相同。

  結構

  如場景2,服務團隊需要一個地方來測試構建版本,這樣他們不會對團隊之外造成負面影響。同時,UI開發人員需要一臺可信的服務器,能夠一直保持穩定,否則他們就無法完成自己的工作。因此,除了我們前面提到的單一團隊場景,有必要加入“開發環境”。

  不考慮UI集成階段,我們看到的順序與場景2相同。交付選項相同,附加條件是只有UI團隊參與在這個流程中。UI團隊對于構建版本質量的意見比服務團隊的意見重要。

  開發交付選項

  何時以及如何將代碼交付給開發環境,是很多緊張情勢的來源。有問題的構建版本到了QA環境,QA團隊只要讓其失敗,然后就可以轉向其他任務,比如為新功能準備測試,或是改進回歸測試。要是有問題的構建版本到了開發環境,整個UI團隊就會發現自己無法工作了。因此,雖然QA交付中的多種交付模式都可以運作,基于自動化測試的方式目前是最成功的模式。

  說明:誰編寫集成測試?

  處理分離的服務層時,提供服務和消費服務的團隊都要編寫自動化測試,這很重要。提供服務層的團隊最了解服務層內部機制,因此能編寫出別人不知道、或是了解其重要性的測試。

  不過,這不能成為消費服務的團隊不寫測試的接口。他們的測試覆蓋的場景不僅服務開發者想不到,而且能測試他們對于服務層的理解。比如:UI開發人員會假定某個給定調用永遠不會返回null或負值。測試過UI使用的所有參數組合后,他們就可以確保自己的假設沒有問題。

  如果你的公司有QA工程師保佑,而不僅僅是只有QA分析人員,他們會發現自己也要針對服務層開發自動化測試。這常常與自動化UI測試連在一起,特別是動作結果不需要通過用戶界面驗證的時候。

  場景4:采用并行功能開發的多個團隊

  這種情況很棘手。目前,前面提到的各個場景都假定只有一個單獨的開發主干。一旦開始處理多個開發團隊針對同一代碼庫并行開發,就必須決定何時、如何將功能代碼從團隊分支轉移到基本開發主干中。下面兩種模型是我見過的成功范例。

  功能推送模型

  在該模型中,不管何時,只要確定沒有問題,每個團隊都會把自己的變更推送到主干中。該模型的優點在于:團隊可以自給自足。

  合并-推送

  該模型中最常用的策略是:在本地合并及測試。只要測試通過,修改的部分就可以推送到主分支。

  該模型最大的風險是缺乏原子合并。很可能某個團隊修改了某個函數的名字或簽名,而另一個團隊正在加入新的文件使用了同一個函數。如果兩個更改同時簽入,構建版本就會失敗,而且版本控制系統不會報告任何沖突。

  鎖定-合并-推送

  該方式需要版本控制系統支持鎖定。推送新的構建版本時,主干應該是鎖定的。新的功能要在本地與主干合并,完成冒煙測試,然后推送到主干。雖然合并的問題可以在鎖定時解決,任何測試的失敗必須馬上把鎖釋放掉。

  功能拉模型

  該模型中,團隊永遠不能發布他們的變更。相反,變更控制團隊的人負責將功能拉入到主干中。這樣一來,QA團隊就只會接收到他們準備好測試的變更。

  功能拉模型需要高級的版本控制系統,支持集成工作項跟蹤。僅僅給變更打上任務號是不夠的,成員必須要說明“將功能X合并入分支Y”,并讓版本控制系統識別所有需要的變更。這可以手工完成,但是會耗費很長時間,而且很容易出錯。

  對于簡單的合并,變更控制工程師可以自己一個人搞定。復雜的合并,特別是團隊們沒有定期更新自己分支的情況下,需要開發相關功能的團隊協助合并工作。

  結構

  不管功能特性如何進入主干,結構都是一樣的。每個團隊都有自己的集成環境,他們會不斷發布,就像基線場景一樣。這些團隊特定的集成環境向通用集成環境導入代碼,然后就走正常流程了。

  修正補丁(hot fix)如何處理?

  所有這些場景中,我們都沒有提到修正補丁的概念。這是有意為之,如果遵循持續交付的思想和理念,是沒有所謂的修正補丁的。一旦變更進入集成環境,它們就會很快進入生產環境。在這個理論下,不需要修正補丁,只有正常的bug修復,其優先級偶爾會超過功能特性的開發。

  然而,現實世界不是完全由理論指導的。功能常常會停滯在QA階段,其時間超過預期,要么是質量問題,或者僅僅是因為它們的規模。與之類似,生產部署也可能延遲,因為業務需要,比如合同強制要求,或是早已公之于眾的特定日期升級計劃。類似事件發生時,修正補丁就有必要了。此時,最佳方案是拋開流程,完成工作優先。不要讓形式成為公司和客戶不必要的負擔。一旦塵埃落定,危機解決,就可以開始研究問題發生的根本原因了。

  持續交付的目標,不是要讓修正補丁更易于處理,而是要制定出編碼和測試的標準,消除對修正補丁的需要。每次流程失敗的時候,就是你學習如何改進代碼標準和測試實踐的機會,避免重大bug再次發生。同樣地,這也能為你提供理由,檢查日程表制定方針中的缺陷,看看是什么導致流程的停滯和問題。如果無法同時在這兩方面聚焦,你就永遠不能保證所有的bug修復都可以通過嚴格控制的流程。

  簡而言之,持續改進是任何形式的持續交付的根本組件。

  關于作者

  Jonathan Allen從2006年開始就為InfoQ撰寫新聞報道,目前是.NET領域的首席編輯。如果您希望為InfoQ編寫新聞或是有價值的文章,請通過jonathan@infoq.com聯系他。

2
0
 
標簽:持續交付
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()