2018 年 11 月

第 33 卷,第 11 期

.NET Core - .NET Core 的发布选项

作者 Jamie Phillips | 2018 年 11 月

随着两项新技术的引入:框架依赖部署 (FDD) 和独立式部署 (SCD),.NET Core 的出现提供了发布应用程序的新模式。每个选项都有优点和缺点,需要在发布前加以考虑。在本文中,我将通过生成示例应用程序和讨论各自的优缺点来探讨这两种方法。然后,我将简要介绍其他部署选项,即在开发的早期阶段使用的 CoreRT。

使用 .NET Core 发布需要考虑大量信息。通常,.NET Framework 中针对桌面或控制台应用程序的典型部署选项是一个可执行文件。对于 ASP.NET 应用程序,它是 DLL。这些是唯一可用的选项,且它们依赖于生成的应用程序的类型。使用新模式时,重要的一点在于是否已安装 .NET Core。FDD 技术要求安装 .NET Core,而 SCD 技术本身随附 .NET Core 运行时,这意味着它可以在未安装 .NET Core 的计算机上运行。现在,ASP.NET 应用程序可以另外打包为独立的控制台应用程序,因为它将其自己的 Web 服务器 (Kestrel) 作为可执行文件。

对于我将要探讨的这两种方法,我将使用 .NET Core SDK 2.1 以及命令行接口。可以在 microsoft.com/net/download 中查看有关安装 .NET Core 2.1 的说明。我几乎只使用 PowerShell 运行我在文章中介绍的所有命令,但也可以使用命令行提示符 (cmd.exe),实际上,这些命令将在其他操作系统(包括 macOS 和 Linux)的 shell 上执行。最后,若要试用 SCD,适用于 Linux 的 Windows 子系统 (WSL) 对于针对 Linux 创建的任何可执行文件进行第一手测试非常有用。可以在 bit.ly/2Nj7FuQ 中了解有关 WSL 的详细信息。

示例应用程序

我将创建一个非常基础的示例应用程序,以使用 .NET Core 2.1 来测试两种发布方法。这将是一个打印“hello”以及提供的名称的控制台应用程序。可以使用 cmd.exe 或 PowerShell 来输入以下代码:

> dotnet new console -o Publishing

创建新控制台应用程序后,我将打开 Program.cs 并输入以下代码:

using System;
namespace Publishing
{
  class Program
  {
    static void Main(string[] args) => Console.WriteLine($"Hello {args[0]}!");
  }
}

在我执行应用程序时,我将看到以下内容:

> dotnet run -- Jamie

Hello Jamie!

正如你所看到的那样,它按预期工作。另请注意,两个连字符语法 (--) 是在使用“dotnet run”命令时向应用程序传递参数的方式。控制台应用程序完成后,我可以深入探讨发布方法。

发布命令

首先,让我们看一下发布命令提供的选项,方法是使用“-h”(帮助)选项执行该命令:

dotnet publish [<PROJECT>] [-c|--configuration] [-f|--framework] [--force]
                           [--manifest]
                           [--no-build] [--no-dependencies] [--no-restore]
                           [-o|--output]
                           [-r|--runtime] [--self-contained]
                           [-v|--verbosity] [--version-suffix]

请注意,在发布 .NET Core 应用程序时,这两种选项尤为有用:运行时选项和独立式选项。结合使用这两种选项,运行时选项指定在创建 SCD 时应面向的运行时,而独立式选项指示具有需要创建的运行时的独立式包。如果传递运行时选项,则会自动应用独立式选项。

其他要注意的选项是非还原和非生成选项。在运行发布命令时,非还原选项不会执行隐式还原。非生成选项不会生成项目或运行还原命令,因此,发布命令将针对现有生成运行。这在持续集成/持续部署应用场景中非常有用,因为不会多次触发生成和还原。最后,框架选项允许指定框架版本。可在 bit.ly/2pizcOL 中找到官方文档。

框架依赖部署

FDD 是在执行 .NET Core CLI 下的发布命令时的默认选项。发布命令创建一个平台未知的应用程序,它可以在等效于或高于用于生成应用程序的次要版本的任何 .NET Core 运行时上运行(尽管版本 2.0 不会运行面向 1.x 的版本,且 3.0 不会运行面向 2.x 的版本)。因为目标运行时不与应用程序一起打包,所以此选项生成最小的包。如果有多个应用程序要部署到系统,则允许运行时在应用程序之间共享将减少总体内存和系统上的磁盘使用量。共享运行时的另一个优点是任何未来运行时更新都将应用于所有应用程序。

让我们看一下将示例应用程序作为 FDD 发布,方法是在命令行输入以下内容:

> dotnet publish

这将在 bin\Debug\netcoreapp2.0 文件夹中创建输出,包括文件 publishing.dll。此 5KB DLL 是应用程序。接下来,使用 dotnet CLI 运行应用程序,如以下所示:

> dotnet Publishing.dll Jamie

Hello Jamie!

就这么简单。

FDD 提供了几个好处,包括更小的部署包、系统管理的运行时,以及在多个应用间共享运行时以减少磁盘和内存使用量的功能。此外,还保证应用能够在安装了兼容的运行时的任何平台上运行。

