在 Google Cloud 上部署 .NET 应用

本文概述了如何在 Google Cloud 上部署 .NET 应用,并介绍了如何为应用选择正确的部署方法。

简介

Microsoft .NET Framework 为应用开发提供了丰富的工具和库。随着 Windows 对 Docker 支持的出现以及在 Linux 上运行 .NET Core 应用的实现,.NET 应用程序现在也能够支持各种部署目标。

为了使开发和测试更高效,您可以使应用部署自动化并与持续集成和交付 (CI/CD) 流水线进行结合。但是,为了选择正确的工具并构建 CI/CD 流水线,您必须首先确定如何在生产环境中运行应用以及要采用的部署方法。

在 Google Cloud 上部署 .NET 应用没有唯一的最佳方法,您应该根据应用和需求选择部署选项。例如,如果您的应用要求完整的 .NET Framework 或必须运行在 IIS 上,则您的部署将基于 Windows。如果您的应用可以使用 .NET Core 支持的功能运行,则可以选择在 Linux 下部署。

本文介绍了运行 .NET应用以及在 Google Cloud 上部署它们的各种方法,以及每个选项适用的条件。最终,您的部署选项将汇总为一个决策树,以帮助您确定最适合您的 .NET 应用的 Google Cloud 组件和方法。

部署模型

自动部署应用有两种基本方法。部署包可以被“推送”到应用服务器,或者应用服务器从已知位置“拉取”应用包。以下部分讨论了这两种模型之间的差异。

基于推送的部署

在“基于推送的部署”中,初始状态下,仅部署服务器可使用部署工件(zip 文件、NuGet 包或其他工件)。 部署服务器可以是专用机器,也可以是 CI 系统承担的角色。

执行部署时,部署服务器上的进程连接应用服务器、复制部署工件并启动安装。如果有多个应用服务器,则此过程将并行重复或者按顺序重复(更常见),从而将工件部署到所有应用服务器。

下图演示了此流程。

基于推送的部署

您可以使用各种配置管理工具来进行基于推送的自动化部署。其中一些工具遵循命令式方法,以类似脚本的方式定义部署步骤的顺序。 虽然这种方法很直观,但很容易出现“配置偏差”。也就是说,经过一段时间后,多台机器的状态可能不一致,无法完全反映您的预期状态。因此,许多工具允许您定义所需的状态,让工具来决定实现此状态所需的步骤。

在 Windows 上,此部署模型的常用工具包括:

常用的开源工具包括 AnsibleChefPuppet。虽然这些工具主要针对 Linux,但它们也能够部署 Windows 目标。

安全

要使部署服务器将部署推送到应用服务器,必须提供反向通道。例如,Web Deploy 和 Octopus Deploy 使用自定义协议和端口实现此任务,而 Ansible 则使用 SSH。

无论工具使用何种协议,都必须保证通信的安全,防止攻击者使用反向通道部署恶意应用。最重要的是,安全通信要求部署服务器能够通过应用服务器进行身份验证。

SSH 可以使用公钥身份验证。通过相应的 IAM 配置,您可以让 Google Cloud 自动将用于 SSH 的公钥分发到应用服务器。但如果您不使用 IAM,则 Google Cloud 无法为您管理密钥,您必须自行管理此任务。

一种方案是使用 Active Directory。当部署服务器和应用服务器都运行 Windows 并且都是 Active Directory 网域的成员时,将使用 Kerberos 处理身份验证。 但是,运行容错 Active Directory 环境需要至少两个额外的虚拟机实例来运行网域控制器。如果您的配置使用自动扩缩,则所有服务器也需要动态加入网域,这会减慢服务器的启动过程。自动扩缩还可能导致过时的计算机对象在目录中累积,从而需要额外的清理逻辑。如果您要在基于云端的环境中使用 Active Directory,则必须考虑这些额外因素。

在没有 Active Directory 的情况下,需要使用 NTLM 或其他方式(如 HTTP 基本身份验证)进行身份验证。这两种方法都要求凭据在部署服务器和应用服务器之间保持同步并安全存储。这都是颇具挑战性的任务。

