快速搞懂.NET 5/.NET Core應用程序的發佈部署

.NET Framework時代,.NET 應用程序大多直接部署運行在Windows服務器上,當然也可以通過Mono部署運行在Linux上。無論部署exe,還是IIS站點、或是Windows Service,編譯後的程序直接copy、簡單配置部署上即可。

有了.NET Core之後,.NET應用程序完美支持跨平台部署,.NET 5 Release後,沿襲了.NET Core應用程序發佈模式。

支持跨平台部署運行,.NET 5/.NET Core的應用程序面臨著多平台,多場景的部署需求。比如說:部署在Windows、Linux、MaxOS…,OS層面是否需要部署.NET Runtime運行時,.NET Runtime運行時的版本選擇,等等。

因此,今天我們研究一下.NET 5/.NET Core應用程序的部署發佈。

一、兩種應用程序發佈模式

  1. 以自包含的方式發佈應用程序

      這種模式包含.NET運行時和應用程序及其依賴項的應用程序。我們可以在未安裝.NET運行時的操作系統上運行它。

      總結一句話:把.NET Runtime運行時打包到程序運行目錄中,應用程序運行的主機不需要安裝.NET Runtime運行時。

  2. 以依賴於框架的方式發佈應用程序

      生成一個僅包含應用程序本身及其依賴項的應用程序。應用程序的運行環境必須單獨安裝.NET運行時。

      總結一句話:不包含.NET Runtime運行時,只有應用程序本身和依賴的應用程序。應用程序運行的主機需要單獨安裝應用程序所需的.NET Runtime運行時。

二、NET 5/.NET Core的應用程序的發佈指令:dotnet publish

  dotnet publish -將應用程序及其依賴項發佈到指定的文件夾中,以方便後續部署到目標託管系統。

  關於dotnet publish的使用說明,可以參考以下鏈接://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?WT.mc_id=DT-MVP-5003918

dotnet publish [<PROJECT>|<SOLUTION>] [-c|--configuration <CONFIGURATION>]
    [-f|--framework <FRAMEWORK>] [--force] [--interactive]
    [--manifest <PATH_TO_MANIFEST_FILE>] [--no-build] [--no-dependencies]
    [--no-restore] [--nologo] [-o|--output <OUTPUT_DIRECTORY>]
    [-p:PublishReadyToRun=true] [-p:PublishSingleFile=true] [-p:PublishTrimmed=true]
    [-r|--runtime <RUNTIME_IDENTIFIER>] [--self-contained [true|false]]
    [--no-self-contained] [-v|--verbosity <LEVEL>]
    [--version-suffix <VERSION_SUFFIX>]

dotnet publish -h|--help

   dotnet publish 將編譯應用程序,讀取其在項目文件中指定的依賴項,然後將結果文件集發佈到目錄中。輸出包括以下內容

  • 具有dll擴展名的程序集中的中間語言(IL)代碼。
  • 一個.deps.json文件,其中包含項目的所有依賴項
  • 一個.runtimeconfig.json文件,它指定應用程序期望的共享運行時,以及該運行時的其他配置選項(例如,垃圾收集類型)。
  • 應用程序依賴的應用程序,從NuGet緩存目錄複製到輸出文件夾中。

    從上述描述中,我們可以發現,通過dotnet publish指令,我們可以編譯應用程序,生成並輸出指定運行環境的交付物。

    我們新建一個.NET 5的Console應用程序,同時引用Newtonsoft.Json Nuget包。

    

    Main函數的代碼:

using System;

