Quarkus+CloudRunで作る爆速アプリケーション


# はじめに
[Retail AI Adventurers Advent Calendar 2023](https://qiita.com/advent-calendar/2023/rai-adventurers)の21日目の投稿です。
昨日は[@t-hiroyuki](https://qiita.com/t-hiroyuki)さんの「[M1チップ搭載のMac & RancherDesktop環境下におけるTestcontainersを使用したテスト失敗の原因の考察とその解決策](https://qiita.com/t-hiroyuki/items/b286c1c03c8f545db28d)」でした。
彼と話していると何だか癒されます。

# 自己紹介
Retail AIの基盤チーム(Infrastructure Systems Development Group)に所属しています。
興味がある方は、以下を参照下さい。
会社HP：[https://www.retail-ai.jp/](https://www.retail-ai.jp/)
採用ページ：[https://recruit.jobcan.jp/retail-ai/](https://recruit.jobcan.jp/retail-ai/)
今年に入って自作キーボードを5つ(ErgoArrows、CHARYBDIS、keyball61、keyball39、killer whale)購入し、現在2個故障中、1つ未作成です。

# 目次
- 背景
- Quarkusについて
- Kotlinでの実装
- アプリケーションの作成
- DockerImageの作成
- CloudRunにデプロイ
- まとめ

# 背景
4〜5ヶ月前だったか社内プロジェクトにおいて、アサインされて初めて知りました。
「かーかす？何ですか、それ？」
PHP歴10数年の私が、それ以来簡単なアプリを作るときに「Quarkusで作るにはどのJavaのライブラリ・・・」なんて考えるようになるとは。

直近では自分用にバーコード(JANコード）やQRコードを生成するものを作りました。

# [Quarkus](https://ja.quarkus.io/)について
Quarkusは、Java用のフルスタックKubernetesネイティブフレームワークで、Javaのマイクロサービスアプリケーションの開発と運用に特化しています。その主な特徴や他のフレームワークとの違いについて以下の点が挙げられます。

__1. コンパイル時最適化:__ Quarkusはビルド時に多くの処理を行うことで、ランタイムのオーバーヘッドを減少させます。これは、特にコンテナ化されたマイクロサービスやサーバレス環境でのパフォーマンスを向上させるために重要です。
__2. メモリ使用量の削減:__ Quarkusアプリケーションは、従来のJavaアプリケーションに比べてメモリ使用量が少なくなる傾向があります。これは、特にリソースが限られている環境で有利です。
__3. 起動時間の短縮:__ Quarkusは起動時間が非常に短いことで知られており、これもサーバレス環境やマイクロサービスアーキテクチャに適しています。
__4. 拡張性と開発者フレンドリー:__ Quarkusは、多数のライブラリやフレームワークと統合することが可能で、開発者が既存の知識やコードベースを活用できるように設計されています。
__5. Kubernetesネイティブ:__ QuarkusはKubernetesとの統合を重視しており、Kubernetes環境でのデプロイや管理が容易です。

他のフレームワークと比較して、Quarkusは特にコンテナ化された環境やマイクロサービスアーキテクチャに最適化されている点が大きな優位性です。従来のJava EEやSpring Bootなどのフレームワークに比べて、より効率的なリソース利用と高速なスタートアップを実現しています。これにより、クラウドネイティブアプリケーションの開発において重要な要素であるスケーラビリティとパフォーマンスが強化されています。
***

# Kotlinでの実装

KotlinはJava Virtual Machine（JVM）上で動作するモダンなプログラミング言語であり、Javaとの互換性を持っているため、Quarkusのフレームワーク内で効果的に利用することができます。

QuarkusでKotlinを使用する利点は以下の通りです

__互換性:__ KotlinはJavaとの高い互換性を持っており、既存のJavaライブラリやフレームワークをそのまま使用できます。これにより、Quarkusの機能とKotlinの利点を組み合わせて使用することが可能です

__簡潔な構文:__ Kotlinは簡潔で読みやすい構文を持っており、開発者の生産性を高めます。Quarkusと組み合わせることで、開発プロセスがさらに効率的になります。

__安全なコード:__ Kotlinはnull安全な言語設計をしており、NullPointerExceptionのような一般的な問題を回避するのに役立ちます。

__コルーチンによる非同期処理:__ Kotlinのコルーチンは非同期プログラミングを簡単に実現します。これは、リアクティブプログラミングやマイクロサービスアーキテクチャにおいて特に有用です。

QuarkusはこれらのKotlinの特徴を活用し、クラウドネイティブJavaアプリケーションの開発をさらに強化します。したがって、Quarkusフレームワークを使用してKotlinでアプリケーションを作成することは、非常に有効な選択肢です。

# SpringBoot3との比較
Javaのフレームワークで有名なSpringBoot3と何が違うのか、調べた範囲で比較します。

### Dockerfileの自動生成
SpringBootでDockerfileは手動で生成するか、ビルドプラグインを用いる方法のようです。
[OCI イメージのパッケージ化](https://spring.pleiades.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#build-image)

Quarkusではビルドを実行するとDockerfileが4種類作成されます。
このあたりがクラウドファーストな感じがしますね。

- legacy-jar
- jvm
- native-micro
- native

### コンテナイメージの軽量化
先ほどの４つのDockerファイルの中身を確認して、使用しているイメージをそれぞれ調べました。

- legacy-jar、jvm

```
docker pull registry.access.redhat.com/ubi8/openjdk-17:1.16  # 401.94MB
```


- native

```
docker pull registry.access.redhat.com/ubi8/ubi-minimal  # 93.17MB
```

- native-micro

```
docker pull quay.io/quarkus/quarkus-micro-image:2.0  # 28.4MB
```

Java17のイメージで他に例を出すと

```
gcr.io/distroless/java17-debian12:latest  #  225.17MB
eclipse-temurin:17-jdk-alpine             #  315.08MB
openjdk:17-jdk-slim                       #  407.74MB
```
Quarkusのnative-microのイメージ容量があまりにも軽量すぎます。

# 検証

terraformのコードはこんな感じで
```
# .tfenvの定数をインポート
variable "credentials" {}
variable "project_id" {}
variable "region" {}
variable "project_name" {}
variable "vpc_name" {}
variable "firewall_name" {}
variable "service_name" {}
variable "container_image" {}

# プロバイダの設定
provider "google" {
  credentials = var.credentials
  project     = var.project_id
  region      = var.region
}

# VPCの作成
resource "google_compute_network" "my_network" {
  name                    = var.vpc_name
  auto_create_subnetworks = false
}

# ファイアウォールの設定
resource "google_compute_firewall" "my_firewall" {
  name        = var.firewall_name
  network     = google_compute_network.my_network.self_link
  source_tags = ["web"]

  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }
}

# Cloud Runの設定
resource "google_cloud_run_v2_service" "my_service" {
  name     = var.service_name
  location = var.region
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
    containers {
      image = var.container_image
    }
    timeout = "1s"
  }
}

data "google_iam_policy" "admin" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

resource "google_cloud_run_v2_service_iam_policy" "noauth" {
  name        = google_cloud_run_v2_service.my_service.name
  location    = google_cloud_run_v2_service.my_service.location
  project     = google_cloud_run_v2_service.my_service.project
  policy_data = data.google_iam_policy.admin.policy_data
}

```
Qurakusのコード
[https://github.com/satoshihiraishi/quarkus-helloworld](https://github.com/satoshihiraishi/quarkus-helloworld)
nativeでイメージを作成し、作成されたイメージは87.9MBでした。

SpringBootのコード
[https://github.com/satoshihiraishi/springboot-helloworld](https://github.com/satoshihiraishi/springboot-helloworld)
openjdk:17-jdk-slimを使用し、作成されたイメージは457.23MBでした。


![スクリーンショット 2023-12-21 2.36.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1083101/339e3f56-02bc-1a7d-a3dd-21306f2f0b4f.png)
シンプルに「Hello World!」を表示するだけです。SprngBootはCLIで作成するとこの表示になりますが、Quarkusは異なったhtmlが出力されるので同じように合わせました。
それぞれのイメージを作成し、CloudRunにデプロイしてcurlコマンドで１０秒毎に20回アクセスしました。

# 結果
Cloud Loggingから算出しています。

SpringBoot：平均 5.9 ms
![スクリーンショット 2023-12-21 3.12.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1083101/e0fa10b9-b8a2-07ce-d799-2c85bbf8d8fd.png)

Quarkus：平均 5.2 ms
![スクリーンショット 2023-12-21 3.14.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1083101/51d0c4cc-b966-356d-d4bc-8a62c03bfbe2.png)

比較対象が文字の出力だけなので、もうちょっと違いが出やすい題材にすればとか、
SpringBootのイメージもdistrolessを使えばもう少し早くなってQuarkus超えてたかも、
などの反省は次に活かしたいと思います。

# まとめ

Quarkusいいよ。

# 次回
私が会社に自作キーボードを使ってるのを見て、凄く興味を持ってる[@k-yoshigai](https://qiita.com/k-yoshigai)さんがGo言語について書く予定です。