RFC-0148:CI 指南

RFC-0148:持續整合指南
狀態已接受
區域
  • 開發人員
  • 管理事宜
說明

適用於 Fuchsia 生態系統中的專案與基礎架構擁有者,旨在打造可永續發展的持續整合 (CI) 體驗。

變更
作者
審查人員
提交日期 (年/月)2021-12-02
審查日期 (年/月)2022-01-18

摘要

適用於 Fuchsia 生態系統中的專案與基礎架構擁有者,旨在建立可永續發展的 CI (持續整合) 體驗。

提振精神

直到 2021 年中,我們保留了大部分的原始碼,並預先建構在一個「沙發樹」中。因此,基礎架構及其擁有者主要都是支援該樹狀結構。

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) - 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「Iterate」階段。

設計

下列「避免」子章節列舉了會對專案的 CI、專案貢獻者和/或基礎架構擁有者帶來負面影響的常見錯誤。相反地,「必需」和「考量」子區段則提供準則,協助瀏覽前述的陷阱等。並未完整列出所有清單,其中包括效能追蹤和花粉偵測等事項,這些因素也能改善長期專案的健康狀態,但並不是達到最小可行性 CI 實作的必要條件。

避免:基礎架構依附於專案內部

當基礎架構仰賴專案內部時,雙方會越來越難變更。在 Fuchsia 工作時,如要調整基礎架構的尖端,一直是一大難題,也是貢獻者對工程程序的一大關注。

例如,過去使用的基礎架構會知道許多 (也依然知道) 的 Fuchsia 建構系統內部細節,而這造成了開發中的尖端。也就是說,如果基礎架構違反任何基礎架構的期望,就不能任意變更。基礎架構程式碼並不包含 Fuchsia 程式碼,因此預期很難找到:通常只有在預先提交或提交後執行階段發生故障時才會知道這些程式碼。其他有害的例子包括結帳中的基礎架構硬式編碼路徑、測試名稱等。這類參照項目往往會隨機累積,隨著時間逐漸產生越來越多的阻礙。

越來越多的分支版本與/或運作時間較長,導致基礎架構與專案相容變得越來越困難。基礎架構在專案歷史記錄中都有版本,或者基礎架構的上線版本必須維持與專案所有有效分支版本的相容性。

此外,當基礎架構為大量專案專屬知識編碼時,每個專案都有各自的專屬 CI 指令碼,而這些指令碼會有線性擴充的實作和維護成本。

避免:難以複製基礎架構行為

如果貢獻者無法重現基礎架構正在執行的操作,基礎架構的結果就會變得不像實際行動。

如要針對無法重現的測試失敗進行偵錯,您需要為基礎架構重複提交修補程式,直到測試通過為止。相較於本機偵錯,這種做法速度較慢,資源也更為密集。這也給了本機測試的想法,因為通過/失敗的關聯度低,與基礎架構執行測試的關聯相當低。

對於難以重現或無法在本機重現的建構,也是一樣的情況。基礎架構的設定方式不應與開發人員工作流程大有差異。舉例來說,截至本文撰寫時,Fuchsia SDK 還是較不易在本機建構。基礎架構會保有自己的邏輯,這與僅限內部使用的 fx 指令碼有顯著差異,且沒有任何自動化檢查作業會回報這些輸出內容。

在產生的案例中,無法重現的基礎架構行為可能會強制「暫時」停用失敗的建構或測試,藉此解除封鎖提交作業並復原 CI。處於這種狀態時,他們就可以進一步降低堆疊的中斷情形,進而有效因解決的不精確性而永久停用。

避免:浮動依附元件

專案應避免使用浮動依附元件,例如「即時擷取最新版本的 Bazel」。浮動依附元件包括機器預先安裝的軟體。

任何浮動依附元件皆可流入建構和測試,並呈現「非密封」。使用浮動依附元件時,基礎架構的結果無法完全歸因於實際 CL 或測試中的修訂版本,因為這些結果並不是唯一的可能的變更來源。請注意,基礎架構本身的部分通常可以有效成為浮動依附元件。測試結果是網路連線難以預測的常見原因之一。

浮動依附元件會形成相應的更大的頭痛,導致建構的預期穩定。例如,發布分支版本通常只接受使用熱修補程式,以盡量降低出現新錯誤的風險,但浮動依附元件一律代表這類風險。

此外,這些措施也為神秘的「本機可運作,但不受基礎架構影響」現象,反之亦然。

必須存在:可重現的結帳程序

專案的結帳功能必須可完全重現,只要透過「乾淨」工作區完成一系列步驟即可。該工作區可以是開發人員的機器或基礎架構機器使用 commit-ish 執行現有結帳的「更新」作業,一律必須產生相同的結果,就像在任何時間點根據修訂內容建立新的結帳流程一樣。這表示所有擷取的依附元件都必須固定。固定 (非浮動) 依附元件是理想的加密編譯方式,且可確定性,例如內容雜湊。不可變更的參照也可接受,例如使用語意版本做為 Git 標記 (但建議使用前者)。

如此一來,開發人員不僅能提供可重現的結帳體驗,在剛開始使用專案時也能享有絕佳體驗,還能降低專案上受到專案檢視畫面的干擾,避免與開發人員的觀點不同。

非可重現性也可能是因為原始碼或二進位檔遭到刪除,且/或在任何時間點無法存取。託管位置必須經過 Fuchsia 基礎架構擁有者核准,才能整合至專案的結帳功能。

一定要有:結帳、建構和測試的區別

專案的結帳、建構和測試階段必須清楚區隔。基礎架構會強制執行安全邊界,並最佳化結帳、建構和測試執行階段和資源用量。將階段明確區隔可有效將失敗歸因,特別是基礎架構故障與使用者錯誤。例如,失敗的建構作業應可歸因於程式碼問題,而不是擷取遠端依附元件時發生逾時的情況。

結帳階段會擷取原始碼和所有依附元件。進入結帳程序後,首先必須取得建構所需的一切資訊。這意味著建構階段是密封的,也就是說,無法即時擷取任何依附元件。

建構作業必須能在沒有網路連線的情況下執行。實際上,使用遠端分散式編譯器時,可能仍可存取網際網路,但會將其視為效能最佳化 (不應變更建構結果)。這項規定也適用於離線工作或網際網路連線有限的使用者 (例如 Airborne 使用者)。

專案不得假設建構和測試階段是在基礎架構中的同一部機器上執行。舉例來說,Fchsia 建構作業會在多個機器上執行 (核心數量較多),與測試自動化調度管理工具和執行工具。這可讓基礎架構更有效率地分配機器資源,並加快建構速度。

同樣地,測試應保持密封狀態,也就是已明確對應輸入內容。詳情請參閱「測試範圍」。測試不應假設在執行搭載的機器上存在完整的結帳功能或建構項目,且不應依賴於同一部機器上執行的其他測試。基礎架構可能會將測試分割到不同的機器,只傳遞明確對應的輸入。

至於 Linter,他們可能會在結帳後或在建構後執行,以便在程式碼分析和/或程式碼審查中提供非二元的成功/失敗提示。在結帳過程中運作的 Linter 可視為屬於結帳階段的一部分;同樣地,在建構輸出內容上運作的 Linter 也可視為建構階段的一部分。系統會假設這些容器在相關階段上執行。

考慮:可重現的建構問題

在理想情況下,只要結帳和依附元件都相同,那麼無論是在開發人員的機器或基礎架構機器上,任何兩個版本都應能夠產生位元率相同的輸出。如果位元/位元相同,版本至少應在功能上相同。可重現的建構作業 (例如可重現的結帳功能) 有助於在使用者之間及階段建立一致的專案檢視畫面。

建構可重現性並不取決於系統佈建的工具或服務,例如取決於系統的 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-BorgCIPD 託管。如上一節所述,如果用於建構每個專案的邏輯已明確繪製,則主要也應共用用於建構的基礎架構程式碼。

透過設定偏好,新的持續整合 (CI) 的實作成本應比從頭開始編寫新的 CI 程式碼更低,這對於需要快速啟動的專案有利。對共用基礎架構程式碼集和服務的持續支援和維護來說,這些 API 也能受益。

考量:建構輸出內容抽象化

為方便消耗建構成果,建構作業應針對其輸出途徑區域訂有詳盡記錄。基礎架構可能是這個途徑區域的取用者,以便執行各種建構後動作,例如將資料上傳至 BigQuery、資料分割和執行測試,或執行二進位檔大小檢查。這與「中繼」建構輸出內容不同,應視為內部,而非直接由下游取用者仰賴。

專案定義的工具也可以是建構輸出內容的取用端。舉例來說,構件工具會讀取 Fuchsia 的建構輸出內容,以便找出並整理雲端儲存空間中的建構構件。基礎架構僅適用於使用基礎架構專屬引數 (即儲存空間值區名稱和專屬建構 ID) 叫用工具。

建構合約可能符合某些常見的基礎架構 API。這有助於確保整合功能保持完善,例如與基礎架構的程式碼涵蓋率服務整合。如果是產生程式碼涵蓋率指標的內部版本變更,您不需要在基礎架構端變更程式碼。

建構合約時應測試,例如,變更結構定義不會導致下游消費者發生硬式編碼轉換。

考量:主要優先的開發方式

專案應盡全力確保建構作業的健康。如此一來,所有貢獻者都居住在最新版本的程式碼附近,不必終止分支版本,也不必使用舊版樹狀結構處理導致的錯誤。這有助於減少合併衝突,避免協作者在任何特定時間產生明顯不同的專案檢視畫面。

根據預設,基礎架構的預先提交程序會嘗試將 CL 重新建立至樹狀結構開端 (因為這是測試乾淨提交內容的 Proxy),因此對貢獻者的工作流程十分實用,以便盡可能接近這項行為。就像開發人員對程式碼集的查看方式類似,基礎架構也應如此。

基礎架構的次提交後,可在新 CL 登陸時持續測試樹狀結構開源,藉此保持建構的良好健康狀態。如果建構作業在樹狀結構時顯示為紅色,則基礎架構應能迅速回報此情況,並由開發人員採取行動。

您可以將沙箱分支版本用於不該提交的程式碼。請注意,這類元件的使用方式通常屬於常規例外情況,而非基礎架構支援的一流流程。

考量:迅速推出和發布頻率

每項專案都應嘗試快速發布依附元件。基礎架構應將滾動依附元件程序自動化,藉此促進此功能,專案擁有者應以高優先順序修正失敗的滾動嘗試。理想情況下,依附元件會在發布後的 O(小時) 內收集。依附元件停滯不前,較難向前復原和/或套用 cherry-picks。這對於有時效性的安全性修補程式特別重要。

在同一頁面中,每項專案都應嘗試快速發布。基礎架構應在程式碼成功整合至主線 (通常稱為「持續部署」) 後,自動將發布程序自動化,有助於解決這個問題。專案擁有者應投入大量資源來編寫自動化測試,這樣才能穩定地整合下游發布的內容,並遵循主要的開發模型。

基礎架構也應提供專案依附元件圖表的瀏覽權限,其中的專案組成「節點」,而捲動和發布會形成「邊緣」。專案擁有者應能夠追蹤整個圖中的 CL 流動情形,並掌握 CL 已登陸或受困等的位置。

實作

這個 RFC 將提供高階指南,協助您瞭解專案應如何使用基礎架構,但這個 RFC 會特別提供實作細節。每個專案可能會遵循任意數量的規範,我們不希望透過指定具體細節的方式建立人為限制。目前,新的樹狀結構外專案仍在起步階段,我們在這裡列出的所有專案,都可能會隨著專案發展而過時。

安全性考量

雖然我們鼓勵專案擁有自己的建構和測試邏輯,但基礎架構仍必須擁有安全性界線。每項專案的原始碼和/或成果必須能夠安全地匯入下一個項目,這樣多專案生態系統才能最終運送到產品。

持續整合工作的輸入必須受到信任:所有原始碼和二進位檔都必須從 Fuchsia 基礎架構擁有者核准的代管位置擷取。結帳階段完成後,您就無法再輸入任何輸入內容,而基礎架構應強制執行這項動作,例如在建構階段嘗試擷取依附元件時,應會導致發生錯誤。

工作的任何輸出都應提供來源。也就是說,Artifact 是從 revision:X 建構的專案建構而成。上傳構件時,基礎架構應強制將構件上傳至具有適當範圍的儲存空間。例如,必須防止依賴內部原始碼的專案將構件上傳至公開值區。

測試

在此 RFC 中提及的 CI 系統,將能以類似目前 Fuchsia 專案的方式,大規模建構並測試新專案。這樣可以減少專案貢獻者需要在桌面執行的手動測試和偵錯工作,好讓基礎架構機器卸載工作。

在基礎架構方面,Fchsia 的 CI 已廣泛投入大量心力,可針對本身的程式碼大規模進行自動化測試。換句話說,CI 能夠測試變更本身。雖然我們可能需要進行一般化作業,但大部分在建構新的持續整合方式時都會繼承這些功能。

說明文件

這個 RFC 會做為新專案與現有專案的參考資料。

在基礎架構端,將這些功能一般化 (使程序大部分為自助式服務) 後,我們將針對新的 CI 設定撰寫說明文件。此外,我們也會將現有說明文件的一般化,以考慮新的樹狀結構外專案,而不只是套用至樹狀結構內基礎架構。

缺點、替代方案和未知

和許多軟體開發最佳做法一樣,遵循這些最佳做法對專案貢獻者而言可能會需要更多時間。舉例來說,追蹤浮動依附元件是常用的快速指令,可在剪接邊緣上快速疊代,而且無需使用滾輪。可主張在短期內雖然是有用的入侵行為,但這些債務應視為技術債,而這和 RFC 中的其他不建議做法一樣,應視為技術債。

這在 Fuchsia 開發期間,便難以找到每個新專案在技術債之間取得最佳平衡點。我們會持續減少建構、測試和基礎架構技術債,而這些債務往往耗費時間達到專案目標。此 RFC 並非試圖防範技術債,而是希望讓這些權衡資訊更明智且事半功倍。