Azure Container Apps (ACA) に Node.js Express カスタムコンテナイメージをデプロイする

# Azure Container Apps (ACA) に Node.js Express カスタムコンテナイメージをデプロイする

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境で、Node.js Express Web アプリケーションをコンテナとして起動する方法を紹介します。
![express_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/8c0f6394-4c69-022a-771e-ecdae958259b.png)

## 目的

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

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

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

## 実現すること

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

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

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Azure|クラウド環境|
|Azure Container Apps|コンテナ実行環境|
|app-hello-express|カスタムコンテナ|
|node.js|Node.js 実行環境|
|server.js|JS スクリプト|
|express|Web サーバー|

:::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
```

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

```sh
$ node -v
v19.8.1
```

```sh
$ npm -v
9.5.1
```

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 を表示する手順 

### Node.js Express Web サービスの作成  

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

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

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

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

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

```sh
$ cd ~/tmp/hello-express
```

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

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

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

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

```
$ docker images | grep app-hello-express
app-hello-express   latest    6c3f5b84bbf4   42 minutes ago   269MB
```

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

Docker Hub にログインします。  

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

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

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

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

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

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

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

### シェル変数の作成

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

```sh
location_name=japaneast
resource_group_name=rg-hello
containerapp_env_name=cae-hello
containerapp_name=ca-hello-express
container_image_name=app-hello-express: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
```

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

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

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

#### コンテナ アプリ

コンテナ アプリの作成とデプロイを行います。    

```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 5000 \
    --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-express.nicehill-fafa9131.japaneast.awsomecontainerapps.io
```

#### Azure Portal の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/cfc856d7-e1d6-10a5-dde8-d3fc526b4675.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/57633ac3-fb5c-e936-85d3-7d508e47c176.png)

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

### コンテナの動作確認

ターミナルから curl コマンドで確認します。  

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

出力(※ time_total は省略)

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

:::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 44K
drwxr-xr-x  1 root root 4.0K Aug 10 01:53 .
drwxr-xr-x  1 root root 4.0K Aug 10 02:59 ..
drwxr-xr-x  2 root root 4.0K Aug 10 01:53 build
drwxr-xr-x 60 root root 4.0K Aug 10 01:53 node_modules
-rw-r--r--  1 root root  22K Aug 10 01:53 package-lock.json
-rw-r--r--  1 root root   53 Aug 10 01:53 package.json
```

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

```
# apt update
# apt install procps
# top
top - 03:02:17 up 14:43,  0 user,  load average: 0.17, 0.25, 0.26
Tasks:   4 total,   1 running,   3 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.2 us,  0.9 sy,  0.0 ni, 95.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15990.8 total,   3803.5 free,   2525.0 used,  10018.4 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13465.8 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0    2328    892    800 S   0.0   0.0   0:00.01 dumb-init
      8 root      20   0  604752  52288  35892 S   0.0   0.3   0:00.13 node
     15 root      20   0    2576    936    844 S   0.0   0.0   0:00.01 sh
    203 root      20   0    8596   4544   2668 R   0.0   0.0   0:00.00 top
```

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

```
top - 03:02:27 up 14:43,  0 user,  load average: 0.15, 0.25, 0.26
Tasks:   4 total,   1 running,   3 sleeping,   0 stopped,   0 zombie
%Cpu0  :  1.0 us,  0.0 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  3.0 us,  0.7 sy,  0.0 ni, 96.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  2.0 us,  0.0 sy,  0.0 ni, 98.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  1.7 us,  1.0 sy,  0.0 ni, 97.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15990.8 total,   3813.5 free,   2513.5 used,  10019.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13477.3 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0    2328    892    800 S   0.0   0.0   0:00.01 dumb-init
      8 root      20   0  604752  52288  35892 S   0.0   0.3   0:00.13 node
     15 root      20   0    2576    936    844 S   0.0   0.0   0:00.01 sh
    203 root      20   0    8596   4544   2668 R   0.0   0.0   0:00.00 top
```

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

```cs
# cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
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 12|
|Node.js|v18.17.0|
|Express|4.18.2|

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

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

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

|No|状況|リクエスト所要時間(秒)|
|:--|:--|:--|
|1|デプロイ直後|0.245390秒|
|2|連続して2回目|0.255941秒|
|3|さらに3回目|0.214685秒|
|4|スケールゼロ回復直後|10.778261秒|
|5|連続して2回目|0.189288秒|
|6|さらに3回目|0.197779秒|

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

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

メトリック：CPU使用率

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

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

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

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

## まとめ

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

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

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

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/74764a835be31c1e73c6

https://qiita.com/studio_meowtoon/items/9d91e53d6ff4e5753b05