ASP.NET Core Web API + React で作成したホロジュール Web アプリを Azure App Service へデプロイした

## はじめに

前回作成した、ホロライブの配信スケジュールを表示する Web アプリを Azure App Service へデプロイしてみました。
思っていた以上に簡単にデプロイできたのでまとめておきます。

今回のプログラムは、Visual Studio で「React.js での ASP.Net Core」テンプレートを利用して作成し、フロントエンドを React、バックエンドを ASP.NET Core Web API という構成にしています。
バックエンドとフロントエンドのプロジェクトをあわせてホストすることになります。

## Azure App Service とは

いわゆる PaaS に該当し、OS やミドルウェア（Webサーバーやアプリケーションサーバーなど）のインストールや管理をすることなく、ビルド済みのアプリケーションをデプロイして公開できる仕組みです。

[App Service の概要](https://docs.microsoft.com/ja-jp/azure/app-service/overview)

OS やミドルウェアの管理を Azure 側に任せ、開発者はそれより上位のランタイムやフレームワークやアプリに関わることになります。
Azure App Service のプランによりますが、自動スケールや高可用性の対応、Windows や Linux のサポート、GitHub、Azure DevOps、任意の Git リポジトリからの自動デプロイも可能です。

## 振り返り

これまで、このような順序で開発を行ってきました。

1. ホロライブの配信スケジュール「ホロジュール」をスクレイピングして MongoDB へ蓄積する Python プログラム
    * [WebページのスクレイピングとYouTube Data APIで動画情報取得（ホロライブの動画配信予定を収集）](https://qiita.com/kerobot/items/6009cb0db643bceaf4e9)
    * [Python3でWebスクレイピングとYouTube Data APIとMongoDB（ホロライブの動画配信予定を収集 その２）](https://qiita.com/kerobot/items/fa728f665d559b969e1c)
    * [Azure VM の Ubuntu 20.04(LTS) で Python3 と MongoDB を利用する（ホロライブの動画配信予定を収集 その２の続き）](https://qiita.com/kerobot/items/41f266dd52988f9bb68e)

2. MongoDB へ蓄積した配信スケジュールを参照する Python Flask Web API
    * [Python + Flask + MongoDB を利用した Web API の作成と Azure VM + Nginx への配置（ホロライブの動画配信予定を収集 その３）](https://qiita.com/kerobot/items/bd504b0d787de63c364e)
    * [Let's Encrypt を利用した SSL 証明書の作成と Nginx on Ubuntu 20 の設定](https://qiita.com/kerobot/items/f4575d9afc1c320f5ef1)
    * [Python の Flask Web API に JWT による認証を組み込む](https://qiita.com/kerobot/items/8ef8d7416742eedcea49)

3. Python Flask Web API を用いて配信スケジュールを表示する Android アプリ
    * [Kotlinの基本的な言語仕様を学習したまとめ](https://qiita.com/kerobot/items/ac8fa69955f9d3414f81)
    * [ホロジュール Android アプリを Kotlin で作る（ホロライブの動画配信予定を収集 その４ やっと完成）](https://qiita.com/kerobot/items/241a4f86cb360977a8fe)

4. Python Flask Web API を用いて配信スケジュールを表示する ASP.NET Core Web API + React Web アプリ
    * [Windows 10 + Node.js + VSCode で React の開発環境を準備する](https://qiita.com/kerobot/items/febe52f8f8f126a763b9)
    * [React と ASP.NET Core (C#) でホロジュール Web アプリをつくってみた](https://qiita.com/kerobot/items/fda428b9b8e13ef4e8a9)

今回は 4 で作成した、ASP.NET Core Web API + React Web アプリを、Azure App Service へデプロイしてみます。

## 開発環境

* フロントエンド React
  * node v14.18.2 / npm 6.14.15 (nvm)
  * React 16.12.0
  * ECMAScript 5
* バックエンド ASP.NET Core Web API
  * .NET 5 (Visual Studio 2022 v17.0.3)
  * ASP.NET Core 5.0
  * C# 9

## プログラムの変更

Azure App Service へデプロイするため、プログラムを少し修正しました。

1. シークレット情報の取得方法

    バックエンドの ASP.NET Core Web API で利用するシークレット情報を UserSecrets ではなく環境変数から取得するように変更しました。

    [Azure Key Vault について](https://docs.microsoft.com/ja-jp/azure/key-vault/general/overview)

    Azure App Service へデプロイするにあたり、シークレット情報を Azure Key Vault で管理し、アプリケーション設定から参照するためです。

    ```csharp
    public void ConfigureServices(IServiceCollection services)
    {
        var userName = Environment.GetEnvironmentVariable("API_USERNAME");
        var password = Environment.GetEnvironmentVariable("API_PASSWORD");
        var baseUrl = Environment.GetEnvironmentVariable("API_URL");
        services.AddTransient<IDataService>(_ => new DataService(userName, password, baseUrl));
        services.AddControllersWithViews();
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/build";
        });
    }
    ```

1. Web API のルーティングの指定

    他のルーティングとの競合を避けるため、ルーティングパターンを変更しました。

    [ASP.NET Web API 2 での属性ルーティング](https://docs.microsoft.com/ja-jp/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2)

    規則ルーティング（Conventional routing）から、Web API 2 の属性ルーティング （Attribute routing）へ変更しています。

    ```csharp
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
        app.UseSpaStaticFiles();
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
        ...
    }
    ```

    ```csharp
    [Route("api/[controller]")]
    [ApiController]
    public class AccessTokenController : ControllerBase
    {
        ...
    }
    ```

## プログラムのデプロイ

ここから、 Azure App Service へのデプロイ手順となります。

Azure Portal にログインしてから作業を行います。（ここまでの手順は割愛）

### リソースグループ

まずは、今回のデプロイのためのリソースグループを作成します。
![01_リソースグループ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/d7290b0d-d214-2ad0-1c9e-ee6c1adbef5d.jpeg)

サブスクリプションとリージョン（地域）を選択し、リソースグループ名を入力して、リソースグループを作成します。
![02_リソースグループ作成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/0f64e199-d3cd-a6b6-9299-7a95e575c26a.jpeg)

### App Service プラン

つぎに、App Service プランを作成します。
![03_AppServiceプラン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/e6ac247f-69b6-f9dc-e878-6b4b482752cf.jpeg)

今回は、F1プラン＝無料プランを選択しました。

F1プランは無料ですが、RAM 1GB、ストレージ 1GB、共有環境 (60 CPU 分/日) などの制限があります。
![04_AppServiceプラン_スペック選択.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/edc68715-01ef-54b8-b6c6-fba25f110ed3.jpeg)

サブスクリプションと作成したリソースグループを選択し、プランの名前とリージョン（地域）とサイズを指定します。
![05_AppServiceプラン作成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/b048c0ef-b8c7-07ae-b73b-0cc3262b68f6.jpeg)

### キーコンテナ（KeyVault）

キーコンテナを作成してシークレットを管理します。

ここだけ Azure Cloud Shell を利用してコマンドを実行しました。

キーコンテナを作成します。

```powershell
> az keyvault create --name "holodulekv" --resource-group "holoresource" --location "Japan East"
```

シークレットを登録します。

```powershell
> az keyvault secret set --vault-name "holodulekv" --name "API-USERNAME" --value "********"
> az keyvault secret set --vault-name "holodulekv" --name "API-PASSWORD" --value "********"
> az keyvault secret set --vault-name "holodulekv" --name "API-ENDPOINT" --value "********"
```

登録したシークレットはこんな感じで確認します。

```powershell
> az keyvault secret show --vault-name "holodulekv" --name "API-USERNAME"
> az keyvault secret show --vault-name "holodulekv" --name "API-PASSWORD"
> az keyvault secret show --vault-name "holodulekv" --name "API-ENDPOINT"
```

このように登録されます。
![06_KeyVaultコンテナ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/8ecb3730-533e-551f-df46-04c26bb70b30.jpeg)

### デプロイ

Visual Studio から Azure App Service へのデプロイを行います。

デプロイするプロジェクトの発行をクリックし、ターゲットとして Azure を選択します。
![07_web.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/ac2eb92e-2ab1-0f0f-fd0d-594132bad74c.jpeg)

特定のターゲットとして Azure App Service (Windows) を選択します。
![08_web.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/c2cc8b91-6179-356d-b0fe-abbfa8bab392.jpeg)

名前を指定し、サブスクリプションとリソースグループとホスティングプランを選択して、 Azure App Service インスタンスを新規作成します。
![09_appservice作成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/fb8c9a4b-f835-73f8-2054-365be5855745.jpeg)

作成した Azure App Service インスタンスを選択して完了をクリックします。
![10_web.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/47f378c0-e2bc-c09b-1272-ca895aa82735.jpeg)

デプロイの準備が完了しました。
![11_発行.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/d6ad95f7-95cc-b118-cf26-25444998a485.jpeg)

発行をクリックしてデプロイを行います。

ブラウザが自動で表示されますが、この時点ではまだ Webアプリを利用することができません。
引き続き、環境変数とシークレットを接続するための、KeyVault へのアクセスポリシーやアプリケーション設定を行います。

デプロイが完了した時点で、リソースグループは下記のようになります。
![12_リソースグループ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/deb41d8d-c103-1ed3-5ed3-6f1dcfca4b77.jpeg)

### アクセスポリシー（KeyVault）

キーコンテナのアクセスポリシーを開きます。
![13_アクセスポリシー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/509d6997-4681-8b08-e1af-b63bbb47a69f.jpeg)

Web アプリに対して、シークレットの取得を許可します。
![14_アクセスポリシーの追加.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/c2c0193b-a958-9186-418a-4a5785689a2b.jpeg)

このようになります。
![15_アクセスポリシー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/9ad8f14d-10d2-0b40-0065-c18aa9a4a67e.jpeg)

### シークレット識別子（KeyVault）

シークレットとアプリケーションを接続するため、シークレットの識別子をメモしておきます。

登録されているシークレットを選択します。
![16_シークレット.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/d9d7d103-9cde-fbd2-2b03-2bff45b05486.jpeg)

現在のバージョンのシークレットを選択します。
![17_シークレット.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/04d75aa7-2184-c454-471a-05a93b4357c1.jpeg)

アプリケーション設定で利用するため、シークレット識別子をメモしておきます。
![18_シークレット.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/b888e72e-a4a8-c07d-d744-9444b95e5df6.jpeg)

### アプリケーション設定

Web アプリの構成でアプリケーション設定を追加してシークレットと紐づけます。
![19_構成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/89974a83-4450-38e7-f4b3-f9616d71290b.jpeg)

名前をプログラムから参照する環境変数名、値にKeyVault参照を設定します。
KeyVault参照は以下のいずれかのパターンで設定します。

```text
@Microsoft.KeyVault(SecretUri={シークレット識別子})
@Microsoft.KeyVault(VaultName={KeyVault名};SecretName={シークレット名};SecretVersion={シークレットバージョン})
```

![20_アプリケーション設定追加.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/dc79138c-be29-202a-1900-46d230e3baa9.jpeg)

アプリケーション設定の保存後、下記のように「キーコンテナの参照」が成功していればOKです。
![21_構成保存.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/38dabf07-01f1-4353-2ed7-3a75706d90e6.jpeg)

ここまででデプロイは完了です。OSやミドルウェアのインストールが不要であることが実感できると思います。

## 動作確認

Azure Web Service へデプロイした Web アプリにアクセスできました。
![22_動作確認.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/5987228b-c91e-b9c3-b2c3-0fe99dead4cc.jpeg)

## おわりに

プログラムのアプリケーション設定とKeyVaultのシークレットを接続するのにハマってしまい、実現するまでにかなり遠回りしましたが、Azure App Service へのデプロイについては、拍子抜けするほど簡単に行うことができました。
今回はフリープランを利用していますので、フリープラン以外で利用できる機能などの確認や、これまでに Python で作成したアプリのデプロイなども試してみようと思います。