GA 後の記事は、こちらを参照してください。
gooner.hateblo.jp
先日の Microsoft Ignite で発表された Azure Container Apps で、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 をデプロイする
過去の記事では Container Apps Environment のみ Bicep を使ってデプロイしていましたが、今回は Container Apps も合わせてデプロイします。詳細は後述しますが、現時点では Azure CLI を使って KEDA の Scaling rules が設定できないためです。
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-preview' = { name: appInsightsName location: location kind: 'web' properties: { ApplicationId: appInsightsName Application_Type: 'web' Flow_Type: 'Redfield' Request_Source: 'CustomDeployment' } } resource environment 'Microsoft.Web/kubeEnvironments@2021-02-01' = { name: environmentName location: location properties: { type: 'managed' internalLoadBalancerEnabled: false appLogsConfiguration: { destination: 'log-analytics' logAnalyticsConfiguration: { customerId: logAnalyticsWorkspace.properties.customerId sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey } } containerAppsConfiguration: { daprAIInstrumentationKey: appInsights.properties.InstrumentationKey } } } 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 environmentId string @secure() param storageConnectionString string param location string = resourceGroup().location resource containerApp 'Microsoft.Web/containerApps@2021-03-01' = { name: 'queue-reader-function' kind: 'containerapp' location: location properties: { kubeEnvironmentId: environmentId configuration: { activeRevisionsMode: 'single' secrets: [ { name: 'storage-connection' value: storageConnectionString } ] } template: { containers: [ { image: 'thara0402/queue-reader-function:0.1.0' name: 'queue-reader-function' 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 module environment 'environment.bicep' = { name: 'container-app-environment' params: { environmentName: environmentName } } module storage 'storage.bicep' = { name: 'storage' params: { storageAccountName: storageAccountName } } module containerapp 'containerapp.bicep' = { name: 'container-app' params: { environmentId: environment.outputs.environmentId storageConnectionString: storage.outputs.storageConnectionString } }
Azure CLI を使って、デプロイします。
$ az group create -n <ResourceGroup Name> -l canadacentral $ az deployment group create -f ./deploy/main.bicep -g <ResourceGroup Name>
作られたリソースは、こんな感じです。
Container Apps の Revision を確認すると、オートスケーリングが設定されたことが分かります。
結果確認
Azure CLI を使って、Revision の Replicas 数を確認しておきます。
$ az containerapp revision show --app queue-reader-function -n <Revision Name> -g <ResourceGroup Name> -o table
サーバーレスの設定を行ったので、Replicas 数が 0
であることを確認できます。
myqueue-items
の Queue を作ってメッセージを 10 個ほど送信すると、Replicas 数が 4
に増えたことを確認できます。
Log Analytics でクエリを実行すると、Functions が実行されたログを確認できます。
ContainerAppConsoleLogs_CL | project TimeGenerated, ContainerAppName_s, RevisionName_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 を使ったほうがいいですが、Functions で非対応の開発言語でカスタムハンドラを作らざるを得ないケースでは Container Apps が選択肢になると思います。
Azure Functions Core Tools には func kubernetes install コマンドがあるので、いずれ Container Apps にも対応する可能性があるので、さらに簡単にデプロイできそうです。
今回のソースコードは、こちらのリポジトリで公開しています。
github.com
【補足】Azure CLI で Scaling rules が設定できない件
まだ Preview ということもあり、現時点では Azure CLI を使って KEDA の Scaling rules が設定できないです。(HTTP の Scaling rules は設定できる)
Container Apps はデプロイされますが、Scaling rules が null となります。
$ az containerapp create -n queue-reader-function -g <ResourceGroup Name> \ -e <Environment Name> -i thara0402/queue-reader-function:0.1.0 \ --ingress internal --target-port 80 --revisions-mode single \ --scale-rules ./deploy/queuescaler.json --max-replicas 10 --min-replicas 0 \ -s storage-connection="<Storage Connection>" \ -v FUNCTIONS_WORKER_RUNTIME=dotnet,AzureWebJobsStorage=secretref:storage-connection
queuescaler.json のフォーマットが間違っているのか、Azure CLI のバグなのか、フィードバックしています。
[{ "name": "queue-scaling-rule", "type": "azureQueue", "auth": [ { "secretRef": "storage-connection", "triggerParameter": "connection" } ], "queueLength": 3, "queueName": "myqueue-items" }]