前回: Micronaut 2.x 入門に続いて今回はネイティブビルドしたMicronautアプリをAWS Lambdaにアップロードしていきます。
versionが2.0以降初期で作成されるファンクションアプリが大きく変わったので少し混乱しましたが、公式のブログに細かく作り方が記載されていたので、それを元に作成していきます。
環境
- macOS Catalina: 10.15.4
- メモリ: 16GB
- Micronaut: 2.0.1
- Java: 11.0.8.j9-adpt
- Docker for Mac: 2.3.0.4
- AWSアカウント
MicronautでLambda Function用アプリケーション作成
作業用ディレクトリを作成
$ mkdir 02-native-function && cd 02-native-function
Micronaut CLIを利用して作成していきます。今回は言語にJavaを使います。
# CLIでファンクションアプリを作成
$ mn create-function-app example.micronaut.complete --features=aws-lambda,graalvm
# 確認
$ tree complete
complete/
├── Dockerfile
├── README.md
├── bootstrap
├── build.gradle
├── deploy.sh
├── docker-build.sh
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── micronaut-cli.yml
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── example
│ │ └── micronaut
│ │ ├── Book.java
│ │ ├── BookLambdaRuntime.java
│ │ ├── BookRequestHandler.java
│ │ └── BookSaved.java
│ └── resources
│ ├── META-INF
│ │ └── native-image
│ │ └── example.micronaut
│ │ └── complete-application
│ │ └── native-image.properties
│ ├── application.yml
│ └── logback.xml
└── test
└── java
└── example
└── micronaut
└── BookRequestHandlerTest.java
16 directories, 21 files
# アプリケーションフォルダに移動
$ cd complete
作成されたファイルを見ていこう
src/main/java/example/micronaut/
に下記4ファイルが作成されます。
- Book.java
- BookLambdaRuntime.java
- BookSaved.java
- BookRequestHandler.java
それでは、1つずつファイル内容を見ていきます。
[Book.java]
入力を保存するためのモデルクラスです。
package example.micronaut;
import edu.umd.cs.findbugs.annotations.NonNull;
import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.NotBlank;
@Introspected
public class Book {
@NonNull
@NotBlank
private String name;
public Book() {
}
@NonNull
public String getName() {
return name;
}
public void setName(@NonNull String name) {
this.name = name;
}
}
-
java:BookLambdaRuntime.java
カスタムランタイムを実装するためのクラスです。
package example.micronaut;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import io.micronaut.function.aws.runtime.AbstractMicronautLambdaRuntime;
import java.net.MalformedURLException;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import edu.umd.cs.findbugs.annotations.Nullable;
public class BookLambdaRuntime extends AbstractMicronautLambdaRuntime<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent, Book, BookSaved> {
public static void main(String[] args) {
try {
new BookLambdaRuntime().run(args);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
@Override
@Nullable
protected RequestHandler<Book, BookSaved> createRequestHandler(String... args) {
return new BookRequestHandler();
}
}
いまいち筆者も良くわかっておりませんが、ソースコードの中身を見るにAPIGatewayからのLambda呼び出しをするための受付用のクラスのようなものでしょうか。
S3フックイベントのLambdaを作るときは AbstractMicronautLambdaRuntime
のジェネリクスがS3用になるのではないでしょうか。
-
[java:BookSaved.java]
戻り値を保存するためのモデルクラスです。
package native.lambda
import io.micronaut.core.annotation.Introspected
@Introspected
class BookSaved {
var name: String? = null
var isbn: String? = null
}
-
[java:BookRequestHandler.java]
メインの処理を行うクラスです。
package example.micronaut;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.function.aws.MicronautRequestHandler;
import java.util.UUID;
@Introspected
public class BookRequestHandler extends MicronautRequestHandler<Book, BookSaved> {
@Override
public BookSaved execute(Book input) {
BookSaved bookSaved = new BookSaved();
bookSaved.setName(input.getName());
bookSaved.setIsbn(UUID.randomUUID().toString());
return bookSaved;
}
}
APIの入力値を Book.java
で値を受け取り、戻り値を BookSaved.java
に詰めて返却します。
Lambda関数の作成
Micronautで作成したアプリケーションを上げるための準備を進めていきます。
1. AWSコンソールにログイン
AWSコンソールにログインしましょう。
もしまだ未作成の場合は、AWSアカウントの作成を行なってから再度こちらに戻ってきてください。
2. Lambda作成
ログイン後に サービス > Lambda
を開きましょう。
画面右上の[関数の作成]を押します。
以下のように選択と入力をしましょう。
MicronautはJavaのアプリですが、ネイティブビルドしたアプリケーションをあげるため、ランタイムはJavaではなく「独自のブートストラップ」を選択しましょう。
基本設定の修正
右上の編集ボタンから以下のように変更します。
- ハンドラ: example.micronaut.BookRequestHandler
- メモリ: 512MB
ネイティブイメージの作成
CLIで作成する際に --features=graalvm
をつけたことで、ネイティブビルド用のファイルが作成されています。
FROM gradle:6.3.0-jdk11 as builder
COPY --chown=gradle:gradle . /home/application
WORKDIR /home/application
RUN ./gradlew build --no-daemon
FROM amazonlinux:2018.03.0.20191014.0 as graalvm
ENV LANG=en_US.UTF-8
RUN yum install -y gcc gcc-c++ libc6-dev zlib1g-dev curl bash zlib zlib-devel zip
ENV GRAAL_VERSION 20.1.0
ENV JDK_VERSION java11
ENV GRAAL_FILENAME graalvm-ce-${JDK_VERSION}-linux-amd64-${GRAAL_VERSION}.tar.gz
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} -o /tmp/${GRAAL_FILENAME}
RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \
&& mv /tmp/graalvm-ce-${JDK_VERSION}-${GRAAL_VERSION} /usr/lib/graalvm
RUN rm -rf /tmp/*
CMD ["/usr/lib/graalvm/bin/native-image"]
FROM graalvm
COPY --from=builder /home/application/ /home/application/
WORKDIR /home/application
RUN /usr/lib/graalvm/bin/gu install native-image
RUN /usr/lib/graalvm/bin/native-image --no-server -cp build/libs/complete-*-all.jar
RUN chmod 777 bootstrap
RUN chmod 777 complete
RUN zip -j function.zip bootstrap complete
EXPOSE 8080
ENTRYPOINT ["/home/application/complete"]
#!/bin/bash
docker build . -t complete
mkdir -p build
docker run --rm --entrypoint cat complete /home/application/function.zip > build/function.zip
デプロイ用のファイルを作成
Dockerfile
を動かす deploy.sh
を使ってネイティブビルド用の成果物を作成していきます。
(筆者のネットワークの問題なのかもしれませんが、非常に時間がかかりました...。)
$ sh ./deploy.sh
Sending build context to Docker daemon 17.63MB
Step 1/24 : FROM gradle:6.3.0-jdk11 as builder
---> 0290cb9c9a7b
Step 2/24 : COPY --chown=gradle:gradle . /home/application
---> 287bbae39066
...
# ファイルはbuild配下に作成されます
$ ls build
function.zip
Lambdaにアップロード
デプロイされたファイルをLambdaにアップロードしていきます。
上で作成したLambda関数を開きましょう。
[関数コード] のアクションを押して、「.zipファイルをアップロード」を押して、先ほど作成した function.zip
を選択してアップロードしてください。
Lambdaを動かしてみよう
まずは動かすためのテストイベントを作成します。
上の「テストイベントの設定」を押して以下のように設定します。
{
"body": "{\"name\":\"Book Test!\"}"
}
設定が完了したら「テスト」を押します。
一番下のログ出力の結果を見てみましょう。
Init Duration: 450.70 ms
Duration: 189.76 ms
Init Durationは起動時間で、Durationは処理時間です。
起動時間が 0.45秒なので非常に速いことがわかります。
コールドスタートであるJavaを元に作成したアプリケーションでこの時間になるのは感動的ですね!
おまけ
ネイティブビルドではなく、通常のJarファイルでデプロイしたアプリケーションでの実行速度を見てみましょう。
作成してアップロードするところは省略します。
Init Duration: 2909.91 ms
初期起動時間は約3秒です。
ネイティブビルドとの差は約2.5秒ほどあります。
まとめ
簡単にAWS Lambdaまであげれることが確認できました!
CLI経由でアプリケーションを作成するだけで Dockerfile
やアップロード用の deploy.sh
が生成されるのでデプロイファイルもお手軽に作成できますね。
起動速度も通常のJavaで動かすよりはるかに速いことが体験できました。
次回はMicronautでDBへ接続して処理するようなWebアプリケーションを作成していきたいと思います。
参考文献
この記事は以下の情報を参考にして執筆しました。