Azure Container Apps (ACA) に C# ASP.NET Core Webサービスをデプロイする (Docker Hub カスタムコンテナイメージ)

# Azure Container Apps (ACA) に C# ASP.NET Core Webサービスをデプロイする (Docker Hub カスタムコンテナイメージ)

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境で C# ASP.NET Core Web アプリケーションをコンテナとして起動する方法を紹介します。
![aspnet-core_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/97ba52f3-f3e5-0c54-cf3a-7f7deed5a359.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

Microsoft Azure Container Apps (ACA) に C# ASP.NET Core Web アプリケーションのカスタムコンテナイメージをデプロイします。  

**DLL ファイル形式のアプリをコンテナとして起動**
```mermaid
flowchart LR
    terminal --> kestrel
    subgraph Azure
        subgraph ACA[Azure Container Apps]
            subgraph container[app-hello-aspnet-core]
                subgraph dotnet
                    kestrel <--> app[WebApp.dll]
                end
            end
        end
    end
style Azure fill:#EEFDFF, stroke:#74B9DC
style ACA fill:#5EBCFF, stroke:#0077CC
style container fill:#FFD7E4, stroke:#E13C73
style dotnet fill:#DCFFDC, stroke:#2F872F
style app fill:#ECECFF, stroke:#9674DC
style kestrel fill:#FFFFDE, stroke:#B4AE43
```

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Azure|クラウド環境|
|Azure Container Apps|コンテナ実行環境|
|app-hello-aspnet-core|カスタムコンテナ|
|dotnet|.NET 実行環境|
|kestrel|Web サーバー|
|WebApp.dll|.NET アプリケーション|

:::note alert
この記事では、**Docker Hub** に公開する**パブリックアクセス可能なカスタムコンテナイメージ**を使用しています。実際のシステム開発では、どのレジストリからコンテナイメージを取得するかは異なる場合がありますので、適宜ご確認ください。
:::

## 技術トピック

### Microsoft Azure Container Apps (ACA) とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Microsoft Azure Container Apps (ACA)](https://learn.microsoft.com/ja-jp/azure/container-apps/overview)

:::note info
Azure 上で実行されるコンテナアプリケーションのプラットフォームです。  

ACA は、Docker コンテナと Kubernetes クラスタの環境をサポートしており、開発者はコンテナイメージを作成して、Kubernetes リソースとしてデプロイすることができます。また、カスタムドメイン名をサポートしているため、自社のブランドを維持することができます。  

|メリット|説明|
|:--|:--|
|簡単なセットアップとデプロイ|ACA は、簡単なユーザーインターフェイスを備えており、数回のクリックでコンテナアプリケーションをセットアップしてデプロイすることができます。|
|スケーラビリティ|ACA は、自動的にスケーリングを行うことができます。アプリケーションに必要なリソースが増加した場合、ACA は自動的に必要なリソースを追加して処理を分散します。|
|セキュリティ|ACA は、コンテナアプリケーションを実行する際に必要なセキュリティ機能を提供しています。例えば Azure Active Directory との統合や、コンテナレベルでのアクセス制御などがあります。|
|コスト効率|ACA は、必要なときに必要なリソースを追加するため、無駄なリソースの使用を最小限に抑えることができます。また、必要なリソースに対してのみ支払いを行うため、コスト効率的に利用することができます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

.NET SDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/d68031c91462f6808dff)

```sh
$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

Azure CLI [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/9721041f9e6e3e987cf0)

```sh
$ az --version
azure-cli                         2.45.0
core                              2.45.0
telemetry                          1.0.8
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

## Hello World を表示する手順 

### C# ASP.NET Core Web サービスの作成  

こちらの関連記事で手順がご確認いただけます。

https://qiita.com/studio_meowtoon/items/d469de7f4cfe62aa5641

:::note info
ここまでの手順で、ローカル環境の **Ubuntu** にアプリの**カスタムコンテナイメージ**をビルドすることができました。
:::

