Microsoft Build 2022 で Azure Container Apps が GA されました。東日本リージョンでも使えるようになりましたので、KEDA を使って Azure Queue Storage のバックグラウンド処理を構築してみました。
Azure Container Apps の KEDA サポート
Container Apps では、コンテナのインスタンス(Replicas)の水平オートスケーリングに対応しており、4種類のスケーリング トリガーがあります。
- HTTP Request
- Event-driven
- CPU
- Memory
Event-driven については、KEDA でサポートされているすべてのイベントに対応しています。今回は、Azure Queue Storage のスケーリング トリガーを試してみます。
Replicas をゼロにスケーリングすると課金されることはありませんが、CPU と Memory はゼロにスケーリングできないので注意が必要です。
docs.microsoft.com
バックグラウンド処理を作成する
バックグラウンド処理を C# で作る場合、2つの方法があります。
- Azure Functions を使う
- BackgroundService クラスを使って実装する
今回は、Azure Functions の Queue Trigger テンプレートをそのまま使います。
public class Function { [FunctionName(nameof(Function))] public void Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem, ILogger log) { log.LogInformation($"C# Queue trigger function processed: {myQueueItem}"); } }
Azure Functions Core Tools で func init
コマンドに --docker-only
を指定すると、既存のプロジェクトに Dockerfile を追加できます。
$ func init --docker-only --dotnet
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS installer-env # Build requires 3.1 SDK COPY --from=mcr.microsoft.com/dotnet/core/sdk:3.1 /usr/share/dotnet /usr/share/dotnet COPY . /src/dotnet-function-app RUN cd /src/dotnet-function-app && \ mkdir -p /home/site/wwwroot && \ dotnet publish *.csproj --output /home/site/wwwroot # To enable ssh & remote debugging on app service change the base image to the one below # FROM mcr.microsoft.com/azure-functions/dotnet:4-appservice FROM mcr.microsoft.com/azure-functions/dotnet:4 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]
Dockerfile が追加されたので、Ducker Hub に Image をプッシュしておきます。
Container Apps をデプロイする
Bicep を使って、Container Apps をデプロイします。
environment.bicep では、Environment、LogAnalytics、Application Insights のリソースを定義します。
param environmentName string param logAnalyticsWorkspaceName string = 'logs-${environmentName}' param appInsightsName string = 'appins-${environmentName}' param location string = resourceGroup().location resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' = { name: logAnalyticsWorkspaceName location: location properties: any({ retentionInDays: 30 features: { searchVersion: 1 legacy: 0 enableLogAccessUsingOnlyResourcePermissions: true } sku: { name: 'PerGB2018' } }) } resource appInsights 'Microsoft.Insights/components@2020-02-02' = { name: appInsightsName location: location kind: 'web' properties: { Application_Type: 'web' WorkspaceResourceId:logAnalyticsWorkspace.id } } resource environment 'Microsoft.App/managedEnvironments@2022-03-01' = { name: environmentName location: location properties: { appLogsConfiguration: { destination: 'log-analytics' logAnalyticsConfiguration: { customerId: logAnalyticsWorkspace.properties.customerId sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey } } daprAIInstrumentationKey: appInsights.properties.InstrumentationKey zoneRedundant: false } } output location string = location output environmentId string = environment.id
storage.bicep では、Storage Accounts のリソースを定義します。ConnectionString
を作っている部分がポイントです。
param storageAccountName string param location string = resourceGroup().location var strageSku = 'Standard_LRS' resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = { name: storageAccountName location: location kind: 'Storage' sku:{ name: strageSku } } output storageId string = stg.id output storageConnectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(stg.id, stg.apiVersion).keys[0].value}'
containerapp.bicep では、バックグラウンド処理を実行する Functions の Docker Image を指定して Container Apps のリソースを定義します。
バックグラウンド処理なので Ingress は有効化せず、Functions の構成に必要となる環境変数とシークレットに Storage の ConnectionString
を指定します。
myqueue-items
の Queue 内のメッセージが 3 個を超えると新しい Replicas を作成して、最大 10 個までオートスケーリングさせます。
param containerAppName string param location string = resourceGroup().location param environmentId string param containerImage string param revisionSuffix string @allowed([ 'multiple' 'single' ]) param revisionMode string = 'single' @secure() param storageConnectionString string resource containerApp 'Microsoft.App/containerApps@2022-03-01' = { name: containerAppName location: location properties: { managedEnvironmentId: environmentId configuration: { activeRevisionsMode: revisionMode dapr:{ enabled:false } secrets: [ { name: 'storage-connection' value: storageConnectionString } ] } template: { revisionSuffix: revisionSuffix containers: [ { image: containerImage name: containerAppName resources: { cpu: '0.25' memory: '0.5Gi' } env: [ { name: 'AzureWebJobsStorage' secretref: 'storage-connection' } { name: 'FUNCTIONS_WORKER_RUNTIME' value: 'dotnet' } ] } ] scale: { minReplicas: 0 maxReplicas: 10 rules: [ { name: 'queue-scaling-rule' azureQueue: { queueName: 'myqueue-items' queueLength: 3 auth: [ { secretRef: 'storage-connection' triggerParameter: 'connection' } ] } } ] } } } }
main.bicep では、それぞれの bicep ファイルをモジュールとして定義します。
param environmentName string = 'env-${resourceGroup().name}' param storageAccountName string = resourceGroup().name param location string = resourceGroup().location param containerAppName string = 'queue-reader-function' param containerImage string = 'thara0402/queue-reader-function:0.1.0' param revisionSuffix string = '' @allowed([ 'multiple' 'single' ]) param revisionMode string = 'single' module environment 'environment.bicep' = { name: 'container-app-environment' params: { environmentName: environmentName location: location } } module storage 'storage.bicep' = { name: 'storage' params: { storageAccountName: storageAccountName location: location } } module containerapp 'containerapp.bicep' = { name: 'container-app' params: { environmentId: environment.outputs.environmentId containerAppName: containerAppName containerImage: containerImage revisionSuffix: revisionSuffix revisionMode: revisionMode storageConnectionString: storage.outputs.storageConnectionString location: location } }
Azure CLI を使って、デプロイします。
$ az group create -n <ResourceGroup Name> -l japaneast $ az deployment group create -f ./deploy/main.bicep -g <ResourceGroup Name>
作られたリソースは、こんな感じです。
Container Apps の Revision を確認すると、オートスケーリングが設定されたことが分かります。
結果確認
Azure CLI を使って、Revision の Replicas 数を確認しておきます。
$ az containerapp revision show --revision <Revision Name> -n queue-reader-function -g <ResourceGroup Name> -o table
サーバーレスの設定を行ったので、Replicas 数が 0
であることを確認できます。
myqueue-items
の Queue を作ってメッセージを 10 個ほど送信すると、Replicas 数が 3
に増えたことを確認できます。
Log Analytics でクエリを実行すると、Functions が実行されたログを確認できます。
ContainerAppConsoleLogs_CL | project TimeGenerated, ContainerAppName_s, Log_s | where ContainerAppName_s contains "queue-reader-function" | order by TimeGenerated desc
まとめ
Azure Container Apps で、KEDA を使って Azure Queue Storage のバックグラウンド処理を構築してみました。
今回のように C# で Queue Trigger のケースは素直に Azure Functions を使ったほうがいいですが、従量課金プランにおける 1 回の実行が 10 分までなどの制限が要件にマッチしないケースでは Container Apps が選択肢になると思います。
Azure Functions Core Tools には func kubernetes install コマンドがあるので、いずれ Container Apps にも対応する可能性があるので、さらに簡単にデプロイできそうです。
今回のソースコードは、こちらのリポジトリで公開しています。
github.com