持續交付:發佈可靠軟件的系統方法

1 持續交付和持續部署

持續交付 是目前的一個挺火的概念,它所描述的軟件開發,是從原始需求識別到 最終產品部署到生產環境這個過程中,需求以小批量形式在團隊的各個角色間順 暢流動,能夠以較短地週期完成需求的小粒度頻繁交付。頻繁的交付週期帶來了 更迅速的對軟件的反饋,並且在這個過程中,需求分析、產品的用戶體驗和交互 設計、開發、測試、運維等角色密切協作,相比於傳統的瀑布式軟件團隊,更少 浪費。

持續交付是經典的敏捷軟件開發方法(例如XP,scrum)的自然延伸,以往的敏捷 方法並沒有過多關注開發測試前後的活動,例如前期的需求分析,產品的用戶體 驗設計,產品的部署和運行維護等。隨着伴隨着敏捷的很多思想和原則在前後端 領域的運用和昇華,以及UX、DevOps等實踐的逐漸興起,我們在持續交付這個新 的大概念下看到了敏捷方法和更多實踐活動的結合和更大範圍的應用。

舉個flickr的例子,這個產品一週之內平均部署好幾十次1, 幾乎每個開發人員的每 個修改就會導致一次部署。這對於產品來說,不僅僅意味着可以更快從用戶那裏 得到關於產品的使用反饋,從而驗證產品的想法,更可以迅速對產品進行改進, 更好地適用用戶的需求和市場的變化。

在這裏我不想過多探討這個端到端的過程,而是隻想就整個軟件交付過程中的一 段進行探討。假設現在需求已經明確,並且已經被劃分爲小的單位(例如用戶故 事user story),我們着重看一看從開發人員拿到用戶故事,到這些用戶故事被實 際部署到生產環境上的這個過程。實際上這個過程當然是越短越好,特別是對於 急需獲得用戶反饋的軟件產品(例如很多互聯網產品)。如果我們做的每一個用戶 故事,甚至是我們的每一次提交,都能夠被自動地部署到生產環境中去,那麼這 種頻繁近乎持續地部署,對於很多軟件開發團隊來說,就成了值得追求的目標。

持續部署和交付之間的另外一個區別在於,很多時候我們可以選擇將功能部署, 但是不讓用戶實際感覺到(dark launch2)。這樣 做可以有些功能需要部署到生產環境中,接受大量真實用戶的非直接測試;或者 有時候希望把一些小的功能組合起來一起展現給用戶。因此,部署可以很頻繁, 然而實際交付給用戶使用則可能根據計劃,比部署的頻率低。

當然持續部署並非沒有投入和成本,產品的基礎和特點不同,獲得這種狀態所需 要的投入就越大。對於缺乏自動化測試覆蓋的遺留系統,以及對安全性要求特別 高的產品,它們要實現持續部署(甚至頻繁部署)都會需要巨大的投入。但是如 果產品所處的市場環境要求它必須及時相應變化,不斷改進創新服務的話,這種 持續部署的能力,就成了值得投入的目標。

持續部署,依賴於整個團隊對所寫代碼的信心,這種放心,不僅是開發這段代碼 的人對自己寫的代碼的自信,也不是少數人的主觀感覺,必須是團隊或者組織的 所有成員都抱有的基於客觀事實的信心。因此,如何能夠讓任何新的修改都能夠 迅速地、有信心地被部署到生產環境,就成了一個值得解決的問題。後面我們仔 細討論,自動化測試是建立這種信心的根本保證。

在團隊具備信心的基礎上,要實現產品的持續部署,還需要有自動化構建流水線 (build pipeline)。以自動化生產線作比,自動化測試只是其中一道質量保證工 序,而要將產品從原料(需求)轉變爲最終交付給客戶的產品,自動化的生產線是 中樞一般的存在。特別對於軟件產品,多個產品往往要集成在一起才能爲客戶提 供服務。多個產品的自動化構建流水線的設計也就成了一個很重要的問題。

產品在從需求到部署的過程中,會經歷若干種不同的環境,例如QA環境、各種自 動化測試運行環境、生產環境等。這些環境的搭建、配置、管理,產品在不同環 境中的具體部署,都需要完善的工具支持。缺乏這些工具,生產流水線就不可能 做到完全自動化和高效。

因此,持續部署靠自動化測試建立信心,以構建流水線貫穿始終,靠各種工具實 現高效自動化和保持低成本。在下面我將詳細談論一下這幾部分的內容。

2 自動化測試

如何能夠保證我們寫出來的代碼既能準確實現我們的新功能,又能夠不破壞既有 的功能?唯有靠完善的測試。而當我們開始追求頻繁地甚至是持續地部署的時候, 自動化測試是唯一的能夠讓我們持續反覆地驗證軟件的方法。如果一個產品具有 完備的自動化測試用例,那麼任何一次對軟件的修改都能夠得到自動化地迴歸驗 證,如果驗證通過,我們就具備了將這些修改部署到生產環境中的信心。自動化 測試的質量直接決定了我們能否具有持續部署的信心。

關於自動化測試,有很多著作詳細地講述了自動化測試的設計、實現技巧等,這 裏不再嘗試重複這些內容。我想以一個web應用爲例,舉例分析一下到底需要些什 麼樣的測試才能夠讓我們建立對這個產品的信心。

0?tp=webp&wxfrom=5

作爲例子的web應用

這個簡化的例子裏包含了三個軟件開發團隊(A、B、C),這幾個團隊各自有自己 的一些產品,最終他們的產品組合起來給用戶提供完整的體驗。A團隊的產品包含 了3個主要模塊。其中fetcher集成了B和C的產品,定時從其中獲取數據,經過分 析後存入store;frontend則會從store中獲取數據,以web界面與用戶進行交互。 frontend同時還和其他一些組織外的第三方服務集成(例如twitter,weibo等)。

這裏不妨再對A的產品所採用的技術進行進一步限定,以方便我們之後的有些討論。 我們不妨假定frontend和fetcher都基於java平臺,都是標準的j2ee應用,store 採用mysql。frontend和fetcher都通過web api(可能是RESTful web api)於第 三方應用集成。在生產環境中,frontend和fetcher都會部署在tomcat+apache服 務器上。

我們現在可以以這個web應用爲對象,考察一下如果我們在fetcher或者frontend 裏修改了代碼,諸如添加了新的功能,或者修復了bug,我們怎麼樣能使這些修改 有信心地從開發人員的機器流入到產品環境中去。雖然我們以這個簡化的web產品 作爲討論對象,但是我們接下來討論的大部分內容並不侷限於web應用。

簡單地講,A產品的測試大概包含兩個方面:

  • 功能方面的測試。包含A產品自身功能的測試,A產品和B、C產品以及twitter 等外部應用的集成測試。

  • 部署測試。將A產品順利部署到各種環境中,需要大量的部署腳本和產品包等 的支持。部署測試驗證這些腳本和產品包的正確性。

  • 性能測試。在功能正確實現的基礎上,產品的性能也必須滿足預期。

2.1 功能測試

根據分層自動化測試的理念,功能方面的測試又可以分成如下幾層。

最上層的是A和B、C以及外部應用的集成功能測試3。測試模擬真實用戶和產品的交互,和真實 的B、C以及外部應用通信,驗證A系統功能的正確和完備。這種測試通常需要完整 部署A以及相關的所有應用(如B、C),在真實的網絡環境下執行。集成測試涉及的 應用多,測試基礎環境準備複雜,運行時間也通常較長,是最爲昂貴的測試。

