Azure App Service に Orleans をデプロイする

このチュートリアルでは、Azure App Service に Orleans ショッピング カート アプリをデプロイする方法を学習します。 チュートリアルでは、次の機能をサポートするサンプル アプリケーションについて説明します。

  • ショッピング カート: クロスプラットフォーム フレームワークのサポートとスケーラブルな分散アプリケーション機能のために Orleans を使用する、シンプルなショッピング カート アプリケーション。

    • 在庫管理: 商品在庫の編集や作成を行います。
    • 在庫の購入: 購入可能な商品を調べてカートに追加します。
    • カート: カート内の全項目の概要を表示し、それらの項目を管理します。各項目を削除したり、数量を変更したりします。

アプリとその機能について理解したら、GitHub Actions、.NET と Azure CLI、Azure Bicep を使ってアプリを Azure App Service にデプロイする方法について説明します。 さらに、Azure 内でアプリ用の仮想ネットワークを構成する方法について説明します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Azure App Service に Orleans アプリケーションをデプロイする
  • GitHub Actions と Azure Bicep を使ってデプロイを自動化する
  • Azure 内でアプリ用の仮想ネットワークを構成する

前提条件

アプリをローカルで実行する

アプリをローカルで実行するには、Azure Samples: Orleans Cluster on Azure App Service リポジトリをフォークし、ローカル コンピューターにクローンします。 クローンしたら、お好みの 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 App Service にデプロイします。 このアプリでは、在庫の管理、カート内の項目の追加と削除、利用可能な商品の購入を行う機能が公開されます。 クライアントは、Blazor とサーバー ホスティング モデルを使用して構築されます。 アプリのアーキテクチャは次のようになります。

Orleans: ショッピング カート サンプル アプリのアーキテクチャ。

上の図は、クライアントがサーバー側の Blazor アプリであることを示しています。 これは、対応する Orleans グレインを使用したいくつかのサービスで構成されています。 各サービスは、次のように Orleans グレインとペアになっています。

  • InventoryService: IInventoryGrain を使用し、在庫を商品カテゴリ別に分割します。
  • ProductService: IProductGrain を使用し、1 つの商品を 1 つのグレイン インスタンスに Id で結び付けます。
  • ShoppingCartService: IShoppingCartGrain を使用し、使用するクライアントに関係なく、1 人のユーザーが 1 つのショッピング カート インスタンスのみを持つようにします。

ソリューションには 3 つのプロジェクトが含まれています。

  • Orleans.ShoppingCart.Abstractions: アプリのモデルとインターフェイスを定義するクラス ライブラリ。
  • Orleans.ShoppingCart.Grains: アプリのビジネス ロジックを実装するグレインを定義するクラス ライブラリ。
  • Orleans.ShoppingCart.Silos: Orleans サイロをホストするサーバー側 Blazor アプリ。

クライアントのユーザー エクスペリエンス

ショッピング カートのクライアント アプリには複数のページがあり、それぞれが異なるユーザー エクスペリエンスを提供します。 アプリの UI は、MudBlazor NuGet パッケージを使用して構築されています。

ホーム ページ

ユーザーがアプリの目的を理解し、ナビゲーション メニューの各項目の意味を説明するための、いくつかの簡単なフレーズ。

Orleans: ショッピング カート サンプル アプリ、ホーム ページ。

在庫の購入ページ

購入可能なすべての商品を表示するページ。 このページからカートに項目を追加できます。

Orleans: ショッピング カート サンプル アプリ、在庫の購入ページ。

空のカート ページ

カートに何も追加していない場合、カートに項目がないことを示すメッセージがページに表示されます。

Orleans: ショッピング カート サンプル アプリ、空のカート ページ。

在庫の購入ページでカートに追加された項目

在庫の購入ページでカートに項目が追加されると、その項目がカートに追加されたことを示すメッセージがアプリに表示されます。

Orleans: ショッピング カート サンプル アプリ、在庫の購入ページでカートに追加された商品。

商品管理ページ

ユーザーはこのページから在庫を管理できます。 商品を追加したり、編集したり、在庫から削除したりできます。

Orleans: ショッピング カート サンプル アプリ、製品管理ページ。

商品管理ページの新規作成ダイアログ

ユーザーが [Create new product](新しい商品の作成) ボタンをクリックすると、新しい商品を作成できるダイアログがアプリによって表示されます。

Orleans: ショッピング カート サンプル アプリ、製品管理ページ - [新しい商品の作成] ダイアログ。

カート ページの項目

カートに項目が入っている場合は、それらを表示して数量を変更したり、カートから削除したりすることもできます。 ユーザーには、カート内の項目の概要と税込み合計価格が表示されます。

Orleans: ショッピング カート サンプル アプリ、カート ページの項目。

重要

