译 | .NET Core 基础架构进化之路(一)
原文:Matt Mitchell
翻譯:Edi Wang
隨著 .NET Core 3.0 Preview 6 的推出,我們認為簡要了解一下我們基礎設施系統(tǒng)的歷史以及過去一年左右所做的重大改進會很有用。
如果您對構建基礎結構感興趣,或者想要了解我們?nèi)绾螛嫿ㄅc .NET Core 一樣大的產(chǎn)品,那么此帖子將很有趣。它不描述應在下一個應用程序中使用的新功能或示例代碼。如果您喜歡這些類型的帖子,請告訴我們。我們有幾個類似計劃,但希望知道此類信息是否對你有幫助。
一點歷史
.NET Core 項目始于 3 多年前,與傳統(tǒng)的微軟項目相比,這是一個重大轉變。
在 GitHub 上公開開發(fā)
多個集成在一起的獨立 Git 倉庫,而不是一個單獨的龐大倉庫
面向多個平臺
其組件可能以多個"車輛"的形式發(fā)布(例如,Roslyn 作為 Visual Studio 和 SDK 的組件發(fā)布)
我們早期的基礎設施決策是圍繞必要性和權宜之計做出的。我們使用 Jenkins 進行 GitHub PR 和 CI 驗證,因為它支持跨平臺 OSS 開發(fā)。我們的官方版本位于 Azure DevOps(當時稱為 VSTS)和 TeamCity(由ASP.NET核心使用),其中存在簽名和其他關鍵運輸基礎結構。
我們使用手動更新包依賴項版本和有點自動化的 GitHub PRs 的組合將存儲庫集成在一起。團隊獨立構建了包裝、布局、本地化和所有其他工具所需的工具,這些在大型開發(fā)項目中出現(xiàn)的任務。
雖然并不理想,但從某種意義上說,這在早期就足夠有效了。隨著項目從 .NET Core 1.0 和 1.1 發(fā)展到 2.0 及之后,我們希望投資一個更加集成的開發(fā)棧、更快的發(fā)布節(jié)奏和更簡單的服務。我們希望生成一個新的帶有最新運行時的 SDK,每天發(fā)布多次。我們希望在不降低獨立存?zhèn)}庫的開發(fā)速度的情況下進行所有這些工作。
.NET Core 面臨的許多基礎結構挑戰(zhàn)源于倉庫結構的隔離、分布式性質(zhì)。雖然多年來它變化很大,但該產(chǎn)品由 20-30 個獨立 Git 倉庫(ASP.NET Core 直到最近還擁有更多)組成。一方面,有許多獨立的開發(fā)孤島往往使這些孤島的開發(fā)非常高效:開發(fā)人員可以在庫中快速迭代,而不必擔心技術棧的其余部分。另一方面,它使整個項目的創(chuàng)新和集成效率降低得多。一些示例:
如果我們需要推出新的簽名或打包功能,那么在使用不同工具的眾多獨立存儲庫中執(zhí)行此操作的成本非常高。
跨棧移動更改速度很慢且成本高昂。對于"低"位置棧中的修復和功能(例如 corefx 庫)可能在幾天內(nèi)在 SDK(棧的"頂部")中看不到。如果我們在 dotnet/corefx 中進行修復,則必須構建該更改,并將新版本流入引用它的任何上棧組件(例如 dotnet/core 設置和ASP.NET Core),在那里將測試、提交和構建該更改。然后,這些新組件將需要將這些新輸出進一步向上流,依此類推,直到達到頭。
譯者注:[棧] 的原文為 Stack,不是指棧數(shù)據(jù)結構,而是描述組成整個.NET Core的各種組件,它們一起,是一個棧。
在所有這些情況下,在許多層面上都有失敗的機會,進一步減緩了這一進程。隨著 .NET Core 3.0 規(guī)劃的認真開始,很明顯,如果不對我們的基礎結構進行重大更改,我們就無法創(chuàng)建我們想要的范圍的產(chǎn)品發(fā)布。
三管齊下的方法
我們開發(fā)了一個三管齊下的方法來減輕我們的痛苦:
共享工具(又名Arcade) – 在我們的存儲庫中投資共享工具。
系統(tǒng)整合 (Azure DevOps) - 拋棄 Jenkins 并擁抱集成 GitHub CI 的 Azure DevOps。將我們的官方版本從經(jīng)典 VSTS 時代的流程移動到現(xiàn)代配置即代碼。
自動依賴項流和發(fā)現(xiàn) (Maestro) – 顯式跟蹤依賴項,并快速更新它們。
Arcade
在 .NET Core 3.0 之前,有 3-5 種不同的工具實現(xiàn)分散在不同的倉庫中,具體取決于您計數(shù)的方式。
核心運行時倉庫 (dotnet/coreclr, dotnet/corefx 以及dotnet/core-setup) 包含 dotnet/buildtools 工具。
ASP.NET核心的倉庫?有 aspnet/KoreBuild
使用 ?Repo Toolset 的各種倉庫,如dotnet/symreader
其他幾個孤立的倉庫具有獨立的實現(xiàn)。
雖然在這個世界上,每個團隊可以自定義他們的工具,并只構建他們需要的,但它確實有一些顯著的缺點:
開發(fā)人員在倉庫之間奔波的效率較低
示例:當開發(fā)人員從 dotnet/corefx 跑到 dotnet/core-sdk 時,存儲庫的"語言"是不同的。她鍵入什么來編譯和測試?日志放在何處?如果她需要向回購中添加新項目,這是如何做到的?
每個必需的功能都被開發(fā) N 次
示例:.NET Core 產(chǎn)生成噸的 NuGet 包。雖然有一些變化(例如,使用 dotnet/core-setup 生成的 Microsoft.NETCore.App 共享運行時包,與 Microsoft.AspNet.WebApi.Client 等"普通"軟件包的構建方式不同),但生成它們的步驟相當類似。
遺憾的是,由于倉庫的布局、項目結構等存在分歧,因此這些打包任務需要實現(xiàn)的方式不同。存儲庫如何定義應生成哪些包、這些包中的內(nèi)容、其元數(shù)據(jù)等。如果沒有共享工具,團隊通常更容易實現(xiàn)另一個打包任務,而不是重用另一個打包任務。這當然對資源造成壓力。
通過 Arcade,我們努力將所有倉庫放在一個通用布局、倉庫"語言"和任務集(如果可能的話)。這并非沒有陷阱。任何類型的共享工具最終都解決了一些"金發(fā)(Goldilocks)"問題。如果共享工具過于規(guī)范,則任何重大規(guī)模的項目所需的自定義類型將變得困難,并且更新該工具變得非常困難。
使用新更新很容易破壞倉庫。BuildTools 因此遭受損失。使用它的倉庫與它緊密耦合,以至于它不僅不能用于其他倉庫,而且在 BuildTools 中的任何更改通常以意想不到的方式使使用者崩潰。如果共享工具的規(guī)范性不夠,則存儲庫在工具的使用上往往會出現(xiàn)偏差,而推出更新通常需要在每個單獨的存儲庫中進行大量工作。在這一點上,為什么我們還需要共享工具?
Arcade 實際上嘗試同時使用這兩種方法。它將通用倉庫"語言"定義為一組腳本(請參閱 eng/common)、通用倉庫布局以及作為 MSBuild SDK 推出的通用生成目標集。選擇完全采用 Arcade 的倉庫具有可預測的行為,這使得更改易于跨倉庫推出。不希望這樣做的倉庫可以從各種提供基本功能(如簽名和打包)的 MSBuild 任務包中進行選擇,這些功能在所有存?zhèn)}庫看起來都相同。當我們對這些任務進行更改時,我們會盡力避免重大更改。
讓我們來看看 Arcade 提供的主要功能,以及它們?nèi)绾渭傻轿覀兏蟮幕A架構中。
常規(guī)編譯任務包
這些是 MSBuild 任務的基本層,可以獨立使用,也可以作為 Arcade SDK 的一部分使用。他們是"付費才能玩"("Arcade"因此得名)。它們提供了大多數(shù) .NET Core 倉庫中所需的一組通用功能:
簽名:
Microsoft.DotNet.SignTool
發(fā)布編譯產(chǎn)物(跨倉庫訂閱源):
Microsoft.DotNet.Build.Tasks.Feed
打包:
Microsoft.DotNet.Build.Tasks.Packaging
常見的倉庫目標和行為
這些是作為稱為"Arcade SDK"的 MSBuild SDK 的一部分提供的。通過利用它,倉庫選擇加入默認的 Arcade 編譯行為、項目和項目布局等。
通用倉庫"語言"
一組使用依賴項流在所有 Arcade 存儲庫之間同步的通用腳本文件(稍后將介紹更多)。這些腳本文件引入了采用 Arcade 的倉庫的通用"語言"。對于開發(fā)人員來說,在這些存儲庫之間移動變得更加無縫。此外,由于這些腳本在存儲庫之間同步,因此對 Arcade 存儲庫中的原始副本進行新更改可以快速將新功能或行為引入完全采用共享工具的存儲庫。
共享 Azure DevOps 作業(yè)和步驟模板
雖然定義公共存儲庫"語言"的腳本主要針對與人交互,但 Arcade 還有一組 Azure DevOps 作業(yè)和步驟模板,允許 Arcade 存儲庫與 Azure DevOps CI 系統(tǒng)進行接口。與常規(guī)編譯任務包一樣,步驟模板構成了一個基礎層,幾乎每個倉庫都可以使用(例如,發(fā)送生成遙測)。作業(yè)模板形成更完整的單元,使存儲庫能夠減少對 CI 流程細節(jié)的擔心。
遷移到 Azure DevOps
如上所述,更大的團隊在 2.2 版本中使用了 CI 系統(tǒng)的組合:
AppVeyor 和?Travis 用于 ASP.NET Core 的 GitHub PR
TeamCity 用于官方 ASP.NET 編譯
Jenkins 用于其他 .NET Core 的 GitHub PR 和滾動驗證。
經(jīng)典(非 YAML)Azure DevOps 工作流用于官方的非ASP.NET Core項目
許多區(qū)別只是為了必要性。Azure DevOps 不支持公共 GitHub PR/CI 驗證,因此ASP.NET Core 轉向 AppVeyor 和 Travis 來填補空白,而 .NET Core 則投資 Jenkins。經(jīng)典 Azure DevOps 對構建業(yè)務流程沒有很多支持,因此ASP.NET Core 團隊轉向 TeamCity,而 .NET Core 團隊在 Azure DevOps 上構建了名為 PipeBuild 的工具來提供幫助。所有這些分歧都非常昂貴,即使在一些不明顯的方式:
雖然 Jenkins 是靈活的,但維護大量任務(6000-8000)是一項嚴肅的工作。
在經(jīng)典 Azure DevOps 之上構建我們自己的業(yè)務流程需要很多折衷。已檢查的管道作業(yè)描述并非真正是人類可讀的(它們剛剛導出了手動創(chuàng)建的生成定義的 json 描述),密鑰管理很丑陋,在我們嘗試處理生成要求的廣泛差異。
當正式編譯與夜間(nightly)驗證與 PR 驗證過程在不同的系統(tǒng)中定義時,共享邏輯就變得困難。開發(fā)人員在進行流程更改時必須額外小心,因為很容易爆。我們在一個特殊的腳本文件中定義了 Jenkins PR 作業(yè),TeamCity 有許多手動配置的作業(yè),AppVeyor 和 Travis 使用自己的 yaml 格式,Azure DevOps 具有我們在它之上構建的模糊自定義系統(tǒng)。很容易在 PR 中更改生成邏輯并中斷官方的 CI 構建。為了緩解這種情況,我們確實努力在正式 CI 和 PR 構建中通用的腳本中保留盡可能多的邏輯,但差異總是隨著時間的推移而逐漸減少。某些差異(如在構建環(huán)境中)基本上不可能完全消除。
更改工作流的做法差別很大,而且往往難以理解。開發(fā)人員了解了 Jenkins 用于更新 PR 邏輯的 netci.groovy 文件,但并未轉換為用于正式 CI 構建的 PipeBuild json 文件。因此,對系統(tǒng)的知識通常被隔離到少數(shù)團隊成員中,這在大型組織中并不理想。
當 Azure DevOps 開始推出基于 YAML 的構建管道,并在 .NET Core 3.0 開始啟動時對公共 GitHub 項目的支持,我們認識到我們具有獨特的機會。有了這種新的支持,我們可以將所有現(xiàn)有的工作流從單獨的系統(tǒng)移動到現(xiàn)代 Azure DevOps 中,還可以對如何處理正式的 CI 和 PR 工作流進行一些更改。我們從以下工作大致概要出發(fā):
將所有邏輯保存在代碼中,在 GitHub 中。隨時隨地使用 YAML 管道。
有一個公開和私有項目。
公開項目將通過 GitHub 存儲庫和 PR 運行所有公共 CI,正如我們始終擁有的
私有項目將運行官方 CI 是我們需要進行的任何私人更改的場所,在存儲庫中匹配公共 GitHub 倉庫
只有私有項目才能訪問受限制的資源。
在官方 CI 和 PR 生成之間共享相同的 YAML。使用模板表達式來區(qū)分公共項目和私有項目,其中行為必須分,或者僅訪問私有項目中可用的資源。雖然這通常使整個 YAML 定義更混亂一些,但這意味著:
進行流程更改時,爆掉的可能性較低。
開發(fā)人員只需更改一組位置來更改官方 CI 和 PR 流程。
為常見任務構建 Azure DevOps 模板,以將樣板 YAML 的重復降至最低,并啟用使用依賴項流輕松推出更新(例如遙測)。
到目前為止,所有主 .NET Core 3.0 倉庫都在 Azure DevOps 上,用于其公共 PR 和官方 CI。一個很好的例子管道是 dotnet/arcade 自己本身的官方編譯/PR管道。
譯者注:Arcade 自己的編譯管道 https://github.com/dotnet/arcade/blob/master/azure-pipelines.yml
(文章翻譯未完待續(xù))總結
以上是生活随笔為你收集整理的译 | .NET Core 基础架构进化之路(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: asp.net core 系列之Star
- 下一篇: .NET开发框架(一)-框架介绍与视频演