【（技術）力の試練】Github Copilot と一緒に Azure App Service をデプロイしてみよう

筆者は問い合わせ対応やトラブルシューティングなどの技術支援、エンジニアのトレーニングなどの対応のため検証用の Azure 環境を用意しています。
せっかく準備した Azure 環境なのですが、最近は問い合わせの対応もすくなくちょっと持て余し気味でもったいないな...
そう考えていたところ

:bust_in_silhouette:
**神の名におき 試練を与えよう...**
**ミドルウェアの動作環境を構築し、プラットフォーム技術者としての技量を示すのだ...**

という Azure 環境を活用するのにもってこいな天からのお告げがありました。
しかも試練を克服すると賃金アップが期待できるとのこと！
がぜんやる気が出てきました、ぜひやっていきましょう:money_mouth:

## 方針について

せっかくなので過去案件で使っていた Github Copilot 君と会話しながら進めていきます。

ゼロベースでポチポチリソースを作っていくのは時間がかかることに加えて再現が難しくなるため、Azure Developer CLI (azd) で Bicep ファイルに定義したインフラをデプロイする Infrastructure as Code（IaC）的な手法での構築を目指してみようと思います。

今回は App Service on Linux 上に Django ベースの Web アプリケーションを起動して PostgreSQL サーバー上にアプリの情報を格納する、という構成としました。
上記以外にも Microsoft からいろいろなテンプレートが提供されていますが、これまでの経験からある程度勝手のわかる言語や構成のものを選んでみました。

目指すのはこんな環境です。

![Qiita用図.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/5397eb6c-ffbc-4569-83d8-258102272d2b.png)

https://learn.microsoft.com/ja-jp/azure/app-service/tutorial-python-postgresql-app-django?tabs=copilot&pivots=azure-developer-cli

## デプロイの準備

最終的にデプロイまでにはクライアント側に下記の設定が必要となりました。
（諸々試行錯誤した結果なので、取りこぼしがあるかもしれません）
特に、コンテナイメージの取得やデプロイに Docker を利用しているようなので、Docker Desktop のインストールをした上で起動しておく必要がある点には注意が必要です。
* Git for Windows
* PowerShell
* Azure Developer CLI
* Docker Desktop

https://learn.microsoft.com/ja-jp/devops/develop/git/install-and-set-up-git#install-git-for-windows

https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#install-powershell-using-winget-recommended

https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-windows

https://learn.microsoft.com/ja-jp/training/paths/azure-developer-cli/

https://learn.microsoft.com/ja-jp/virtualization/windowscontainers/quick-start/set-up-environment?tabs=dockerce#install-the-container-runtime

参考情報としてバージョン情報の抜粋を載せておきます。
実施時点での最新版でもない中途半端な感じですが、おそらく最新のバージョンにしてもそこまで以降の手順に影響はないかと思います。

```
azd version
azd version 1.14.0 (commit c928795c47f27d1e997c217147dc649054ac05c8)

git version
git version 2.46.0.windows.1

$PSVersionTable.PSVersion.ToString()
7.4.7

docker info --format '{{.ServerVersion}} {{.OperatingSystem}}'
27.0.3 Docker Desktop
```

## Copilot くんと構築開始

構成ファイルを取得するため、PowerShell か git bash 上で `git clone` していきます。

```
git clone https://github.com/Azure-Samples/msdocs-django-postgresql-sample-app.git 
```

今回はインフラ関係のコードやデータベースへの接続設定を更新する必要がある `starter-no-infra` ブランチに切り替え、Github Copilot が修正箇所を適切に指摘できるか実験してみましょう。

```bash
$ git switch starter-no-infra
branch 'starter-no-infra' set up to track 'origin/starter-no-infra'.
Switched to a new branch 'starter-no-infra'
```

ちなみに変える必要のある個所としてはこの部分です。
この部分をコメントアウトする必要があるというわけなんですが、気づいてくれるでしょうか?
大丈夫かな :unamused:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/20dc3693-98b0-421e-8758-21ae0feae5c0.png)

Vscode で `git clone` したフォルダをひらき、`@workspace` でワークスペース全体を対象として質問をしてみると、ちゃんと更新が必要な箇所の指摘をしてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/450514d0-c535-4539-b151-197e52f43705.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/d010ad58-1265-42dd-97cd-fdecc8316c5f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/15450c2b-74d3-492c-8d5a-adb5b0de9456.png)