无论您使用的是 Linux 还是 Windows,都需要采用不同于 IAM 的机制来保护部署服务器和应用服务器之间的通信。但是,使用多种机制来控制对系统的访问会增加整体复杂性,从而增加意外错误配置的风险。

操作系统更新

能够在应用服务器上高效地部署新版本的应用包非常重要,而在服务器上维护基础操作系统也极为关键。这意味着安装安全补丁。对于较大的服务器组,您应该将该过程自动化,以尽可能降低风险和减少更新时不可用的服务器数量。

您也可以使用推送方法来进行操作系统更新,其中部署服务器会在应用服务器上触发操作系统更新。在 Linux 上,通常使用 SSH 来远程运行更新命令。在 Windows 上,PowerShell Remoting(依赖于 WinRM)是一种常见的选择。在这两种机制中,您都必须安全地进行身份验证并安全地存储凭据。

自动调节

在应用服务器数量不变的静态环境中,部署服务器会提前知道所有部署目标。在云端环境中,自动增加和减少应用服务器的数量通常较为有利。当您使用基于推送的部署时,这会带来两个挑战:

  • 添加新的应用服务器时,请将其注册到部署服务器,以确保未来的部署中包含新服务器。
  • 新服务器需要接收其初始部署。

自动调节事件不是由部署服务器启动的,而是由底层托管实例组启动,它的运行级别低于部署服务器。

新的应用服务器实例必须将自己注册到部署服务器并触发部署,然后才能开始处理请求。下图演示了此流程。

基于推送的部署中的自动扩缩

要使此方法生效,不仅需要部署服务器联系应用服务器并对其进行身份验证,还需要应用服务器联系部署服务器并对其进行身份验证。

最后,新发布的服务器还必须具有最新的操作系统安全补丁。在自动调节过程中启动更新会导致严重的延迟。因此,用于创建应用服务器虚拟机的映像必须已安装更新。您可以通过两种方式进行管理:

  • 使用 Google Cloud 提供的公共映像,这些映像由 Google 保持最新。由于这些映像仅包含操作系统,因此您必须使用启动脚本来处理所有自定义项(您的应用代码、实用工具和操作系统配置),或作为应用部署的一部分进行处理。
  • 维护自定义操作系统映像并使其保持最新。这允许您将自定义项应用于映像,但会增加部署管理的整体复杂性。

执行基于推送的部署非常直观,但在您执行帐号安全、操作系统更新和自动扩缩任务时可能会造成极大程度的复杂性。下一部分将介绍基于拉取的部署,这是一种更具有云端原生性的部署方法。

基于拉取的部署

在基于拉取的部署中,部署是以间接方式执行的。 在 CI 系统生成新版本的部署工件之后,它会将工件发布到存储库。下图演示了此流程。

基于拉取的部署

执行部署时(可能在发布工件后立即执行或在稍晚阶段执行),部署服务器会触发实际部署。同样,部署服务器可能是独立的系统或 CI 系统采用的角色。触发部署时,部署服务器连接应用服务器,使其从中央存储库拉取并安装部署工件。

乍看起来,基于推送的模型和基于拉取的模型之间的差异好像很小,但执行基于拉取的部署有一些重要的影响:

  • 触发应用服务器以拉取部署工件无需在应用或操作系统级别进行。部署服务器可以通过让 Compute Engine 重启或替换虚拟机来触发拉取操作,从而避免与基于推送的部署相关的安全挑战。
  • 部署工件可以是 Docker 映像或虚拟机映像,而不仅是包含应用文件,这可以将部署应用和进行操作系统更新的过程统一起来。

安全

对于某些类型的部署,部署服务器完全不需要与应用服务器进行交互。例如,当部署工件是以下任意一种时,不需要任何交互:

  • 虚拟机映像。
  • 要部署到 Google Kubernetes Engine 的 Docker 映像。
  • 要部署到 App Engine 的软件包。

部署服务器只需要与 Google Cloud API 交互即可启动部署。这意味着部署过程可以依赖于 IAM 提供的身份验证机制,从而无需管理密钥或凭据。

