在 Azure 容器应用上部署和缩放 ASP.NET Core 应用

部署到 Azure 的应用在遇到间歇性高需求时受益于可缩放性可满足该需求。 可缩放的应用可以在工作负载高峰期间横向扩展以确保容量,然后在高峰下降时自动纵向缩减,从而降低成本。 水平缩放(横向扩展)添加资源的新实例,例如 VM 或数据库副本。 本文演示如何通过执行以下任务将水平可缩放的 ASP.NET Core 应用部署到 Azure 容器应用

  1. 设置示例项目
  2. 将应用部署到 Azure 容器应用
  3. 缩放应用并排除故障
  4. 创建 Azure 服务
  5. 连接 Azure 服务
  6. 配置和重新部署应用

本文使用了 Razor Pages,但主要使用其他 ASP.NET Core 应用。

在某些情况下,基本的 ASP.NET Core 应用能够进行缩放,无需特殊考虑。 但使用某些框架功能或体系结构模式的应用需要额外的配置,包括:

  • 保护表单提交:Razor Pages、MVC 和 Web API 应用通常依赖于表单提交。 默认情况下,这些应用使用跨站点伪造令牌和内部数据保护服务来保护请求。 将这些应用部署到云时,必须将其配置为在安全、集中式位置管理数据保护服务。

  • SignalR 线路:Blazor Server 应用需要使用集中式 Azure SignalR 服务才能安全地缩放。 这些服务还利用上述数据保护服务。

  • 集中式缓存或状态管理服务:可缩放的应用可以使用 Azure Cache for Redis 来提供分布式缓存。 可能需要 Azure 存储来存储框架(如 Microsoft Orleans)的状态,这有助于编写跨多个不同应用实例管理状态的应用。

本文中的步骤演示如何通过将可缩放的应用部署到 Azure 容器应用来正确解决上述问题。 本教程中的大多数概念也适用于缩放 Azure 应用服务实例。

设置示例项目

使用 GitHub Explorer 示例应用来执行本教程。 使用以下命令从 GitHub 中克隆应用:

git clone "https://github.com/dotnet/AspNetCore.Docs.Samples.git"

转到 /tutorials/scalable-razor-apps/start 文件夹,并打开 ScalableRazor.csproj

示例应用使用搜索表单按名称浏览 GitHub 存储库。 该表单依赖内置的 ASP.NET Core 数据保护服务来处理防伪问题。 默认情况下,应用在容器应用上水平缩放时,数据保护服务会引发异常。

测试应用程序

  1. 在 Visual Studio 中启动应用。 该项目包含一个 Docker 文件,这意味着可以选中运行按钮旁的箭头,以使用 Docker 桌面设置程序或标准 ASP.NET Core 本地 Web 服务器启动应用。

使用搜索表单按名称浏览 GitHub 存储库。

A screenshot showing the GitHub Explorer app.

将应用部署到 Azure 容器应用

Visual Studio 用于将应用部署到 Azure 容器应用。 容器应用提供托管服务,旨在更轻松地托管容器化应用和微服务。

注意

为应用创建的许多资源都需要一个位置。 对于该应用,位置并不重要。 实际应用应选择离客户端最近的位置。 建议选择你所在位置附近的位置。

  1. 在 Visual Studio 解决方案资源管理器中,右键单击顶部项目节点并选择“发布”。

  2. 在发布对话框中,选择“Azure”作为部署目标,然后选择“下一步”。

  3. 对于“特定目标”,选择“Azure 容器应用(Linux)”,然后选择“下一步”。

  4. 新建要部署到的容器应用。 选择绿色的 + 图标打开一个新对话框并输入以下值:

    A screenshot showing Visual Studio deployment.

    • 容器应用名称:保留默认值或输入名称。
    • 订阅名称:选择要部署到的订阅。
    • 资源组:选择“新建”,新建名为 msdocs-scalable-razor 的资源组。
    • 容器应用环境:选择“新建”以打开容器应用环境对话框并输入以下值
      • 环境名称:保留默认值。
      • 位置:选择你所在位置附近的位置。
      • Azure Log Analytics 工作区:选择“新建”以打开日志分析工作区对话框。
        • 名称:保留默认值。
        • 位置:选择你所在位置附近的位置,然后选择“确定”关闭对话框。
      • 选择“确定”关闭容器应用环境对话框。
    • 选择“创建”以关闭原始容器应用对话框。 Visual Studio 在 Azure 中创建容器应用资源。
  5. 创建资源后,确保在容器应用列表中选中它,然后选择“下一步”。

  6. 你需要创建 Azure 容器注册表来存储为应用发布的映像项目。 选择容器注册表屏幕上的绿色 + 图标。

    A screenshot showing how to create a new container registry.

  7. 保留默认值,然后选择“创建”。

    A screenshot showing the values for a new container registry.

  8. 创建容器注册表后,确保选中它,然后选择“完成”以关闭对话框工作流并显示发布配置文件的摘要。

    如果 Visual Studio 提示你允许管理员用户访问已发布的 docker 容器,请选择“是”。

  9. 选择发布配置文件摘要右上角的“发布”,将应用部署到 Azure。