### プロジェクトフォルダに移動

プロジェクトフォルダに移動します。  
※ ~/tmp/WebApp をプロジェクトフォルダとします。

```sh
$ cd ~/tmp/WebApp
```

### コンテナイメージの確認

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker      [ OK ]
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナイメージを確認します。

```sh
$ docker images | grep app-hello-aspnet-core
app-hello-aspnet-core    latest    0d756b19ebac    25 minutes ago    212MB
```

### Docker Hub にコンテナイメージを登録  

Docker Hub のアカウントを作成します。  

https://hub.docker.com/signup

Docker Hub にログインします。  

```sh
$ docker login
Login Succeeded
```

コンテナイメージにタグを付けます。

:::note alert
ここで **$USER** という埋め込み変数は、**Ubuntu** のシェル変数 **USER** のことです。つまり、カスタムコンテナイメージを **Docker Hub** にプッシュする際に指定する**アカウント名**と、**Ubuntu** の**ユーザー名**を同じにすると、イメージの取り扱いが簡単になります。ただし、この方法は検証用途に限定されます。
:::

```sh
$ docker tag app-hello-aspnet-core:latest \
    $USER/app-hello-aspnet-core:latest
```

Docker Hub にコンテナイメージをプュシュします。

```sh
$ docker push $USER/app-hello-aspnet-core:latest
```

:::note info
ここまでの作業で、**Docker Hub** に $USER/app-hello-aspnet-core:latest として**カスタムコンテナイメージ**をプッシュすることができました。  
:::

### シェル変数の作成

:::note alert
**シェル変数**として、**Ubuntu** に以下の値を作成します。状況により、コンテナ アプリの名前を変える必要があります。
:::

```sh
location_name=japaneast
resource_group_name=rg-hello
containerapp_env_name=cae-hello
containerapp_name=ca-hello-aspnet-core
container_image_name=app-hello-aspnet-core:latest
```

### Azure 環境にサインイン

こちらの関連記事で手順がご確認いただけます。

https://qiita.com/studio_meowtoon/items/9721041f9e6e3e987cf0

Azure CLI でログインします。  

```sh
$ az login
```

### Azure 環境

#### リソースグループ

リソースグループを作成します。

```sh:Microsoft.Resources/resourceGroups
$ az group create \
    --name $resource_group_name \
    --location $location_name
```

#### コンテナ アプリ環境

拡張機能をインストールします。  
※ 初回のみ必要となります。  

<details>
<summary>手順を表示します。</summary>

##### 拡張機能

拡張機能をインストールします。  

```sh
$ az extension add --name containerapp --upgrade
```

Microsoft.App 名前空間を登録します。  

```sh
$ az provider register --namespace Microsoft.App
```

Microsoft.OperationalInsights プロバイダーを登録します。  

```sh
$ az provider register --namespace Microsoft.OperationalInsights
```

</details>

コンテナ アプリ環境を作成します。    
※ Kubernetes 基盤の環境がベースとなっているので少し時間がかかります。  

```sh:Microsoft.App/managedEnvironments
$ az containerapp env create \
    --resource-group $resource_group_name \
    --name $containerapp_env_name \
    --location $location_name
```

#### コンテナ アプリ

コンテナ アプリの作成とデプロイを行います。    
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh:Microsoft.App/containerApps
$ az containerapp create \
    --resource-group $resource_group_name \
    --environment $containerapp_env_name \
    --name $containerapp_name \
    --image $USER/$container_image_name \
    --target-port 80 \
    --ingress 'external' \
    --min-replicas 0
```

:::note alert
**--min-replicas 0:** と設定することにより、アクセスが300秒間ない場合にアプリケーションがスケールゼロ状態に移行します。
:::

:::note warn
コンテナ アプリを削除する場合は以下のコマンドを実行します。

```sh
$ az containerapp delete \
    --resource-group $resource_group_name \
    --name $containerapp_name