在A產品和其他產品接口確定的情況下,如果我們將A的外部依賴應用全部打樁 (stub),只關注於A產品自身的功能實現情況,這種測試我們估且稱之爲A的功能 測試(有時候也叫A的驗收測試)。以A爲例,這意味着我們會將fetcher、db還有 frontend都部署起來,將所有外部應用如B、C、weibo都進行打樁。大家可能會覺 得其上層的集成測試已經可以測到A的功能了,何必搞這麼複雜又引入打樁?但是 這一層測試相比於上層的集成測試有這麼幾個好處:

  • 成本更低。單純部署A比部署整個產品族的成本更低;而且因爲A的外部依賴都 是stub,因此執行速度也會更快;而且因爲打樁了外部依賴,不再需要考慮其 他產品的測試數據(fixture)準備,功能測試的測試數據準備的工作量相對也會 減少。大家可能認爲打樁本身是個很高的成本,但是實際上有很多工具和庫以 及讓打樁變的很容易,例如對於web api,採用嵌入式web服務器可以很容易實 現這些api的模擬,這部分的成本很低。

  • 更穩定。一般來講,牽涉的應用越多,測試越不穩定。在B、C等都是真實應用 的情況下,任何應用中的問題都可能導致測試失敗,甚至網絡、部署上的問題 也可能導致測試失敗。因此A的功能測試相對來講更加穩定。

  • 覆蓋率高。因爲成本更低,因此可以以同樣成本編寫和維護更多測試。以web應 用爲例,目前有很多功能測試工具可以針對各種web交互進行測試。而且在外部 依賴打樁的情況,可以簡單操縱stub模擬外部依賴接口的各種特殊情況,達到 對A在各種接口異常情況下功能的測試覆蓋。

  • 測試組織更良好。假設A、B、C都能夠以這種方式對自身的功能進行完整驗證, 那麼A、B、C組成的整個系統的集成驗證就可以只驗證他們之間接口假設的正確 性,因此集成測試就可以只依靠貫穿3個產品功能的少量的測試,就可以保證 整個產品族的功能正確。

A產品的功能測試通常需要將A產品部署後才能進行。例如fetcher和frontend需 要部署到tomcat裏,store需要準備好mysql,還要將各自的配置文件寫好,然後 運行測試。非但如此,爲了確信這個產品部署到生產環境能運行地和跑功能測試 時一樣,我們還要確保功能測試運行的環境和實際生產環境儘量保持一致,例如 運行在同樣的操作系統上,同樣版本的tomcat、mysql服務器等。

這種分層測試的思想在整個自動化測試的設計和組織上都有體現。下層的測試相 對於上層的測試,覆蓋的範圍更小,但是對功能的覆蓋更全面。按照這個思路, A產品的功能測試下,又可能有fetcher和frontend兩個組件自己的功能測試,而 在fetcher內部,又可能有各層各模塊的測試,再有針對每個類、函數或者方法的 單元測試。

最外圍的功能測試將A產品當作一個黑盒,這樣的測試是淺層次的,不能完全覆蓋 所有場景,而且通常編寫和維護成本高,運行時間長,並且受環境因素影響大4。 如果一個產品的測試多數是這樣的,很容易形成頭重腳輕的冰激凌型結構5

如果我們注意豐富底層的單元測試和小模塊的功能測試,那麼上層只需要較少的 測試就可以達到較高的覆蓋率,所有這些測試,以一個金字塔的形式,組合在一 起確保A以及整個產品族的功能正確和完備,這樣的測試組合穩定性和覆蓋率高, 而且開發成本較前一種冰激凌型低。如果這些測試都能夠通過,那麼團隊就有信 心將自己的代碼修改部署到產品環境中去,這些自動化測試,就構成了產品的驗 證和功能防護網。

這裏不得不提一下測試驅動開發(TDD)。前面提到了這麼多的測試,如果系統 在設計上對測試不友好,以致很難甚至無法寫自動化測試,那麼自然無法談用自 動化測試來保障功能。如果嘗試先寫功能後補測試,甚至希望另一個團隊來寫自 動化測試,實踐證明,想擁有完善、組織良好的測試用例也只是一個美好的願望。 測試驅動開發不僅能夠很大程度上驅動出對測試友好的軟件設計,也從一開始就 保障了高測試覆蓋率,以及組織良好、乾淨的測試代碼。

2.2 部署測試

將產品部署到生產環境中與只是在開發環境下測試有很大的區別。我們都知道把 A產品部署到一羣tomcat服務器上和把A在jetty或者IDE中跑起來有很大區別,僅 僅保證後一點完全不能讓我們有信心我們的產品能夠在生產環境下成功部署運行。 爲了能夠讓我們的產品能夠自動化地部署到生產環境中去,就需要有自動化的部 署工具和腳本。

