Azure App Serviceってどんなの？

### 概要

Azure App Serviceとは、WebアプリやAPIサーバなどをホスティングできるPaaSです。

例えば、ローカルで動作するNode.jsアプリや、dockerコンテナで起動するWordPressサイトを、Azure環境上で動くようにすることができます。

Node.jsの場合には、ランタイムとしてNode.jsを選択すれば、package.jsonやアプリケーションコード、その他付帯ファイルをそのままデプロイするだけで動作可能です。dockerコンテナアプリでは、docker-composeファイルやアプリケーションコード、その他付随するファイルをそのままデプロイするだけで動作可能です。

### 他の似たようなサービスとの違いは？

「アプリケーションをデプロイする」という用途に使えるものとして、Azure FunctionsやAzure Container Appsがあります。

それらとの違いについて、動作単位・抽象化レベル・動作制御にフォーカスして整理すると以下のようになります。

|  | 動作単位 | 抽象化レベル | 動作制御 |
| --- | --- | --- | --- |
| Azure Functions | ファンクション | アプリケーション | アプリケーション未満のレイヤは抽象化されているため、コードを動かすミドルウェアなど、動作単位ごとの詳細は指定も確認もできない |
| Azure Container Apps | コンテナ | OS | ミドルウェア未満は抽象化されているため、動作単位ごとの詳細（どのOSのどのプロセスで動いたのか等）は指定も確認もできない |
| Azure App Service | アプリケーション | OS | OS未満は抽象化されているため、動作単位ごとの詳細（どの物理マシンで動作したかなど）は指定も確認もできない |

それを踏まえて、例えば「todoアプリのAPIサーバを立てる」という用途を考えた場合、Azure App Serviceを選択する場合には、以下のような点を確認するべきかと思います

- Azure Functionsで要件を満たせない場合
    - todoの一覧、更新などのAPIをアトミックに更新する必要がある（API毎に更新する運用が許されない）
    - HTTPリクエスト・レスポンスにかかる時間以外にもIPによる疎通確認ができる必要がある
    - 長時間実行するアプリケーションを構成したい
        - Azure Functionsは、短時間のイベント駆動処理に適している
- Azure Container Appsで要件を満たせない場合
    - デプロイしたいアプリがコンテナ形式になっていない

ただし、物理マシン単位で動作制御が求められるようなケースでは、他のコンピューティング系サービスを検討する必要がありますね。

## Azure App Serviceのリソース構造

動作の必要最低限必要なリソースの絞りまとめると次の通り

- App Service
    - Webアプリ・API・モバイルバックエンド、などのインスタンスを表すもの
    - アプリにアクセスするためのURLをはじめ、サービス全体の構成を定義するリソース
    - ただし、実行にあたり必要なコンピューティングリソース設定などは含まれない。これらはApp Service Planが管理する
- App Service プラン
    - CPU、メモリ、ストレージなどのコンピューティングリソースを定義するリソース
    - アプリケーションのスケールアップ（リソースの増加）やスケールダウン（リソースの削減）を可能にする

ちなみに、このようにApp ServiceとApp Serviceプランが別なリソースとして管理される意義をいくつか上げると次のような点が挙げられます

- VMのスペックとアプリの構成を別なサイクルで計画・更新できる
    - 計画されているトラフィック増に対し、App Service プランの変更・追加などで対応できる
- 複数のApp Serviceに対し、同じApp Service Planを割り当てることで、リソースを集中管理し、アプリケーション毎に独立してスケーリングすることができる
    - 例えば、WebサーバとAPIサーバを一つのVMで機能させ、コスパ良く稼働させる場合など

## App Serviceを立ち上げてみる(with Node.js)

### 空のwebアプリを作成するまで

Node.jsで作成したWebアプリを立ち上げてみます。

はじめに、Webアプリリソースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/4d03c317-9b3d-5e84-9ca5-30846a1a96a2.png)


次に、ガイドに沿って最低限の項目を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/fecfc1d2-066b-fe95-67c6-7e323f0e83a3.png)

デプロイが完了したら、リソースに移動します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/7ed4b1cb-5a3a-3b30-aa01-8bbcfdd80a7d.png)

リソース画面でドメインなどの情報が出ていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/a25c1868-4e4a-425e-8783-3b08e23ae1ab.png)

リソース画面で表示されていたドメインにアクセスすると、次のようなダミー画面が表示されることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/ace57ac1-c065-5c73-b44e-e1582bfd5a40.png)

これで空のインスタンスを作成することができました

### ローカルで作成したコードをデプロイする

次に、ローカルで作成したコードを反映していきます。

まずはデプロイするコードを作成します。

```jsx
cd ~
mkdir helloworld
cd helloworld
npm init -y
touch index.js
code .
```

```jsx
const http = require('http');

const server = http.createServer(function(request, response) {
    response.writeHead(200, { "Content-Type": "text/html" });
    response.end("<html><body><h1>Hello World!</h1></body></html>");
});

const port = process.env.PORT || 3000;
server.listen(port);

console.log(`Server running at http://localhost:${port}`);
```

デプロイに必要な情報を取得します。次のコマンドで取得できます。
* az webapp | Microsoft Learn https://learn.microsoft.com/ja-jp/cli/azure/webapp?view=azure-cli-latest#az-webapp-list
* az appservice plan | Microsoft Learn https://learn.microsoft.com/ja-jp/cli/azure/appservice/plan?view=azure-cli-latest#az-appservice-plan-list

(CLIで)参照する場合の例は次のとおりです。

```jsx
az webapp list --resource-group katoakiTest_group --query '[].name'
az webapp list --resource-group katoakiTest_group --query '[].resourceGroup'
az appservice plan list --resource-group katoakiTest_group --query '[].name'
az appservice plan list --resource-group katoakiTest_group --query '[].sku.name'
az appservice plan list --resource-group katoakiTest_group --query '[].location'
```

次に、デプロイに必要な値をセットします。

```jsx
export APPNAME=$(az webapp list --resource-group katoakiTest_group --query '[0].name' | sed 's/"//g')
export APPRG=$(az webapp list --resource-group katoakiTest_group --query '[0].resourceGroup' | sed 's/"//g')
export APPPLAN=$(az appservice plan list --resource-group katoakiTest_group --query '[0].name' | sed 's/"//g')
export APPSKU=$(az appservice plan list --resource-group katoakiTest_group --query '[0].sku.name' | sed 's/"//g')
export APPLOCATION=$(az appservice plan list --resource-group katoakiTest_group --query '[0].location' | sed 's/"//g')
```

※添字は適切な値をセットしてください

デプロイコマンドを実行します。

```jsx
az webapp up --name $APPNAME --resource-group $APPRG --plan $APPPLAN --sku $APPSKU --location "$APPLOCATION"
```

コマンドが成功したら、もう一度サイトへアクセスしてみると、デプロイが反映されたことが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146594/c3e196dd-9c33-8ec2-bc7e-6cea283a628a.png)

## おわりに

App Serviceを利用すると、WebサイトやWeb APIを簡単にスモールスタートで立ち上げられて便利。
既存のサイトからマイグレーションする場合にも、そのままコードを持ってこれるので楽です。
ただ、ファンクションやコンテナレベルで管理したい場合には別のサービスがありますので、サービス毎の適切な動作単位に切り出してうまく使うことが重要ですね。