本教程介绍如何将 Orleans 购物车应用部署到 Azure 应用服务。 本指南逐步讲解支持以下功能的示例应用程序:
购物车:一个简单的购物车应用程序,利用Orleans的跨平台框架支持和可扩展的分布式应用程序功能。
- 库存管理:编辑和/或创建产品库存。
- 商店库存:探索可购买的产品并将其添加到购物车。
- 购物车:通过删除或更改每个商品的数量来查看购物车中所有项目的摘要并管理这些项目。
了解应用及其功能后,了解如何使用 GitHub Actions、.NET 和 Azure CLIs 和 Azure Bicep 将应用部署到 Azure 应用服务。 此外,了解如何在 Azure 中为应用配置虚拟网络。
本教程中,您将学习如何:
- 将 Orleans 应用程序部署到 Azure 应用服务
- 使用 GitHub Actions 和 Azure Bicep 自动部署
- 在 Azure 中为应用配置虚拟网络
先决条件
- GitHub 帐户
- 阅读简介 Orleans
- .NET 8 SDK
- Azure CLI
- .NET 集成开发环境 (IDE)
- 随意使用 Visual Studio 或 Visual Studio Code
在本地运行应用
若要在本地运行应用,请分叉 Azure 示例: Orleans Azure 应用服务存储库上的群集 并将其克隆到本地计算机。 克隆后,在所选的 IDE 中打开解决方案。 如果使用 Visual Studio,请右键单击 。OrleansShoppingCart.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 和服务器托管模型生成的。 应用架构如下:
上图显示客户端是服务器端 Blazor 应用。 它由使用相应 Orleans 粒度的多个服务组成。 每个服务与一个Orleans粒配对,如下所示:
-
InventoryService
:消耗IInventoryGrain
库存按产品类别进行分区的情况。 -
ProductService
:消耗IProductGrain
,即单个产品通过Id
连接到单个粒度实例。 -
ShoppingCartService
:消耗IShoppingCartGrain
,在这种情况下,每个用户只拥有一个购物车实例,无论使用的客户端如何。
该解决方案包含三个项目:
-
Orleans.ShoppingCart.Abstractions
:定义应用的模型和接口的类库。 -
Orleans.ShoppingCart.Grains
:一个类库,定义实现应用业务逻辑的粒子。 -
Orleans.ShoppingCart.Silos
:托管 Orleans silo 的服务器端 Blazor 应用。
客户端用户体验
购物车客户端应用有多个页面,每个页面都表示不同的用户体验。 应用的 UI 是使用 MudBlazor NuGet 包生成的。
主页
几个简单的短语有助于了解应用的用途,并将上下文添加到每个导航菜单项。
商店库存页
显示所有可供购买的产品的页面。 可以从此页面将项目添加到购物车。
空购物车页
如果购物车中未添加任何内容,页面将显示一条消息,指示购物车中没有项目。
在商店库存页面上添加到购物车的项目
当商品添加到购物车时,在商店库存页面上,应用会显示一条消息,指示已添加该项目。
产品管理页
从此页面管理清单。 产品可从库存中添加、编辑和删除。
“产品管理”页“新建”对话框
单击“ 创建新产品 ”按钮将显示允许创建新产品的对话框。
购物车页中的项目
当商品在购物车中时,查看它们、更改其数量,甚至将其删除。 显示购物车中商品和税前总成本的概览。
重要
当此应用在开发环境中运行时,它使用 localhost 群集、内存存储和本地孤立空间。 它还通过使用 Bogus NuGet 包自动生成的假数据来填充库存。 这是有意演示功能。
部署概述
Orleans 应用程序旨在高效地纵向扩展和横向扩展。 为此,应用程序实例直接通过 TCP 套接字进行通信。 因此, Orleans 需要在孤岛之间建立网络连接。 Azure 应用服务通过 虚拟网络集成 和其他配置支持此要求,指示应用服务为应用实例分配专用网络端口。
部署到 Orleans Azure 应用服务时,执行以下操作以确保主机可以通信:
- 按照启用与 Azure 虚拟网络集成指南的说明启用虚拟网络集成。
- 使用 Azure CLI 按照“使用 Azure CLI 配置专用端口计数”部分中的说明配置应用的专用端口。 下面的 “浏览 Bicep 模板”部分中的 Bicep 模板演示如何通过 Bicep 配置此设置。
- 如果部署到 Linux,请确保主机侦听所有 IP 地址,如 “配置主机网络 ”部分中所述。
使用 Azure CLI 配置专用端口计数
az webapp config set -g '<resource-group-name>' --subscription '<subscription-id>' -n '<app-service-app-name>' --generic-configurations '{\"vnetPrivatePortsCount\": "2"}'
配置主机网络
使用虚拟网络(VNet)集成配置 Azure 应用服务并将其设置为为每个应用程序实例提供至少两个专用端口后,向应用进程提供另外两个环境变量: WEBSITE_PRIVATE_IP
WEBSITE_PRIVATE_PORTS
这些变量提供两个重要信息:
- 在虚拟网络中,其他主机可以使用哪个 IP 地址来联系指定的应用实例。
- 该 IP 地址上的哪些端口将路由到该应用实例
该 WEBSITE_PRIVATE_IP
变量指定从 VNet 路由的 IP,但不一定是应用实例可以直接绑定到的 IP 地址。 因此,请指示主机通过在ConfigureEndpoints
方法调用中传递listenOnAnyHostAddress: true
来绑定所有内部地址。 以下示例将 ISiloBuilder
实例配置为利用注入的环境变量,并监听正确的接口:
var endpointAddress = IPAddress.Parse(builder.Configuration["WEBSITE_PRIVATE_IP"]!);
var strPorts = builder.Configuration["WEBSITE_PRIVATE_PORTS"]!.Split(',');
if (strPorts.Length < 2)
{
throw new Exception("Insufficient private ports configured.");
}
var (siloPort, gatewayPort) = (int.Parse(strPorts[0]), int.Parse(strPorts[1]));
siloBuilder
.ConfigureEndpoints(endpointAddress, siloPort, gatewayPort, listenOnAnyHostAddress: true)
上述代码也存在于 Azure 示例: Orleans Azure 应用服务存储库上的群集 中,允许在主机配置其余部分的上下文中查看它。
部署到 Azure 应用服务
典型的 Orleans 应用程序由一组服务器进程(孤岛)组成,其中包含谷物,以及一组客户端进程(通常是 Web 服务器),负责接收外部请求,将其转换为谷物方法调用,并返回结果。 因此,运行 Orleans 应用程序的第一步是启动孤岛群集。 出于测试目的,群集可以包含单个孤岛。
注释
对于可靠的生产部署,需要在群集内有多个存储孤岛,以提高容错能力和扩展性。
在部署应用之前,请创建 Azure 资源组(也可以使用现有资源组)。 若要创建新的 Azure 资源组,请使用以下文章之一:
记下所选的资源组名称;稍后需要它来部署应用。
创建服务主体
若要自动执行应用的部署,需要创建服务主体。 这是一个Microsoft帐户,有权代表你管理 Azure 资源。
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 工作流。 你将了解如何使用 GitHub Actions 配合 Azure Bicep 自动化应用的部署。 Bicep 是一种特定于域的语言 (DSL),使用声明性语法来部署 Azure 资源。 有关详细信息,请参阅什么是 Bicep?。 使用创建服务主体步骤的输出,你需要用 JSON 格式的凭据创建一个名为AZURE_CREDENTIALS
的 GitHub 密钥。
在 GitHub 存储库中,选择 “设置>机密>创建新机密”。 输入名称 AZURE_CREDENTIALS
并将上一步中的 JSON 凭据粘贴到 “值 ”字段中。
有关详细信息,请参阅 GitHub:加密的机密。
准备 Azure 部署
打包应用以供部署。 在 Orleans.ShoppingCart.Silos
项目中,Target
元素被定义为在 Publish
步骤之后运行。 此目标将发布目录压缩到 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 CLIs。 请考虑 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 8.0
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.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
- name: Staging deploy
run: |
az webapp deploy --name ${{ env.UNIQUE_APP_NAME }} \
--slot ${{ env.UNIQUE_APP_NAME }}stg \
--resource-group ${{ env.AZURE_RESOURCE_GROUP_NAME }} \
--clean true --restart true \
--type zip --src-path silo.zip --debug
前面的 GitHub 工作流执行以下作:
- 使用 dotnet publish 命令将购物车应用发布为 zip 文件。
- 使用 “创建服务主体 ”步骤中的凭据登录到 Azure。
- 评估 main.bicep 文件,并使用 az deployment group create 启动部署组。
- 使用 az webapp deploy 将 silo.zip 文件部署到 Azure 应用服务。
- 配置额外的部署到测试环境中。
工作流在推送到 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',
'192.168.0.0/16'
]
}
subnets: [
{
name: 'default'
properties: {
addressPrefix: '172.17.0.0/24'
delegations: [
{
name: 'delegation'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
}
{
name: 'staging'
properties: {
addressPrefix: '192.168.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
stagingSubnetId: vnet.properties.subnets[1].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 文件中遇到 a 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
类型和版本。 它在资源组的位置中,作为StorageV2
和 Standard_LRS
SKU 进行预配。 存储 Bicep 文件将其连接字符串定义为 output
。 稍后,筒仓 Bicep 文件将使用 connectionString
连接到存储帐户。
接下来, 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
是一种 web
类型, logs
资源是一种 PerGB2018
类型。
appInsights
和logs
资源在资源组的位置中被预配。 资源appInsights
通过WorkspaceResourceId
属性链接到logs
资源。 此 Bicep 文件定义应用服务 module
稍后使用的两个输出。
最后, app-service.bicep 文件定义 Azure 应用服务资源:
param appName string
param ___location string
param vnetSubnetId string
param stagingSubnetId 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: 'v8.0'
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsightsInstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsightsConnectionString
}
{
name: 'ORLEANS_AZURE_STORAGE_CONNECTION_STRING'
value: storageConnectionString
}
{
name: 'ORLEANS_CLUSTER_ID'
value: 'Default'
}
]
alwaysOn: true
}
}
}
resource stagingSlot 'Microsoft.Web/sites/slots@2022-03-01' = {
name: '${appName}stg'
___location: ___location
properties: {
serverFarmId: appServicePlan.id
virtualNetworkSubnetId: stagingSubnetId
siteConfig: {
http20Enabled: true
vnetPrivatePortsCount: 2
webSocketsEnabled: true
netFrameworkVersion: 'v8.0'
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsightsInstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsightsConnectionString
}
{
name: 'ORLEANS_AZURE_STORAGE_CONNECTION_STRING'
value: storageConnectionString
}
{
name: 'ORLEANS_CLUSTER_ID'
value: 'Staging'
}
]
alwaysOn: true
}
}
}
resource slotConfig 'Microsoft.Web/sites/config@2021-03-01' = {
name: 'slotConfigNames'
parent: appService
properties: {
appSettingNames: [
'ORLEANS_CLUSTER_ID'
]
}
}
resource appServiceConfig 'Microsoft.Web/sites/config@2021-03-01' = {
parent: appService
name: 'metadata'
properties: {
CURRENT_STACK: 'dotnet'
}
}
此 Bicep 文件将 Azure 应用服务配置为 .NET 8 应用程序。
appServicePlan
和appService
资源在资源组的位置中被预配。 资源 appService
配置为将 S1
SKU 与容量 1
一起使用。 此外,资源配置为使用 vnetSubnetId
子网和 HTTPS。 它还配置 appInsightsInstrumentationKey
检测密钥、 appInsightsConnectionString
连接字符串和 storageConnectionString
连接字符串。 购物车应用使用这些值。
上述适用于 Bicep 的 Visual Studio Code 扩展包括可视化工具。 这些 Bicep 文件都可按如下方式可视化:
过渡环境
部署基础结构可以部署到测试环境。 短暂存在、不可变且一次性使用的测试中心环境在将部署提升到生产环境之前对测试部署非常有用。
注释
如果应用服务在 Windows 上运行,则每个应用服务必须位于其自己的独立应用服务计划中。 或者,若要避免此类配置,请改用 Linux 上的应用服务,并解决此问题。
概要
随着源代码的更新和存储库分支的更改push
main
,deploy.yml工作流运行。 它配置 Bicep 文件中定义的资源并部署应用程序。 可以扩展应用程序以包括新功能,例如身份验证,或支持多个实例。 此工作流的主要目标是演示在单个步骤中预配和部署资源的能力。
除了 Bicep 扩展中的可视化工具外,Azure 门户资源组页面在预配和部署应用程序后看起来与以下示例类似: