Azure Container Apps (ACA) に Spring Boot カスタムコンテナイメージをデプロイする：GraalVM ネイティブイメージ

# Azure Container Apps (ACA) に Spring Boot カスタムコンテナイメージをデプロイする：GraalVM ネイティブイメージ

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境で、GraalVM ネイティブイメージ Spring Boot Web アプリケーションをコンテナとして起動する方法を紹介します。

![spring-boot_graalvm_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/9b93944a-84c6-07e1-ef0d-fbb97a71b1df.png)

## 目的

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

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

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

## 実現すること

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

**ネイティブイメージ形式のアプリをコンテナとして起動**
```mermaid
flowchart LR
    terminal --> app[app]
    subgraph Azure
        subgraph ACA[Azure Container Apps]
            subgraph container[app-hello-spring-boot-native]
                subgraph app[app]
                    tomcat
                end
            end
        end
    end
style Azure fill:#EEFDFF, stroke:#74B9DC
style ACA fill:#5EBCFF, stroke:#0077CC
style container fill:#FFD7E4, stroke:#E13C73
style app fill:#ECECFF, stroke:#9674DC
style tomcat fill:#FFFFDE, stroke:#B4AE43
```

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Azure|クラウド環境|
|Azure Container Apps|コンテナ実行環境|
|app-hello-spring-boot-native|カスタムコンテナ|
|app|ネイティブイメージ アプリケーション|
|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 "17.0.6" 2023-01-17
OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13, 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
```

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

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

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

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

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

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

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

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

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

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

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

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

```
$ docker images | grep app-hello-spring-boot-native
app-hello-spring-boot-native   latest     f0794df4d28c   23 minutes ago   149MB
```

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

Docker Hub にログインします。  

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

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

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

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

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

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

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

### シェル変数の作成

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

```sh
location_name=japaneast
resource_group_name=rg-hello
containerapp_env_name=cae-hello
containerapp_name=ca-hello-spring-boot-native
container_image_name=app-hello-spring-boot-native: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 8080 \
    --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-spring-boot-native.wonderfuldune-033c35fb.japaneast.awesomecontainerapps.io
```

#### Azure Portal の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/823b6f1d-aaa8-e639-b319-298859e417d1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/d6a0610c-252c-ec69-bad3-1f4c2eaa927d.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!"}
```

:::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 67M
drwxr-xr-x 1 root root 4.0K Aug  2 09:18 .
drwxr-xr-x 1 root root 4.0K Aug  2 11:03 ..
-rwxr-xr-x 1 root root  67M Aug  2 09:16 app
```

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

```sh
top - 11:04:32 up 18:34,  0 users,  load average: 0.10, 0.18, 0.29
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.4 us,  1.0 sy,  0.0 ni, 96.5 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
MiB Mem :  15990.8 total,   4189.2 free,   2167.2 used,   9634.4 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13467.9 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 1396004  94288  48104 S   0.0   0.6   0:00.06 app
     24 root      20   0    2888    956    864 S   0.0   0.0   0:00.00 sh
     31 root      20   0    7316   3492   2928 R   0.0   0.0   0:00.00 top
```

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

```sh
top - 11:04:45 up 18:35,  0 users,  load average: 0.09, 0.17, 0.28
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu0  :  2.4 us,  0.3 sy,  0.0 ni, 97.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.7 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  1.0 us,  0.0 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  2.0 us,  0.3 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15990.8 total,   4192.7 free,   2163.4 used,   9634.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  13471.7 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 1396004  94288  48104 S   0.0   0.6   0:00.06 app
     24 root      20   0    2888    956    864 S   0.0   0.0   0:00.00 sh
     31 root      20   0    7316   3492   2928 R   0.0   0.0   0:00.00 top
```

:::note warn
**Spring Boot として動作**していますが、**java コマンドが起動していない**ことが確認できます。つまり **JVM を必要としません**。
:::

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

```cs
# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
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"
UBUNTU_CODENAME=jammy
```

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

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

コンテナ 環境

|項目|バージョン|
|:--|:--|
|Base Image|Ubuntu 22.04.2 LTS|
|Spring Boot|v3.0.4|

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

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

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

|No|状況|リクエスト所要時間(秒)|
|:--|:--|:--|
|1|デプロイ直後|0.233591秒|
|2|連続して2回目|0.175264秒|
|3|さらに3回目|0.173258秒|
|4|スケールゼロ回復直後|7.049757秒|
|5|連続して2回目|0.209536秒|
|6|さらに3回目|0.189759秒|

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

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

```sh:ログ ストリーム
2023-08-02T10:44:27.514912040Z 2023-08-02T10:44:27.514Z  INFO 1 --- [           main] com.example.springboot.Application       : Started Application in 0.042 seconds (process running for 0.045)
2023-08-02T10:55:00.896887847Z 2023-08-02T10:55:00.896Z  INFO 1 --- [           main] com.example.springboot.Application       : Started Application in 0.039 seconds (process running for 0.042)
```

メトリック：CPU使用率

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

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

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

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

## まとめ

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

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

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

## 関連記事

通常のカスタムコンテナイメージのデプロイと比較してみましょう！

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