改進 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 使用的 node 模組,而不是在構建時解析包。雖然我們對這次效能提升感到滿意,但我們想看看我們自己構建的快取任務還能推動多遠。
上次我們討論 CI 工程時,我們的目標平臺涵蓋了 Windows、macOS 和 Linux。如今,VS Code 的目標平臺更加多樣化,例如為其遠端伺服器元件支援 Arm64 和 Alpine Linux。總共有八個不同的目標平臺都共享通用的構建步驟。本文概述了我們如何利用快取任務來減少 CI 的重複工作,並進一步改善我們的構建時間。
改進空間
那麼,所有構建作業中究竟有哪些共同步驟呢?每個構建目標都有一個遵循類似步驟集的作業。在很高的層面上,每個作業都必須:
- 還原依賴
- 檢查 TypeScript 和 JavaScript 程式碼
- 將 TypeScript 編譯為 JavaScript
- 執行單元測試套件
- 執行整合測試套件
- 打包 VS Code
我們的快取任務是加速**還原依賴**步驟的顯而易見的選擇。例如,既然 package-lock.json
檔案很少更改,為什麼還要執行一個耗時的 npm install
步驟,而不是快取上一次執行的結果呢?由於我們之前已經討論過快取包,這篇博文的有趣之處在於我們如何將快取應用到其他步驟。
由於程式碼檢查和編譯是平臺無關的,這些步驟可以很容易地由單個構建代理執行,並與其他依賴平臺的代理共享其結果,而不是讓所有代理重複執行這項工作。我們建立了一個 Linux 構建代理,其唯一職責就是:還原包、檢查程式碼和編譯原始碼。我們所要做的就是與其他代理共享結果。
快取一切
為了在構建代理之間共享快取結果,我們需要平臺無關的快取,而這最初並不被快取任務所支援。因此,一個可選的 platformIndependent
引數被新增到了 Azure Pipelines Artifact Caching Tasks 中。
以下是 VS Code 使用 platformIndependent
引數的方式:
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: keyfile
targetfolder: target
vstsFeed: $(ArtifactFeed)
platformIndependent: true
在快取 node 模組時,使用 package-lock.json
檔案作為快取鍵是合乎邏輯的。當這個檔案改變時,我們必須使快取失效。當快取編譯輸出時,整個程式碼庫都必須作為快取鍵。為了簡化問題,我們決定使用 HEAD 提交作為快取鍵,因為一個新的提交將不可避免地建立一個新的快取條目。這對於我們的目的來說很好用,因為單次構建,儘管跨構建代理執行,總是在單個提交上執行。
另一個缺失的功能是能夠在每個構建作業中建立多個快取。我們現在發現自己要處理兩個快取(node 模組、編譯),卻沒有辦法單獨處理每個快取。快取任務會輸出一個名為 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