部署完成后,Visual Studio 会启动浏览器来显示托管应用。 在表单域中搜索 Microsoft,此时将显示存储库列表。

缩放应用并排除故障

应用目前可以正常运行,但我们希望在更多实例中缩放应用,以实现高流量。

  1. 在 Azure 门户的顶部搜索栏中,搜索 razorscaling-app-**** 容器应用,然后从结果中选中它。
  2. 在“概述”页面的左侧导航中,选择“缩放”,然后选择“+ 编辑和部署”。
  3. 在“修订”页面中,切换到“缩放”选项卡。
  4. 将最小和最大实例都设置为 4,然后选择“创建”。 此配置更改确保应用在四个实例中水平缩放。

导航回应用。 页面加载时,起初一切正常。 但输入搜索词并提交时,可能会出现错误。 如果没有出现错误,请多次提交表单。

排查错误

搜索请求失败的原因尚不清楚。 浏览器工具指示已发回 400 错误请求响应。 但你可以使用容器应用的日志记录功能来诊断环境中发生的错误。

  1. 在容器应用的“概述”页面上,从左侧导航中选择“日志”。

  2. 在“日志”页面上,关闭打开的弹出窗口并导航到“表”选项卡。

  3. 展开“自定义日志”项以显示“ContainerAppConsoleLogs_CL”节点。 此表包含容器应用的各种日志,可通过查询这些日志来解决问题。

    A screenshot showing the container app logs.

  4. 在查询编辑器中,编写一个基本查询来搜索“ContainerAppConsoleLogs_CL 日志”表中的最新异常,例如以下脚本:

    ContainerAppConsoleLogs_CL
    | where Log_s contains "exception"
    | sort by TimeGenerated desc
    | limit 500
    | project ContainerAppName_s, Log_s
    

    上述查询在“ContainerAppConsoleLogs_CL”表中搜索包含词汇“异常”的所有行。 结果按生成时间排序,上限为 500 个,且只包含“ContainerAppName_s”和“Log_s”列,以使结果更易于查看。

  5. 选择“运行”,此时将显示结果列表。 浏览日志,注意其中大部分与防伪令牌和加密有关。

    A screenshot showing the logs query.

    重要

    应用中的错误是由 .NET 数据保护服务引起的。 应用的多个实例正在运行时,无法保证提交表单的 HTTP POST 请求会路由到最初从 HTTP GET 请求加载页面的容器。 如果请求由不同的实例处理,则无法正确处理防伪令牌,并且会发生异常。

    在上述步骤中,此问题是通过将数据保护密钥集中在 Azure 存储服务中并使用 Key Vault 进行保护来解决的。

创建 Azure 服务

为解决上述错误,创建以下服务并连接到应用:

  • Azure 存储帐户:存储数据保护服务的数据。 提供一个集中位置,用于在应用缩放时存储关键数据。 存储帐户还可用于保存文档、队列数据、文件共享和几乎所有类型的 blob 数据。
  • Azure KeyVault:此服务存储应用机密,可帮助管理数据保护服务的加密。

创建存储帐户服务

  1. 在 Azure 门户搜索栏中,输入 Storage accounts 并选择匹配的结果。
  2. 在“存储帐户列表”页面上,选择“+ 创建”。
  3. 在“基本信息”选项卡上,输入下列值
    • 订阅:选择为容器应用选择的订阅。
    • 资源组:选择之前创建的 msdocs-scalable-razor 资源组。
    • 存储帐户名称:将帐户命名为 scalablerazorstorageXXXX,其中 X 是选择的随机数。 该名称必须在全 Azure 中唯一。
    • 区域:选择之前选择的区域。
  4. 将其余值保留为默认值,然后选择“查看”。 Azure 验证输入后,选择“创建”。

Azure 预配新的存储帐户。 任务完成后,选择“转到资源”以查看新服务。

创建存储容器

创建容器来存储应用的数据保护密钥。

  1. 在新存储帐户的概述页面上,选择左侧导航中的“存储浏览器”。
  2. 选择“Blob 容器”。
  3. 选择“+ 添加容器”以打开“新建容器”浮出控件菜单。
  4. 输入名称 scalablerazorkeys,将其余设置保留为默认值,然后选择“创建”。

