Azure Container Apps (ACA) に Java JAX-RS カスタムコンテナイメージをデプロイする

# Azure Container Apps (ACA) に Java JAX-RS カスタムコンテナイメージをデプロイする

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境で、Java JAX-RS Web アプリケーションをコンテナとして起動する方法を紹介します。
![java_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/2debf0a5-df60-adb4-3efa-fe8eb0c69ad9.png)

## 目的

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

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

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

## 実現すること

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

**WAR ファイル形式のアプリをコンテナとして起動**
```mermaid
flowchart LR
    terminal --> app[ROOT.war]
    subgraph Azure
        subgraph ACA[Azure Container Apps]
            subgraph container[app-hello-jaxrs]
                subgraph JVM
                    subgraph jetty
                        app[ROOT.war]
                    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 jetty fill:#FFFFDE, stroke:#B4AE43
```

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Azure|クラウド環境|
|Azure Container Apps|コンテナ実行環境|
|app-hello-jaxrs|カスタムコンテナ|
|JVM|Java 実行環境|
|jetty|Web サーバー|
|ROOT.war|Java アプリケーション|

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

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

### Java JAX-RS Web サービスの作成  

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

https://qiita.com/studio_meowtoon/private/7951cecb164dbd53ecdd

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

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

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

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

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

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

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

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

```
$ docker images | grep app-hello-jaxrs
app-hello-jaxrs                                     latest        a810eb763163   About an hour ago   185MB
```

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

Docker Hub にログインします。  

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

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

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

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

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

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

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

### シェル変数の作成

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

```sh
location_name=japaneast
resource_group_name=rg-hello
containerapp_env_name=cae-hello
containerapp_name=ca-hello-jaxrs
container_image_name=app-hello-jaxrs: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-jaxrs.nicehill-fafa9131.japaneast.awsomecontainerapps.io
```

#### Azure Portal の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/6db15860-ebea-94d0-5d11-69fdb17e30cd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/7c8bdd60-6154-c249-c409-058566030ff9.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
/var/lib/jetty
$ cd webapps
$ ls -lah
total 6M
drwxr-xr-x    1 jetty    jetty       4.0K Aug  1 06:59 .
drwxr-sr-x    1 jetty    jetty       4.0K Aug  1 08:47 ..
-rw-r--r--    1 root     root        6.2M Aug  1 06:05 ROOT.war
```

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

```sh
Mem: 11319464K used, 5055120K free, 9384K shrd, 176248K buff, 8251760K cached
CPU:   0% usr   0% sys   0% nic 100% idle   0% io   0% irq   0% sirq
Load average: 0.69 0.59 0.36 2/1184 57
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 jetty    S    1712m  10%   3   0% /opt/java/openjdk/bin/java -Djetty.home=/usr/local/jetty -Djetty.base=/var/lib/jetty --class-path /var/lib/jet
   50     0 jetty    S     1672   0%   3   0% sh
   57    50 jetty    R     1600   0%   1   0% top
```

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

```sh
Mem: 11328940K used, 5045644K free, 9384K shrd, 176260K buff, 8252424K cached
CPU0:   0% usr   0% sys   0% nic 100% idle   0% io   0% irq   0% sirq
CPU1:   0% usr   0% sys   0% nic 100% idle   0% io   0% irq   0% sirq
CPU2:   0% usr   0% sys   0% nic 100% idle   0% io   0% irq   0% sirq
CPU3:   0% usr   0% sys   0% nic 100% idle   0% io   0% irq   0% sirq
Load average: 0.54 0.56 0.36 3/1184 57
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 jetty    S    1712m  10%   3   0% /opt/java/openjdk/bin/java -Djetty.home=/usr/local/jetty -Djetty.base=/var/lib/jetty --class-path /var/lib/jet
   50     0 jetty    S     1672   0%   3   0% sh
   57    50 jetty    R     1604   0%   0   0% top
```

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

```cs
$ cat /etc/*-release
3.18.2
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.18.2
PRETTY_NAME="Alpine Linux v3.18"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
```

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

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

コンテナ 環境

|項目|バージョン|
|:--|:--|
|Base Image|Alpine Linux v3.18|
|JVM|openjdk version "11.0.20" 2023-07-18|
|Jetty|10.0.15|
|Jersey (JAX-RS)|2.39|

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

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

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

|No|状況|リクエスト所要時間(秒)|
|:--|:--|:--|
|1|デプロイ直後|2.695762秒|
|2|連続して2回目|0.189642秒|
|3|さらに3回目|0.173197秒|
|4|スケールゼロ回復直後|15.125714秒|
|5|連続して2回目|0.176384秒|
|6|さらに3回目|0.195233秒|

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

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

```sh:ログ ストリーム
2023-08-02T05:11:41.698021210Z 2023-08-02 05:11:41.697:INFO :oejs.Server:main: Started Server@1af2d44a{STARTING}[10.0.15,sto=5000] @6293ms
2023-08-02T05:21:49.498379308Z 2023-08-02 05:21:49.498:INFO :oejs.Server:main: Started Server@1af2d44a{STARTING}[10.0.15,sto=5000] @6412ms
```

メトリック：CPU使用率

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

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

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

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

## まとめ

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

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

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

## 推奨コンテンツ

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

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

## 関連記事

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