namespace NET5PublishExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var msg = Newtonsoft.Json.JsonConvert.SerializeObject("Hello .NET 5!");
            Console.WriteLine(msg);
            Console.ReadKey();
        }
    }
}

  首先,編譯一下這個工程dotnet build,這一步很重要。然後,使用命令行執行dotnet publish指令:

   

   我們看一下F:\GitHub\Source\Repos\NET5PublishExample\bin\Debug\net5.0\publish\目錄下生成的文件:

   

    正如上面所說,輸出包括以下內容

  • 具有dll擴展名的程序集中的中間語言(IL)代碼:NET5PublishExample.dll
  • 一個.deps.json文件,其中包含項目的所有依賴項NET5PublishExample.deps.json
  • 一個.runtimeconfig.json文件,它指定應用程序期望的共享運行時,以及該運行時的其他配置選項(例如,垃圾收集類型)。NET5PublishExample.runtimeconfig.json
  • 應用程序依賴的應用程序,從NuGet緩存目錄複製到輸出文件夾中。Newtonsoft.Json.dll

     同時,還生成了一個Windows平台的可執行文件:NET5PublishExample.exe,雙擊可以執行:

     

     另外,使用dotnet NET5PublishExample.dll,也可以直接執行:

      

     然後有幾個疑問:

     NET5PublishExample.dll是不是可以跨平台運行?

     在Linux、MacOS平台下有沒有對應的可執行文件?

     帶着這2個問題,我們繼續往下研究?

三、dotnet publish生成的可執行文件和跨平台二進制文件

     自包含的方式發佈應用程序,依賴於框架的方式發佈應用程序。這兩種發佈模式默認情況下都會生成特定於平台的可執行文件和跨平台二進制文件。

     1. 可執行文件

     可執行文件不是跨平台的。它們特定於操作系統和CPU體系結構。因為Windows和linux下的可執行文件的結構和內容是不同的,所以可執行文件是分操作系統的。

     創建可執行文件時,同時可以選擇將應用程序發佈為自包含或依賴於框架。

     以自包含的方式發佈應用程序包括該應用程序的.NET運行時,並且該應用程序的用戶不必擔心在運行該應用程序之前安裝.NET。可以直接執行!

     發佈為與框架相關的應用程序不包括.NET運行時和庫。僅包括應用程序和第三方依賴項。需要安裝.NET 運行時。

     這裡我們示例2個平台的可執行文件:

     ① windows-x64平台

     

      ② Linux-x64平台

       使用的dotnet publish指令 dotnet publish -r linux-x64 –self-contained false

       

       生成的可執行文件:

       

     2. 跨平台的二進制文件

     將應用程序發佈為依賴於框架的dll文件形式時,就會創建跨平台的二進制文件。該dll文件以項目命名。例如,如果您有一個名為應用程序NET5PublishExample,文件名為NET5PublishExample.dll創建。

     以這種方式發佈的應用程序dotnet <filename.dll>可以通過命令運行,可以在任何平台上運行。

四、關於自包含的發佈選項和示例

    以自包含的方式發佈應用會生成特定於平台的可執行文件

    輸出發佈文件夾包含應用程序的所有組件,包括.NET庫和目標運行時。該應用程序與其他.NET應用程序隔離,並且不使用本地安裝的.NET運行時。因此無需下載並安裝.NET 運行時。

    可執行二進制文件針對指定的目標平台生成。例如,如果您有一個名為NET5PublishExample的應用程序,並且發佈了Windows的自包含可執行文件,則會創建NET5PublishExample.exe文件。對於Linux或macOS發佈,將創建一個NET5PublishExample文件。目標平台和體系結構-r <RID>由dotnet publish命令的參數指定。有關RID的更多信息,請參見.NET RID目錄

    如果應用程序具有特定於平台的依賴項,例如包含特定於平台的依賴項的NuGet程序包,則這些依賴項將與應用程序一起複制到publish文件夾中。

    這種模式的優勢有哪些呢?

  1.     控制.NET版本:可以控制與應用程序一起部署的.NET版本。
  2.     指定運行的平台:因為必須為每個平台發佈應用程序,所以我們需要確定應用程序將在何處運行。如果.NET引入了新平台,則必須先發佈針對該平台的版本,然後才能在該平台上運行您的應用程序。

    同時也帶來了以下問題:

  1.     更大的部署內容:應用程序包括.NET運行時和所有應用程序依賴項,所以所需的下載大小和硬盤空間大於依賴於框架的版本。
  2.     難以更新.NET版本.NET Runtime:(隨應用程序分發)只能通過發佈新版本的應用程序進行升級。但是,.NET將根據應用程序運行的計算機上框架庫的需要更新關鍵的安全補丁。

    例如:

    示例1:發佈一個獨立的應用程序,創建macOS 64位可執行文件,同時包含了.NET 運行時

