軽量かつネイティブに動くJavaを求めてQuarkus 2.0をやってみてLambdaにデプロイした
Javaを尋ねて25年...
こんにちは!みなさんJava使っていますか?私は学生のときにJavaを使い始めて、コミュニティと出会い、仕事にもなって、勢いでサンタクララの本社に突撃したことがあるぐらいJavaが好きでした。そして、時が経ち、軽量で軽快に動く他の言語やフレームワークが出てきたことで、若い方がJavaを使う機会が減ってきたなぁと感じています。多くのエンプラ現場で使われてきて実績十分、起動するまでが遅いけど動き出したらパフォーマンス良好、エコシステムが出来上がっているJavaを、もっと楽しく簡単に、できれば軽快に動く形で使えないものかと、モヤモヤしている中、昨年Quarkusという面白いものを見つけてしまいました。そして、昨日バージョン2.0がリリースされましたので、試してみたいと思います。
Quarkusとは
Quarkusは、Kubernetesなどのコンテナ上での動作に最適化されたフレームワークです。Red Hat社がサポートしていて、Apacheライセンスの下で公開されています。Quarkusを使うことで、メモリ使用量を小さくすることができ、ブート時間も短くなります。更にネイティブアプリとしてコンパイルできるため、メモリ使用量1/10、起動時間1/100を実現するようなとんでもない代物です。サーバーレスやコンテナ環境で使用するメモリ量や起動時間が減れば、そのままコストに大きく影響しますので、試してみる価値は十分にあるフレームワークです。
マシン環境
- macOS Big Sur version 11.4
- MacBook Pro (13インチ, 2020, Thunderbolt 3ポート x 4)
- プロセッサ 2.3 GHz クアッドコアIntel Core i7
- メモリ 32 GB 3733 MHz LPDDR4X
- グラフィックス Intel Iris Plus Graphics 1536 MB
環境構築
まずは必要となる各種ミドルウェアのセットアップを行います。まずは以下のソフトウェアをダウンロードしておいてください。
Dockerを起動
今回、最終目標としてLambda上で動くカスタムランタイムとして、ネイティブコンパイルされたコードを実行しようと思っています。コンパイルする環境としてMacOSでは、実行ファイルはそのままLambda上で動きません。そこで、Amazon Linux 2上でコンパイルします。
Dockerをダウンロードしてインストールしたあと、Amazon Linux 2のImageをローカルに持ってきます。
$ docker pull amazonlinux
以後、このImageから起動したコンテナ内に入っての処理となります。
基本的なライブラリ等のセットアップ
まずは基本から
$ bash $ yum update -y $ yum install wget tar git java maven awscli unzip gcc zlib-devel -y $ yum groupinstall "Development Tools" -y $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" $ brew --version Homebrew 3.2.1
GraalVMのセットアップ
$ cd /opt/ $ wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.1.0/graalvm-ce-java11-linux-amd64-21.1.0.tar.gz $ tar -xzf graalvm-ce-java11-darwin-amd64-21.1.0.tar.gz $ vi /root/.bash_profile export PATH=/opt/graalvm-ce-java11-21.1.0/:$PATH export PATH=/home/linuxbrew/.linuxbrew/bin:$PATH export JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto.x86_64/ $ source ~/.bash_profile $ gu install native-image
JavaのSDKを切り替え
$ alternatives --config java java -version openjdk version "11.0.11" 2021-04-20 LTS OpenJDK Runtime Environment Corretto-11.0.11.9.1 (build 11.0.11+9-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.11.9.1 (build 11.0.11+9-LTS, mixed mode)
SAMのセットアップ
$ brew tap aws/tap $ brew install aws-sam-cli $ sam --version SAM CLI, version 1.26.0
サンプルプロジェクトのセットアップ
公開されているプロジェクトからLambdaにネイティブコードを載せる例があましたので紹介します。
$ cd /usr/local/src/ $ git clone https://github.com/quarkusio/quarkus-quickstarts.git $ cd /usr/local/src/quarkus-quickstarts/funqy-quickstarts/funqy-amazon-lambda-http-quickstart/ $ ./mvnw clean install -Dnative
次にSAMを使ってLambdaにデプロイします。先にアクセスキーの設定を忘れずに。うまくいくと、AWS側でCloudFormationが走りまして、API Gateway + Lambdaの環境が自動生成されます。
$ aws configure $ sam deploy -t target/sam.native.yaml -g
動作確認
AWSのマネジメントコンソールからLambdaにデプロイされているか確認します。
CloudFormationからLambdaとAPI Gatewayが作成されていることが分かります。
プロパティを見ると、カスタムランタイムとしてデプロイされていることが分かります。
ブラウザなどから動作確認してみましょう。実際に動作確認できました。
$ curl https://nxct3826wb.execute-api.ap-northeast-1.amazonaws.com/hello "Hello World"
ソースコードはこちら
package org.acme.funqy; import io.quarkus.funqy.Funq; public class PrimitiveFunctions { @Funq public String hello() { return "Hello World"; } @Funq public String toLowerCase(String val) { return val.toLowerCase(); } @Funq("double") public int doubleIt(int val) { return val + val; } }
こちらはパラメータ付きの例
$ curl https://nxct3826wb.execute-api.ap-northeast-1.amazonaws.com/greet?name=akari {"message":"Hello akari"}
ソースコードはこちら
package org.acme.funqy; import io.quarkus.funqy.Funq; import javax.inject.Inject; public class GreetingFunction { @Inject GreetingService service; @Funq public Greeting greet(Friend friend) { Greeting greeting = new Greeting(); greeting.setMessage(service.greet(friend.getName())); return greeting; } }
まとめ
Quarkusを用いたことで、Javaのコードをネイティブにコンパイルして、SAMでローカルで動作確認をし、最後にLambdaにデプロイして動作するところまで確認できました。デプロイされたコードは、14MBでカスタムランタイム上で実行されるため、Lambdaの動作に必要なメモリは最小限で済み、起動時の遅延もありません。理想的な形となりました。今回は、Lambdaにデプロイしましたが、コンテナイメージとして作ることもできますので、実際の応用範囲は広いと思います。
参考資料
QUARKUS - AMAZON LAMBDA WITH RESTEASY, UNDERTOW, OR VERT.X WEB