新容器显示在页面列表中。

创建密钥保管库服务

创建密钥保管库以保存用于保护 Blob 存储容器数据的密钥。

  1. 在 Azure 门户搜索栏中,输入 Key Vault 并选择匹配的结果。
  2. 在密钥保管库列表页面上,选择“+ 创建”。
  3. 在“基本信息”选项卡上,输入下列值
    • 订阅:选择之前选择的订阅。
    • 资源组:选择之前创建的 msdocs-scalable-razor 资源组。
    • 密钥保管库名称:输入名称 scalablerazorvaultXXXX。
    • 区域:选择你所在位置附近的区域。
  4. 将其余设置保留默认值,然后选择“查看 + 创建”。 等待 Azure 验证设置,然后选择“创建”。

Azure 预配新的密钥保管库。 任务完成后,选择“转到资源”以查看新服务。

创建密钥

创建密钥以保护 blob 存储帐户中的数据。

  1. 在密钥保管库的主概述页面上,从左侧导航中选择“密钥”。
  2. 在“创建密钥”页面上,选择“+ 生成/导入”以打开“创建密钥”浮出控件菜单。
  3. 在“名称”字段中输入 razorkey。 将其余设置保留为默认值,然后选择“创建”。 新密钥显示在密钥列表页面上。

连接 Azure 服务

容器应用需要与存储帐户和密钥保管库服务建立安全连接,才能解决数据保护错误并正确缩放。 使用以下步骤将新服务连接在一起:

重要

传播通过服务连接器和其他工具进行的安全角色分配通常需要一两分钟,在极少数情况下可能需要长达八分钟。

连接存储帐户

  1. 在 Azure 门户中,导航到“容器应用”概述页面。
  2. 在左侧导航中,选择“服务连接器”
  3. 在“服务连接器”页面上,选择“+ 创建”以打开“创建连接”浮出控件面板并输入以下值
    • 容器:选择之前创建的容器应用。
    • 服务类型:选择“存储 - Blob”。
    • 订阅:选择之前使用的订阅。
    • 连接名称:保留默认值。
    • 存储帐户:选择之前创建的存储帐户。
    • 客户端类型:选择“.NET”。
  4. 选择“下一步: 身份验证”进入下一步。
  5. 选择“系统分配的托管标识”并选择“下一步: 网络”。
  6. 保留选中的默认网络选项,然后选择“查看 + 创建”。
  7. Azure 验证设置后,选择“创建”。

服务连接器在容器应用上启用系统分配的托管标识。 它还为标识分配“存储 Blob 数据参与者”角色,以使标识可对存储容器执行数据操作。

连接密钥保管库

  1. 在 Azure 门户中,导航到“容器应用”概述页面。
  2. 在左侧导航中,选择“服务连接器”。
  3. 在“服务连接器”页面上,选择“+ 创建”以打开“创建连接”浮出控件面板并输入以下值
    • 容器:选择之前创建的容器应用。
    • 服务类型:选择“密钥保管库”。
    • 订阅:选择之前使用的订阅。
    • 连接名称:保留默认值。
    • 密钥保管库:选择之前创建的密钥保管库。
    • 客户端类型:选择“.NET”。
  4. 选择“下一步: 身份验证”进入下一步。
  5. 选择“系统分配的托管标识”并选择“下一步: 网络”。
  6. 保留选中的默认网络选项,然后选择“查看 + 创建”。
  7. Azure 验证设置后,选择“创建”。

服务连接器为标识分配角色,以使标识可对密钥保管库密钥执行数据操作。

配置和重新部署应用

已创建必要的 Azure 资源。 在本节中,应用代码配置为使用新资源。

  1. 安装以下 NuGet 包:

    • Azure.Identity:提供类以使用 Azure 标识和访问管理服务。
    • Microsoft.Extensions.Azure:提供有用的扩展方法来执行核心 Azure 配置。
    • Azure.Extensions.AspNetCore.DataProtection.Blobs:允许将 ASP.NET Core DataProtection 密钥存储在 Azure Blob 存储中,确保可在 Web 应用的多个实例之间共享密钥。
    • Azure.Extensions.AspNetCore.DataProtection.Keys:启用使用 Azure 密钥保管库密钥加密/包装保护静态密钥功能。
    dotnet add package Azure.Identity
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Extensions.AspNetCore.DataProtection.Blobs
    dotnet add package Azure.Extensions.AspNetCore.DataProtection.Keys
    
  2. 使用以下突出显示的代码更新 Program.cs

    using Azure.Identity;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
    var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];
    
    builder.Services.AddRazorPages();
    builder.Services.AddHttpClient();
    builder.Services.AddServerSideBlazor();
    
    builder.Services.AddAzureClientsCore();
    
    builder.Services.AddDataProtection()
                    .PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
                                                    new DefaultAzureCredential())
                    .ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
                                                    new DefaultAzureCredential());
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    
    app.Run();
    