FDD 的缺点是,系统范围的运行时更新可能导致出现问题,因为它们可能不兼容,且你的应用程序仅能在编译的框架版本(或更高版本)上运行。它还要求用户在执行应用程序前需要在其计算机上已安装该版本的 .NET Core 运行时。

独立式部署

现在,让我们看看 .NET Core CLI 提供的第二种发布方法:SCD。我将使用以前使用过的同一发布命令,但这次,我将传递运行时标识符 (RID) 作为发布命令的选项。RID 告知 .NET Core CLI 在创建应用程序时要面向的平台。在此示例中,我将创建应用程序的 Windows 10 x 64 版本。可从任何操作系统面向任何平台,允许从 Windows 创建其他操作系统的可执行文件。以下是我为此示例使用的命令:

> dotnet publish -r win10-x64

请注意,在 netcoreapp2.0 文件夹下有一个创建的子文件夹,它以传递的目标运行时命名。在 win10-x64 文件夹中,现在可以看到可执行文件,而不是 DLL。可执行文件的大小为 77 KB,它比以前的应用程序多出 72 KB。我可以运行可执行文件,以确定它仍然可以正常运行:

> .\Publishing.exe Jamie

Hello Jamie!

现在,我有适用于 Windows 10 x 64 的 SCD,接下来,我将生成面向 Ubuntu x64 的 SCD,如下所示:

> dotnet publish -r ubuntu-x64

然后,我可以利用 WSL 来测试 Ubuntu 版本是否按预期执行。我从 Window 中打开 WSL,然后将新创建的应用程序设置为可执行文件,这样一来,我可以执行它以确认其能够正常运行。以下是该输入和生成的输出:

> chmod +x Publishing
> ./Publishing Jamie

Hello Jamie!

SCD 的优点是,.NET Core 运行时受应用程序的控制并随它一起提供。这对于用户来说很方便,因为他们无需安装运行时,且任何系统运行时更改都不会影响你的应用程序。另一方面,SCD 会生成更大的部署包,因为运行时随应用程序一起提供。还需要提前知道你的目标平台,以生成对应于每个目标平台的包。此外,每次包含安全或 bug 修补程序的运行时更新时,必须在计算机上重新发布每个应用程序,以获取这些修补程序,而不是安装单个计算机范围的运行时更新。

在过去,分发 .NET 应用程序的方法要求安装 .NET Framework 或随安装程序一起将其打包。使用 .NET Core 和 SCD,无需再创建特殊安装程序来交付基本应用程序。

尽管 SCD 具有诸多优点,但在将这些应用程序部署到 Linux 系统时,还存在一些问题。SCD 中不包含 .NET Core 的运行时依赖项,这意味着用户无需安装运行时。但是,用户需要为 Linux 分发安装程序包管理器中的六个依赖项。例如,在 Ubuntu 上,仍需安装以下依赖项:

liblttng-ust0
libcurl3
libssl1.0.0
libkrb5-3
zlib1g
libicu52 (for 14.x)
libicu55 (for 16.x)
libicu57 (for 17.x)
libicu60 (for 18.x)

若要解决此问题,开发人员需要使用本机包格式打包用于分发的 SCD 应用程序,以便能够定义其他依赖项。

虽然这可能是一个缺点,但优点是,如果需要安装 .NET Core 运行时,则用户不必添加任何要求的其他包存储库。

SCD 有很多优点,值得我们为此进行更多的探讨。尽管有多个使 SCD 工作的部分,但作为 .NET CLI 安装的一部分,有两个使其正常运行的重要组件。第一个组件是共享运行时,它是 .NET Core 运行时的可再发行版本,由 CLI 和最终用户使用。第二个组件是共享主机,它负责使用作为发布过程的一部分生成的 DLL。共享主机是泛型 AppHost,它允许任何 .NET Core 库 (DLL) 作为应用程序执行。在执行“dotnet run my.dll”时,my.dll 被托管在此共享主机的内部。在打包 SCD 应用程序时,执行的操作是将共享运行时、共享主机和应用程序 DLL 置于可执行文件包中、适用于 Windows 的 .exe 中或适用于 Linux 和 macOS 的相应可执行文件中。可在 bit.ly/2QCgZIp 中的 .NET CLI 存储库中找到此设计的实际文档。

运行时标识符

RID 表示为平台(而不是 Windows)创建独立式部署所支持的平台。例如,Windows 10 支持 x86、x64、ARM 和 ARM64。支持 ARM 地址 Windows 10 物联网 (IoT) 设备。在 Linux 上,仅支持 x64,其中包含以下分发:Ubuntu、RedHat、CentOS、Fedora、Debian、Gentoo、OpenSuse、Oracle、Tizen 和 LinuxMint。在 macOS 中,支持从 10.10 到 10.13 的版本。最后,在 .NET Core 2.0 或更高版本中支持 Android,其中还引入了生成特定目标组的所有选项的可移植 RID。可在 bit.ly/2PKXRXi 中的 RID 目录中找到更多信息。

CoreRT 部署

