| RFC-0148:CI 指南 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 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) - Drivers
- Jiaming Li (lijiaming@google.com) - 產品開發套件、工作站 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) - Platform Infrastructure
- Petr Hosek (phosek@google.com) - Toolchain
- Sébastien Marchand (sebmarchand@google.com) - 1P 基礎架構
社交:
這項設計最初是透過 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 建構作業會在獨立機器上執行 (核心數較多),與測試協調器和執行器不同。基礎架構可藉此更有效率地分配機器資源,並加快建構作業。
同樣地,測試應為封閉式,也就是明確對應輸入內容。詳情請參閱「測試範圍」。測試不應假設執行測試的機器上存在完整結帳或建構程序,也不應依賴在同一部機器上執行的其他測試。基礎架構可能會將測試分片到不同的機器上,只傳遞明確對應的輸入內容。
至於 Lint 工具,則可在結帳或建構後執行,在程式碼分析和/或程式碼審查的環境中,提供非二進位檔的通過/失敗提示。在簽出時運作的 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 程式碼,大部分應在指令碼或程式庫層級共用。設定可考量專案之間的所有必要差異,例如存放區 URL、服務帳戶、簽出策略、建構進入點、構件上傳目的地等。
我們支援兩種結帳工具:Jiri 或 Git (含或不含子模組)。 專案應使用其中一個選項。預先建構的依附元件應由 Git-on-Borg 或 CIPD 代管。如果每個專案的建構邏輯都已按照上述章節妥善抽象化,建構基礎架構的程式碼也應大多共用。
相較於從頭編寫新的 CI 程式碼,偏好設定可降低新 CI 的實作成本,這對需要快速啟動的專案很有幫助。此外,他們還能享有共用基礎架構程式碼集和服務的持續支援與維護服務。
請考慮:建構輸出抽象化
為方便使用建構構件,建構作業應針對輸出表面積提供文件齊全的合約。基礎架構很可能需要使用這個介面區域,才能執行各種建構後動作,例如將資料上傳至 BigQuery、分片及執行測試,或是執行二進位大小檢查。這與「中繼」建構輸出內容不同,後者應視為內部項目,下游消費者不應直接依賴。
專案定義的工具也可以是建構輸出的消費者。舉例來說,artifactory 工具會讀取 Fuchsia 的建構輸出內容,在雲端儲存空間中找出並整理建構作業構件。基礎架構只負責使用基礎架構專屬引數 (即儲存空間 bucket 名稱和專屬建構 ID) 叫用工具。
建構合約可能遵循一些常見的基礎架構 API。這有助於確保整合功能穩定運作,例如與基礎架構的程式碼涵蓋率服務整合。變更產生程式碼涵蓋範圍指標的建構內部項目,不應需要基礎架構端的程式碼變更。
應測試建構合約,例如架構變更不會導致下游消費者發生硬轉換。
考量:以主分支優先的開發方式
專案應盡量讓建構作業在樹狀結構頂端保持正常。這樣一來,所有貢獻者都能使用最新版程式碼,不必建立分支版本或使用舊版樹狀結構來避開錯誤。這有助於減少合併衝突,並避免貢獻者在任何特定時間對專案有顯著不同的看法。
根據預設,基礎架構的預先提交會嘗試將 CL 重新設定為樹狀結構頂端 (因為這是測試乾淨提交的 Proxy),因此貢獻者的工作流程盡可能接近此行為,是實務上的做法。開發人員對程式碼集有類似的看法,基礎架構也應如此。
基礎架構的 postsubmit 會在新的 CL 登陸時持續測試 tip-of-tree,有助於確保建構作業在 tip-of-tree 保持正常運作。如果樹狀結構頂端的建構作業變成紅色,基礎架構應迅速回報,並由開發人員採取行動。
沙箱分支可用於不打算提交的程式碼。 請注意,這類用途通常是例外情況,並非基礎架構支援的一流流程。
考量:快速推出和發布週期
每個專案都應嘗試以快速的頻率推出依附元件。基礎架構應自動化推出依附元件的程序,以利進行這項作業,專案擁有者則應優先修正失敗的推出嘗試。理想情況下,依附元件會在發布後 O(小時) 內推出。相依性越舊,就越難向前推出和/或套用 Cherry-pick。這對時效性高的安全修補程式尤其重要。
同樣地,每個專案都應嘗試以快速的節奏發布。基礎架構應自動化發布程序,在程式碼順利整合至主線後,協助完成這項作業 (通常稱為「持續部署」)。專案擁有者應投入大量資源編寫自動化測試,確保可根據主要優先開發模型,可靠地整合近乎樹狀結構頂端的版本。
基礎架構也應提供專案依附元件關係圖的瀏覽權限,其中專案會形成「節點」,而推出和發布會形成「邊緣」。專案擁有者應能追蹤流經圖表的 CL,並找出 CL 的最終位置或停滯位置等。
實作
這份 RFC 提供專案應如何與基礎架構介接的高階指南,但刻意省略實作細節。每個專案可透過多種方式遵循指南,我們不想透過規定具體細節來人為設限。新的樹狀結構外專案目前仍處於起步階段,因此我們在此規劃的任何內容,都可能隨著專案發展而過時。
安全性考量
雖然專案應擁有自己的建構和測試邏輯,但基礎架構仍須擁有安全界線。每個專案的原始碼和/或構件必須能夠安全地流入下一個專案,這樣多專案生態系統最終才能將產品發布。
CI 工作的輸入內容必須可信:所有原始碼和二進位檔都必須從 Fuchsia 基礎架構擁有者核准的代管位置擷取。結帳階段完成後,就無法再輸入任何內容,基礎架構應強制執行這項規定,例如在建構階段嘗試擷取依附元件時,應會導致錯誤。
工作的所有輸出內容都應提供來源,也就是構件是從 project 在 revision:X 建立。上傳構件時,基礎架構應強制將構件上傳至具有適當範圍的儲存空間。舉例來說,如果專案依附於內部原始碼,就不得將構件上傳至公開值區。
測試
本 RFC 中提及的 CI 系統,將能以類似於目前 Fuchsia 專案的方式,大規模建構及測試新專案。這樣一來,專案貢獻者就能減少在辦公桌上進行的手動測試和偵錯作業,將工作卸載至基礎架構機器。
在基礎架構方面,Fuchsia 的 CI 已進行大量作業,可大規模自動測試自身的程式碼;換句話說,CI 能夠測試自身的變更。雖然可能需要進行一些一般化作業,但建構新 CI 時,我們大致會沿用這些功能。
說明文件
這份 RFC 將做為新專案和現有專案的參考資料。
在基礎架構方面,我們將在這些功能普遍適用後,撰寫新的 CI 設定說明文件,讓這個程序大部分都能自助完成。我們也會將現有文件一般化,以因應新的樹狀結構外專案,而非僅適用於樹狀結構內基礎架構。
缺點、替代方案和未知事項
與許多軟體開發最佳做法一樣,遵循這些最佳做法可能會讓專案貢獻者付出更多前期心力。舉例來說,追蹤浮動依附元件是常用的捷徑,可快速疊代尖端技術,不必使用滾輪。可以說,這些做法在短期內是實用的權宜之計,但應視為技術債,如同本 RFC 中其他不建議的做法。
每個新專案的最佳技術債務平衡點不明,因為在 Fuchsia 開發期間也是如此。我們會持續償還建構、測試和基礎架構的技術債務,這些債務通常是為了達成專案目標而產生。這份 RFC 並非要避免技術債務,而是要讓這類取捨更明智且有目的。