上述更改允许应用使用集中式、可缩放的体系结构来管理数据保护。 DefaultAzureCredential 在重新部署应用时发现之前启用的托管标识配置。

更新 appsettings.json 文件的 AzureURIs 部分中的占位符,以包含以下内容:

  1. <storage-account-name> 占位符替换为 scalablerazorstorageXXXX 存储帐户的名称。

  2. <container-name> 占位符替换为 scalablerazorkeys 存储容器的名称。

  3. <key-vault-name> 占位符替换为 scalablerazorvaultXXXX 密钥保管库的名称。

  4. 将密钥保管库 URI 中的 <key-name> 占位符替换为之前创建的 razorkey 名称。

    {
      "GitHubURL": "https://api.github.com",
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*",
      "AzureURIs": {
        "BlobStorage": "https://<storage-account-name>.blob.core.windows.net/<container-name>/keys.xml",
        "KeyVault": "https://<key-vault-name>.vault.azure.net/keys/<key-name>/"
      }
    }
    

重新部署应用

应用现已正确配置为使用之前创建的 Azure 服务。 重新部署应用以应用代码更改。

  1. 在解决方案资源管理器中,右键单击项目节点并选择“发布”。
  2. 在发布配置文件摘要视图中,选择右上角的“发布”按钮。

Visual Studio 将应用重新部署到之前创建的容器应用环境。 进程完成后,浏览器将启动到应用主页。

通过在搜索字段中搜索 Microsoft 来再次测试应用。 现在,页面应在每次提交时重新加载正确的结果。

为本地开发配置角色

应用的现有代码和配置也可以在开发期间在本地运行时正常运行。 之前配置的 DefaultAzureCredential 类能够获取本地环境凭据,以向 Azure 服务进行身份验证。 你需要将分配给应用托管标识的角色分配给你自己的帐户,以使身份验证正常进行。 该帐户应是用于登录 Visual Studio 或 Azure CLI 的帐户。

登录本地开发环境

你需登录到 Azure CLI、Visual Studio 或 Azure PowerShell,然后 DefaultAzureCredential 才能获取你的凭据。

az login

为开发人员帐户分配角色

  1. 在 Azure 门户中,导航到之前创建的 scalablerazor**** 存储帐户。
  2. 在左侧导航栏中,选择“访问控制(IAM)”。
  3. 选择“+ 添加”,然后从下拉菜单中选择“添加角色分配”。
  4. 在“添加角色分配”页面上,搜索 Storage blob data contributor,选择匹配结果,然后选择“下一步”。
  5. 确保选中“用户、组或服务主体”,然后选择“+ 选择成员”。
  6. 在“选择成员”浮出控件中,搜索你自己的 user@domain 帐户并从结果中选中它。
  7. 选择“下一步”,然后选择“查看 + 分配”。 Azure 验证设置后,再次选择“查看 + 分配”。

如前所述,传播角色分配权限可能需要一两分钟,在极少数情况下最多需要八分钟。

重复上述步骤为帐户分配角色,以使帐户可访问密钥保管库服务和机密。

  1. 在 Azure 门户中,导航到之前创建的 razorscalingkeys 密钥保管库。
  2. 在左侧导航栏中,选择“访问控制(IAM)”。
  3. 选择“+ 添加”,然后从下拉菜单中选择“添加角色分配”。
  4. 在“添加角色分配”页面上,搜索 Key Vault Crypto Service Encryption User,选择匹配结果,然后选择“下一步”。
  5. 确保选中“用户、组或服务主体”,然后选择“+ 选择成员”。
  6. 在“选择成员”浮出控件中,搜索你自己的 user@domain 帐户并从结果中选中它。
  7. 选择“下一步”,然后选择“查看 + 分配”。 Azure 验证设置后,再次选择“查看 + 分配”。

可能需要再次等待此角色分配传播完毕。

然后,可以返回到 Visual Studio 并在本地运行应用。 代码应继续按预期运行。 DefaultAzureCredential 使用来自 Visual Studio 或 Azure CLI 的现有凭据。

可靠的 Web 应用模式

请观看适用于 .NET 的可靠 Web 应用模式YouTube 视频文章,了解如何创建新式、可靠、高性能、可测试、经济高效且可缩放的 ASP.NET Core 应用,无论是从头开始创建还是重构现有应用。