当您使用仅包含应用文件和二进制文件的部署工件(如 zip 或 NuGet 包)时,可以通过以下方式触发部署:

  • 如果服务器被配置为在操作系统启动时拉取并安装最新的部署工件,则可以通过让 Google Cloud 重启虚拟机来触发更新。虽然看起来重启会耗费不必要的时间,但这可以使部署服务器免于对应用服务器进行身份验证。
  • 与基于推送的部署一样,部署服务器可以通过反向通道远程触发更新。但是,这种方法也面临与基于推送的部署相同的凭据管理安全隐患和挑战。
  • 部署服务器可以运行代理,该代理会观察存储库是否有新的部署工件。检测到新工件时,服务器会自动对其进行部署。一个潜在的问题是,可能会出现多个应用服务器最终同时安装更新,进入不可用状态的情况。为避免这种情况,代理可以跟踪存储库中的服务器状态,并基于此服务器状态信息有序地发布更新。

无论使用哪种方式,请确保您能够控制存储库的写入访问权限,以防止服务器拉取和安装恶意软件包。

操作系统更新

当 Docker 或虚拟机映像被用作部署工件时,这些工件会合并应用文件和依赖项。这使得您可以使用相同的部署机制来更新操作系统和更新应用。此时,您应确保可以为两个不同的事件构建和发布新的部署工件。一个是有新的应用版本可用,另一个是操作系统或其他依赖项的新安全更新发布。

在其他情况下,当部署工件仅包含应用文件时,保持操作系统为最新版本是一个独立任务。因此,在基于推送的部署中讨论的内容同样适用。

自动调节

应用服务器拉取部署工件与自动调节的理念十分契合,并且它避免了自动调节与基于推送的部署结合使用带来的大部分复杂性。每当自动扩缩事件导致新的应用服务器发布时,服务器都会联系存储库并拉取和安装最新的部署包。

如果您使用的是虚拟机或 Docker 映像,则拉取映像的机制由 GCP 提供。如果您使用的是其他软件包(如 zip 或 NuGet 归档),则必须将应用服务器配置为在启动后启动部署。您可以通过自定义虚拟机映像或使用启动脚本来执行此操作。

部署目标

过去,.NET 应用程序仅在 Windows 上运行,而且 Windows 不支持容器。这使您在确定运行应用的环境时几乎没有太多选择。

随着 .NET Core 的出现,您可以决定是在 Windows 上还是在 Linux 上运行应用。由于两个操作系统都支持容器,您现在可以对目标环境进行选择。

操作系统

多年来,虽然 Mono 提供了在 Windows 以外的平台上部署 .NET 应用的方法,但直到 .NET Core 发布之后,Linux 才成为 Microsoft 开发堆栈完全支持的平台。

.NET Core 仅提供 .NET Framework 的部分功能。因此,使用 .NET Core 的应用会受到某些限制。 更重要的是,将现有应用从 .NET Framework 移植到 .NET Core 并非总是轻而易举和经济实惠的,有时候甚至完全不可行。

因此,选择部署模型和目标时的一个基本问题是,是使用 Linux(需要 .NET Core)还是 Windows(支持 .NET Core 或 .NET Framework)。

在 Linux 上运行 .NET 应用的潜在好处包括:

  • 您可以使用 App Engine 柔性环境,一个全托管式环境。
  • 您可以使用 GKE,一个支持容器编排的代管式环境。
  • 您可以避免支付付费 Compute Engine 映像与 Windows 许可相关的额外费用。

您必须将这些好处与在 Linux 上使用 .NET Core 的以下潜在弊端进行权衡:

  • 将现有 .NET 应用移植到 .NET Core 所需的工作量可能会抵消潜在的费用节省。或者如上所述,可能根本无法将现有的 .NET 应用移植到 .NET Core。
  • Linux 不支持 IIS。.NET Core 网络服务器 Kestrel 有非常好的性能表现,但它的功能集与 IIS 并不相同。因此,您可能必须将 Kestrel 与 Nginx 等网络服务器结合使用。
  • 在 Linux 上没有与 Windows 服务直接对等的服务。虽然您通常可以将 Windows 服务转换为可作为守护进程运行的 Linux 控制台应用,但这种转换不一定总是很容易。
  • 对 Linux 上的 .NET Core 应用进行问题排查和调试需要的工具和技能与 Windows 上的 .NET 应用不同。如果您的团队对 Linux 的经验有限,这可能会是一项有挑战性的任务。