配置同樣是部署過程中的一個重要環節。數據庫等各種服務器的地址、賬號密碼, 所有第三方依賴的地址(endpoint)、key文件等。這些配置可能會在不同的環境 下有些許的變化。不同的網絡環境例如DNS、防火牆也可能對產品的正確運行產生 影響。確保這些配置在對應環境下能夠正常工作是持續部署的關鍵。統一環 境而不是維護多種配置能夠讓產品在不同環境下的配置一致從而簡化了部署腳 本,但是仍然需要測試這些環境確實能夠和統一的配置良好地工作。

如果我們用這些部署腳本將產品部署到環境中然後運行自動化測試,那麼這些自 動化測試實際上能夠幫我們間接驗證部署腳本的正確性。然而這也可能會導致產 品的功能bug和部署腳本的問題的反饋夾雜在一起,讓識別問題更加麻煩;同時 也會讓部署腳本的反饋週期變得更長。

無論部署測試的方法如何,部署工具和腳本的測試與產品的功能測試一樣,都是 確保產品能夠持續部署的要素。後面我們專門討論工具的時候,會詳細討論如何 對環境、部署相關的工具和腳本進行自動化測試。

2.3 性能測試

性能測試也是產品交付之前的一道重要保障。在實際交付到用戶手中之前,必須 保證現有的系統能夠有足夠的容量支撐預期的用戶量。性能測試的設計和實現同 樣已經有很多資源可以參考,這裏也不再嘗試重複已有內容。

性能測試和功能測試的最大不同在於,很大一部分性能測試是需要運行在和實際 產品環境完全相同的環境,很多時候甚至直接用生產環境作爲性能測試的環境。 從準備這個環境以及自動化整個測試過程來講,和功能測試並沒有本質上的不同, 而只有簡單與複雜的區別,我們會在之後的工具和環境中詳細討論這些內容。

3 環境(environment)

環境是一個比較寬泛的概念。這裏要說的環境,特指我們的應用所部署並運行的 環境。一個環境包含了產品所涉及的從服務器(硬件或者虛擬機)、網絡(DNS、 proxy、firewall etc.)到操作系統、應用軟件等所有內容。

軟件的開發到部署,所涉及到的環境至少有如下幾種。

首先是開發環境,這裏狹義地指開發者的單機開發環境。開發環境是任何應用首 先運行的環境,任何代碼都會首先在開發環境中首先得到一些手工或者自動的驗 證。自動化測試首先也會在開發環境上運行。開發環境未必和生產環境高度相似, 例如A產品可能部署在linux平臺上,而開發卻用windows或者mac;生產環境中用 的是tomcat,而開發環境中用jetty來作爲j2ee容器。

然後是生產環境(production環境)。這是最爲重要的環境,配備有最高級的硬 件設備,部署着所有的應用,集成在一起爲其客戶提供服務。爲了保證性能和穩 定性,多半會運行load balance軟硬件,擁有良好的安全配置。服務器們被安置 在良好的物理環境中,並被時刻監控着運行狀態。總之,這是最爲複雜、重要的 環境。

在開發環境和生產環境之間有很多環境,這些環境的複雜程度介於開發和生產環 境之間。

A的環境

例如QA環境。顧名思義這是給大家進行功能測試的環境,大家未必只是QA們,而 這裏功能測試多半是手工。這個環境通常和產品環境具有一定的相似度,會部署 一些真實的第三方應用。這個QA環境有時候也會兼用作演示(showcase)環境,抑 或將演示環境獨立出來。

3.1 自動化測試環境

除了這個QA環境,還有一系列用於自動化測試的環境。這些環境和自動化以及持 續集成緊密關聯。

