将 Orleans 部署到 Azure 应用服务

本教程介绍如何将 Orleans 购物车应用部署到 Azure 应用服务。 本教程将引导你部署一个支持以下功能的示例应用程序:

  • 购物车应用程序:一个简单的购物车应用程序,它使用 Orleans 的跨平台框架支持及其可缩放的分布式应用程序功能。

    • 库存管理:编辑和/或创建产品库存。
    • 商店库存:浏览可购产品并将其添加到购物车。
    • 购物车:查看购物车中所有商品的摘要和管理这些商品;删除或更改每个商品的数量。

了解应用及其功能后,你将了解如何使用 GitHub Actions、.NET 和 Azure CLI 以及 Azure Bicep 将该应用部署到 Azure 应用服务。 此外,你将了解如何在 Azure 中为该应用配置虚拟网络。

在本教程中,你将了解如何执行以下操作:

  • 将 Orleans 应用程序部署到 Azure 应用服务
  • 使用 GitHub Actions 和 Azure Bicep 自动完成部署
  • 在 Azure 中为应用配置虚拟网络

先决条件

在本地运行应用

若要在本地运行应用,请创建 Azure 示例:Azure 应用服务上的 Orleans 群集存储库的分支并将其克隆到本地计算机。 克隆后,在所选的 IDE 中打开解决方案。 如果使用的是 Visual Studio,请右键单击“Orleans.ShoppingCart.Silo”项目并选择“设为启动项目”,然后运行应用。 否则,可使用以下 .NET CLI 命令运行应用:

dotnet run --project Silo\Orleans.ShoppingCart.Silo.csproj

有关详细信息,请参阅 dotnet run。 运行应用后,可以四处导航,并可以自由测试其功能。 在本地运行时,该应用的所有功能依赖于内存中持久性和本地群集,它使用 Bogus NuGet 包来生成虚构产品。 通过在 Visual Studio 中选择“停止调试”选项或者在 .NET CLI 中按 Ctrl+C 停止应用

购物车应用内部

Orleans 是用于生成分布式应用程序的可靠且可缩放的框架。 在本教程中,你会使用 Orleans 将一个简单的购物车应用部署到 Azure 应用服务。 该应用公开用于管理库存、在购物车中添加和删除商品以及购买产品的功能。 客户端是使用带有服务器托管模型的 Blazor 生成的。 该应用的体系结构如下:

Orleans:购物车示例应用体系结构。

上图显示了客户端是服务器端 Blazor 应用。 它由多个服务组成,这些服务使用相应的 Orleans grain。 每个服务与一个 Orleans grain 配对,如下所示:

  • InventoryService:使用 IInventoryGrain,其中的库存按产品类别分区。
  • ProductService:使用 IProductGrain,其中的单个产品按 Id 关联到单个 grain 实例。
  • ShoppingCartService:使用 IShoppingCartGrain,其中的单个用户只有单个购物车实例,而与使用方客户端无关。

该解决方案包含三个项目:

  • Orleans.ShoppingCart.Abstractions:一个类库,定义应用的模型和接口。
  • Orleans.ShoppingCart.Grains:一个类库,定义实现应用业务逻辑的 grain。
  • Orleans.ShoppingCart.Silos:托管 Orleans silo 的服务器端 Blazor 应用。

客户端用户体验

购物车客户端应用具有多个页面,每个页面代表不同的用户体验。 应用的 UI 是使用 MudBlazor NuGet 包生成的。

主页

显示几个简单的短语让用户了解应用的用途,并为每个导航菜单项添加上下文。

Orleans:购物车示例应用,主页。

商店库存页面

显示所有可购产品的页面。 可通过此页面将商品添加到购物车。

Orleans:购物车示例应用,商店库存页面。

空购物车页面

如果你尚未在购物车中添加任何商品,该页面会显示一条消息,指出购物车中没有商品。

Orleans:购物车示例应用,空购物车页面。

已在商店库存页面上将商品添加到购物车

如果已在商店库存页面上将商品添加到购物车,则应用会显示一条消息,指出商品已添加到购物车。

Orleans:购物车示例应用,已在商店库存页面上将商品添加到购物车。

产品管理页面

用户可通过此页面管理库存。 可以在库存中添加、编辑和删除产品。

Orleans:购物车示例应用,产品管理页面。

产品管理页面中的“新建”对话框

当用户单击“创建新产品”按钮时,应用会显示一个对话框让用户创建新产品。

Orleans:购物车示例应用,产品管理页面 -“创建新产品”对话框。

购物车页面中的商品

将商品添加到购物车后,你可以查看这些商品,更改其数量,甚至可将其从购物车中删除。 将向用户显示购物车中商品的摘要和税前总金额。

Orleans:购物车示例应用,购物车页面中的商品。

重要

当此应用在开发环境中本地运行时,该应用将使用 localhost 群集、内存中存储和本地 silo。 它还会使用通过 Bogus NuGet 包自动生成的虚构数据来为库存播种。 此操作是为了演示功能而有意执行的。

部署到 Azure App Service

典型的 Orleans 应用程序由服务器进程 (silo) 群集(grain 驻留在其中)和一组客户端进程(通常是 Web 服务器,它们接收外部请求,将请求转换为 grain 方法调用并返回结果)组成。 因此,要运行 Orleans 应用程序,首先需要启动 silo 群集。 对于测试,群集可以由一个 silo 组成。

注意

要进行可靠的生产部署,需要在群集中包含多个 silo 以实现容错和缩放。

在部署应用之前,需要创建一个 Azure 资源组(或者可以选择使用现有的资源组)。 若要创建新的 Azure 资源组,请参阅以下文章之一:

请记下所选的资源组名称,因为稍后需要用它来部署应用。

创建服务主体

若要自动部署应用,需要创建一个服务主体。 这是一个有权代表你管理 Azure 资源的 Microsoft 帐户。

az ad sp create-for-rbac --sdk-auth --role Contributor \
  --name "<display-name>"  --scopes /subscriptions/<your-subscription-id>

创建的 JSON 凭据如下所示,但其中包含了客户端、订阅和租户的实际值:

{
  "clientId": "<your client id>",
  "clientSecret": "<your client secret>",
  "subscriptionId": "<your subscription id>",
  "tenantId": "<your tenant id>",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com/",
  "resourceManagerEndpointUrl": "https://brazilus.management.azure.com",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com",
  "managementEndpointUrl": "https://management.core.windows.net"
}

将命令输出复制到剪贴板,然后继续执行下一步。

创建 GitHub 机密

GitHub 提供了用于创建加密机密的机制。 创建的机密可在 GitHub Actions 工作流中使用。 你将了解如何结合 Azure Bicep 使用 GitHub Actions 来自动部署应用。 Bicep 是一种特定于域的语言 (DSL),使用声明性语法来部署 Azure 资源。 有关详细信息,请参阅什么是 Bicep。 需要使用创建服务主体步骤的输出,创建名为 AZURE_CREDENTIALS、包含 JSON 格式的凭据的 GitHub 机密。

在 GitHub 存储库中,选择“设置”>“机密”>“创建新机密”。 输入名称 AZURE_CREDENTIALS,然后在“值”字段中粘贴在上一步骤中创建的 JSON 凭据。

GitHub 存储库:设置 > 机密

有关详细信息,请参阅 GitHub:加密的机密

准备 Azure 部署

需要打包该应用以进行部署。 在 Orleans.ShoppingCart.Silos 项目中,我们定义了在 Publish 步骤之后运行的 Target 元素。 这会将发布目录压缩成 silo.zip 文件:

<Target Name="ZipPublishOutput" AfterTargets="Publish">
    <Delete Files="$(ProjectDir)\..\silo.zip" />
    <ZipDirectory SourceDirectory="$(PublishDir)" DestinationFile="$(ProjectDir)\..\silo.zip" />
</Target>

可通过多种方式将 .NET 应用部署到 Azure 应用服务。 在本教程中,你将使用 GitHub Actions、Azure Bicep 以及 .NET 和 Azure CLI。 考虑 GitHub 存储库根目录中的 ./github/workflows/deploy.yml 文件:

name: Deploy to Azure App Service

on:
  push:
    branches:
    - main

env:
  UNIQUE_APP_NAME: cartify
  AZURE_RESOURCE_GROUP_NAME: orleans-resourcegroup
  AZURE_RESOURCE_GROUP_LOCATION: centralus

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Setup .NET 6.0
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 6.0.x

    - name: .NET publish shopping cart app
      run: dotnet publish ./Silo/Orleans.ShoppingCart.Silo.csproj --configuration Release

    - name: Login to Azure
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}
    
    - name: Flex bicep
      run: |
        az deployment group create \
          --resource-group ${{ env.AZURE_RESOURCE_GROUP_NAME }} \
          --template-file '.github/workflows/flex/main.bicep' \
          --parameters location=${{ env.AZURE_RESOURCE_GROUP_LOCATION }} \
            appName=${{ env.UNIQUE_APP_NAME }} \
          --debug

    - name: Webapp deploy
      run: |
        az webapp deploy --name ${{ env.UNIQUE_APP_NAME }} \
          --resource-group ${{ env.AZURE_RESOURCE_GROUP_NAME  }} \
          --clean true --restart true \
          --type zip --src-path silo.zip --debug

上述 GitHub 工作流将:

推送到 main 分支会触发该工作流。 有关详细信息,请参阅 GitHub Actions 和 .NET

提示

如果在运行工作流时遇到问题,可能需要验证是否为服务主体注册了所有必需的提供程序命名空间。 以下提供程序命名空间是必需的:

  • Microsoft.Web
  • Microsoft.Network
  • Microsoft.OperationalInsights
  • Microsoft.Insights
  • Microsoft.Storage

有关详细信息,请参阅解决资源提供程序注册错误

Azure 对资源施加命名限制和约定。 需要更新以下各项的 deploy.yml 文件值:

  • UNIQUE_APP_NAME
  • AZURE_RESOURCE_GROUP_NAME
  • AZURE_RESOURCE_GROUP_LOCATION

将这些值设置为唯一的应用名称,以及 Azure 资源组名称和位置。

有关详细信息,请参阅 Azure 资源的命名规则和限制

浏览 Bicep 模板

运行 az deployment group create 命令时,它会评估 main.bicep 文件。 此文件包含要部署的 Azure 资源。 此步骤的作用是预配所有部署资源。

重要

如果使用的是 Visual Studio Code,则使用 Bicep 扩展可以改善 Bicep 创作体验。

有许多 Bicep 文件,其中每个文件包含资源或模块(资源集合)。 main.bicep 文件是入口点,主要由 module 定义构成:

param appName string
param location string = resourceGroup().location

module storageModule 'storage.bicep' = {
  name: 'orleansStorageModule'
  params: {
    name: '${appName}storage'
    location: location
  }
}

module logsModule 'logs-and-insights.bicep' = {
  name: 'orleansLogModule'
  params: {
    operationalInsightsName: '${appName}-logs'
    appInsightsName: '${appName}-insights'
    location: location
  }
}

resource vnet 'Microsoft.Network/virtualNetworks@2021-05-01' = {
  name: '${appName}-vnet'
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '172.17.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'default'
        properties: {
          addressPrefix: '172.17.0.0/24'
          delegations: [
            {
              name: 'delegation'
              properties: {
                serviceName: 'Microsoft.Web/serverFarms'
              }
            }
          ]
        }
      }
    ]
  }
}

module siloModule 'app-service.bicep' = {
  name: 'orleansSiloModule'
  params: {
    appName: appName
    location: location
    vnetSubnetId: vnet.properties.subnets[0].id
    appInsightsConnectionString: logsModule.outputs.appInsightsConnectionString
    appInsightsInstrumentationKey: logsModule.outputs.appInsightsInstrumentationKey
    storageConnectionString: storageModule.outputs.connectionString
  }
}

上面的 Bicep 文件定义了以下内容:

  • 对应于资源组名称和应用名称的两个参数。
  • storageModule 定义,用于定义存储帐户。
  • logsModule 定义,用于定义 Azure Log Analytics 和 Application Insights 资源。
  • vnet 资源,用于定义虚拟网络。
  • siloModule 定义,用于定义 Azure 应用服务。

一个非常重要的 resource 是虚拟网络。 vnet 资源使 Azure 应用服务能够与 Orleans 群集通信。

每当在 Bicep 文件中遇到 module 时,都会通过另一个包含资源定义的 Bicep 文件对其进行评估。 遇到的第一个模块是 storageModule,它在 storage.bicep 文件中定义:

param name string
param location string

resource storage 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: name
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

