譯者 | 劉汪洋
審校 | 重樓
概括:這篇文章分享了作者在使用 Github Actions 作為 CI/CD 工具時遇到的一些問題和解決方案,包括如何避免重復代碼、如何管理環境變量、如何使用緩存和工件、如何利用復用工作流等。
開始構建發布流水線
GreptimeDB 自開源伊始,就采用 GitHub Actions 實現了自動化軟件構建過程,從而誕生了首個發布流水線。
對于開源項目,構建一個穩定且一致的發布流水線具有以下關鍵價值:
- 供應隨時可用的軟件構件:身為軟件供應鏈的上游生產者,我們必須為不同的下游用戶提供安全、可信賴、隨時可用的軟件構件,如二進制文件、鏡像等。
- 優化開發者體驗:用戶可無需繁瑣配置或從零開始設置和編譯,即可獲取適合各自平臺的即時可執行的軟件構件。
- 實現發布工作流的自動化測試:結合不同類型的回歸測試(例如性能、穩定性、集成測試等)和自動發布流程,以提高軟件的整體質量。
雖有其他替代方案,如 Circle CI、Travis CI、GitLab CI,或自托管的開源項目如 Tekton 和 Argo Workflow, 但選擇 GitHub Actions 的理由顯而易見:它與 GitHub 生態系統融合,為用戶提供了便捷的操作界面和豐富的軟件市場訪問權限。
然而,用戶友好并不意味著維護輕松。相反,GitHub Actions 的維護可能變得復雜。GreptimeDB 的最初開源版本中的 release.yml 僅包含了精煉的183行代碼。但隨著許多貢獻者的修改,這份文件逐漸演變并整合了:
- 構建多樣化平臺上的構件;
- 構建激活軟件構件的不同功能開關;
- 在實際構建之前執行集成測試;
- 將軟件構件推送到不同倉庫(如 DockerHub、ACR、S3等);
- 控制不同的發布條件(如手動觸發、錯誤容限等);
- 等等。
還有,由于一些特殊需求(如調試發布、每日構建等),在不同的內部倉庫中產生了許多只有微小差異的相似流水線分支,這增加了維護的壓力。
隨著構建要求的復雜性不斷提高,release.yml 文件迅速變得龐大,充滿了冗余配置,維護難度增大。如果不及時進行重構,發布流水線將面臨迅速甚至徹底的失效風險。
發布流水線退化問題分析
隨著構建要求的復雜性不斷提高,release.yml 文件迅速變得龐大,充滿了冗余配置,維護難度增大。如果不及時進行重構,發布流水線將面臨迅速甚至徹底的失效風險。
發布流水線退化問題分析
審查release.yml文件后,我們需要識別一些促使它迅速退化的因素。只有深入了解問題的根源,我們才能制定合適的重構方案。
- 語言局限性:GitHub Actions 的基于 YAML 的領域特定語言(DSL)與通用編程語言相比表現力不足,從而可能產生冗余和難以維護的代碼。
- 調試難度高:GitHub Actions 因難以調試而聞名,特別是項目使用的 Rust 語言編譯成本較高,進一步加長了調試周期。盡管如 act 等工具可以在本地執行 GitHub Actions,但實際運行操作仍然必須進行,因此無法有效縮短編寫-運行-調試的周期。
- 動作解耦不足:GitHub Actions 通過 Composite 組合不同動作。我們由于缺乏經驗,未將邏輯分解為獨立動作,而是將所有內容集中到一個 YAML 文件中,因此維護起來較為困難。
- 重復構建問題:由于 GitHub 缺乏支持 ARM64 的虛擬機實例,為了優化編譯性能,我們選擇在 GitHub 的 x86_64 虛擬機實例上進行 AMD64 和 ARM64 軟件構件的跨平臺編譯。雖然可以使用 Docker Buildx 激活 QEMU 進行 ARM64 平臺模擬構建,但性能不佳。由于我們依賴于 GitHub Runner 主機環境而非 Dockerfile,實現一致的可重復構建頗具挑戰性。
我們在重構過程的開始階段,應該首先注重可維護性優于性能(構建速度)。發布的流水線將隨項目的增長不斷演化,因此可維護性至關重要。如果忽視了可維護性,可能會逐漸陷入困境,最終導致研發效率的降低。只有保障了可維護性,我們才能著手提高性能。在編譯/構建場景中,合理利用各種緩存機制和優質的構建機器通常能夠提升性能。
重構計劃
重構 YAML 文件不同于常規編程項目,它更多地是一次對配置流程的全面審查,雖然這聽起來并不完全符合邏輯,卻存在較高的偶發復雜性。在此過程中容易陷入難以察覺的問題,進而可能面臨解決的艱難挑戰。以下總結了針對正在經歷此類重構的一些實用建議。
- 采用 Dockerfile 實現構建標準化:雖然基于 Dockerfile 的構建可能影響性能,但這樣的方式增強了可維護性,統一了跨平臺構建流程,確保了構建的可重復性。
- 統一構建命令接口:基于上述考慮,需將各類構建命令精簡為單一 make 命令。將編譯的復雜性限制在 yaml 文件之外,避免發布階段隱藏過多細節,且在開發階段的 Makefile 或腳本中展現出來。通過 Makefile,用戶可以體驗到與發布階段一致的構建過程,從而提升開發效率。
- 采用 AWS EC2:鑒于 GitHub Actions 目前缺乏 ARM64 VM 實例,需要采用交叉編譯。使用 AWS EC2 ARM64 實例來構建 ARM64 平臺的軟件,以實現所有平臺構建過程的標準化。
- 模塊化解耦合:將 release.yml 拆分,使其成為一組明了的任務集合。每個位于 actions 目錄下的 action.yml 文件都應保持簡潔明了,以便根據相同操作自定義各種流程,提高整個過程的靈活性和效率。由于 GitHub Actions 內無組任務機制,此法是最佳方案。
- 簡化任務執行:每個任務需專注于單一、特定的任務,增強其冪等性。這樣在出錯時,重試工作將更容易。此外,有助于更有效地提取頂層控制變量,允許更精確的手動觸發控制。
- 避免在 Actions 中過度加載 Shell 命令:不要在單個 GitHub Actions 步驟中打包過多 Shell 命令,以利于維護。如果命令較多,可考慮轉換為外部腳本并精簡輸入參數,確保腳本的獨立執行和驗證。
- 引入 Pre Job 分配 Runners:Allocate Runners 是首要任務,用于為后續工作分配 Runners 并創建全局版本標簽。例如,使用 EC2 時,Allocate Runners 將通過 EC2 API(由 ec2-github-runner Action 實現)分配相應平臺的 EC2 實例。未來計劃引入更精確的選擇算法以優化 Runner 分配成本。
- 實現全球統一流程:避免創建功能相似的 GitHub Actions 分支,以減少維護負擔。為促進透明的開源開發,所有內部使用的構建流程已合并至主要的 GreptimeDB 存儲庫。代碼若為開源,則軟件產品和構建過程也應同樣開放。
- 在 GitHub 存儲庫中合理使用變量和秘密:不應把大部分外部參數當作機密處理。一些非機密的外部參數應配置為 GitHub 變量,以便未來可以方便修改。應避免在 YAML 中硬編碼將需要頻繁修改的變量。
展望
GreptimeDB 對發布流水線的重構只是其走向成熟旅程中的一個環節。我們正朝著構建更高質量、更強大的 CI 努力發展:
- 拓展平臺生態系統: 我們 windows 系統軟件產品即將發布,屆時歡迎來測試和體驗。
- 增加自動化測試種類:在未來的計劃中,我們將在 CI 流程中融合各類測試方式,例如混沌測試和性能測試,以持續提高軟件的質量。
- 優化 CI 使用成本:通過精確分配各類 Runners,滿足不同用例的需求,我們計劃使整個 CI 運行更為經濟和高效。
- 提升構建效能:雖然重構發布流水線在一定程度上對構建性能造成影響(#2113),但我們計劃通過采用更智能的構建緩存技術,進一步加速構建過程。
- 強化軟件供應鏈的安全性:在現代軟件制品管理中,軟件供應鏈安全日漸重要。作為一個開源項目,我們將確保軟件產品的安全性、可信性和透明度。我們計劃在發布流程中加入基本安全措施,如 SBOM 管理、軟件簽名和驗證等。
雖然充分利用 GitHub Actions 存在挑戰,我們仍將堅持不懈地改進。如你對此感興趣并想要深入了解,歡迎加入我們在 Slack 上的社區進行討論!你的建議對我們下一階段的改進很可能會起到關鍵作用。
譯者介紹
劉汪洋,51CTO社區編輯,昵稱:明明如月,一個擁有 5 年開發經驗的某大廠高級 JAVA 工程師,擁有多個主流技術博客平臺博客專家稱號。
標題:Practical Tips for Refactoring Release CI using GitHub Actions,作者:Greptime