このアプリをローカルの開発環境内で実行する場合、アプリでは localhost クラスタリング、インメモリ ストレージ、ローカル サイロが使用されます。 また、在庫は、Bogus NuGet パッケージを使用して自動的に生成される架空のデータによって設定されます。 これはすべて意図的であり、機能のデモンストレーションを行うことが目的です。

Azure App Service に配置する

一般的な Orleans アプリケーションは、グレインが存在するサーバー プロセス (サイロ) のクラスターと、外部要求を受信し、グレイン メソッドの呼び出しに変換して結果を返す、一連のクライアント プロセス (通常は Web サーバー) で構成されます。 そのため、Orleans アプリケーションを実行するために最初に行う必要があるのは、サイロのクラスターを起動することです。 テスト目的のために、クラスターを 1 つのサイロで構成することができます。

注意

信頼性の高い運用環境のデプロイでは、フォールト トレランスとスケーリングのために、クラスター内に複数のサイロが必要です。

アプリをデプロイする前に、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 は、宣言型の構文を使用して Azure リソースをデプロイするドメイン固有言語 (DSL) です。 詳細については、「Bicep とは」を参照してください。 「サービス プリンシパルの作成」の手順で得た出力を使用し、JSON 形式の資格情報を使って AZURE_CREDENTIALS という名前の GitHub シークレットを作成する必要があります。

GitHub リポジトリ内で、[Settings](設定)>[Secrets](シークレット)>[Create a new secret](新しいシークレットの作成) の順に選択します。 AZURE_CREDENTIALS という名前を入力し、前の手順の JSON 資格情報を [Value](値) フィールドに貼り付けます。

GitHub リポジトリ: [設定] > [シークレット]

詳細については、GitHub: 「Encrypted Secrets (暗号化されたシークレット)」を参照してください。

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>

Azure App Service に .NET アプリをデプロイする方法は多数あります。 このチュートリアルでは、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 ワークフローでは、次の処理が行われます。

ワークフローは、"メイン" ブランチへのプッシュによってトリガーされます。 詳細については、「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 ファイルでは、次のものが定義されています。

  • リソース グループ名とアプリ名のための 2 つのパラメーター。
  • storageModule の定義。ストレージ アカウントを定義します。
  • logsModule の定義。Azure Log Analytics と Application Insights のリソースを定義します。
  • vnet リソース。仮想ネットワークを定義します。
  • siloModule の定義。Azure App Service を定義します。

1 つの非常に重要な resource は、Virtual Network のものです。 vnet リソースによって、Azure App Service が 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 の種類とバージョンに依存します。 これはリソース グループの場所に、StorageV2 および Standard_LRS SKU としてプロビジョニングされます。 ストレージの bicep では、その接続文字列が output として定義されます。 この connectionString は、ストレージ アカウントに接続するために、後でサイロ 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 のリソースは web の種類であり、logs のリソースは PerGB2018 の種類です。 appInsights リソースと logs リソースは、両方ともリソース グループの場所にプロビジョニングされます。 appInsights リソースは、WorkspaceResourceId プロパティを使用して logs リソースにリンクされます。 この bicep では、後ほど App Service の module で使用する 2 つの出力が定義されています。

最後に、app-service.bicep ファイルでは、Azure App Service のリソースが定義されます。

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 App Service を .NET 6 アプリケーションとして構成しています。 appServicePlan リソースと appService リソースは、両方ともリソース グループの場所にプロビジョニングされます。 appService リソースは S1 SKU を使用するように構成されており、容量は 1 です。 さらに、リソースは vnetSubnetId サブネットを使用し、HTTPS を使用するように構成されています。 また、appInsightsInstrumentationKey インストルメンテーション キー、appInsightsConnectionString 接続文字列、storageConnectionString 接続文字列も構成されています。 これらはショッピング カート アプリで使用されます。

前述の Bicep 用 Visual Studio Code 拡張機能には、ビジュアライザーが含まれています。 これらすべての bicep ファイルは、次のように視覚化されます。

Orleans: ショッピング カート サンプル アプリの bicep プロビジョニング ビジュアライザー レンダリング。

まとめ

ソース コードを更新し、変更をリポジトリの main ブランチに push すると、deploy.yml ワークフローが実行されます。 各 bicep ファイルで定義されているリソースがプロビジョニングされ、アプリケーションがデプロイされます。 このアプリケーションを拡張して、認証などの新機能を追加したり、アプリケーションの複数のインスタンスをサポートしたりできます。 このワークフローの主な目的は、リソースのプロビジョニングとデプロイを 1 つのステップで行う機能を示すことです。

bicep 拡張機能のビジュアライザーに加えて、アプリケーションをプロビジョニングしてデプロイした後の Azure portal のリソース グループ ページは次の例のようになります。

Azure Portal: Orleans ショッピング カート サンプル アプリ リソース。

関連項目