比如運行A的功能測試時A所部署的環境(通常被稱作staging環境)。這個環境和 A的生產環境極度類似,因爲我們希望這些功能測試好像就是在測真實部署的A產 品,這樣一旦測試通過,我們就可以放心地將A部署。這種類似體現在:

  • 相同的服務器、網絡環境。兩個環境下的服務器操作系統、服務器軟件首先要 完全相同,用同樣的軟件包安裝,系統的配置也要完全相同。可以說, staging環境中的機器要和生產環境中的機器幾乎完全一樣。網絡環境也要相 似。相似度越高,因爲環境不同而引起的潛在問題就越少。如果產品部署到雲 計算環境中(例如amazon、heroku等),我們很容易建立任意個配置相同的機 器。

  • 相似的拓撲結構。生產環境中爲了提升系統性能和容量通常會採用負載均衡進 行水平擴展。例如我們可能部署多個frontend,store也可能是一個mysql集羣。 staging環境不需要這麼多服務器,但是A產品部署的基本拓撲結構應該保持相 同。

  • 相同的部署方法。如果生產環境中會部署A的rpm包,那麼staging環境中也必 須採用rpm包形式部署;反之如果採用腳本或者chef、puppet等工具,staging 環境也必須用同樣的方法。否則部署方法不同,無法保證在生產環境中部署的 結果和staging環境中一樣,也就增加了出問題的風險。

A的staging環境

staging環境之所以有這個稱謂,就在於它和生產環境的相似。而這種相似,正 是我們進行持續部署的信心所在。單個產品例如A、B的staging環境,可能只包 含A產品自己的模塊,而對它所依賴的B以及其他應用進行打樁,打樁的範圍也可 能根據所依賴應用的特點以及成本、效率等考慮而或多或少。

運行A、B、C的集成功能測試時A、B、C所部署的環境,和上面說到的A的staging 環境很相似,不過範圍更大,部署了更多的產品,因此常常也叫端到端(end to end,e2e)測試環境。這個環境,也是和要儘量和生產環境類似,如果說A的 staging環境模擬的是生產環境中A的那部分,e2e環境就是模擬的整個組織的生產 環境,可以看作是更大範圍的,整個組織級別的staging環境。

生產環境和staging環境及e2e測試環境的最大區別可能在於容量上。通常生產環 境需要有能力給大量的用戶提供服務,因此通常會有很多服務器,而功能測試環 境只是驗證功能正確,並不需要同等數量的服務器來實現這一目的。

生產環境往往有複雜的安全規則設置,這些規則有時候會影響產品的功能(例如 防火牆設置可能會影響多個應用之間的通信);生產環境中諸如數據服務器的密 碼等信息必須保密;生產環境中可能藉助於代理才能訪問互聯網資源,等等。這 些因素,在我們設計構造staging環境的時候,都必需納入考慮。

最後還有持續集成(CI, continuous integration)環境。這是持續集成服務器用 來運行它自己(包括它的agents)以及進行產品的自動化構建的環境。CI服務器 就相當於一個開發人員,自動地監控代碼庫的變化,一旦有變化就自動運行自動 化構建。CI服務器會在這個環境中運行自動化構建的所有內容,作爲持續部署的 中樞,像流水線一樣貫穿整個開發、測試、部署過程。

3.2 自動化環境和生產環境的相似度

不難看出,自動化測試環境和生產環境的相似度影響我們對產品的信心。在越接 近實際生產環境的環境中驗證,我們越能夠有信心將驗證過的東西直接交付給用 戶;而驗證環境的相似度越低,可信度越低。比如說我們如果只在開發環境下用 jetty和內存數據庫來進行A的功能測試,我們肯定會對它是否能夠在複雜的生產 環境下部署產生懷疑。因爲所有關於A的部署腳本、產品包都沒有經過驗證過。

然而理想和現實之間總要做出一些實際的取捨。成本和效率都允許的條件下,如 果所有功能測試都在一個生產環境的副本下執行,那麼我們可以在交付前驗證所 有的因素。然而現實是給所有團隊創建完整的生產環境用作測試成本首先會相當 高昂,生產環境往往有很多服務器集羣,這些集羣通常都已經是組織的巨大投入。

並且很多時候由於技術和其他方面的原因根本無法做到。例如如果生產環境中採 用netscaler作爲負載均衡器(load balancer),而團隊採用amazon之類的雲計算 平臺構建測試環境,目前技術上就很難將netscaler放到雲中去運行。

從另一方面來講,自動化環境和生產環境的高仿真度所來的好處呈邊際效應遞減。 如果說staging環境相對於開發環境讓我們能夠有機會測試所有的部署腳本,並且 能夠測試產品在一個簡化的生產環境中的實際運行情況,從而給了我們更多的信 心,那麼在staging環境中加入負載均衡器並且多用幾臺服務器給我帶來的好處就 遠沒有那麼大了。

