改善 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
步驟,而不是快取先前執行的結果?由於我們之前已經討論過快取套件,因此這篇文章有趣的地方在於我們如何將快取應用於其他步驟。
由於 Linting 和編譯與平台無關,因此這些步驟可以輕鬆地由單一建置代理程式執行,該代理程式會與其他平台相關的代理程式共用其結果,而不是讓所有代理程式重複執行此工作。我們建立了一個 Linux 建置代理程式,其唯一職責正是如此:還原套件、Lint 和編譯原始碼。我們所要做的就是與其他代理程式共用結果。
快取所有項目
為了跨建置代理程式共用快取結果,我們需要與平台無關的快取,而快取工作最初並不支援這種快取。因此,一個可選的 platformIndependent
參數被新增到 Azure Pipelines Artifact Caching Tasks。
以下是 VS Code 如何使用 platformIndependent
參數
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: keyfile
targetfolder: target
vstsFeed: $(ArtifactFeed)
platformIndependent: true
當快取節點模組時,使用 package-lock.json
檔案作為快取金鑰是合乎邏輯的。當此檔案變更時,我們必須使快取失效。當快取編譯輸出時,整個程式碼庫都必須充當快取金鑰。為了簡化操作,我們決定使用 HEAD 提交作為快取金鑰,因為新的提交不可避免地會建立新的快取項目。這對於我們的目的來說運作良好,因為單一建置(儘管跨建置代理程式執行)始終在單一提交上執行。
另一個遺失的功能是每個建置作業建立多個快取的能力。我們現在發現自己同時處理兩個快取(節點模組、編譯),但無法個別定址每個快取。快取工作輸出一個名為 CacheRestored
的環境變數,可用於樂觀地跳過建置工作。此環境變數在與單一快取互動的建置中運作良好,但在多個快取中則不太理想,讓我們想知道 CacheRestored
正在參考哪個快取。再一次,另一個可選的 alias
參數被新增到 Azure Pipelines Artifact Caching Tasks。
以下是我們如何使用 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 工作的重複,這些工作現在可以只執行一次並在跨平台特定代理程式之間共用。
考慮到特定的使用案例:建置重新提交,仍然有一個最終最佳化的空間。我們有時必須在先前建置的提交上重新觸發 VS Code 建置,因為測試可能不穩定或某些代理程式可能隨機失敗。理想情況下,共用代理程式不會還原或重新編譯通用程式碼,而是將控制權讓給平台相關的代理程式來執行他們的工作。我們注意到的問題是,編譯快取套件非常龐大,還原它們大約需要 8 分鐘,但這一切都是徒勞的,因為如果該快取存在,共用代理程式只會讓出控制權。因此,一個新的可選 dryRun
參數再次新增到 Azure Pipelines Artifact Caching Tasks,這讓我們可以在不還原快取套件的情況下檢查其是否存在,有效地將我們的建置重新提交時間縮短 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