Azure Developer CLIを用いてWeb App for Containersへデプロイする

# はじめに

　業務で[App Service - Web App for Containers](https://azure.microsoft.com/ja-jp/products/app-service/containers)を利用し、Webアプリケーションの構築を行っています。その開発環境を構築するために、[Azure Developer CLI](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/overview)(以降はazdとする)を試しました。
　本記事では、azdとazdでWeb App for Containersを実現するための問題点とその対応について記載します。

# Azure Developer CLI

　クラウド上にサービスを展開する際の開発フローでは、devcontainer等でローカルに構築した開発環境での動作確認後、クラウド上に予め構築した各メンバーで共有の開発環境にデプロイし、動作検証を行うことが多いと思います。そのため、クラウド上での動作検証でエラーを出すと、他メンバーの作業の遅延を齎す可能性があります。また、クラウドサービスの細かく検証するために自分で検証用のクラウド環境を1から構築する必要があり大変労力を使います。

　これらの問題を解決する1つのツールとして、Azureで[2023年5月にGA](https://devblogs.microsoft.com/azure-sdk/azd-ga-announcement/)された[azd](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/overview)に注目しました。azdは開発効率化のためのツールです。azdを使用することで、個人専用のアプリケーションがデプロイされたAzure環境を1コマンドで構築することができます。専用のため、自由に作って試して壊せる環境です。その環境で動作確認してからチーム共有のクラウド環境にデプロイするため、クラウド上でのエラーが少なくなることが考えられます。また、クラウドサービス検証のための構築作業を大幅に軽減し気軽に実施することができます。

### [導入フロー](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/overview#typical-azd-workflow)

　azdでは導入を簡略化するために、いくつか[テンプレート](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/azd-templates?tabs=csharp#choose-a-template)が用意されています。
1から導入するのは非常に労力が掛かるため、基本的にテンプレートからパクることから始まります。例えば、[C# API と Azure 上のSQL Databaseを使用した静的React Web アプリ + 関数](https://github.com/Azure-Samples/todo-csharp-sql-swa-func)を作成したい場合、以下の少ないコマンド数で構築が完了します。
```bash
# テンプレートの構成でプロジェクトを初期化
$ azd init --template Azure-Samples/todo-csharp-sql-swa-func

# Azureリソースとアプリケーションコードをデプロイ
$ azd up
```
`azd init --template`を実行することで自身のプロジェクトにazdに必要な要素を構築するため、ディレクトリ構成等で参考になります。

他のテンプレート一覧: https://github.com/topics/azd-templates

### 各種コマンド

　azdで環境構築する方は、基本的に以下の4つのコマンドさえ覚えておけば十分です。
```bash
$ azd up # アプリケーションとインラフを両方デプロイ
$ azd deploy # アプリケーションのみデプロイ
$ azd provision # インラフのみデプロイ
$ azd down # azdで作成したリソースを全て削除
```
他にもコマンドはありますが、多く使用するのは上記のコマンドです。`azd up`を実行すると自動的に環境が作成されます。`azd down`を実行するとその環境は綺麗さっぱりなくなります。
非常に簡単なコマンドで環境を自由に作って壊してということが可能になります。

### [azdに必要な構成ファイル](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/overview#typical-azd-workflow)

```
project
|- azure.yaml // azdデプロイ時のワークフローの定義ファイル
|- infra/ // AzureリソースのBicep群
|- .azure // ローカルで扱うazdの設定ファイル
```

#### azure.yaml

　azdデプロイ時の[ワークフローの定義ファイル](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/azd-schema)になります。以下はAppServiceにNode.jsのWebアプリケーションをデプロイする定義ファイルです
```yaml
name: yourApp
metadata:
  template: yourApp@0.0.1-beta
services:
  web:
    project: ./src/web # package.jsonが存在するWebプロジェクトのディレクトリを指定
    dist: build # buildによって成果物が格納されるディレクトリ
    language: js # アプリケーションコードの言語
    host: appservice # デプロイ先のAzureサービス
```
services.webというプロパティは以下のようなAppServiceに付与したazd専用の特殊なタグ(`tags: { 'azd-service-name': 'web' }`)をつけることでデプロイ先のAzureリソースを認識させています。

```bicep
resource appService 'Microsoft.Web/sites@2022-03-01' = {
  name: name
  location: location
  tags: { 'azd-service-name': 'web' }
  ...
}
```

他にもオプションは存在し、特定の`host`では`docker`プロパティを指定することでDockerイメージの構築を実施することもできます。

#### infra/　

　この階層にはBicepで書かれた沢山のIaCコードを格納していきます。azdを導入する上で一番の労力はこのBicepコードをガリガリと書いていく作業です。導入フローでも説明したテンプレートにはBicepのコードが沢山含まれています。プロジェクトによってAzure構成が異なるため、テンプレート丸パクリでは難しいですが、作業コストを軽減するためにできるだけプロジェクトに似た構成のテンプレートからパクることを推奨します。

頑張りましょう！

#### .azure/

　`azd init`を実行すると`.azure`配下に設定ファイルが構築されます。azdコマンドを実行する際は、常に`.azure`配下の設定ファイルが参照されます。`infra/main.bicep`でのBicepのoutputで指定した変数が設定ファイルに記載され、azdコマンドの実行で使用することができます。

### 導入における[制限](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/supported-languages-environments)

　azdは新しいツールなため、まだまだ制限は厳しめです。azdが導入可能かどうかの確認が必要です。

  - アプリケーションコードをデプロイ可能なAzureサービスに[制限](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/supported-languages-environments#supported-azure-compute-services-host)がある
  - デプロイ可能なアプリケーションコードの言語には[制限](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/supported-languages-environments#supported-programming-languages)がある


# azdでWeb App for Containersを構築

　ここまで、Azure Developer CLIについて書いてきましたが、今回やりたいのはazdでWeb App for Containersを構築することです。

## 問題

　[azdの制限](#azdの制限)により、現在AppSerivceではコンテナイメージのデプロイをサポートしていません。同じことをやろうとした方が[issue](https://github.com/Azure/azure-dev/issues/1608)に上げていました。しかしながら、issueのやりとりの通り、Azure Container Apps(ACA)を使う無理やりする方法が提案されていた (むむむ、、こしゃくな、、)

## 対応策

　[azure.yaml](#azureyaml)では、`hooks`というプロパティが存在し、Azureインフラのリソースもしくはアプリケーションのデプロイ前/後のイベントをフックし、[任意のスクリプトを実行](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/azd-extensibility)することができます。

今回、実現するために書いたazure.yamlとスクリプトは以下になります。
```yaml
name: web
services:
  web:
    project: ./src/web # package.jsonが存在するWebプロジェクトのディレクトリを指定
    dist: build # buildによって成果物が格納されるディレクトリ
    language: js # アプリケーションコードの言語
    host: appservice # デプロイ先のAzureサービス
hooks:
  postdeploy: # アプリケーションのデプロイ後にスクリプトを実行
    shell: sh # sh or pwshを指定可能
    run: | # 複数行の記載が可能
      echo "Deploy Web App"
      ./deploy-webapp.sh 
```
azure.yamlでは、アプリケーションコードのデプロイ後のイベントをフックし、AppServiceにデプロイするスクリプトを実行しています。
```bash
#!/bin/bash

echo "========================================"
echo "login ACR"
echo "========================================"
ACR_USER=`az acr credential show --name ${ACR_NAME} --resource-group ${AZURE_RESOURCE_GROUP} --query username -o tsv`
ACR_PASS=`az acr credential show --name ${ACR_NAME} --resource-group ${AZURE_RESOURCE_GROUP} --query passwords[0].value -o tsv`
az acr login -n ${ACR_NAME} --username ${ACR_USER} --password ${ACR_PASS}

echo "========================================"
echo "webapp build and push"
echo "========================================"
cd ./src/web
docker build -t ${ACR_NAME}.azurecr.io/${ACR_REPOSITORY_NAME}:latest .
docker push ${ACR_NAME}.azurecr.io/${ACR_REPOSITORY_NAME}:latest

# webappにデプロイ(latestを常に取得するようにしているのでAppServiceを再起動)
echo "========================================"
echo "Restart WebApp"
echo "========================================"
az webapp restart -g ${AZURE_RESOURCE_GROUP} -n ${APPSERVICE_WEBAPP_NAME}
```
deploy-webapp.shの中身はシンプルです。
1. Azure Container Registry(ACR)にログイン
2. Dockerイメージのビルド/プッシュ
3. latestを常に取得するようにしているためAppServiceを再起動

　スクリプト内の環境変数やACR、AppServiceはinfra/配下にBicepコードをガリガリ書く必要があります。dockerファイルも予め準備する必要があります。今回提示したazure.yamlやスクリプトは単なる一例なので参考程度にしてください。

# まとめ

　開発環境として、気軽に作って試して壊せる自分しかイジらない専用のAzure環境を構築するために、[Azure Developer CLI](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/overview)(以降はazdとする)の説明と、azdで[App Service - Web App for Containers](https://azure.microsoft.com/ja-jp/products/app-service/containers)を構築する上での問題とその対応策について記載しました

azdは以下の旨味があります。
- 開発/本番環境で運用しているクラウド環境を1コマンドで気軽に作って試して壊せる自分用のAzure環境の構築が可能
- 事前に個人専用のクラウド環境での動作確認が可能なため、チーム共有の開発環境へのデプロイ時にクラウド特有のエラーが少なくなる
- クラウドサービス検証のための構築作業を大幅に軽減し、気軽に検証を進めることができる
- オンボーディング作業が軽減されます

　azdはWeb App for Containersへのデプロイをサポートしていないため、イベントフックと独自のカスタムスクリプトをワークフローに仕込むことで対応しました。もしazdがネイティブにサポートされていなくても、このイベントフックを使いこなすことで大抵の環境は対応できると思います。

　AppServiceの上にコンテナを載せるというアプローチは多くの方がとっているはずですが、それがサポートされていないとなると、まだまだ機能が潤沢ではない印象です。
[2023年5月にGA](https://devblogs.microsoft.com/azure-sdk/azd-ga-announcement/)にリリースされたため、言語やAzureサービスの制約が厳しいですが、これからサポートされることを願います。

　最近、Azure Developer CLIはAzure Deployment Environmentsにサポートしたようで、今後はそれを触っていこうと思っています。