Azure Container Apps (ACA) に Spring Boot Webサービスをデプロイする (Docker Hub カスタムコンテナイメージ)

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

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境で Spring Boot Web アプリケーションをコンテナとして起動する方法を紹介します。
![spring-boot_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/c0561297-fcc8-510f-54ae-f1fb10f6bc83.png)

## 目的

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

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

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

## 実現すること

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

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

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Azure|クラウド環境|
|Azure Container Apps|コンテナ実行環境|
|app-hello-spring-boot|カスタムコンテナ|
|JVM|Java 実行環境|
|app.jar|Java アプリケーション|
|tomcat|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
```

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

```sh
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)
```

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

```sh
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.18, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

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

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

</details>

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

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

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

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

## Hello World を表示する手順 

### Spring Boot Web サービスの作成  

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

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

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

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

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

```sh
$ cd ~/tmp/hello-spring-boot
```

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

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

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

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

```sh
$ docker images | grep app-hello-spring-boot
app-hello-spring-boot   latest    39115028afa6   12 seconds ago   390MB
```

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

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

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

https://hub.docker.com/signup

:::note warn
Docker Hub は、コンテナの共有、保存、および管理のためのクラウドベースのレジストリです。Docker Hub には、コミュニティによって作成された公式およびコミュニティによるイメージが格納されています。また、**ユーザーがカスタムコンテナイメージをアップロードおよび共有する**こともできます。
:::

</details>

Docker Hub にログインします。  

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

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

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

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

<details>
<summary>説明を開きます。</summary>

:::note info
**docker tag** コマンドは、ローカルに保存されているコンテナイメージに、指定したタグを付けることで、コンテナイメージをリネームすることができます。  

この例では、**app-hello-spring-boot** という名前の**コンテナイメージ**に、**user/app-hello-spring-boot:latest** という**タグ**を付けています。ここで user は、Docker Hub 上のユーザー名を表します。タグには、コロン : 以降にバージョンなどの情報を含めることができますが、ここでは latest を指定しているため、最新バージョンを表しています。
:::

</details>

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

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

<details>
<summary>説明を開きます。</summary>

:::note info
**docker push** コマンドは、コンテナイメージを Docker Hub のリポジトリにプッシュするために使用されます。user/app-hello-spring-boot はプッシュするイメージの名前とタグを指定し、latest はイメージのバージョンを指定しています。  

これにより、Docker Hub 上に **user** というユーザー名の**リポジトリ**が作成され、その中に **app-hello-spring-boot** という名前の**コンテナイメージ**がアップロードされます。  
:::

</details>

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

### シェル変数の作成

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

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

:::note info
このコマンドは、Azure CLI を使用して Azure のリソースグループを作成しています。

|コマンド|内容|
|:--|:--|
|az group create|Azure のリソースグループを作成するためのコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--name|rg-hello|リソースグループの名前を指定します。|
|--location|japaneast|リソースグループを作成する場所を **japaneast** に指定しています。 **japaneast** は Azure の日本東部地域を指します。|
:::

</details>

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

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

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

<details>
<summary>説明を開きます。</summary>

:::note info
このコマンドは、Azure Container Apps 環境を作成しています。

|コマンド|内容|
|:--|:--|
|az containerapp env create|Container Apps 環境を作成するためのコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|アプリケーションをデプロイするリソースグループの名前です。|
|--name|cae-hello|Container Apps 環境の名前です。|
|--location|japaneast|リソースグループを作成する場所です。**japaneast** は Azure の日本東部地域を指します。|
:::

</details>

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

```sh
$ az containerapp env delete \
    --resource-group $resource_group_name \
    --name $containerapp_env_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 8080 \
    --ingress 'external' \
    --min-replicas 0
```

<details>
<summary>説明を開きます。</summary>

:::note info
このコマンドは、Azure Container App インスタンスを作成するためのコマンドです。以下が各オプションが実行する内容です。