容器

容器特别适合在单个进程中运行的应用。例如:

  • Windows 服务
  • 充当守护进程的 Linux 控制台应用
  • 自托管的 WCF 服务
  • Kestrel 托管的 ASP.NET MVC 或 Web API 应用

许多.NET 应用使用的是 IIS。IIS 通常用于管理多个应用(在不同的虚拟目录和应用池中),因此可能与单进程模式不匹配。

您可以采用不同的方法将基于 IIS 的设置转移到容器:

  • microsoft/iis 映像为基础,将 IIS(包括所有虚拟目录和池)放入基于 Windows 的单个 Docker 映像中。除非应用紧密耦合,否则不建议采取这种方法,因为它不允许单独更新和部署应用。
  • 为每个应用使用基于 Windows 的独立 Docker 映像,每个应用都运行 IIS。这能确保您可以独立管理应用。但是,如果您需要操作大量这些容器,IIS 会产生大量不可忽略的开销。
  • 将部分或全部应用从 IIS 迁移到 Kestrel。 由于 Kestrel 可以部署在基于 Windows 的容器或基于 Linux 的 Docker 容器中,这种方法允许您独立地管理每个容器。

IIS 允许多个网页应用在一个网站下运行,共享一个域名。将应用封装到单独的容器中后,您可以使用基于内容的负载平衡来获得相同的功能。与此类似,通过使用 Google HTTP 负载平衡器,您无需在 Kestrel 服务器前端部署自定义反向代理。

大多数应用都可以容器化(极少数不可以)。但是,一些容器化场景也带来了挑战:

  • 对于 IIS 托管的应用,应用部署通常已经自动化。但配置 IIS(创建应用池和绑定等)的步骤必须手动进行。在移动到容器时,您还必须将所有这些初始步骤自动化。
  • 依赖配置文件或位于磁盘上的数据的应用可能需要改变。例如,可以从环境变量获取配置信息,并且可以将相关文件和文件夹作为卷装载。这使映像保持无状态并且不受限于特定于环境的配置。

最后,如果您使用基于 Windows 的 Docker 容器,请注意,Google Cloud 当前不支持 Hyper-V,并且不允许运行 Hyper-V 容器。因此,您只能在 Google Cloud 中部署 Windows Server 容器。Windows Server 容器比 Hyper-V 容器更轻量化,并提供不同级别的隔离

部署限制条件

如本部分所述,应用构建方法中的某些因素可能会对您使用的部署方法造成限制。

应用架构

选择部署目标和模型时要考虑的一个关键因素是应用的架构。一方面,应用可以遵循单体式应用架构模式,所有的应用逻辑都在单个代码库中实现,并在单个进程或 IIS 应用池中运行。另一方面,应用可以遵循微服务模式。在此方法中,一个应用包含许多个服务,这些服务在不同的进程和 IIS 应用池中运行,或作为独立的 Windows 服务运行。

最后,您可能拥有使用统一部署策略部署的多个独立应用,其中每个应用都是单体式应用。在本讨论中,可以认为该方法等同于微服务模式。

在微服务架构中,为了使应用经济高效地运行,同时保持服务隔离且可独立管理,您可以为每个服务分配专用虚拟机,从而确保可独立管理和部署服务。但是这种方法可能导致大量虚拟机利用率过低,并产生不必要的费用。对于这类应用,使用允许更紧密的“打包”方式的部署模型(特别是基于容器的模型),可能更为经济高效。

有状态和无状态

在设计云端应用时,请尝试将应用保持无状态,并使用基于 GCP 的存储服务在外部管理状态。无状态应用的优势包括:

  • 它们可以冗余部署,以提高可用性和容量。
  • 请求可以在实例之间自由分配。
  • 它们非常适合自动扩缩。
  • 如果发生故障,可以重新创建环境(无论是容器还是虚拟机),而不会有数据丢失的风险。

设计无状态应用并非易事,许多旧应用不采用这种做法。不过,分析是否可以让应用成为无状态应用还是值得一试的。

会话状态

