Help us understand the problem. What is going on with this article?

Micronaut 2.x 入門 ~AWS Lambdaにネイティブビルドしてデプロイ~

前回: 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]
入力を保存するためのモデルクラスです。

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
    カスタムランタイムを実装するためのクラスです。
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]
    戻り値を保存するためのモデルクラスです。
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]
    メインの処理を行うクラスです。
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 を開きましょう。
画面右上の[関数の作成]を押します。

以下のように選択と入力をしましょう。
create-lambda-function.png
MicronautはJavaのアプリですが、ネイティブビルドしたアプリケーションをあげるため、ランタイムはJavaではなく「独自のブートストラップ」を選択しましょう。

基本設定の修正

Lambda作成後の画面で基本設定を編集していきます。
lambda-info-setting.png

右上の編集ボタンから以下のように変更します。

  • ハンドラ: example.micronaut.BookRequestHandler
  • メモリ: 512MB

lambda-info-setting-datail.png
保存します。

ネイティブイメージの作成

CLIで作成する際に --features=graalvm をつけたことで、ネイティブビルド用のファイルが作成されています。

Dockerfile
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"]
deploy.sh
#!/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-upload.png

Lambdaを動かしてみよう

まずは動かすためのテストイベントを作成します。
上の「テストイベントの設定」を押して以下のように設定します。

bookTest
{
  "body": "{\"name\":\"Book Test!\"}"
}

test-event.png
設定が完了したら「テスト」を押します。
一番下のログ出力の結果を見てみましょう。
mn-lambda-native.png
Init Duration: 450.70 ms
Duration: 189.76 ms

Init Durationは起動時間で、Durationは処理時間です。
起動時間が 0.45秒なので非常に速いことがわかります。

コールドスタートであるJavaを元に作成したアプリケーションでこの時間になるのは感動的ですね!

おまけ

ネイティブビルドではなく、通常のJarファイルでデプロイしたアプリケーションでの実行速度を見てみましょう。
作成してアップロードするところは省略します。
mn-lambda-normal.png
Init Duration: 2909.91 ms
初期起動時間は約3秒です。

ネイティブビルドとの差は約2.5秒ほどあります。

まとめ

簡単にAWS Lambdaまであげれることが確認できました!
CLI経由でアプリケーションを作成するだけで Dockerfile やアップロード用の deploy.sh が生成されるのでデプロイファイルもお手軽に作成できますね。
起動速度も通常のJavaで動かすよりはるかに速いことが体験できました。

次回はMicronautでDBへ接続して処理するようなWebアプリケーションを作成していきたいと思います。

参考文献

この記事は以下の情報を参考にして執筆しました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした