[JAVA] Getting Started with Micronaut 2.x ~ Native Build and Deploy to AWS Lambda ~

Last time: Following Introduction to Micronaut 2.x, this time we will upload the natively built Micronaut app to AWS Lambda.

I was a little confused because the function application created in the initial version after 2.0 has changed significantly, but since the official blog has detailed instructions on how to make it, I will create it based on it.

environment

Create an application for Lambda Function with Micronaut

Create a working directory

$ mkdir 02-native-function && cd 02-native-function

We will create it using the Micronaut CLI. This time we will use Java as the language.

#Create a function app with CLI
$ mn create-function-app example.micronaut.complete --features=aws-lambda,graalvm

#Verification
$ 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

#Move to application folder
$ cd complete

Let's look at the created file

The following 4 files will be created in src / main / java / example / micronaut /.

Now let's look at the file contents one by one.

[Book.java] A model class for saving input.

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;
    }
}

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();
    }
}

I'm not sure, but is it like a reception class for making Lambda calls from API Gateway to see the contents of the source code? When creating Lambda for S3 hook event, I think the generics of ʻAbstractMicronautLambdaRuntime` will be for S3.

BookSaved.java


package native.lambda
import io.micronaut.core.annotation.Introspected

@Introspected
class BookSaved {
    var name: String? = null
    var isbn: String? = null
}

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;
    }
}

Receives the API input value in Book.java, packs the return value in BookSaved.java, and returns it.

Creating a Lambda function

We will proceed with preparations to raise the application created with Micronaut.

1. Log in to the AWS console

Log in to the AWS Console. If you haven't created one yet, please create an AWS account and then come back here again.

2. Create Lambda

After logging in, open Services> Lambda. Press [Create Function] at the top right of the screen.

Let's select and enter as follows. create-lambda-function.png Micronaut is a Java app, but for natively built applications, choose "your own bootstrap" instead of Java for the runtime.

Modify basic settings

Edit the basic settings on the screen after creating Lambda. lambda-info-setting.png

Make the following changes from the edit button on the upper right.

lambda-info-setting-datail.png you save.

Creating a native image

By adding --features = graalvm when creating with CLI, the file for native build is created.

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

Create a file for deployment

We will use deploy.sh to run Dockerfile to create artifacts for native builds. (Maybe it's my network problem, but it took a long time ...)

$ 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
 ...
 
 #The file is created under build
 $ ls build
 function.zip

Upload to Lambda

Upload the deployed file to Lambda. Let's open the Lambda function created above.

Press the Action in [Function Code], press "Upload .zip file", and select the function.zip you created earlier to upload. lambda-upload.png

Let's move Lambda

First, create a test event to make it work. Press "Set Test Event" above and set as follows.

bookTest


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

test-event.png Press "Test" when the settings are complete. Let's see the result of the log output at the bottom. mn-lambda-native.png Init Duration: 450.70 ms Duration: 189.76 ms

Init Duration is the startup time and Duration is the processing time. You can see that it is very fast because the startup time is 0.45 seconds.

It's impressive to have this time with an application created based on Java, which is a cold start!

bonus

Let's look at the execution speed in an application deployed with a regular Jar file instead of a native build. I will omit the part to create and upload. mn-lambda-normal.png Init Duration: 2909.91 ms The initial startup time is about 3 seconds.

The difference from the native build is ** about 2.5 seconds **.

Summary

I was able to confirm that I could easily get to AWS Lambda! You can easily create a deploy file because Dockerfile and deploy.sh for upload are generated just by creating an application via CLI. I was able to experience that the startup speed is also much faster than running with normal Java.

Next time, I would like to create a Web application that connects to the DB with Micronaut and processes it.

References

This article was written with reference to the following information.

Recommended Posts

Getting Started with Micronaut 2.x ~ Native Build and Deploy to AWS Lambda ~
Build AWS Lambda with Quarkus
Getting started with Java lambda expressions
How to deploy to AWS using NUXTJS official S3 and CloudFront? With docker-compose
Getting Started with Java_Chapter 8_About Instances and Classes
How to deploy a container on AWS Lambda
Deploy Rails to ECS Fargate with AWS Copilot
Getting started with Kotlin to send to Java developers
How to build API with GraphQL and Rails
Getting Started with Doma-Introduction to the Criteria API
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (5)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (6)
Going back to the beginning and getting started with Java ① Data types and access modifiers
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (3)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (2)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (1)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (4)
I started MySQL 5.7 with docker-compose and tried to connect
Getting Started with Legacy Java Engineers (Stream + Lambda Expression)
Build WordPress environment with Docker (Local) and AWS (Production)
Getting Started with DBUnit
Getting Started with Ruby
Getting Started with Swift
Getting Started with Docker
Getting Started with Doma-Transactions
Getting started with Java and creating an AsciiDoc editor with JavaFX
Use Java included with Android Studio to build React Native
How to get started with Gatsby (TypeScript) x Netlify x Docker
Build a Node-RED environment with Docker to move and understand
Getting Started with Doma-Annotation Processing
Getting Started with Java Collection
Getting Started with JSP & Servlet
Getting Started with Java Basics
Getting Started with Spring Boot
Getting Started with Ruby Modules
[AWS] $ docker build. * Error * Gem :: Ext :: BuildError: ERROR: Failed to build gem native extension.
Install gem in Serverless Framework and AWS Lambda with Ruby environment
Returning to the beginning, getting started with Java ② Control statements, loop statements
Try running SlackBot made with Ruby x Sinatra on AWS Lambda
Create a periodical program with Ruby x AWS Lambda x CloudWatch Events