ASP.NET 和 ASP.NET MVC 应用通常使用会话来跟踪用户状态,使应用具有状态。但是,有多种选项可以限制会话的影响:

  • 如果会话数据量很小,您可以将状态存储在加密或签名的 cookie 中。
  • 您可以使用 SQLServer 提供程序,而不是使用默认的 InProc 会话状态提供程序。但是,这需要一个 SQL Server 实例,这会产生额外的费用,并且会影响应用的延迟和可用性。
  • 您可以利用 Cloud Load Balancing 中的会话粘性。此功能可确保将来自单个客户端的所有请求路由到同一个应用实例。但是,使用会话亲和性会对负载平衡的公平性产生负面影响,也就是说,某些应用实例可能会收到比其他实例更多的请求。此外,如果应用实例因任何原因终止,则该实例处理的任何会话都将丢失,从而可能影响最终用户。因此,依赖会话亲和性不是一个理想的解决方案,但它通常是稳健性和费用之间一个可行的折衷方案。

内存缓存

应用通常使用内存缓存来避免冗余计算或数据库查找。这会在应用的多个实例同时运行时导致问题,因为缓存会变得不一致。

为避免不一致性,请直接使用或通过 IDistributedCache 接口使用分布式缓存。缓存服务器(如 Redis 或 Memcached)通常具有相对较低的资源需求,但它们确实增加了整体设置的复杂性。

存储

图片、附件或媒体文件形式的数据通常存储在磁盘上。一般不选择使用虚拟机上的永久磁盘,因为这会阻止数据在多台计算机之间共享,并且如果重新创建虚拟机实例,存在数据丢失的风险。您可以改为使用以下任一方法:

  • 将数据移动到文件共享服务器。这最大限度地减少了对应用的影响。但是,运行高可用性 SMB 或 NFS 服务器意味着需要额外的费用和维护工作。
  • 将数据移动到 Cloud Storage。虽然这需要更改应用,但 Cloud Storage 具有高可用性,比运行文件服务器更经济高效,并且无需额外的维护工作。

部署策略

部署应用的新版本时,必须将风险和对最终用户的影响降至最低。实现此目标的三种最常见策略是重建部署、蓝/绿部署和滚动部署。

重建策略

重建策略的做法是停止所有服务器上运行的应用,部署新版本并启动应用。此策略的明显缺点是会导致服务中断,但它避免了当两个不同版本的应用暂时共存并访问相同数据时可能出现的潜在问题。

蓝/绿策略

蓝/绿策略(也称为红/黑策略)的做法是在一组新服务器上部署新的应用版本。部署完成后,将流量从旧服务器切换到新服务器。这种方法会暂时性地需要最多两倍于生产环境所需的服务器,但可以避免服务中断。

此策略的前提条件是应用的两个版本可以暂时共存并且不会相互干扰。对于访问数据库的应用,这要求数据库架构更改的每次迭代都必须向后兼容到至少前一个版本。

滚动部署策略

滚动部署的做法是逐个更新服务器。与蓝/绿策略一样,这意味着在一段时间内,应用的两个不同版本是共存的。但与蓝/绿部署不同的是,流量是逐渐从旧版本转移到新版本。随着越来越多的服务器完成更新,越来越多的用户被路由到新版本,直到最后一个服务器完成更新时,所有用户都使用了新版本。此方法的一个主要好处是可以在所有用户受到影响之前及早发现潜在问题,这有助于降低整体风险。

由于滚动部署需要两个版本的应用共存,因此此策略通常还需要进行负载平衡器配置,以避免用户在两个版本间来回切换。

部署选项

到目前为止,本文已经讨论了部署模型、目标和策略。接下来的部分将介绍在 Google Cloud 上部署 .NET 应用的特定选项。

App Engine 柔性环境 (Linux)

App Engine 柔性环境为 .NET Core 应用提供平台即服务 (PaaS) 环境。由于 App Engine 柔性环境是基于 Linux 的,因此仅适用于 .NET Core 应用。

App Engine 柔性环境在内部使用容器来运行和扩缩应用,但您无需构建或管理容器映像,而是可以直接部署应用的二进制文件。在 App Engine 柔性环境中,您可以通过使用服务来运行分解为许多较小微服务的应用。

App Engine 柔性环境可以根据负载自动扩缩应用实例的数量,但遵守您为应用配置的任何限制。默认保留至少两个实例,但您也可以对此进行更改

