Facebook 是如何做大規模程式碼部署的

wrm發表於2017-10-28

隨著時間的推移,軟體行業已經提出了多種方法來更快更好更安全地交付程式碼。其中大部分的努力都集中在諸如持續整合、持續交付、敏捷開發、DevOps 和測試驅動開發等方面。所有這些方法都有一個共同的目標:讓開發人員能夠以安全、小型和漸進的步驟將程式碼快速正確地提供給使用者。

Facebook 的開發和部署過程已經得到有機地發展,以涵蓋這些快速迭代技術的大部分內容,同時也避免特殊依賴任何一項單獨的技術。這種靈活、實用的方法使我們能夠在快速的時間內成功地釋出我們的網路和移動產品。

多年來,我們使用簡單的主程式釋出分支策略每天部署三次程式碼到 Facebook 前端。工程師們會從主幹分支中選擇一些通過了一系列自動化測試的程式碼變更推送到每天的釋出分支上(這個過程也叫做“cherry-picking”)。總的來說,我們每天選擇的變更(cherry-picks)數量為 500 到 700。剩下的沒有被 cherry-picked 的變更就推入到每週的釋出分支中。

從 2007 年的幾位工程師到現在的數千名工程師,這個系統的擴充套件性一直很好。好訊息是,隨著我們增加了更多的工程師,我們完成的工作也越來越多——程式碼交付的速度與團隊的規模成比例。然而,除了適當的工具和自動化系統之外,它還需要釋出工程師花費一定的手工勞動,來完成每天和每週的程式碼推送和釋出。我們知道,隨著團隊的不斷擴大,批量處理日漸增多的交付程式碼塊將無法持續。

到了 2016 年,我們看到 branch/cherry-pick 模型已經達到了極限。我們每天接收的推送到主分支上的變更超過 1000 個,而每週的接收的變更有時多達 10,000 個。因此每週需要進行大量的手工工作來協調和交付這樣大型的釋出任務,這是不可持續的。

2016年4月,我們決定將 facebook.com 移到一個準持續的“push-from-master”系統上。在接下來的一年裡,我們逐漸把它推出,首先讓50%的員工用上新程式碼,然後讓0.1%的生產環境用上新程式碼,再到1%,再增加到10%。這些程式中的每一步都是對我們的工具和過程的測試,測試它們處理日益增加的推送頻率的能力,從而得到真實的反饋。我們的主要目標是確保新系統能讓人們的體驗更好——或者至少,不會讓它變得更糟。經過了幾乎整整一年的規劃和開發之後,在2017年4月的3天內,我們使100%的生產環境能夠執行直接從master部署的程式碼。

大規模持續交付

雖然一個真正的持續交付系統會將每一次提交的程式碼變更及時釋出到生產環境中,但根據Facebook的程式碼提交速度,我們需要開發一個每隔幾個小時就能處理數十到數百個程式碼變更的系統。在這種準連續交付模式中所做的改變通常是小型且增量的,很少會對實際的使用者體驗有明顯的影響。每一次釋出都以分層的方式在幾小時內部署到100%的生產環境,因此一旦發現任何問題,我們可以隨時停止釋出。

Facebook 是如何做大規模程式碼部署的

首先,程式碼變更通過一系列自動化的內部測試後才能被提交到主分支,進而被推送給Facebook的員工。在這一階段引入的任何迴歸,都會使我們收到推送阻塞警報,而有個緊急停止按鈕也可以使我們阻止程式碼的進一步釋出。如果一切正常,我們會將變更推送到2%的生產環境,在那裡我們會繼續收集訊號和監測警報,尤其是對於那些我們的自動測試和員工的內部測試沒有發現的邊界情況。最後,我們才將這些變更100%部署到生產環境中,由名為Flytrap的工具收集使用者報告,並在異常時給我們傳送警報。

許多變更最初都是由 Gatekeeper 系統控制的,這使得我們能夠獨立地釋出移動端和 web 端程式碼而不依賴於新功能,同時有助於降低由任何特定更新導致問題的風險。如果確實發現了問題,我們只需要關閉 gatekeeper,而不是回退到之前的版本或修復當前版本。

這個準持續(”quasi-continuous”)釋出週期有幾個優點:

1. 它不再需要熱補丁

在每天部署三次的策略下,如果一個關鍵變更必須立刻釋出,而不是在它預定的推送時間裡,這時就必須有人來打熱補丁。這些帶外推送是破壞性的,因為他們通常需要一些人力操作,而且可能會撞上下一個預定的推送。在新系統中,絕大多數需要熱補丁的程式都可以簡單地提交給master,並在下一個版本中進行釋出。

2. 為全球工程師團隊提供了更好支援