dotnet publish -r osx-x64

    生成的文件列表如下:包含macOS 64可執行文件NET5PublishExample,以及包含了對應macOS 64平台下的.NET 運行時

    

      示例2:發佈一個獨立的應用程序,創建Windows 64位可執行文件,同時包含了.NET 運行時

 dotnet publish -r win-x64

    生成的文件列表如下:包含Windows 64可執行文件NET5PublishExample.exe,以及包含了對應Windows 64平台下的.NET 運行時

       

五、關於依賴框架的發佈選項和示例

  發佈為依賴框架的應用程序是跨平台的,並且不包含.NET運行時。應用程序的運行需要單獨安裝指定版本的.NET運行時

  應用程序的跨平台二進制文件可以使用dotnet <filename.dll>命令運行,並且可以在任何平台上運行。如果應用程序使用具有特定於平台的實現的NuGet包,則所有平台的依賴項都將與應用程序一起複制到publish文件夾中。

  可以通過將-r <RID> –self-contained false參數傳遞給dotnet publish命令來為特定平台創建可執行文件。當-r參數被省略,為當前平台創建一個可執行文件。具有目標平台特定於平台的依賴關係的任何NuGet軟件包都將複製到publish文件夾中。

  這種模式帶來的優勢有:

  1.   小型部署:僅分發應用程序及其依賴項。.NET運行時和庫由用戶安裝,所有應用共享運行時。
  2.   跨平台:應用程序和任何基於.NET的庫都可以在其他操作系統上運行
  3.   使用最新的.NET運行時:該應用程序使用目標系統上安裝的最新運行時(在.NET的目標主要,次要家族中)。這意味着應用程序會自動使用最新的.NET運行時修補版本。

   同時也帶來了以下問題:

  1.    需要預安裝.NET 運行時僅在主機系統上已安裝應用目標的.NET運行時版本時,應用才能運行。
  2.    .NET可能會更改:.NET運行時和庫可能會在運行該應用程序的計算機上更新。

   例如:

   示例1:發佈一個當前平台的依賴框架的跨平台應用程序,不包含.NET 運行時,將與dll文件一起創建一個針對當前平台的可執行文件。   

dotnet publish

   

   使用dotnet NET5PublishExample.dll,可以直接運行(本機已經安裝.NET運行時,NET5PublishExample.dll是跨平台的二進制文件

   示例2:發佈一個依賴框架的跨平台應用程序(Linux 64位),不包含.NET 運行時,將創建一個Linux 64位可執行文件以及dll文件。    

dotnet publish -r linux-x64 --self-contained false

      使用dotnet NET5PublishExample.dll,可以直接運行(本機已經安裝.NET運行時,NET5PublishExample.dll是跨平台的二進制文件

      

  六、ReadyToRun編譯選項

   通過將應用程序程序集編譯為ReadyToRun(R2R)格式,可以改善.NET應用程序的啟動時間和延遲。R2R是一種提前(AOT)編譯的形式。

   R2R二進制文件通過減少應用程序加載時即時(JIT)編譯器需要完成的工作量來提高啟動性能。與JIT產生的代碼相比,二進制文件包含相似的本機代碼

   但是,R2R二進制文件較大,因為它們既包含中間語言(IL)代碼(某些情況下仍然需要此代碼)和同一代碼的本機版本。僅當發佈針對特定運行時環境(RID)(例如Linux x64或Windows x64)的應用程序時,R2R才可用。

   總結一下:通過R2R方式,可以直接將代碼編譯為Native Code,減少.NET 程序第一次加載時JIT編譯帶來的性能消耗,以提升.NET應用的首次加載性能。類似於ngen的程序集預加載。關於Ngen可以參考這個鏈接:ngen

   對應的dotnet publish指令選項:   dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true

 

   以上是.NET 5/.NET Core應用程序的發佈部署的一些研究和分享。

   推薦一個不錯的知識鏈接://docs.microsoft.com/en-us/dotnet/core/deploying/#publish-framework-dependent?WT.mc_id=DT-MVP-5003918

 

周國慶

2020/2/15