App Engine 柔性环境最适合无状态应用。 虽然您可以禁用自动扩缩以适应有状态应用,但这样做意味着您无法享受代管环境的众多好处。此外,虽然 App Engine 柔性环境允许磁盘访问,但磁盘被认为是临时的,因此无法用于跟踪持久状态。

App Engine 柔性环境为应用的每个实例维护一个专用虚拟机。由于价格是基于运行的虚拟机数量,因此 App Engine 柔性环境在应用利用率高时最具成本效益。但是,如果对应用的访问并不频繁,底层虚拟机可能利用率很低,这反过来又会使 App Engine 柔性环境的成本效益低于其他部署选项,尤其是 GKE。

使用 gcloud 命令行工具进行基于拉取的部署

部署到 App Engine 柔性环境的最常用方法是使用 gcloud 命令行工具。该工具首先用于将部署工件发布到 Cloud Storage 中维护的存储库。每当启动实例时,都会从此存储库中拉取工件。部署默认使用蓝/绿策略。

每个部署都可用一个版本号跟踪。如果出现问题,可以将部署恢复为以前的版本。通过流量拆分,App Engine 柔性环境还允许您并行运行应用的多个版本,并将一定份额的流量导向向任一版本。这使您可以轻松地进行 canary 部署或 A/B 测试。

GKE(Windows 或 Linux)

GKE 提供全托管式的 Kubernetes 环境。Kubernetes 的编排能力使 GKE 特别适合运行由许多容器组成的复杂微服务应用。但是,即使对于不使用微服务模式的应用,GKE 也允许您在共享的基础架构上运行多个容器,既节约资源又易于维护。

GKE 要求将应用的所有部分封装为 Docker 容器。基于 Linux 的容器需要使用 .NET Core 和基于 Linux 的环境。如果您的 CI 系统是基于 Windows 的,那么在 Linux 中构建容器可能会有难度。但是,Azure Pipelines/Team Foundation Server 和 Cloud Build 都为构建 .NET Core 应用以及构建和发布基于 Linux 的容器映像提供了内置支持。

GKE 为无状态应用提供了最大的灵活性。通过使用有状态集和永久性卷,您还可以在 GKE 上运行某些类型的有状态应用。

一个 GKE 集群包括许多被称为“节点”的虚拟机实例,其中安排着容器。在多区域集群或地区集群中,GKE 可以在多个区域中布置节点和工作负载,以确保高可用性。

由于价格计算基于运行的节点数,因此当节点得到充分使用时,GKE 最具成本效益。您可以在同一集群上运行更大的工作负载,也可以根据需要自动扩缩节点数

使用 kubectl 命令进行基于拉取的部署

将应用部署到 GKE 有两个步骤:

  1. 使用 docker push 或其他方法将 Docker 映像发布到 Container Registry 或外部 Docker 注册表。此步骤通常由 CI 系统处理。
  2. 使用 kubectl 触发部署。此步骤既可以由 CI 系统处理,也可以单独处理。由于部署是远程启动的,因此 kubectl 是在 Linux 还是 Windows 上运行是无关紧要的。

GKE 内置了对重新创建和滚动部署策略的支持。虽然控制部署的原语足够灵活,可以允许使用其他部署策略,但使用不同的策略需要额外的工具或脚本。

使用 Spinnaker 进行基于拉取的部署

如果用于编排部署的 GKE 内置功能不足以满足您的需要,您可以将 GKE 与 Spinnaker 结合使用。Spinnaker 内置了对 GKE 的支持,允许您实施更高级的部署策略,包括蓝/绿部署。

由于 Spinnaker 不是托管式服务,因此您必须对其进行单独部署和维护。您可以在单独的 Linux 虚拟机实例GKE 集群中部署 Spinnaker。

Compute Engine(Windows 或 Linux)

Compute Engine 允许您创建和管理虚拟机实例。它支持一系列 Windows Server 版本和 Linux 发行版,以及规模调整和配置选项。这种灵活性使您可以将 Compute Engine 虚拟机实例用于各种工作负载。