我們還可以考慮一下另一個類似的問題:staging環境是部署真的第三方依賴應用, 還是應該將它們無一例外全部打樁呢?如果打樁的話,我們也許喪失了一些真實 的反饋,漏掉了少數的測試用例,但是帶來的好處卻是測試穩定程度、執行速度 以及對A產品自身測試覆蓋率的提升。

另外,是否需要在測試環境中實現某些生產環境中的要素也取決於我們想測試的 點究竟是什麼。如果我們希望測試環境的安全性,或者我們希望測試負載均衡器 的某些設置,那麼我們可能就需要包含這些設備的環境來測試它們。

修改的頻度也是其中一個考慮因素,如果防火牆、負載均衡器、緩存等的設置經 常處於變動狀態,那麼可能在staging環境中複製這些內容就會有較大的價值。 否則如果需要花很大的代價去頻繁測試幾乎不變動的內容,其價值相對來講就會 很小。

速度、成本、穩定等,都是我們在現實項目中可能考慮的因素,並非環境越和實 際生產環境相似,效果就越好。在團隊達成共識的前提下,選擇當前合適自己情 況的方案,是比較實際的做法。如果有少數的情況可能沒有被測試覆蓋到,也可 以持續改進它。

3.3 自動化構建過程的優化

很多時候,如果我們必須在A的staging環境下開發和調試功能測試的話,在日常 開發過程,尤其是TDD過程中,效率往往讓開發人員無法忍受。開發階段的反饋周 期往往必須保持在數秒的級別,超過10分鐘就讓人無法忍受。例如junit單元測試, 每個函數的編寫過程中可能都要修改和運行n次,超過幾秒就讓人無法接受。而功 能測試雖然天生就更復雜些,但是如果整套測試如果需要超過10分鐘甚至更久, 作爲開發人員就不太會頻繁地運行這部分測試。在這樣的背景下,就產生了很多 優化手段,它們的目的都是爲了縮短自動化測試以及整個自動化構建過程的運行 時間。

目前已經有很多優化手段6 。例如,不再將A的各個組件部署到staging環境中,而是部署到開發環境中,採 用輕量級容器如jetty來代替tomcat,採用內存數據庫代替mysql等。也可以採用 諸如htmlunit的框架代替selenium來編寫web功能測試。這些手段的最終目的都是 希望在開發階段能夠以最小的成本、最快的速度來運行儘可能的自動化驗證,以 獲得儘可能快的反饋。

在優化的環境中運行A的功能測試,固然不能讓我們獲得和在staging環境下運行 測試相同的信心,但是實踐中,在很多情況下,已經能夠提供足夠高的可信度, 這種可信度對於某些非關鍵性產品來說,可能已經足夠讓他們放心將產品部署到 生產環境中去了。與此同時,帶來的是開發效率和質量的大幅度提升。

3.4 環境的創建和維護

大量環境的管理和維護,本身就構成了一個巨大的問題。在傳統的基於物理機器 的運維時代,這麼多環境的安裝、維護成本高昂,因此極易造成一套環境多用途、 多團隊共享的情況,無法保證環境的乾淨、可靠。但在雲計算資源逐漸可能會低 於電費的今天,軟件團隊將能夠藉助於虛擬機和雲計算,以更低的成本去按需創 建各種環境,甚至開發環境也可以用虛擬機代替。可以說,雲計算是持續交付的 基石。

4 持續集成

持續集成作爲敏捷方法的一項核心實踐,由來已久7。在持續 交付中,持續集成服務器將從開發到部署過程中各個環節銜接起來,組成一個自 動化的構建流水線(build pipeline),作爲整個交付過程的中樞,發揮着至關重 要的作用。

前面說過,我們希望我們對軟件的修改能夠快速、自動化地經過測試和驗證,然 後部署到生產環境中去。在自動化測試和環境都具備情況下,開發人員除了在本 地運行自動化構建進行驗證外,剩下的工作就主要由持續集成服務器來幫忙完成。