実は該当箇所のコメントアウトを外すだけで動作はするのですが、改善案まできっちり出してくるあたりデキますね :blush: 
では該当箇所のコメントアウトを外して、アプリケーションコードのデプロイを行っていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/62da5b2b-a769-4791-88b9-1a084555f627.png)

`azd init` コマンドを実行し、インフラの定義が含まれる `infra` フォルダーや `azure.yaml` を生成します。
`--init` 以降のテンプレート名はこのアプリケーションで使用するテンプレート名を指定していきます。

```
$ azd init --template python-app-service-postgresql-infra
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/ffc7243f-d307-419a-9ae3-c9e7a1a91e3a.png)

重要なのは `infra` フォルダ内に生成される `main.bicep` と `resource.bicep` です。

`main.bicep` は、重要な変数、パラメーター、名前付け規則などが含まれる最初の読み込みファイルです。
今回生成されたファイルでは `infra` フォルダ内の `resource.bicep` を呼び出しており、作成するリソース名などの具体的な設定内容は `resources.bicep` を参照することになっています。（下記）

```:main.bicep
module resources 'resources.bicep' = {
  name: 'resources'
  scope: resourceGroup
  params: {
    name: name
    location: location
    resourceToken: resourceToken
    tags: tags
    databasePassword: databasePassword
    principalId: principalId
    secretKey: secretKey
  }
}
```

`resources.bicep` 各リソースの定義部分、まずは Azure Apps Services のリソースの定義部分です。
App Service Plan の定義部分にこの App Service を Linux ベースで作成すること、
App Service 側の定義に使用する言語指定 `linuxFxVersion` などが書かれていることがわかります。

```:resources.bicep(Azure Apps Services 部分抜粋)
// The App Service plan is configured to the B1 pricing tier
resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = {
  name: '${appName}-plan'
  location: location
  kind: 'linux'
  properties: {
    reserved: true
  }
  sku: {
    name: 'B1'
  }
}

resource web 'Microsoft.Web/sites@2024-04-01' = {
  name: appName
  location: location
  tags: union(tags, { 'azd-service-name': 'web' }) // Needed by AZD
  properties: {
    siteConfig: {
      linuxFxVersion: 'PYTHON|3.12' // Set to Python 3.12
      ftpsState: 'Disabled'
      minTlsVersion: '1.2'
    }
    serverFarmId: appServicePlan.id
    httpsOnly: true
  }
  identity: {
    type: 'SystemAssigned'
  }
}
```

他のリソース（PostgreSQL、Redis Cache）の定義部分についても、同様に `resources.bicep` に定義が書かれています。

`azd init` を実行すると環境名の入力プロンプトが表示され、Enter キーで次へ進むと前述のファイルが生成されていきます。

以上の手順で作成された `infra` フォルダーにあるコードとしてのインフラストラクチャ ファイルを使用して、Azure にリソースを作成していきます。
途中で作成するサブスクリプション、リージョンなどを選択しておきます。

```
$ azd provision
```

本来はこれで必要なリソースが作成されるはずなのですが、あれれ?

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/ff50bd4d-fd11-43cb-805e-093ed7dd098c.png)

なんか怒られてますね。
ここで画面左下のキラキラ:sparkles:を押してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/deeb296d-db1b-4c6e-b455-eadb437c68de.png)

どうやらリソースの名称が長すぎるようですよ、とエラーを読み解いてくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/716e2051-ff64-484c-9dc3-a7c997ee03be.png)

言うとおりに修正して再実行してみたところ、再度問題発生。
どうやら Bicep ファイル内部で定義している Key Vault の名称が直前に作成したものと同じものになっていたため、以下の制約に引っかかってしまった模様。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/42930590-33fb-45e7-adf8-ac1725e27d4c.png)

https://learn.microsoft.com/ja-jp/azure/key-vault/general/soft-delete-overview#soft-delete-behavior

>アイテム保持ポリシーの期間はキー コンテナーの作成時にのみ構成でき、後で変更できません。 7 日から 90 日までの範囲で設定できますが、既定は 90 日です。 論理的な削除と消去保護の保持ポリシーの両方に同じ期間が適用されます。
>保有期間が経過するまで、論理的に削除されたキー コンテナーの名前を再利用することはできません。

ちょっとここで対処方法を検討してみました :thinking:
調査した結果キーコンテナーの完全削除は実際ちょっと難しそうな印象であったため、同名のリソースを作らないような仕組みを考えていく、という方針にかじを切ってみます。
具体的にはAzure Key Vault のリソース名を一意にするため、末尾に時刻の文字列を付与することにしました。

ここまでは人間の仕事。ここからはズボラをかまして Copilot に働いてもらいましょう:wink:
修正箇所を選択の上 `Ctrl+i` で代わりに書いてもらいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/1233e9c7-6ae8-42c4-8894-274c1bf2b716.png)

ということでこんな感じに更新してみました。

```python:main.bicep
// 現在の時刻を "hhmm" フォーマットで取得
@description('Current time in "hhmm" format')
param currentTime string = utcNow('hhmm')

