BicepでAzure Container RegisterとAzure Container Appsを作ってAzure Functions をのせてみたメモ

## 概要

主に下記を参考にして実装を行った。

[Azure Container Apps で最初のコンテナ化された関数を作成する](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-deploy-container-apps?tabs=acr%2Cbash&pivots=programming-language-typescript)
[Bicep解説動画](https://www.youtube.com/watch?v=6dDJpkLiIFU)
[Azure Container Appsへbicep,Azure Container Registry,GitHub Actionsを使ってデプロイ](https://itc-engineering-blog.netlify.app/blogs/acr-aca-bicep)

## 環境

ユーザはサブスクリプションの管理者として行う。

lib|version
--|--
azure-cli|2.61.0
Bicep CLI |0.28.1 (ba1e9f8c1e)

## ハンズオン
### デプロイ対象のAzure Functionsを作成
お試し用の環境作成

```
func init --worker-runtime node --language typescript --docker
```

<details><div>

```docker:Dockerfile
FROM mcr.microsoft.com/azure-functions/node:4-node18

ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

COPY . /home/site/wwwroot

RUN cd /home/site/wwwroot && \
    npm install && \
    npm run build
```

</div></details>

Helloを返すAPIを作るAzure Functionsのコード作成。

```
func new --name HttpExample --template "HTTP trigger"
```

<details><div>

```ts:HttpExample
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";

export async function HttpExample(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
    context.log(`Http function processed request for url "${request.url}"`);

    const name = request.query.get('name') || await request.text() || 'world';
    const test = process.env.TEST_ENV || 'default';

    return { body: `Hello v1.0.0, ${name}! ${test}` };
};

app.http('HttpExample', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: HttpExample
});
```

</div></details>

### ACRを作成

```bicep:acr.bicep
@minLength(5)
@maxLength(50)
param acrName string = 'acr${uniqueString(resourceGroup().id)}'
param location string = resourceGroup().location
param acrSku string = 'Basic'

resource acrResource 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = {
  name: acrName
  location: location
  sku: {
    name: acrSku
  }
  properties: {
    adminUserEnabled: false
  }
}

output loginServer string = acrResource.properties.loginServer
```

```resourceGroup.bicep
targetScope='subscription'

param resourceGroupName string
param resourceGroupLocation string

resource newRG 'Microsoft.Resources/resourceGroups@2024-03-01' = {
  name: resourceGroupName
  location: resourceGroupLocation
}
```

```acrCreate.sh
#!/bin/bash

BIN_DIR=$(cd $(dirname $0) && pwd)
BICEP_DIR=$(cd $BIN_DIR/../biceps && pwd)
RESOURCE_GROUP_NAME='bicep-test-rg'
LOCATION=japaneast

cd $BICEP_DIR && az deployment sub create \
  --name careteTestContainerResourceGroup \
  --location $LOCATION \
  --template-file resourceGroup.bicep \
  --parameters resourceGroupName=$RESOURCE_GROUP_NAME resourceGroupLocation=$LOCATION


cd $BICEP_DIR && az deployment group create \
  --name careteTestACR \
  --template-file acr.bicep \
  -g $RESOURCE_GROUP_NAME
```

`acrhogehoge.azurecr.io`が作成されたとする。

### Dockerのイメージを作成

ここでは[公式手順](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-deploy-container-apps?tabs=acr%2Cbash&pivots=programming-language-typescript)の`<DOCKER_ID>`と`<LOGIN_SERVER>`に同じ値を入れるものとする。

`<LOGIN_SERVER>` = `acrhogehoge.azurecr.io`とする。
イメージ名は`azurefunctionshogeimage`
タグ名は`v1.0.0`とする。
ビルド対象はカレントディレクトリ`.`とする。

```
docker build --tag acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0 .
```

ビルドが成功したら動作確認を行う。

```
 docker run -p 8080:80 -it acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0
```

http://localhost:8080/api/HttpExample

Helloが返ってくればOK。

### ACRにImageをPush
まずはACRにログイン
```
az acr login --name acrhogehoge
```

タグを作成。`<DOCKER_ID>`と`<LOGIN_SERVER>`に同じ値を入れたので一見冗長に見える。

```
docker tag acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0 acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0
```

```
docker push acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0
```
### サービスプリンシパルの作成
作成したACRのPULL権限を持ったサービスプリンシパルを作成する。([参考](https://learn.microsoft.com/ja-jp/azure/container-registry/container-registry-auth-service-principal))
これは、これから作成するContainerAppsではACRからイメージをPULLする必要があるからである。

サービスプリンシパルの意味があいまいだったので[もう多分怖くないサービスプリンシパル](https://tech-lab.sios.jp/archives/23371)を確認した。

この設定を怠って下記のエラーでBicepがしばらく作れなかった。

```
{"status":"Failed","error":{"code":"DeploymentFailed","target":"/subscriptions/0000-0000-0000-9e21-hogehoge/resourceGroups/bicep-test-rg/providers/Microsoft.Resources/deployments/hogehoge","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-deployment-operations for usage details.","details":[{"code":"InvalidParameterValueInContainerTemplate","message":"The following field(s) are either invalid or missing. Field 'template.containers.test-container-app-1.image' is invalid with details: 'Invalid value: "acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0": GET https:?scope=repository%3Aazurefunctionsimage%3Apull&service=acrhogehoge.azurecr.io: UNAUTHORIZED: authentication required, visit https://aka.ms/acr/authorization for more information.';."}]}
```

```sh:createSP.sh
#!/bin/bash

ACR_NAME="acrhogehoge" 
ACR_ID=$(az acr show --name $ACR_NAME --query id --output tsv)
SERVICE_PRINCIPAL_NAME=$ACR_NAME-pull

PASSWORD=$(az ad sp create-for-rbac --name $SERVICE_PRINCIPAL_NAME --scopes $ACR_ID --role acrpull --query "password" --output tsv)
USER_NAME=$(az ad sp list --display-name $SERVICE_PRINCIPAL_NAME --query "[].appId" --output tsv)

echo "Service principal ID: $USER_NAME"
echo "Service principal password: $PASSWORD"
```

下記が表示されたとして以降は進めていく。

```
Service principal ID: hogeSPID
Service principal password: hogeSPPASS
```


### App Container Appsの作成
ログアナリティクスと[コンテナ環境](https://learn.microsoft.com/ja-jp/azure/container-apps/environment)とコンテナアプリを作成。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/413c646a-bd47-9f06-4fb9-9da397599427.png)

[visualizer](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/visual-studio-code?tabs=CLI#open-bicep-visualizer)

```bicep:containerApps.bicep
param location string = resourceGroup().location
param environmentName string
param containerAppName string
param containerName string
param logAnalyticsWorkspaceName string
param acrName string
param spPass string
param spId string
param imgTag string

// Log Analytics ワークスペース
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: logAnalyticsWorkspaceName
  location: location
  properties: {
    sku: {
      name: 'PerGB2018'
    }
    retentionInDays: 30
  }
}


// Container Apps 環境
resource environment 'Microsoft.App/managedEnvironments@2023-11-02-preview' = {
  name: environmentName
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: logAnalytics.properties.customerId
        sharedKey: logAnalytics.listKeys().primarySharedKey
      }
    }
    workloadProfiles: [
      {
        name: 'Consumption'
        workloadProfileType: 'Consumption'
      }
    ]
  }
}

param registryPassName string = 'registry-password'

// コンテナアプリ
resource name_resource 'Microsoft.App/containerapps@2023-11-02-preview' = {
  name: containerName
  location: location
  properties: {
    environmentId: environment.id
    configuration:{
      ingress: {
        external:true
        allowInsecure: false
        targetPort: 80
        traffic:[{
          latestRevision:true
          weight:100
        }]
      }
      secrets:[{
        name: registryPassName
        value: spPass
      }]
      registries: [
        {
          server: '${acrName}.azurecr.io'
          username: spId
          passwordSecretRef: registryPassName
        }
      ]
    }
    template: {
      containers: [
        {
          name: containerAppName
          image: 'acrv623i6lefoy6c.azurecr.io/azurefunctionsimage:${imgTag}'
          resources: {
            cpu:json('0.25')
            memory: '.5Gi'
          }
        }
      ]
      scale: {
        minReplicas: 0
        maxReplicas: 1
      }
    }
    workloadProfileName: 'Consumption'
  }
}
```


Service principalの `hogeSPID`と`hogeSPPASS`はここで使用する。
下記のように実行。

```sh:createContainerApps.bash
#!/bin/bash

BIN_DIR=$(cd $(dirname $0) && pwd)
BICEP_DIR=$(cd $BIN_DIR/../biceps && pwd)
RESOURCE_GROUP_NAME='bicep-test-rg'
LOCATION=japaneast

cd $BICEP_DIR && az deployment group create \
  --name careteTestAppContainer \
  --template-file containerApps.bicep \
    --parameters environmentName=test-container-env-1 containerAppName=test-container-app-1 containerName=test-container-1 logAnalyticsWorkspaceName=test-log-analytics-1  acrName=acrhogehoge spPass=hogeSPPASS spId=hogeSPID imgTag=v1.0.0 \
  -g $RESOURCE_GROUP_NAME
```

デプロイされたことを確認した。

この次のステップは[パイプラインからのデプロイ](https://qiita.com/hibohiboo/items/b7136a2c94a514f03053)

## 更新

 - ACRのイメージをタグ名も何も変えずに更新すると、ContainerAppsを再度デプロイしなくても更新される。

```
docker build --tag acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0 .
docker push acrhogehoge.azurecr.io/azurefunctionshogeimage:v1.0.0
```

 - タグ名を更新したときはbicepもそれに合わせて直してデプロイする必要がある。

上記の手順をおこなった後、`createContainerApps.bash`を再実行

## Log Analyticsの警告が表示されて調べたメモ

[よくある質問](https://jpazmon-integ.github.io/blog/LogAnalytics/HowToMigrateToAmaFromLA/#Q1-Log-Analytics-%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88%E3%81%AF-2024-%E5%B9%B4-8-%E6%9C%88%E3%81%AB%E5%BB%83%E6%AD%A2%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%81%8C%E3%80%81Log-Analytics-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%82%E5%BB%83%E6%AD%A2%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%81%8B%E3%80%82)で回答されているが、廃止させるのは「エージェント」であり、「ワークスペース」は廃止されない。心配は不要であった。


## 参考
[クイック スタート: Bicep ファイルを使用したコンテナー レジストリの作成](https://learn.microsoft.com/ja-jp/azure/container-registry/container-registry-get-started-bicep?tabs=CLI)
[bicep定義](https://learn.microsoft.com/ja-jp/azure/templates/microsoft.containerregistry/registries?pivots=deployment-language-bicep)
[WSL2 に Docker をインストールする](https://qiita.com/hiromasa-masuda/items/4902686b404682376c29) ... 古い。
[WSL2 でビルドした Docker イメージを Azure Container Registry へ push する](https://qiita.com/hiromasa-masuda/items/1c525661588d4dadbeb4)

[bicep - appcontainer](https://learn.microsoft.com/ja-jp/azure/templates/microsoft.app/containerapps?pivots=deployment-language-bicep)
[bicep - loganalytics - Microsoft.OperationalInsights ](https://learn.microsoft.com/ja-jp/azure/templates/microsoft.operationalinsights/allversions)
[Log Analytics sku](https://learn.microsoft.com/ja-jp/azure/automation/update-management/enable-from-template#before-using-the-template)

[Bicep解説動画](https://www.youtube.com/watch?v=6dDJpkLiIFU)
[リソースの視覚化](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/quickstart-create-bicep-use-visual-studio-code?tabs=CLI#visualize-resources)
[もう多分怖くないサービスプリンシパル](https://tech-lab.sios.jp/archives/23371)

[Azure Container Appsへbicep,Azure Container Registry,GitHub Actionsを使ってデプロイ](https://itc-engineering-blog.netlify.app/blogs/acr-aca-bicep)

[Azure Container Apps を少し触ったメモ](https://zenn.dev/microsoft/articles/azure-container-app-lab)