目前市面上有很多持續集成服務器軟件,例如jenkins,go,bamboo, cruisecontrol,travis-ci等,這些軟件有的支持構建流水線的概念,有的有構 建流水線插件,持續集成服務器主要通過調用產品的自動化構建腳本8來執行你 所配置的各階段任務。

我們先以A產品的構建流水線爲例,看看其中主要有什麼樣的內容。

4.1 單個產品的構建流水線

A的構建流水線

產品A的構建流水線自動化了從編譯、靜態檢查、打包、在不同環境下進行部署 並運行自動化測試、發佈產品包以及完成最後部署整個過程。從開發人員提交修 改到源代碼庫中那一刻開始,剩下的所有步驟都由構建流水線自動完成。

開發人員在開發過程中,首先會在開發環境中完成開發驗證,自動化測試的編寫、 調試和修改,TDD,自動化構建腳本的編寫,部署腳本的編寫等,都在開發環境 中完成。這裏是所有修改的入口,所有驗證的初始發生地。我們應該儘量做到所 有的開發和驗證都能夠在開發環境中完成。

而當開發和驗證完成,確認修改正確後,就可以將代碼提交到源代碼庫中。持續 集成服務器持續監視着代碼庫的修改情況,自動將最新的修改更新到持續集成環 境中,開始從編譯打包到部署的一系列自動化過程。

以A產品爲例,這個自動化過程包含了若干個階段,之所以分成若干個階段,是 爲了更加直觀地展現這個過程。後面我們會談到,因爲優化的關係,不同產品的 構建流水線可能形態上會有些不同,但是它們都包含了如下幾個重要階段。

首先是打包階段。打包是一個籠統的說法,其本質是將應用準備成能夠在生產環 境中部署的形式。capistrano部署rails應用直接將源代碼checkout到生產環境, j2ee則規定了web應用必須以war包形式部署到容器中。這兩種形式雖然都能夠實 現部署的目的,但是更好地是將產品以產品包的形式發佈出去,例如對linux平臺 以rpm或者deb包的形式發佈。

不論產品選擇何種形式發佈,有一個需求是共同的。所有產品都必需能夠支持在 安裝後、服務啓動前對配置文件進行修改。war包是不符合這個要求的,因爲配 置文件被包含在war包中,只有j2ee容器啓動之後才能修改其中配置文件(現在 有一些辦法能夠將war包中的配置文件從war中提取出來)。

在打包之前,還有必要對包的可用性進行儘可能充分的驗證。靜態檢查、單元測 試等任務可以在打包之前進行,如果功能測試成本不高,甚至也可以考慮放到打 包之前運行。這樣,我們可以對打出來的包的功能有一定的信心。這種信心對提 升整個構建流水線的效率和正確率是很重要的,因爲越往後的階段成本相對來說 越高,反饋越慢,因此前面的階段驗證越充分,後面的階段成功的可能性越大, 而失敗之後的錯誤追蹤也更加容易。

打包之後我們就可以將產品包部署到staging環境下進行功能測試。這個階段的 任務首先是要準備一個乾淨的staging環境。爲此我們必須首先準備好必須的服 務器以及網絡環境,然後安裝操作系統並作基本的系統配置(例如DNS等),然 後利用我們的部署腳本和產品包將產品部署到環境中去。

這個過程中很重要的一條原則就是在staging環境中和生產環境中所採用的部署 方法必須一樣,是同一種方法,同一套腳本,同一組產品包。只有這樣我們才能 夠有信心將經過驗證的產品包放心地用這一套腳本部署到生產環境中去。這就類 似於前面提到的環境相似度原則,用於staging環境的任何部署腳本、產品包如 果有和生產環境不同的地方,都有可能在生產環境中導致問題,這些問題必然會 成爲我們持續部署的阻礙因素。

在環境部署好之後,就可以對環境中的產品運行功能測試。如果這些測試全部通 過,那麼我們就可以選擇將部署腳本和產品包發佈到倉庫(repository)中去。 這些交付物(artifact)會在之後的測試、部署中被用到,同時其他產品團隊也會 需要這些交付物去部署它們自己的環境,進行集成測試等。