为确保独立部署和维护应用,请为每个虚拟机实例仅部署单个应用或服务。为确保高可用性,请为每个应用运行至少两个虚拟机实例,且每个实例位于不同地区。因此,无论预期的负载是多少,您都可以假设需要的虚拟机实例数量是要部署的应用或服务数量的两倍。

Compute Engine 提供了一种通过托管实例组实现自动调节的简单方法。托管实例组还提供了实现滚动部署的方法,本文稍后将对此进行讨论。

由于 Compute Engine 是按虚拟机实例计费的,因此当应用接收的负载很大时(这会转化为虚拟机实例的高利用率),在 Compute Engine 上运行应用是最具成本效益的。相反,如果服务和应用的数量很多但平均利用率很低,则其他部署选项(如 GKE)通常更经济实惠,因为它们允许多个应用使用通用基础架构而不会牺牲工作负载隔离。

运行 Windows 虚拟机实例要求您使用付费映像。这些映像包含 Windows 的许可副本,因此会导致额外费用。因此,与使用 CentOS 或 Debian 等 Linux 发行版的虚拟机相比,Windows 虚拟机通常更不划算,因为前者不会产生任何许可费用。

您可以使用 SSH 或 RDP 来手动设置虚拟机实例,手动部署应用或为第一次部署准备的机器进行初始配置。但是,这可能会导致机器具有与其他虚拟机实例不同的唯一配置。长远来看,手动设置虚拟机实例可能会变得复杂且耗力。因此,建议将其自动化为可重复进行的过程。

在 Compute Engine 上的应用部署自动化包括以下任务:

  1. 为第一次应用部署预配和准备虚拟机实例。
  2. 执行应用部署。
  3. 维护操作系统(安装安全更新)。

以下两部分介绍如何使用基于拉取的部署方法以统一的方式处理所有三个步骤。虽然这些部分中描述的方法的机制和工具不同,但总体思路与使用 GKE 部署基于容器的应用是类似的。

使用托管实例组进行基于拉取的部署

托管实例组最常用于实现自动调节,但它们也提供了一种处理滚动部署的方法。在创建使用应用新版本的实例模板后,您可以使用滚动更新功能将使用旧模板的虚拟机实例替换为使用新模板的实例。

此方法的前提条件是应用的新版本可用作实例模板。为此,您可以采用以下两种方式:

  • 定义使用一个公共操作系统映像的实例模板。 使用启动脚本配置系统并从 Cloud Storage 存储分区、NuGet 存储库、Docker 注册表或其他来源安装应用。下图演示了此方法。

    使用代管实例组和公共映像进行基于拉取的部署

  • 创建自定义虚拟机映像为 CI/CD 过程的一部分,这个过程通常称为“映像刻录”。在此方法中,您使用一个公共操作系统映像来生成新的虚拟机实例,在其上安装最新的应用,从实例创建虚拟机映像,并通过 Google Cloud 项目提供该映像。使用 Packer 这类工具可以将整个过程完全自动化,然后可以在实例模板中引用生成的映像。下图演示了此方法。

    使用代管实例组和自定义映像进行基于拉取的部署

创建自定义映像(第二个选项)的缺点是,映像刻录是一个相对缓慢的过程,通常需要几分钟。因此,该方法不仅增加了 CI/CD 过程的复杂性,而且还降低了 CI/CD 过程的速度。从好的方面来看,使用自定义映像启动新虚拟机是一个简单而快速的过程,这在使用自动调节时非常有用。

使用启动脚本来部署应用(第一个选项)的优缺点恰好相反。它不会导致 CI/CD 过程中映像刻录的开销,但会减慢创建虚拟机实例的过程。此外,如果启动脚本不完全可靠,或者提供应用的二进制文件下载来源的系统不具备高可用性,此方法可能降低可用性。

应用本身和配置的复杂性决定了最适合某个应用的方法。在某些情况下,最好将两种方法结合使用:

  • 自定义映像包含所有配置和依赖项,但不包含实际的应用二进制文件。当配置或任何依赖项发生更改时,刻录一个新映像,但不会针对每个应用构建进行更新。这有助于避免应用 CI/CD 流水线的减速。
  • 使用启动脚本安装应用。为了最大限度地降低风险和避免减速,这个过程应该尽可能简单。

