改善 CI 建置時間
2020 年 2 月 18 日,作者:Ethan Dennis、@erdennis13 和 João Moreno、@joaomoreno
Visual Studio Code 是一個大型專案,包含許多活動部件和活躍的參與者名單。我們已展示我們如何積極使用 Azure Pipelines 來維持良好的工程實務,方法是維護我們的建置和持續整合基礎架構。在這篇部落格文章中,我們將討論如何使用 Azure Pipelines Artifact Caching Tasks 來大幅縮短我們的 CI 建置時間。
我們在先前的部落格文章中描述了我們如何將 CI 建置時間縮短了 33%。這是透過使用自訂建置工作來完成的,這些工作會快取 VS Code 使用的節點模組,而不是在建置時解析套件。雖然我們對效能提升感到滿意,但我們想看看可以將我們建置的快取工作推進多遠。
當我們上次談論我們的 CI 工程時,我們的目標平台涵蓋 Windows、macOS 和 Linux。截至今日,VS Code 的目標平台更加多樣化,例如適用於其遠端伺服器元件的 Arm64 和 Alpine Linux。總共有八個不同的目標,它們都共用常見的建置步驟。這篇文章概述了我們如何利用快取工作來減少 CI 重複並進一步改善我們的建置時間。
仍有改進空間
那麼,所有建置作業之間的常見步驟究竟是什麼?每個建置目標都有一個作業,它遵循一組類似的步驟。從非常高的層次來看,每個作業都必須
- 還原相依性
- Lint TypeScript 和 JavaScript
- 將 TypeScript 編譯為 JavaScript
- 執行單元測試套件
- 執行整合測試套件
- 封裝 VS Code
我們的快取工作是加速還原相依性步驟的明顯選擇。例如,當 package-lock.json
檔案很少變更時,為什麼要執行昂貴的 npm install
步驟,而不是快取先前執行的結果?由於我們先前已討論過快取套件,因此這篇文章有趣之處在於我們如何將快取應用於其他步驟。
由於 lint 和編譯與平台無關,因此這些步驟可以輕鬆地由單一建置代理程式執行,該代理程式會與其他平台相關的代理程式共用其結果,而不是讓所有代理程式重複執行此工作。我們建立了一個 Linux 建置代理程式,其唯一職責正是如此:還原套件、lint 和編譯原始碼。我們所要做的就是與其他代理程式共用結果。
快取所有內容
為了跨建置代理程式共用快取結果,我們需要與平台無關的快取,這最初不受快取工作支援。因此,在 Azure Pipelines Artifact Caching Tasks 中新增了一個可選的 platformIndependent
參數。
以下是 VS Code 如何使用 platformIndependent
參數
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: keyfile
targetfolder: target
vstsFeed: $(ArtifactFeed)
platformIndependent: true
在快取節點模組時,使用 package-lock.json
檔案作為快取金鑰是合乎邏輯的。當此檔案變更時,我們必須使快取失效。在快取編譯輸出時,整個程式碼庫必須充當快取金鑰。為了簡化操作,我們決定使用 HEAD commit 作為快取金鑰,因為新的 commit 不可避免地會建立新的快取項目。這對我們的目的來說運作良好,因為單一建置(儘管跨建置代理程式執行)始終在單一 commit 上執行。
另一個缺少的功能是每個建置作業建立多個快取的能力。我們現在發現自己要處理兩個快取(節點模組、編譯),但無法個別處理每個快取。快取工作會輸出一個名為 CacheRestored
的環境變數,可用於樂觀地跳過建置工作。此環境變數在與單一快取互動的建置中運作良好,但在多個快取中不太好用,讓我們想知道 CacheRestored
指的是哪個快取。再次地,在 Azure Pipelines Artifact Caching Tasks 中新增了另一個可選的 alias
參數。
以下是我們如何使用 alias
參數
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: "yarn.lock"
targetfolder: "node_modules"
vstsFeed: "$(ArtifactFeed)"
alias: "Packages"
- script: |
yarn install
displayName: Install Dependencies
condition: ne(variables['CacheRestored-Packages'], 'true')
在這裡,別名 Packages
會附加到環境變數輸出,讓我們可以在單一建置作業中快取 NPM 套件和編譯輸出。我們終於消除了許多 CI 工作的重複,這些工作現在可以只執行一次並在跨平台特定的代理程式之間共用。
在給定的特定使用案例中,仍然有一個最終最佳化的空間:建置重新提交。有時我們必須在先前建置的 commit 上重新觸發 VS Code 建置,因為測試可能不穩定或某些代理程式可能會隨機失敗。理想情況下,共用代理程式不會還原或重新編譯通用程式碼,而是會將控制權移交給平台相關的代理程式來執行其工作。我們注意到的問題是,編譯快取套件非常龐大,還原它們需要大約 8 分鐘,但這是徒勞的,因為如果快取存在,共用代理程式只會讓出控制權。因此,再次在 Azure Pipelines Artifact Caching Tasks 中新增了一個新的可選 dryRun
參數,這讓我們可以檢查快取套件是否存在,而無需還原它,有效地將我們的建置重新提交時間縮短了 8 分鐘。
在我們的建置中使用 dryRun
參數看起來像這樣
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: commit
targetfolder: output
vstsFeed: "$(ArtifactFeed)"
dryRun: true
- script: |
npm run compile install
displayName: Install Dependencies
condition: ne(variables['CacheExists'], 'true')
請注意,這也引入了一個新的 CacheExists
變數,它與 dryRun
參數一起運作。
結果
一旦實作這些變更,我們看到總建置時間大幅縮短。下表顯示了 VS Code 目標的每個平台的總建置時間變化
平台 | 之前 | 之後 | 節省時間 |
---|---|---|---|
Windows | 58 分鐘 | 44 分鐘 | 24% |
Windows 32 | 59 分鐘 | 46 分鐘 | 22% |
Linux | 38 分鐘 | 23 分鐘 | 39% |
macOS | 68 分鐘 | 42 分鐘 | 38% |
Linux Arm | 22 分鐘 | 21 分鐘 | 5% |
Linux Alpine | 23 分鐘 | 26 分鐘 | -13% |
Linux Arm 和 Linux Alpine 目標僅建置 VS Code 遠端伺服器元件,因此它們的原始建置時間已足夠好。但由於它們與標準 VS Code 用戶端平台共用一些常見工作,因此我們決定讓它們依賴於通用建置代理程式。由於在某種情況下增加了額外負荷,因此導致建置時間略有增加。
建置重新提交看到了大幅改善,因為可以完全跳過共用代理程式工作。以下是一些 macOS 的數字範例
平台 | 之前 | 之後 | 節省時間 |
---|---|---|---|
macOS | 68 秒 | 34 秒 | 50% |
總而言之,我們很高興看到 VS Code 的 CI 建置時間總共縮短了約 50%!最好的消息是,您可以從我們的建置定義中汲取靈感,以實現您自己的建置時間改進。
快取愉快,
Ethan Dennis,開發人員服務資深軟體工程師 @erdennis13
João Moreno,VS Code 資深軟體工程師 @joaomoreno