接下來是在e2e環境中的測試。首先自然也是要準備好所需要的服務器等基礎設 施,然後將集成測試所涉及的所有產品都部署到該環境中去,再運行測試。

部署集成測試環境需要各產品都提供完善的部署手段,換句話說所有的產品都必 須提供能夠將自己部署到一個乾淨環境中去所需的包、腳本、工具等。

如果集成測試也通過,那麼我們就可以選擇將產品包部署到實際生產環境中去了。 這一過程所包含的具體內容,視不同產品的複雜程度、生產環境的特點、組織的 策略等,可能會有很大的不同。

構建流水線的各個階段之間的觸發方式,通常是自動的,上一個階段成功之後, 下一個階段就會被自動觸發執行。但是在某些情況下,有些階段的觸發可能是手 動的。例如publish和deploy兩個階段,在很多情況下可能是手動的。deploy階段 的觸發,因爲涉及到生產環境的安全性,還往往可能需要觸發的時候進行身份驗 證。

4.1.1 提交門限的概念

從構建流水線圖中我們可以看到,持續集成服務器是在不斷監視着源代碼庫的變 化,一旦有人提交就會觸發構建過程,如果其中發生問題,則會將結果反饋給團 隊。整個構建過程通常會需要一段時間,我們希望整個構建過程的成功率儘可能 高,或者更準確點講,儘量反映開發人員在本地開發環境中無法驗證或者發現不 了的問題。

因此我們希望開發人員在將修改提交到代碼庫之前,能夠在自己的開發環境中進 行充分的驗證,至於不同開發人員之間的修改的集成、更復雜環境下產品的驗證 這些在本地環境中較難低成本驗證的東西,構建流水線會幫助我們提供反饋。但 是如果本地驗證不夠充分,甚至不作本地驗證就隨意將代碼提交,構建流水線就 可能會被大量低級錯誤所充斥,大部分時間處於失敗狀態,最後就像被DDoS了攻 擊一樣,失去了給團隊提供更有價值反饋的能力。

所以,團隊內的開發人員應該首先在本地進行充分驗證,然後再提交。多充分算 充分呢?這基於所驗證內容的成本和團隊的共識。如果所有構建內容能夠在本地 10分鐘之內執行完成,我們就可以約定提交之前必須在本地執行所有構建內容; 反之如果整個構建過程耗時超過30分鐘,每次提交前都執行全部構建就會嚴重拖 慢開發進程,打亂開發節奏,這種情況下團隊可以約定將一部分內容作爲提交前 必須執行的內容。

這種整個團隊爲了提高構建流水線的成功率,約定的在提交前必須執行並保證通 過的構建內容,就成爲構建門限,或者成爲本地構建。很明顯,本地構建佔整個 構建過程的比重越大,團隊就能越早在本地就得到儘可能多的反饋,整個持續交 付過程就更加流暢。

然而本地構建的一個重要要求就是要耗時短。有時候可以通過構建的優化來減少 構建時間,不同產品的特點不同,構建複雜度以及時間也會有區別。

4.1.2 構建流水線的優化和變化

我麼以A產品爲例討論了它的構建流水線(見圖),然而我們給出了構建流水線設 計並非是唯一的方案。構建流水線的目標,那就是能夠給團隊以持續部署的信心。 在滿足這個目標的前提下,流水線的具體實現形式可能會有不同程度的變化。

整個構建流水線各階段的執行時間是影響其設計的一個重要因素。如果package 和staging兩個階段可以在5分鐘內完成,也許我們不需要把它們分成兩階段來獲 得反饋。如果A沒有和其他任何產品的集成,那麼e2e測試階段也可以去掉。對於 更加簡單的應用,也許只要一個階段就可以包含所有的構建內容。

另一個因素是整個過程的組織形式和視覺呈現要求。將不同的構建內容顯式分成 不同的階段可以對各階段的反饋有更明確的瞭解。特別是在某些階段需要手動觸 發時,這種階段的分隔就更加有價值了。

不管怎麼優化,都必需遵循構建流水線的基本目標原則,如果優化的結果過度偏 離了它的目標,就不再是優化的問題,而是能否起作用的問題。


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