我們試著儘量合理安排每天三次部署的時間,以適應我們在世界各地的工程辦公室。但即使是這樣的努力,每週一次的釋出也要求所有的工程師在某個特定的日期和時間內集中注意力,然而這些時間在他們的時區並不總是很方便。新的準持續系統意味著世界各地的工程師都可以根據需要開發並交付的程式碼。

3. 它迫使我們開發下一代工具、自動化和流程,以使公司能夠擴大規模

我們所做的這個專案,可以作為跨越多個團隊和系統的壓力測試。我們改進了推動工具、diff 審查工具、測試基礎架構、容量管理系統、流量路由系統,以及許多其他方面。這些團隊都聚集在了一起,因為他們希望看到一個釋出週期更快的自動化部署系統儘快成功。我們所做的改進將有助於確保公司為未來的發展做好準備。

4. 它使使用者體驗更好、更快

當需要數天或數週的時間來觀察程式碼的執行狀況時,工程師們可能已經轉向了新的任務。在持續交付的情況下,工程師不必等待一週或更長的時間才能得到他們提交的程式碼的反饋。他們可以更快地瞭解到哪些地方不work,並及時進行小的增強或修復,而不是等到下一次大的釋出。從基礎設施的角度來看,新系統使我們能夠更好地應對那些可能影響使用者的稀有事件。最終,這將使工程師更加貼近使用者,不僅有助於產品開發還有助於提高產品可靠性。

持續釋出到移動端

在web平臺上發展出一個準連續系統在某種程度上是可能的,因為我們擁有完整的技術棧,而且可以構建和改進我們需要的工具,使之成為現實。而移動平臺上則面臨更多的挑戰,因為許多現有的移動平臺的開發和部署工具使快速的迭代變得比較困難。

Facebook致力於改善這一情況,並建立和開源了一整套專門針對移動平臺上快速開發的工具,包括NuclideBuckPhabricator、各種iOS類庫、React NativeInfer。總之,這一系列構建和測試棧使我們能夠產生高質量的程式碼,以便快速部署到移動平臺上。

我們的持續整合棧主要分成三層:構建、靜態分析和測試。

Facebook 是如何做大規模程式碼部署的

當開發人員將程式碼提交到移動主分支時,會在所有受影響的產品上對程式碼進行構建。對於移動端來說,這意味著每次提交都要重新構建Facebook、Messenger、Pages Manager、Instagram以及其他應用程式。我們還為每個產品構建了多個版本,以確保能夠涵蓋這些產品支援的所有晶片架構和模擬器。

在構建過程中,我們會執行Infer,它集合了Linters(檢查程式碼風格和錯誤的小工具)和靜態分析工具,用於捕獲空指標異常、資源和記憶體洩漏、未使用的變數和有風險的系統呼叫,並標記出違反Facebook編碼規則的情況。

第三個併發系統是移動自動化測試,包括數千個單元測試、整合測試以及由Robolectric、XCTest、JUnit和WebDriver等工具驅動的端到端的測試。

不僅每次提交時會執行構建和測試棧,而且在程式碼變更的生命週期內也會執行多次。僅在安卓系統上,我們每天就能完成5萬到6萬次構建。

Facebook 是如何做大規模程式碼部署的

通過將傳統的持續交付技術應用到移動棧,我們已經從四周釋出一版發展到兩週釋出一版,再到現在的一週釋出一版。目前我們在移動平臺上使用的就是之前基於web的策略:branch/ cherry- pick模型。雖然我們每週只釋出一個版本,但在現實環境中儘早測試程式碼仍然很重要,因為這樣工程師就可以儘快得到反饋。我們每天都會為我們的金絲雀使用者(包括大約100萬個Android beta測試人員)提供新的移動候選版本。

Facebook 是如何做大規模程式碼部署的

與此同時,我們的釋出頻率增加了,我們的移動工程師團隊已經增長了15倍,我們的程式碼交付速度也已經大大提高。儘管如此,從我們2012年到2016年的資料來看,無論是按程式碼行數還是按推送數量來衡量,工程師在Android和IOS方面的生產率都保持不變。同樣,無論部署多少次,移動版本出現的關鍵問題的數量幾乎沒有變化,這說明我們的程式碼質量並沒有隨著程式碼規模的增大而受到影響。

隨著現有的工具和方法不斷取得進展,這是在釋出工程領域工作的一個令人興奮的時刻。我為Facebook的團隊感到非常自豪,他們一起合作為我們提供了我認為是這個規模下最先進的web和移動部署系統。這一切能成為可能還有一部分原因是擁有一個強大的中央釋出工程團隊,因為它是基礎設施工程領域的“第一類公民”(First-class citizen)。Facebook的釋出團隊將繼續為開發人員和使用者推動改善釋出流程的計劃,也會繼續分享我們的經驗、工具和最佳實踐。

相關文章