var key = listKeys(storage.name, storage.apiVersion).keys[0].value
var protocol = 'DefaultEndpointsProtocol=https'
var accountBits = 'AccountName=${storage.name};AccountKey=${key}'
var endpointSuffix = 'EndpointSuffix=${environment().suffixes.storage}'

output connectionString string = '${protocol};${accountBits};${endpointSuffix}'

Bicep 文件接受使用 param 关键字声明的参数。 同样,它们也可以使用 output 关键字来声明输出。 存储 resource 依赖于 Microsoft.Storage/storageAccounts@2021-08-01 类型和版本。 它作为 StorageV2Standard_LRS SKU 在资源组位置进行预配。 存储 Bicep 将其连接字符串定义为 output。 此 connectionString 稍后由 silo Bicep 用来连接到存储帐户。

接下来,logs-and-insights.bicep 文件定义 Azure Log Analytics 和 Application Insights 资源:

param operationalInsightsName string
param appInsightsName string
param location string

resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: appInsightsName
  location: location
  kind: 'web'
  properties: {
    Application_Type: 'web'
    WorkspaceResourceId: logs.id
  }
}

resource logs 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
  name: operationalInsightsName
  location: location
  properties: {
    retentionInDays: 30
    features: {
      searchVersion: 1
    }
    sku: {
      name: 'PerGB2018'
    }
  }
}

output appInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey
output appInsightsConnectionString string = appInsights.properties.ConnectionString

此 Bicep 文件定义 Azure Log Analytics 和 Application Insights 资源。 appInsights 资源的类型为 weblogs 资源的类型为 PerGB2018appInsights 资源和 logs 资源均在资源组位置进行预配。 appInsights 资源通过 WorkspaceResourceId 属性链接到 logs 资源。 此 Bicep 中定义了稍后将由应用服务 module 使用的两个输出。

最后,app-service.bicep 文件定义了 Azure 应用服务资源:

param appName string
param location string
param vnetSubnetId string
param appInsightsInstrumentationKey string
param appInsightsConnectionString string
param storageConnectionString string

resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: '${appName}-plan'
  location: location
  kind: 'app'
  sku: {
    name: 'S1'
    capacity: 1
  }
}

resource appService 'Microsoft.Web/sites@2021-03-01' = {
  name: appName
  location: location
  kind: 'app'
  properties: {
    serverFarmId: appServicePlan.id
    virtualNetworkSubnetId: vnetSubnetId
    httpsOnly: true
    siteConfig: {
      vnetPrivatePortsCount: 2
      webSocketsEnabled: true
      netFrameworkVersion: 'v6.0'
      appSettings: [
        {
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
          value: appInsightsInstrumentationKey
        }
        {
          name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
          value: appInsightsConnectionString
        }
        {
          name: 'ORLEANS_AZURE_STORAGE_CONNECTION_STRING'
          value: storageConnectionString
        }
      ]
      alwaysOn: true
    }
  }
}

resource appServiceConfig 'Microsoft.Web/sites/config@2021-03-01' = {
  name: '${appService.name}/metadata'
  properties: {
    CURRENT_STACK: 'dotnet'
  }
}

此 Bicep 文件将 Azure 应用服务配置为 .NET 6 应用程序。 appServicePlan 资源和 appService 资源均在资源组位置进行预配。 appService 资源配置为使用 S1 SKU,该 SKU 的容量为 1。 此外,该资源配置为使用 vnetSubnetId 子网并使用 HTTPS。 此文件还配置了 appInsightsInstrumentationKey 检测密钥、appInsightsConnectionString 连接字符串和 storageConnectionString 连接字符串。 这些信息由购物车应用使用。

上述适用于的 Bicep 的 Visual Studio Code 扩展包含一个可视化工具。 下面是所有这些 Bicep 文件的可视化效果:

Orleans:购物车示例应用 Bicep 预配可视化工具显示画面。

摘要

更新源代码并将更改 push 到存储库的 main 分支时,将运行 deploy.yml 工作流。 该工作流将预配 Bicep 文件中定义的资源并部署应用程序。 可以扩展该应用程序以包含新功能(例如身份验证)或支持应用程序的多个实例。 此工作流的主要目的是演示如何通过一个步骤来预配和部署资源。

除了 Bicep 扩展中的可视化工具外,在预配并部署应用程序后,会显示如以下示例所示的 Azure 门户资源组页面:

Azure 门户:Orleans 购物车示例应用资源。

另请参阅