RFC-0148:持續整合指南 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 針對 Fuchsia 生態系統中的專案和基礎架構擁有者,提供建立可持續的持續整合 (CI) 體驗的指南。 |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2021-12-02 |
審查日期 (年-月-日) | 2022-01-18 |
摘要
針對 Fuchsia 生態系統中的專案和基礎架構擁有者提供指南,協助他們打造可持續的持續整合 (CI) 體驗。
提振精神
在 2021 年中之前,我們將大部分的來源程式碼和預先建構項目集中在一個「Fuchsia 樹狀結構」中。因此,基礎架構和其擁有者大多致力於支援這棵樹。
隨著新的樹狀結構外專案 (例如 RFC-0095) 推出,樹狀結構內貢獻者可能會變成樹狀結構外貢獻者。樹狀結構外 CI 系統應提供與樹狀結構內相近或更佳的體驗,且使用者應能輕鬆地在專案之間切換。否則,使用樹狀結構外的工作會導致工作效率損失,進而阻礙平台的演進。
同時,基礎架構團隊規模無法依樹狀結構外專案的數量線性縮放。我們需要將 CI 功能從「主要針對 Fuchsia 專案量身打造」推廣至「Fuchsia 生態系統中的許多專案可用」。否則,每個專案都需要自訂基礎架構和專屬維護人員。
過去幾年,我們在建構及維護 Fuchsia CI 的過程中學到許多寶貴經驗,這些經驗為我們提供了基礎,讓我們瞭解未來在專案基礎架構整合方面應採取、繼續執行和/或避免的做法。最終,我們的 CI 系統目標是讓專案易於變更、難以損壞,且能有效地推出:這份 RFC 為專案和基礎架構擁有者提供高層級建議,讓上述系統能盡可能達成這些目標。
相關人員
協助人員:
- Hunter Freyer (hjfreyer@google.com)
審查者:
- Aidan Wolter (awolter@google.com) - 產品組裝
- Chase Latta (chaselatta@google.com) - 產品開發套件
- David Gilhooley (dgilhooley@google.com) - 驅動程式
- Jiaming Li (lijiaming@google.com) - Product Development Kit, Workstation OOT
- Marc-Antoine Ruel (maruel@google.com) - 工程生產力
- Nicolas Sylvain (nsylvain@google.com) - 工程效率
- Renato Mangini Dias (mangini@google.com) - Bazel
諮詢:
- Anirudh Mathukumilli (rudymathu@google.com) - 基礎架構
- Nathan Mulcahey (nmulcahey@google.com) - 基礎架構
- Oliver Newman (olivernewman@google.com) - 平台基礎架構
- Petr Hosek (phosek@google.com) - 工具鍊
- Sébastien Marchand (sebmarchand@google.com) - 第一方基礎架構
社會化:
這項設計最初是透過 Fuchsia 工程生產力電子郵件名單進行社交化,並在 Google 文件中進行迭代,然後與相關利害關係人分享,以便識別上述章節中列出的審查人員。接著,我們依據 RFC 範本將其轉換為 Markdown,並移至 RFC「迭代」階段。
設計
下方的「避免」子區段列舉了會對專案 CI、專案貢獻者和/或基礎架構擁有者造成負面影響的常見陷阱。相反地,「必備」和「考慮」子區段則是指南,可協助您避開上述陷阱和其他問題。這並非完整的清單:這些項目不包含效能追蹤、異常偵測等考量,這些考量也可能會改善長期專案健全度,但在實作最小可行 CI 時並非必要。
避免:基礎架構依附於專案內部
如果基礎架構依賴專案內部,兩者都會變得更難變更。在 Fuchsia 中進行工作時,當您進行看似無害的變更,卻會遇到基礎架構的尖銳邊緣,這一直是長期以來的痛點,也是貢獻者對工程流程提出的重大抱怨之一。
舉例來說,基礎架構曾經知道 (現在仍知道部分) Fuchsia 建構系統的許多內部細節,這會在開發過程中造成尖銳邊緣,也就是說,如果 Fuchsia 建構違反基礎架構的任何預期,就無法自由變更。基礎架構程式碼不會與 Fuchsia 程式碼一併執行,因此很難發現其預期值:這些預期值通常只會在提交前或提交後的執行階段,在發生錯誤時才會顯示。其他有害的例子包括在檢查中硬式編碼基礎架構路徑、測試名稱等。這類參照項目通常會自然累積,隨著時間推移,會逐漸造成越來越大的摩擦。
分支越多和/或分支生命週期越長,要讓基礎架構與專案相容就越困難。基礎架構必須在專案記錄中設有版本,或是基礎架構的即時版本必須與專案的所有有效分支保持相容性。
此外,當基礎架構編碼包含大量專屬專案知識時,每個專案都可能會有一組專屬的 CI 指令碼,這會導致實作和維護成本呈線性成長。
避免:複製基礎架構行為的非必要行為
如果貢獻者無法重現基礎架構的運作情形,基礎架構的結果就無法提供太多可行的做法。
如要對無法重現的測試失敗進行偵錯,您必須不斷向基礎架構提交修補程式,直到測試通過為止。這通常比在本機偵錯耗時且耗用更多資源。這也讓人認為在本機測試是無意義的,因為基礎架構執行的測試與通過/失敗的關聯性偏低。
對於難以重現或無法在本機重現的版本,也適用相同的做法。基礎架構不應以不明顯的方式,將建構作業設定為與開發人員工作流程大相逕庭。舉例來說,截至本文撰寫時,Fuchsia SDK 仍難以在本機建構。基礎架構會維護自己的邏輯,與僅限內部使用的 fx 指令碼有顯著差異,且沒有自動化功能可檢查兩者是否產生相同的輸出內容。
在極端情況下,無法重現的基礎架構行為可能會強制「暫時」停用失敗的版本或測試,以便解除提交封鎖,並復原 CI。在這種狀態下,它們會因堆疊損壞而進一步降級,由於修正問題不切實際,因此實際上會永久停用。
避免:浮動依附元件
專案應避免使用浮動依附元件,例如「即時擷取最新版本的 Bazel」。浮動依附元件包括機器預先安裝的軟體。
任何浮動依附元件都可能流入建構作業和測試,導致這些作業和測試不具密封性。在浮動依附元件存在的情況下,基礎架構的結果無法完全歸因於測試中的確切 CL 或提交,因為這些並非唯一可能的變更來源。請注意,基礎架構本身的部分通常可以有效地成為浮動依附元件。網路不穩定是導致測試結果不穩定的常見原因。
浮動相依項目會造成更大的麻煩,因為建構作業的穩定性越高,相依項目的浮動程度就越高。舉例來說,發布分支通常只接受修補程式,以盡量降低引入新錯誤的風險,但浮動依附元件一向代表這類風險。
這類問題也可能導致「在本機運作,但在基礎架構中無法運作」的神秘現象,反之亦然。
必備條件:可重現的結帳流程
專案的簽出作業必須能夠在「乾淨」工作區中,透過一系列簡單的步驟完全重現。這個工作區域可以是開發人員的機器或基礎架構機器。在commit-ish 中對現有檢查點進行「更新」時,必須一律產生相同的結果,就好像在任何時間點從該 commit-ish 重新建立檢查點一樣。也就是說,所有擷取的依附元件都必須固定。理想的固定 (非浮動) 依附元件應是經過加密編譯且具決定性的,例如內容雜湊。不變參照也是可接受的,例如將語意版本做為 Git 標記,但前者是首選。
可重現的檢查作業不僅可為開發人員提供絕佳的專案起步體驗,還能讓基礎架構的專案檢視畫面不太可能與開發人員的檢視畫面有所出入。
無法重現的情況也可能來自於原始碼或二進位檔遭到刪除,以及/或在任何時間點無法存取。託管位置必須經過 Fuchsia 基礎架構擁有者核准,才能整合至專案的檢查作業。
必備條件:檢查、建構和測試之間應有明確的區隔
專案必須明確區分檢出、建構和測試階段。這項設定對於基礎架構強制執行安全性限制,以及最佳化檢查、建構和測試執行階段以及資源使用量,都相當重要。明確區分各個階段,也能更準確地歸因於失敗,尤其是基礎架構失敗和使用者錯誤。舉例來說,建構失敗應歸因於程式碼問題,而非擷取遠端依附元件時逾時。
檢出階段會擷取原始碼和任何依附元件。在檢查階段後,必須具備建構所需的所有內容。也就是說,建構階段是密封的,也就是無法即時擷取任何依附元件。
版本必須能夠在沒有網際網路連線的情況下執行。實際上,使用遠端分散式編譯器時,它仍可能會存取網際網路,但這只是為了效能最佳化 (不應變更建構結果)。這項規定也適用於離線工作或網路存取受限的使用者,例如在飛機上使用服務的使用者。
專案不得假設建構和測試階段會在基礎架構中的相同機器上執行。舉例來說,Fuchsia 建構作業會在測試調度器和執行程序的不同機器 (具有更多核心) 上執行。這可讓基礎架構更有效率地分配機器資源,並加快建構作業。
同樣地,測試也應封閉,也就是說,測試的輸入內容會明確對應。詳情請參閱「測試範圍」。測試不應假設在執行測試的機器上有完整的檢出或建構,也不應依賴在同一台機器上執行的其他測試。基礎架構可能會將測試分割到不同的電腦上,只傳遞明確對應的輸入內容。
至於 Linter,則可在檢查或建構完成後執行,在程式碼分析和/或程式碼審查的情況下提供非二進位通過/失敗提示。針對檢查作業的 lint 可視為檢查階段的一部分;同樣地,針對建構輸出的 lint 可視為建構階段的一部分。可以假設這些階段會在與其相關聯的機器上執行。
考量:可重現的建構
在理想情況下,無論是在開發人員的機器上還是基礎架構機器上,只要使用相同的檢查和依附元件,任何兩個版本都應產生位元組為一比一相同的輸出內容。如果不是位元級相同,至少應在功能上相同。可重現的版本 (例如可重現的檢出作業) 有助於在不同使用者和時間點,建立一致的專案檢視畫面。
建構可重現性包括不依賴系統佈建的工具或服務,例如不依賴系統中的 curl、ping、ip 等。此建構作業應只依賴檢查作業,因此負責供應所有建構依附元件。同樣地,專案應避免使用任何不易跨平台移植的技術。理想情況下,專案應可在 Debian/Ubuntu Linux、macOS 或 Windows 的純安裝環境中執行。
請注意,實際啟動結帳程序所需的最低依附元件集合,絕不應流出結帳程序。舉例來說,如果需要使用 bash 執行檢查,且建構作業也需要使用 bash,則檢查作業應會拉入供應商提供的 bash。然後,建構作業應使用供應商提供的 bash,而不是用於啟動檢查的 bash。
為了加快提交前的建構作業,基礎架構可能會在檢查階段從快取中播放建構目錄。如果系統無法始終正確處理漸進式建構作業,這項策略可能會導致非確定性行為。在提交前,偶爾發生的增量建構問題通常值得為了建構速度而妥協。不過,這項最佳化功能不應用於預提交作業之外,也絕對不應用於正確性和安全性不受損的正式版本。
考量:明確分層的專案和基礎架構
基礎架構負責為大規模專案自動化建構和測試作業。強調「大規模自動化」:專案應支援在本機執行這些工作,且不必仰賴基礎架構。
這表示基礎架構只保留少數邏輯,用於建構及測試任何特定專案。這些功能應由專案本身提供,並由基礎架構叫用,且不必瞭解除了已知的進入點、輸出內容和設定之外的內容。有個實用的概念模型,就是將基礎架構視為新貢獻者,讓他們透過專案的「入門」指南進行建構和測試。
舉例來說,fint 是 Fuchsia 建構系統的抽象概念,可從基礎架構的觀點隱藏其內部。有了 fint,基礎架構甚至不會知道或在意 Fuchsia 使用 GN。這樣一來,Fuchsia 貢獻者在修改建構作業時,遇到的尖銳邊緣數量就會減少。
基礎架構也不應保留設定,以便擷取任何專案依附元件,例如 Bazel、Python3、各種工具鍊等。專案本身應會宣告依附元件。除了用於啟動結帳程序的必要工具,基礎架構機器不應預設包含任何依附元件。專案擁有者應預期日後可用的預先安裝工具組合會減少。
但在某些情況下,專案仍需要瞭解基礎架構的預期值。基礎架構後續處理的部分特殊輸出內容應遵循基礎架構定義的合約。舉例來說,在 Gerrit 中顯示的二進位大小報表或程式碼涵蓋率報表,應符合預期的格式。如此一來,基礎架構就不需要針對每個使用特定基礎架構功能的專案進行自訂處理。
考量:偏好使用 CI 設定而非程式碼
為了擴大支援的專案數量,基礎架構應優先採用新設定,而非新程式碼。舉例來說,用於建構類似專案類別的 CI 程式碼,應大多在指令碼或程式庫層級共用。設定可用於處理專案之間的任何必要差異,例如存放區網址、服務帳戶、檢查策略、建構進入點、構件上傳目的地等。
我們支援兩種檢出工具:Jiri 或 Git (不論是否有子模組)。專案應使用下列其中一個選項。預先建構的依附元件應由 Git-on-Borg 或 CIPD 代管。如果建構每個專案的邏輯已按照上述章節所述進行良好抽象化,建構基礎架構的程式碼也應盡可能共用。
透過設定優先順序,新 CI 的實作成本應會低於從頭開始編寫新 CI 程式碼,這對需要快速啟動的新專案而言是一大福音。他們還能從共用基礎架構程式碼集和服務的持續支援和維護中受益。
考量:建構輸出內容抽象化
為了方便使用建構構件,建構作業應針對其輸出介面區域提供完善的契約文件。基礎架構可能會是這個介面區塊的使用者,以便執行各種建構後動作,例如將資料上傳至 BigQuery、分割並執行測試,或執行二進位大小檢查。這與「中繼」版本輸出內容相反,後者應視為內部,且不會直接取決於下游使用者。
專案定義的工具也可以是建構輸出的使用者。舉例來說,Artifactory 工具會讀取 Fuchsia 的建構輸出內容,以便在雲端儲存空間中找出及整理建構成果。基礎架構只負責使用基礎架構專屬的引數 (例如儲存值區名稱和不重複的建構 ID) 叫用工具。
建構合約可能會遵循某些常見的基礎架構 API。這有助於確保整合功能的穩定性,例如與基礎架構的程式碼涵蓋率服務整合。產生程式碼涵蓋率指標的建構作業內部變更,不應需要基礎架構端的程式碼變更。
應測試建構合約,例如結構定義變更不會導致下游使用者發生硬轉換。
考量:以主程式為優先的開發
專案應以樹狀結構的頂端為目標,確保建構作業順利進行。這可讓所有貢獻者都使用最新版本的程式碼,而不需要分支或使用較舊版本的樹狀結構來避開錯誤。這有助於減少合併衝突,並避免貢獻者在任何特定時間點對專案有截然不同的觀點。
根據預設,基礎架構的提交前作業會嘗試將 CL 重新命名為樹狀結構的頂端 (這是測試簡潔提交的代理程式),因此貢獻者的作業流程實際上會盡可能接近這種行為。就像開發人員對程式碼集有類似的觀點一樣,基礎架構也應如此。
基礎架構的提交後處理功能會在新的 CL 上架時持續測試樹頂,有助於維持樹頂的建構作業正常運作。如果在樹狀結構頂端顯示紅色,基礎架構應快速回報這項資訊,並由開發人員採取行動。
您可以將沙箱分支用於不打算提交的程式碼。請注意,這類使用方式通常是例外狀況,並非基礎架構支援的一流資料流。
考量:快速推出和發布頻率
每個專案都應嘗試以快速的節奏推出其相依項目。基礎架構應透過自動化依附元件推送程序,協助完成這項作業,而專案擁有者應以高優先順序修正失敗的推送嘗試。理想情況下,依附元件會在發布後的 O(小時) 內回滾。依附元件越舊,回溯和/或套用精選項目的難度就越高。這對時間敏感的安全性修補程式尤其重要。
同樣地,每個專案都應盡可能以快速的節奏進行發布。基礎架構應在程式碼順利整合至主線後,自動執行發布程序 (通常稱為「持續部署」),以利這項作業。專案擁有者應投入大量心力編寫自動化測試,以便按照主開發模型,可靠地將樹狀結構頂端附近的版本整合至下游。
基礎架構也應提供專案的依附元件圖,其中專案會形成「節點」,而版本和發布會形成「邊緣」。專案擁有者應能追蹤流經圖表的 CL,並找出 CL 已抵達或卡住的位置等。
實作
本 RFC 提供高層級指南,說明專案應如何與基礎架構介接,但刻意不提及實作細節。每個專案都可以以多種方式遵循指南,我們不希望透過規定特定事項而造成人為限制。新的樹狀結構外專案目前仍在起步階段,隨著專案的演進,我們在此處對應的內容可能會過時。
安全性考量
雖然我們鼓勵專案擁有自己的建構和測試邏輯,但基礎架構仍必須擁有安全性界限。每個專案的來源程式碼和/或構件必須能夠安全地流入下一個專案,才能讓多專案生態系統最終出貨至產品。
CI 工作中的輸入內容必須可信任:所有原始碼和二進位檔都必須從 Fuchsia 基礎架構擁有者核准的代管位置擷取。結帳階段完成後,就無法再有輸入內容,這應由基礎架構強制執行,例如在建構階段嘗試擷取依附元件時,應會導致錯誤。
任務的任何輸出內容都應提供來源,也就是構件是從 修訂版本:X的專案建構而成。上傳構件時,基礎架構應強制將構件上傳至適當範圍的儲存空間。舉例來說,如果專案依賴內部原始碼,就必須禁止將構件上傳至公開值區。
測試
本 RFC 中提及的 CI 系統,可讓您以類似於目前 Fuchsia 專案的方式,大規模建構及測試新專案。這樣一來,專案貢獻者在辦公桌上需要進行的手動測試和偵錯作業量就會減少,可將工作卸載至基礎架構機器。
在基礎架構方面,Fuchsia 的持續整合功能已經過廣泛的測試,可針對其自身程式碼進行自動化測試:換句話說,持續整合功能能夠測試自身的變更。雖然可能需要一些泛化,但我們在建構新的 CI 時,將會大量繼承這些功能。
說明文件
這份 RFC 可做為新專案和現有專案的參考依據。
在基礎架構方面,我們會在這些功能通用化後,針對新的 CI 設定撰寫說明文件,讓這個程序幾乎可自行服務。我們也會將現有的說明文件泛用化,以便納入新的樹狀結構外專案,而非只套用於樹狀結構內基礎架構。
缺點、替代方案和未知事項
如同許多軟體開發最佳做法,遵循這些最佳做法可能會讓專案貢獻者事先付出更多心力。舉例來說,追蹤浮動依附元件是常用的捷徑,可在尖端快速疊代,而不需要使用滾輪。雖然這些方法在短期內可視為實用的駭客攻擊手法,但應視為技術債務,並列入本 RFC 中其他不建議的做法。
我們無法確定如何為每個新專案找到最佳的技術債務平衡點,這點與 Fuchsia 的開發過程相同。我們會持續償還建構、測試和基礎架構技術債務,這些通常是為了達成專案目標。這項 RFC 並非為了避免技術債務,而是為了讓這類權衡更加明智且有意義。