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.
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
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;
}
}
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();
}
}
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.
[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;
}
}
Receives the API input value in Book.java
, packs the return value in BookSaved.java
, and returns it.
We will proceed with preparations to raise the application created with Micronaut.
Log in to the AWS Console. If you haven't created one yet, please create an AWS account and then come back here again.
After logging in, open Services> Lambda
.
Press [Create Function] at the top right of the screen.
Let's select and enter as follows. Micronaut is a Java app, but for natively built applications, choose "your own bootstrap" instead of Java for the runtime.
Edit the basic settings on the screen after creating Lambda.
Make the following changes from the edit button on the upper right.
you save.
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
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 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.
First, create a test event to make it work. Press "Set Test Event" above and set as follows.
bookTest
{
"body": "{\"name\":\"Book Test!\"}"
}
Press "Test" when the settings are complete. Let's see the result of the log output at the bottom. 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!
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. Init Duration: 2909.91 ms The initial startup time is about 3 seconds.
The difference from the native build is ** about 2.5 seconds **.
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.
This article was written with reference to the following information.
Recommended Posts