如果您要部署具有相同基本配置的许多不同应用或服务,使用这种混合方法可以使您无需构建和维护数十或数百个几乎一样的映像。

您可以使用代管实例组来为 Linux 和 Windows 工作负载编排部署。对于 Linux,使用代管实例组在虚拟机实例上部署 Docker 容器是可行且受到平台支持的。但仅建议用于利用率很高的应用。在其他情况下,为每个虚拟机部署一个 Docker 容器与使用 GKE 或 App Engine 柔性环境相比几乎没有什么优势。

如果要使用 Windows Server 容器,请遵循以下准则来使用 Compute Engine 和代管式实例组运行容器:

  • 使用预安装了 Docker 的定制映像或以下公共映像之一:
    • Windows Server 2019 Datacenter Core for Containers
    • Windows Server 2019 Datacenter for Containers
  • 在虚拟机启动期间,使用启动脚本拉取 Docker 映像并将其作为 Windows Server 容器启动。您可以使用适当的端口映射来公开容器内运行的服务。

请注意,只有在启动 Docker 服务后启动脚本才能运行。要顺利应对 Docker 可用之前脚本就开始运行的情况,请在脚本中加入适当的重试逻辑。

在非云端环境中创建基于 Windows 的映像时,您可能依赖于 Microsoft Deployment Toolkit (MDT)Windows 部署服务 (WDS)。但是,由于管理映像和基于自定义映像创建虚拟机实例是 Compute Engine 的核心功能,所以此附加工具不是必需的。Compute Engine 不仅支持启动脚本,还支持基于 Windows 的虚拟机实例的专用脚本。因此,通常不需要使用自定义 unattend.xml 文件。但是,请务必在创建映像之前使用 GCESysprep 对 Windows 安装进行“通用化”

使用 Spinnaker 进行基于拉取的部署

托管实例组提供了一种轻量化且可靠的方式来实现滚动部署,但托管实例组的功能可能不足以满足某些应用的需要。要实现更复杂的部署策略和流水线,可以使用 Spinnaker。

Spinnaker 在 Compute Engine 上编排部署的基本方法与上文讨论的类似,也就是说,它也依赖于映像刻录。因此,要注意的问题是一样的。

由于 Spinnaker 不是代管式服务,因此您必须对其进行单独部署和维护。您可以在单独的 Linux 虚拟机实例或 GKE 集群中部署 Spinnaker。

基于推送的远程部署

前面部分中讨论的基于拉取的部署选项有各种好处,但它们并不适合所有类型应用。特别是,有状态的应用通常不适合这种方法,而可能更适合基于推送的方法。

在基于推送的方法中,需要单独处理三个部署任务(预配虚拟机实例、执行应用部署和维护操作系统)。您可以为三个任务使用相同的工具,但是为每个任务使用不同的工具也并不鲜见。

您可以使用 Terraform 等自动化工具,以与其他基础架构相同的方式预配应用服务器虚拟机实例。您可以使用启动脚本或专用脚本来安装应用自动部署所需的工具。例如,如果您要使用 Puppet、Chef 或 Octopus Deploy,则必须确保已安装这些工具的代理软件。

从安全角度来看,为了减少攻击面,请确保部署服务器与应用服务器虚拟机实例上运行的任何代理之间的任何通信都使用内部网络。此外,请确保使用的端口不会公开给公共互联网。

在不使用自动调节的环境中,将基于 Windows 的应用服务器加入 Active Directory 域是集中管理配置的可行方法。您还可以使用 Active Directory 来控制维护操作系统等管理任务。

选择部署选项

正如本文开头所述,在 Google Cloud 上部署 .NET 应用没有唯一的最佳方法,您应该根据应用和需求选择部署选项。若要选择正确的模型,首要问题之一是使用 .NET Core 还是 .NET Framework,以及由此产生的,在 Linux 还是 Windows 上部署。确定目标操作系统后,请使用以下决策树来帮助您确定合适的部署模型。

在 Linux 上部署 .NET Core 应用的决策树:

使用 .NET Core 和 Linux 进行部署的决策树

在 Windows 上部署 .NET Core 或 .NET Framework 应用的决策树:

使用 Windows 进行部署的决策树

后续步骤