|コマンド|内容|
|:--|:--|
|az containerapp create|Container Apps を作成する為のコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|デプロイするリソースが属するリソースグループの名前を指定します。|
|--environment|cae-hello|Container Apps 環境の名前を指定します。|
|--name|ca-hello-spring-boot|Container Apps の名前を指定します。|
|--image|$USER/app-hello-spring-boot:latest|実行するコンテナイメージの名前を指定します。ここでは、前述の手順で Docker Hub にプッシュしたイメージ名を使用しています。|
|--target-port|8080|コンテナが公開するポートを指定します。ここでは、Spring Boot アプリケーションがポート 8080 で公開されているため、8080 を指定しています。|
|--ingress|'external'|コンテナを外部からアクセスできるようにするためのイングレス設定を指定します。ここでは、外部からのアクセスを許可する 'external' を指定しています。|
|--min-replicas|0|最小レプリカ数を指定します。ここでは、最小レプリカ数を 0 に設定しています。|
:::

</details>

:::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-spring-boot.wonderfulgrass-e3276e77.japaneast.awsomecontainerapps.io
```

#### Azure Portal の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/f0a9bffb-fe47-6e5c-3233-969145182b4c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/0b53ce46-2df2-f15a-9c39-4eaed6524fae.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 を押します。  

:::note info
**az containerapp exec** コマンドは、Azure Container Apps 内の**コンテナ対して対話型シェルを実行**するために使用されます。
:::

```
# pwd
/app
# ls -lah
total 17M
drwxr-xr-x 1 root root 4.0K Aug  2 00:00 .
drwxr-xr-x 1 root root 4.0K Aug  2 01:22 ..
-rw-r--r-- 1 root root  17M Aug  1 23:46 app.jar
```

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

```sh
top - 01:24:20 up  8:54,  0 users,  load average: 0.14, 0.22, 0.18
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.0 us,  0.8 sy,  0.0 ni, 96.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15990.8 total,   4770.9 free,   2144.2 used,   9075.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13491.0 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 3518000 129108  20636 S   0.0   0.8   0:05.56 java
     33 root      20   0    2616    528    464 S   0.0   0.0   0:00.00 sh
     40 root      20   0    9080   3692   3180 R   0.0   0.0   0:00.00 top
```

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

```sh
top - 01:24:28 up  8:54,  0 users,  load average: 0.13, 0.22, 0.18
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu0  :  3.0 us,  0.7 sy,  0.0 ni, 96.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  2.0 us,  0.7 sy,  0.0 ni, 97.0 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
%Cpu2  :  3.3 us,  0.7 sy,  0.0 ni, 96.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,   4765.2 free,   2149.5 used,   9076.0 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13485.6 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 3518000 129108  20636 S   0.0   0.8   0:05.57 java
     33 root      20   0    2616    528    464 S   0.0   0.0   0:00.00 sh
     40 root      20   0    9080   3692   3180 R   0.0   0.0   0:00.00 top
```

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

```cs
# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.5 LTS"
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.5 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

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

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

コンテナ 環境

|項目|バージョン|
|:--|:--|
|Base Image|Ubuntu 20.04.6 LTS|
|JVM|openjdk version "11.0.18" 2023-01-17|
|Spring Boot|v2.7.8|

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

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

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

|No|状況|リクエスト所要時間(秒)|
|:--|:--|:--|
|1|デプロイ直後|0.359941秒|
|2|連続して2回目|0.198545秒|
|3|さらに3回目|0.215914秒|
|4|スケールゼロ回復直後|15.883182秒|
|5|連続して2回目|0.193921秒|
|6|さらに3回目|0.190443秒|

ログ ストリーム：起動時間を表示するログ

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

```sh:ログ ストリーム
2023-08-02T00:57:26.494853482Z 2023-08-02 00:57:26.494  INFO 1 --- [           main] com.example.springboot.Application       : Started Application in 8.001 seconds (JVM running for 9.591)
2023-08-02T01:10:53.504096753Z 2023-08-02 01:10:53.503  INFO 1 --- [           main] com.example.springboot.Application       : Started Application in 8.197 seconds (JVM running for 9.837)
```

メトリック：CPU使用率

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

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

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

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

## まとめ

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

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

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

## 比較してみよう

今回と同じ仕様を実装する記事がご確認いただけます。

https://qiita.com/studio_meowtoon/items/7fdaad559236f3f782f2