過去にQiitaに投稿した内容のアーカイブです。
Quarkus(https://quarkus.io/) とはSUPERSONIC SUBATOMIC JAVA
で、
A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.
だそうです。 何言ってるかわかりませんがすごそうです。
GraalVMの機能でJavaのプログラムをネイティブに変換することが可能なので、AWS Lambdaのカスタムランタイムと組み合わせることで AWS Lambda上で動作するJavaアプリケーションの特性である コールドスタートが遅い問題 の解決に期待ができます。
公式サイトの手順に従い試してみましたので、手順を残します。
QUARKUS - BUILDING A NATIVE EXECUTABLE https://quarkus.io/guides/building-native-image
QUARKUS - AMAZON LAMBDA https://quarkus.io/guides/amazon-lambda
環境
- Docker Desktop (Mac) 2.3.0.4
- VSCode + Visual Studio Code Remote - Containers extension
- Amazon Linux 2 (on Docker)
雛形アプリのデプロイ手順
手順1. Amazon Linux 2の環境設定
DockerイメージのAmazon Linux 2はtar
やunzip
ができないので色々入れておきます。
全部必要かわからないですが、これぐらい入れておきました。
yum install -y sudo shadow-utils procps tar.x86_64 gzip xz unzip witch git python3 tree
手順2. GraalVMのインストール
公式サイトからダウンロードして展開します。
curl -s -L -o /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz
tar zxf /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz -C /opt/
ln -s /opt/graalvm-ce-java11-20.1.0 /opt/graalvm
JAVA_HOME
をGraalVMにして、MavenでのビルドにGraalVMを使用します。
export GRAALVM_HOME=/opt/graalvm
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH
最後にNativeビルド時に必要なnative-image
をインストールします。コマンドはgu
(GraalVM Updater)です。
gu install native-image
手順3. Mavenのインストール
Quarkusのビルドで使用するMavenは3.6.2以上のバージョンが必要です。yum
でインストールできるバージョンが古かったので、公式サイトからダウンロードしてインストールしました。
curl -s -L -o /tmp/apache-maven-3.6.3-bin.tar.gz https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
tar zxf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/
ln -s /opt/apache-maven-3.6.3 /opt/apache-maven
mvnのバージョン確認
bash-4.2# mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/apache-maven
Java version: 11.0.7, vendor: GraalVM Community, runtime: /opt/graalvm-ce-java11-20.1.0
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix"
bash-4.2#
GraalVMのJVMで動作していることがわかります。
手順4. Mavenでプロジェクト作成
mvn
コマンドでプロジェクトを生成します。
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
-DarchetypeVersion=1.6.0.Final
しばらくするとプロンプトで質問されますので答えます。 []で囲んだ部分がユーザー入力です。
Define value for property 'groupId': [myGroup]
Define value for property 'artifactId': [myArtifact]
Define value for property 'version' 1.0-SNAPSHOT: : []
Define value for property 'package' myGroup: : [example]
Confirm properties configuration:
groupId: myGroup
artifactId: myArtifact
version: 1.0-SNAPSHOT
package: example
Y: : [Y]
artifactId
(myArtifact)でディレクトリが作成され、プロジェクトが生成されます。
作成直後のプロジェクト構成はこんな感じ。
bash-4.2# tree myArtifact/
myArtifact/
├── build.gradle
├── gradle.properties
├── payload.json
├── pom.xml
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── example
│ │ ├── InputObject.java
│ │ ├── OutputObject.java
│ │ ├── ProcessingService.java
│ │ ├── StreamLambda.java
│ │ ├── TestLambda.java
│ │ └── UnusedLambda.java
│ └── resources
│ └── application.properties
└── test
├── java
│ └── example
│ └── LambdaHandlerTest.java
└── resources
└── application.properties
9 directories, 14 files
bash-4.2#
手順5. Lambdaで起動するHandlerの設定
Lambdaで呼び出されるHandlerはresources/application.properties
のquarkus.lambda.handler
にて設定します。
quarkus.lambda.handler=test
上記設定の場合は、以下のtest
と名前をつけたクラスが呼び出されます。
@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}
あとは通常のLambdaと同様にコーディングします。
雛形にはtest
の他にstream
なども用意されています。
手順6. デプロイパッケージの作成
一旦雛形のままデプロイパッケージを作成してみます。
mvn clean package -Pnative
めちゃくちゃ時間がかかります。10分以上かかると思います。
手順7. デプロイパッケージの内容の確認
無事デプロイパッケージができると、target/function.zip
が生成されいます。試しに展開してみると、中身はbootstrap
のみでした。
bash-4.2# unzip function.zip
Archive: function.zip
inflating: bootstrap
bash-4.2#
手順8. Lambdaのデプロイ
target
ディレクトリ内には、manage.sh
というファイルも生成されており、ここからAWS上にデプロイしたりできるようです。
私はカスタムランタイムが初めてだったこともあり、マネジメントコンソールから試してみました。
デプロイパッケージ作成後に、target/sam.native.yaml
というファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。
関数を新規作成します。
ランタイムをユーザー独自のブートストラップを提供する
とします。
関数を作成したら、プログラムをアップロードします。
関数コードのところのアクション
から.zipファイルをアップロード
を選び、function.zip
を選択します。
ハンドラーはnot.used.in.provided.runtime
となります。
環境変数にDISABLE_SIGNAL_HANDLERS
を追加し値をtrue
とします。
ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。
{
"name": "Bill",
"greeting": "hello"
}
動作結果がこちら。無事動きました。
AWS SDK を追加
AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。
手順1. SSL通信の有効化
resources/application.properties
に以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)
quarkus.ssl.native=true
手順2. 依存関係の追加
まずは、quarkus-jaxb
が必要とのことで、これを追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaxb</artifactId>
</dependency>
次にAWS SDKのライブラリーを追加するのですが、
For native image, however the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).
翻訳すると
ただし、ネイティブイメージの場合、GraalVMコンパイルの問題(現在)のため、同期モードを使用するときは、Apache HTTPクライアントよりもURL接続クライアントを優先する必要があります。
だそうです。そのため、以下のような記述となります。
<properties>
<aws.sdk2.version>2.10.69</aws.sdk2.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<exclusions>
<!-- exclude the apache-client and netty client -->
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</exclusion>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
<version>1.0.0.Final</version>
</dependency>
</dependencies>
手順3. Javaコードの作成
S3にアクセスする例ですが、S3Clientを生成する際に、httpClient
にて明示的にUrlConnectionHttpClient
を指定します。
S3Client s3 = S3Client.builder()
.region(Region.AP_NORTHEAST_1)
.httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
.build();
手順4. SSL通信に必要な設定
SSL通信を行うためにデプロイパッケージに以下を含める必要があります。
- カスタムBootstrap
- libsunec.so
- cacerts
まず、src/main/zip.native/
ディレクトリを作成し、bootstrap
を作成します。
#!/usr/bin/env bash
./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts
次にlibsunec.so
とcacerts
をコピーします。この2つのファイルはGraalVMに含まれています。
cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/
cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/
手順5. デプロイパッケージの作成
デプロイパッケージの作成手順は変わりません。
mvn clean package -Pnative
手順6. デプロイパッケージの内容の確認
SSL有効化した状態でデプロイパッケージを作成すると、target/function.zip
の中身が変わります。
bash-4.2# unzip function.zip
Archive: function.zip
inflating: bootstrap
inflating: cacerts
inflating: libsunec.so
inflating: runner
bash-4.2#
事前準備したbootstrap``cacerts``libsunec.so
が含まれているのがわかります。
終わりに
通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。