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
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
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"
}]