// resourveToken の末尾に現在の時刻を追加
var resourceToken = '${toLower(uniqueString(subscription().id, name, location))}${currentTime}'
```

再実行。今度はうまくリソースが作成できていそうですね。
Cache for Redis のデプロイにかなり時間がかかるので全体で 20 分弱かかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/ab7b9ed2-19b0-42a6-8dc4-4601bfce8266.png)

Azure ポータルなどで、リソースが作成されていることを確認してみましょう。
試しに az コマンドで作成したリソースグループのリソースを一覧にしてみたところ、想定通り `infra` フォルダ内で定義されていたリソースが作成されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/b1f1dad0-91ef-4dc3-9c96-e7f91e029aba.png)

次は `src` フォルダーに定義されているアプリまたはサービスをデプロイします。

```
$ azd deploy
```

ここで事前にインストールした Docker Desktop を起動しておきます。
内部的に docker のコマンドを使用するためです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/de28a3c9-a4d0-4630-b083-469bdd002a52.png)

成功するとこんな感じに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/71402881-6257-442b-8ad9-63e219a7551a.png)

ここまで実施したらあとはアプリの動作状況を確認するだけ。と言いたいところですが
Django のマイグレーションファイル (migrations/) を適用し、データベースに必要なテーブルやカラムを作成する必要があります。

参考: マイグレーション | Django documentation | Django
https://docs.djangoproject.com/ja/5.2/topics/migrations/

この作業は Azure App Services 上でマイグレーション用のコマンドを実行する必要がありますので、azd deploy の際に表示された SSH 接続用の URL にアクセスすればよいのですが...おや?

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/1d7c34a2-3015-4c61-8c0f-aff86ffa9085.png)

ない。URLが無い。
本来は AZD の出力で SSH セッションの URL が表示される想定なのですが。
目的達成を目前にしてよくわからない現象に遭遇してしまいました。
Github Copilot に雑に聞いてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/506ffc3e-b093-4f03-bc65-532a379b150f.png)

どうやら `https://<app-name>.scm.azurewebsites.net/webssh/host` がその URL だよと言われているようです。
さてこれ本当かな、と一応裏を取ってみましょう。
ここで表示されるものは App Service の SSH アクセス用の URL なので、公開情報的には下記に言及がありました。内容見た範囲では Copilot の言っていることは正しそうです。
自力でコードを見て即この説明ができるとはなかなかやりますね。

https://learn.microsoft.com/ja-jp/azure/app-service/configure-linux-open-ssh-session?pivots=container-linux#open-ssh-session-in-browser

適当な Web ブラウザに表示された URL を打ち込み、無事に ssh 接続のプロンプト画面が表示されたら、マイグレーションのコマンドを実行しましょう。

`python manage.py migrate`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/211c99c4-fcf4-443b-9d92-c191ad10400b.png)

では改めて、`azd deploy` 実行時に表示されたエンドポイントへアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/a28eb629-acfb-4bbc-9bb6-274b970d1316.png)

デモアプリ起動を確認できました！！
UI を捜査して適当にデータを格納してみます。うまく動いているようですね。
ひとまず構築成功といってよいでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/32b370b1-258f-4457-9781-a12261c303ce.png)

一通り遊んだら、リソースを削除しておきましょう。削除操作は `azd down --purge` です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3561718/894e6774-8bf7-4e4a-a21b-eeb15319647f.png)

## まとめ

まずは当初予定していた環境を構築することができて安心しました。
今回は Github Copilot のサポートを受けやすいようにという意図もありこのような方法（IaC）での構築にチャレンジしてみましたが、想像以上にやりやすいと感じます。

今回のようにリソースの構成なども含めてコードに定義されている状況の場合は特に、全体の構成の把握やトラブルシューティングを強力にサポートしてくれます。
実はここに書かなかっただけで自力で解決したエラーなどもたくさんあるのですが、直接の解決法を提示されなくても仮説をたてるためにいろいろなヒントをくれるこういったツールはやはり便利。

今後もぜひ活用していきたいものだと思いました。