```
:::

コンテナ アプリの FQDN をシェル変数 **containerapp_fqdn** として取得します。

```sh
$ containerapp_fqdn=$(az containerapp show \
    --resource-group $resource_group_name \
    --name $containerapp_name \
    --query 'properties.configuration.ingress.fqdn' \
    --output tsv)
```

コンテナ アプリの FQDN を確認します。

```sh
$ set | grep containerapp_fqdn
containerapp_fqdn=ca-hello-aspnet-core.yellowcliff-d8392fa5.japaneast.awsomecontainerapps.io
```

#### Azure Portal の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/40e3686b-fb64-2f3f-06c3-e38b81262b7c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/75409725-89aa-7ef2-946b-f5b69899fa8a.png)

:::note info
ここまでの作業で、**Azure Container Apps** に**コンテナ アプリ**をデプロイすることができました。
:::

### コンテナ動作の確認
 
ターミナルから curl コマンドで確認します。 

```sh
$ curl https://$containerapp_fqdn/api/data -w '\ntime_total: %{time_total}s\n'
```

出力(※ time_total は省略)

```sh
{"message":"Hello World!"}
```

<details>
<summary>詳細を表示します。</summary>

```
$ curl -v https://$containerapp_fqdn/api/data -w "\n"
*   Trying xx.xx.xx.xx:443...
* Connected to ca-hello-aspnet-core.thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io (xx.xx.xx.xx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=WA; L=Redmond; O=Microsoft Corporation; CN=thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io
*  start date: Mar 24 02:56:09 2023 GMT
*  expire date: Mar 18 02:56:09 2024 GMT
*  subjectAltName: host "ca-hello-aspnet-core.thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io" matched cert's "*.thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io"
*  issuer: C=US; O=Microsoft Corporation; CN=Microsoft Azure TLS Issuing CA 01
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x55b45d69de90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /api/data HTTP/2
> Host: ca-hello-aspnet-core.thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io
> user-agent: curl/7.81.0
> accept: */*
>
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 200
< content-type: application/json
< date: Fri, 24 Mar 2023 23:08:38 GMT
< server: Kestrel
<
* Connection #0 to host ca-hello-aspnet-core.thankfulstone-5dxxxx03.japaneast.azurecontainerapps.io left intact
{"message":"Hello World!"}
```

:::note info 
Azure Container Apps 自体が SSL/TLS 対応および HTTP/2 プロトコルをサポートしています。
:::

</details>

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することが出来ました。 
:::

### コンテナに接続

コンテナ アプリに接続します。

```sh
$ az containerapp exec \
    --resource-group $resource_group_name \
    --name $containerapp_name 
```

コンテナに接続後にディレクトリを確認します。 
※ コンテナから出るときは ctrl + D を押します。  

```
# pwd
/App
# ls -lah
total 240K
drwxr-xr-x 1 root root 4.0K Aug  3 01:10 .
drwxr-xr-x 1 root root 4.0K Aug  3 02:17 ..
-rwxr-xr-x 1 root root 177K Aug  3 01:10 WebApp
-rw-r--r-- 1 root root  388 Aug  3 01:10 WebApp.deps.json
-rw-r--r-- 1 root root 9.0K Aug  3 01:10 WebApp.dll
-rw-r--r-- 1 root root  20K Aug  3 01:10 WebApp.pdb
-rw-r--r-- 1 root root  469 Aug  3 01:10 WebApp.runtimeconfig.json
-rw-r--r-- 1 root root  410 Aug  3 00:16 appsettings.Development.json
-rw-r--r-- 1 root root  434 Aug  3 00:16 appsettings.json
-rw-r--r-- 1 root root  482 Aug  3 01:10 web.config
```

top コマンドで状況を確認します。

```
# apt update
# apt install procps
# top
top - 02:18:38 up  6:03,  0 users,  load average: 0.17, 0.26, 0.26
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.8 us,  0.8 sy,  0.0 ni, 97.3 id,  0.0 wa,  0.0 hi,  0.2 si,  0.0 st
MiB Mem :  15990.8 total,   4957.6 free,   2071.8 used,   8961.4 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13552.9 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 7104376  73784  60572 S   0.3   0.5   0:00.46 dotnet
     21 root      20   0    2480    516    448 S   0.0   0.0   0:00.00 sh
    363 root      20   0    6996   3352   2868 R   0.0   0.0   0:00.00 top
```

top コマンドで cpu を表示した状況です。

```sh
top - 02:18:52 up  6:03,  0 users,  load average: 0.13, 0.25, 0.25
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu0  :  2.0 us,  1.0 sy,  0.0 ni, 96.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  3.3 us,  0.7 sy,  0.0 ni, 95.7 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
%Cpu2  :  2.3 us,  1.0 sy,  0.0 ni, 96.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  1.7 us,  0.7 sy,  0.0 ni, 97.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15990.8 total,   4957.1 free,   2071.8 used,   8961.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13553.0 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 7178120  73816  60576 S   0.0   0.5   0:00.47 dotnet
     21 root      20   0    2480    516    448 S   0.0   0.0   0:00.00 sh
    363 root      20   0    6996   3352   2868 R   0.0   0.0   0:00.00 top
```

コンテナの情報を表示してみます。

```cs
# cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

:::note warn
このコンテナは Debian GNU/Linux をベースに作成されています。つまり、Debian GNU/Linux と同じように扱うことができます。
:::

### パフォーマンスの計測

コンテナ 環境

|項目|バージョン|
|:--|:--|
|Base Image|Debian GNU/Linux 11|
|dotnet|7.0|
|ASP.NET Core|7.0|

コンテナ リソースの割り当て

|項目|設定値|
|:--|:--|
|CPU コアの数|0.5|
|メモリ サイズ(Gi)|1Gi|

リクエスト処理完了までの所要時間

|No|状況|リクエスト所要時間(秒)|
|:--|:--|:--|
|1|デプロイ直後|0.346665秒|
|2|連続して2回目|0.197049秒|
|3|さらに3回目|0.191124秒|
|4|スケールゼロ回復直後|7.104071秒|
|5|連続して2回目|0.202285秒|
|6|さらに3回目|0.196141秒|

ログ ストリーム：起動時間を表示するログ
※該当するログ情報なし

|状況|アプリ起動時間(秒)|
|:--|:--|
|デプロイ直後|---秒|
|スケールゼロ回復直後|---秒|

メトリック：CPU使用率

|状況|CPU使用率|
|:--|:--|
|デプロイ後：ピーク値|0.00707コア|
|スケールゼロ回復後：ピーク値|0.01コア|

メトリック：メモリ使用量

|状況|メモリ使用量|
|:--|:--|
|デプロイ後：ピーク値|28.9MB|
|スケールゼロ回復後：ピーク値|26.6MB|

:::note warn
アプリケーションがスケールゼロ状態から再びアクティブになった後、リクエストを処理するまでにかなり時間がかかる状況がわかります。ただし**スケールゼロ状態**は、--**min-replicas 1:** と設定することにより**回避可能**です。
:::

## まとめ

Azure Container Apps 環境で、Dockerfile からビルドした C# ASP.NET Core Web サービスのカスタムコンテナイメージを起動することができました。

:::note warn
**.NET SDK**、**Docker**、**Azure CLI** を使って、ASP.NET Core アプリの開発からコンテナイメージの作成、Azure 環境へのデプロイまで、**すべてをターミナルから行うことができます**。このように、クラウドでのシステム開発に必要なスキルや理解を深めることができます。初めての人でも簡単に手順を追うことができるので、ぜひ挑戦してみてください。
:::

どうでしたか？ 検証目的として、WSL Ubuntu で、C# ASP.NET Core Web アプリケーションを Azure Container Apps 環境でコンテナとして手軽に起動することができます。ぜひお試しください。今後も Azure の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/dde5ed96239a6442a9fa

## 関連記事

#### Java Spring Boot

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db