.NET Core 程序的打包与分发 (Linux 篇 Part. II)

前文讲述了如何在 Microsoft 支持的发行版上使用 Self-contained 方式部署 .NET Core Console App。这种部署方式非常简单,但也有不少问题,主要是以下几个方面:

  1. Side by side 部署非常浪费空间。
    每个 app 自带完整的运行环境与基础库,大约占空间 50MB。如果是部署自己编写的服务器软件,那么这个开销还可以接受,但实际上 Console App 除了服务器程序,还有可能是分发给最终用户的客户端程序。如果用户使用的每个 .NET Core 程序都是以这种形式分发,会造成不可忽视的空间浪费。
    另外,虽说这种部署方式有着不同 app 之间环境互不干扰的优点,但是实际上在 .NET Core 环境里,这个优点并不明显,因为 .NET Core 的共享安装方式本身就支持 side by side,安装新版 .NET Core 完全不干扰旧版 .NET Core 程序。
  2. 只适用于 ABI 稳定,且被 Microsoft 支持的发行版。
    Self-contained 的 .NET Core App 并非像 golang 程序那样静态链接了所有依赖,而是依赖发行版的很多软件包,因此并非随便扔哪个发行版都能直接跑起来。
    比如 .NET Core 1.x 的 binary 直接链接到带版本号的库文件,换一个发行版就有大概率跑不起来;.NET Core 2.0 改为运行时动态载入库,一定程度上解决了这个问题,但在 Arch Linux 等比较激进的发行版上仍然无法正常运行,总有各种大大小小的问题,比如 OpenSSL 缺少 SSLv3 等。
  3. runtime 更新不方便
    每个仍然在支持周期内的 .NET Core runtime 都会定期更新, 这些更新通常并不改变功能或兼容性,但可能包含 bug 修复和安全更新。如果采用 Self-contained 部署方式,意味着 runtime 更新要由应用程序维护者来进行,这样不仅会造成重复工作,也有可能造成更新不及时等问题。

本文以 Arch Linux 和 Ubuntu 为例,介绍 shared runtime 部署方式,用于解决这些问题。选择 Ubuntu 是因为其 dotnet 包支持非常完善,而选择 Arch Linux 则是因为有 .NET Core Runtime.NET Core SDK 的 AUR 包,同时它也是典型的简单粗暴的滚动发行版。

依赖

本文的依赖与前文基本相同。撰写上一篇文章时, .NET Core 的最新稳定版仍然是 1.1,而编写本文示例时 .NET Core 2.0 已经发布正式版,因此本文使用的所有 .NET Core SDK/runtime 均为 2.0 版本。另外,本文建议使用 Ubuntu 16.04 完成打包,而不是 CentOS 7.x。

使用 FPM 制作 pacman 包需要安装bsdtar

示例程序

本文所介绍的分发对象是一个非常简单的小程序 sslver,它尝试载入 libcrypto 库,调用 SSLeay() (或 OpenSSL_version_num(),在 OpenSSL 1.1 及更高版本上 )获取 OpenSSL 的版本,并将其打印为人类可读的形式。

最终的效果与前一篇文章相同,用户安装软件包后可使用 sslver 命令查看 OpenSSL 版本。

本次生成示例程序的过程比上次简单,不需要修改 csproj 文件。只需要将 Program.cs 替换为示例程序片段

生成二进制

与上一篇文章类似,我们使用命令 dotnet publish -c=Release -f=netcoreapp2.0 --output pack_root/opt/sslver 完成最终分发程序的生成。与上一篇文章不同的是,我们没有指定 -r (runtime identifier),因为使用 shared runtime 部署时,生成的二进制文件是平台通用的。

在 pack_root/opt/sslver 目录中,我们可以看到只有4个文件:

sslver.deps.json, 指定 runtime 依赖
sslver.dll, 程序本体
sslver.pdb, 符号(如果不需要使用源码调试,可以删除)
sslver.runtimeconfig.json, 一些 runtime 配置,常见的一些选项如是否使用 server GC 等都在这个文件里配置

我们最终分发的就是这些文件。

打包

由于发布的文件内并没有原生的可执行文件,我们需要使用dotnet命令来运行 sslver.dll。因此需要在/usr/bin下加入一个stub脚本程序用于运行dotnet

mkdir -p pack_root/usr/bin
cat > pack_root/usr/bin/sslver <<__EOF__
#!/bin/sh
exec /usr/bin/dotnet /opt/sslver/sslver.dll "\$@"
__EOF__

然后同样的,设置权限

find pack_root/ -type d | xargs chmod 755
find pack_root/ -type f | xargs chmod 644
chmod +x pack_root/usr/bin/sslver

调用 FPM

目标为 Arch Linux:

fpm -s dir -t pacman --name "sslver" -C pack_root/ --version 1.0.0 --iteration 1 --description "Displays OpenSSL Version" \
--depends dotnet-runtime-2.0 --depends openssl \
--package ./

输出 Created package {:path=>"./sslver-1.0.0-1-x86_64.pkg.tar.xz"}

其中的dotnet-runtime-2.0可以在AUR下载。使用 pacman -U sslver-1.0.0-1-x86_64.pkg.tar.xz安装上述生成的 pacman 包,并使用sslver命令测试运行,输出示例:libcrypto 1.1.0f

目标为 Ubuntu 16.04:

fpm -s dir -t deb --name "sslver" -C pack_root/ --version 1.0.0 --iteration 1 --description "Displays OpenSSL Version" \
--depends dotnet-runtime-2.0.0 --depends libssl-dev \
--package ./

输出 Created package {:path=>"./sslver_1.0.0-1_amd64.deb"}

其中dotnet-runtime-2.0.0是 Microsoft 的官方 .NET Core 源里的包,可以在 .NET Core 网站找到使用方法。

在 Ubuntu 16.04 上使用 dpkg -i sslver_1.0.0-1_amd64.deb安装上述生成的 deb 包,并使用sslver命令测试运行,输出示例:libcrypto 1.0.2g

总结

本文讲解了使用 Shared runtime 方式部署 .NET Core 程序到 Ubuntu 16.04 与 Arch Linux 的步骤。事实上,单从包的大小就可以看出这种方式的优势,相对于前文产生的50MB的 Hello World,本文生成的仅仅是一个不到4KB的包,如果你需要部署大量基于 .NET Core 程序,这种方式显然更加适合。

虽说如此, .NET Core Runtime 的包支持并非在所有发行版上都非常完善,考虑到 .NET Core SDK 一直对 Mono runtime 有着相当完善的支持,早期的 .NET Core 甚至是靠 mono 在 Linux 下 bootstrap (因为 C# 编译器是运行在 .NET 环境下的),使用 .NET Core SDK 在 Linux 下生成 mono 的 binary 也会相当容易,而典型的 mono runtime 程序分发方式与本文大致相同,都是使用共享的 runtime,因此也会有相同的优点。

由于 mono 的 Framework API 实现与 .NET Core 有很多细节上的差异,因此需要进行额外的测试工作,这对于使用 .NET Core、.NET Standard 开发来讲,未免显得有失初衷。不过考虑到 mono 相当全面的运行环境支持,依赖 mono 分发 .NET 程序也是一种值得考虑的选择,比如有运行在 BSD 系统的需求,需要在 IA32/ARM/MIPS 等架构上运行,或者需要使用 GUI。

因此在本文的 Part. III 中,我会详细讲解如何使用 .NET Core SDK 生成并打包可以在 mono runtime 上运行的程序。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注