除 FDD 和 SCD 以外,适用于 Microsoft 的第三个选项名为 CoreRT,它提供了从基于 .NET Core 的代码中生成本机二进制文件的功能。CoreRT 使用 CoreCLR 实时 (JIT) 编译器执行预先 (AoT) 编译。这允许 .NET Core 代码生成可由 C++ 等其他语言使用的单个可执行文件和库。CoreRT 允许 .NET 开发人员创建本机目标平台的库和可执行文件,为 .NET 平台提供更为广阔的使用范围。

开始使用 CoreRT 与向项目添加 NuGet 包一样简单。在我使用的示例项目中,我只需运行以下命令:

> dotnet new nuget

添加了 nuget.config 文件后,我使用以下行调用到 .NET MyGet 源:

<add key="dotnet-core"
  value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"/>
<add key="nuget.org"
  value="https://api.nuget.org/v3/index.json" protocolVersion="3"/>

接下来,我添加 CoreRT NuGet 包,如下所示:

> dotnet add package Microsoft.DotNet.ILCompiler -v 1.0.0-alpha-*

然后,运行发布,传入平台 RID,如以下所示:

> dotnet publish -r win-x64

最后,可以测试应用程序,作为本机编译的应用程序。应用程序在一个名为本机的文件夹中创建,大小约为 6 KB,它与 FDD 应用程序的大小接近且不需要随附任何运行时,因为它使用 SCD。

CoreRT 的优点是对单个本机二进制文件的本机编译,其中包含针对每个目标平台的依赖项。执行速度更快,因为无需使用 JIT 编译,且由于针对每个目标平台进行了编译器优化,所以应用程序应具有更快的吞吐量。

缺点是必须针对每个目标平台编译应用程序,需提前选择目标平台。此外,目前为止还提供预发布阶段的 CoreRT。

CoreRT 应用程序的工作原理与 SCD 应用程序的工作原理非常类似。有一个小型本机执行引擎:CoreRT 本机运行时,它提供运行时服务,例如,将应用程序编译到单个本机包的垃圾回收。此外,还有使用 C# 编写的 CoreRT 的托管的部分。.NET Core 应用程序首先使用 Roslyn 编译器进行编译,然后该应用程序与 CoreRT 和 CoreFX 一起通过中间语言编译器传递,该过程将分析依赖关系并摇晃树,因此,只有绝对最小数量的库才会通过使用基于 LLVM 的编译器被编译到本机代码。最后,链接器用于将 CoreRT 本机运行时与应用程序中已编译的本机输出链接,以生成最终的本机可执行文件。Matt Warren 在 bit.ly/2NRS5pj 中对此主题撰写了引人入胜的博客文章,当然,CoreRT 的 GitHub 存储库在 github.com/dotnet/corert 中提供了设计各部分的链接。

总结

与 FDD 相比,SCD 提供了一个很大的优势,它不需要用户安装任何其他软件来支持已安装的应用程序。我已习惯只为 Windows 构建应用程序,.NET 框架在此操作系统中通常已提供。但在未安装 .NET 框架时,或安装了错误的版本时,它会造成不好的用户体验。

.NET Core 有望更改这一问题,因为它要求用户不仅安装运行时,还要安装支持应用程序的特定版本的运行时。SCD 可能会生成更大的应用程序(从而导致更大的下载),因为运行时随应用程序一起打包,但这使用户能够安装应用程序,而无需担心其他要求。对于 Windows 以外的用户(例如 macOS 和 Linux 用户),SCD 是用户期望使用的常见体验,它将帮助其采用。在由开发人员或组织控制的环境中,这不再是一个问题,FDD 可能具有优势。

CoreRT(编译到本机)部署仍处于非常早期的阶段。这些部署将提供 FDD 和 SCD 的诸多优点,无需安装框架并提供压缩的应用程序文件。但是,若要使此方法发挥作用,还有很长的一段路要走。


Jamie Phillips 是东田纳西州 SentryOne 的高级软件开发工程师。他从 2007 年至今一直致力于 .NET 的开发,且一直对 DevOps 和云具有浓厚的兴趣。可以通过 Twitter:@phillipsj73、他的博客:phillipsj.net 以及 GitHub:phillipsj 关注他。

衷心感谢以下技术专家对本文的审阅:Andrew Hall (Microsoft)、Daniel Oliver、Cameron Presley (SentryOne)
Andrew 是主管 Visual Studio 中 .NET、Web 和 Azure 应用服务工具的项目经理。大学毕业后,他编写了业务线应用程序,并返回学校考取计算机科学专业的硕士学位。然后,他加入了 Visual Studio 的诊断团队,致力于研究调试、分析和代码分析工具。他现在在 .NET 和 Web 工具团队工作,领导项目经理团队致力于核心 .NET 开发体验,其中包括项目、IntelliSense、生产力和 Azure 工具支持。

Cameron Presley 是 Microsoft MVP,他帮助开发人员每天进步。

Daniel Oliver 是田纳西州的一名软件开发人员,他使用 .NET 和 Azure 学习和生成新内容



在 MSDN 杂志论坛讨论这篇文章